diff --git a/Modules/Core/include/mitkDisplayInteractor.h b/Modules/Core/include/mitkDisplayInteractor.h index 12127371e3..ec827ad3af 100644 --- a/Modules/Core/include/mitkDisplayInteractor.h +++ b/Modules/Core/include/mitkDisplayInteractor.h @@ -1,262 +1,280 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDisplayInteractor_h #define mitkDisplayInteractor_h #include "mitkInteractionEventObserver.h" #include namespace mitk { /** *\class DisplayInteractor *@brief Observer that manages the interaction with the display. * * This includes the interaction of Zooming, Panning, Scrolling and adjusting the LevelWindow. * * @ingroup Interaction **/ /** * Inherits from mitk::InteractionEventObserver since it doesn't alter any data (only their representation), * and its actions cannot be associated with a DataNode. Also inherits from EventStateMachine */ class MITKCORE_EXPORT DisplayInteractor : public EventStateMachine, public InteractionEventObserver { public: mitkClassMacro(DisplayInteractor, EventStateMachine) itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** * By this function the Observer gets notified about new events. * Here it is adapted to pass the events to the state machine in order to use * its infrastructure. * It also checks if event is to be accepted when it already has been processed by a DataInteractor. */ virtual void Notify(InteractionEvent *interactionEvent, bool isHandled) override; protected: DisplayInteractor(); virtual ~DisplayInteractor(); /** * Derived function. * Connects the action names used in the state machine pattern with functions implemented within * this InteractionEventObserver. This is only necessary here because the events are processed by the state machine. */ void ConnectActionsAndFunctions() override; /** * Derived function. * Is executed when config object is set / changed. * Here it is used to read out the parameters set in the configuration file, * and set the member variables accordingly. */ virtual void ConfigurationChanged() override; /** * Derived function. * Is executed when config object is set / changed. * Here it is used to read out the parameters set in the configuration file, * and set the member variables accordingly. */ virtual bool FilterEvents(InteractionEvent *interactionEvent, DataNode *dataNode) override; virtual bool CheckPositionEvent(const InteractionEvent *interactionEvent); virtual bool CheckRotationPossible(const InteractionEvent *interactionEvent); virtual bool CheckSwivelPossible(const InteractionEvent *interactionEvent); /** * \brief Initializes an interaction, saves the pointers start position for further reference. */ virtual void Init(StateMachineAction *, InteractionEvent *); /** * \brief Performs panning of the data set in the render window. */ virtual void Move(StateMachineAction *, InteractionEvent *); /** * \brief Sets crosshair at clicked position* */ virtual void SetCrosshair(StateMachineAction *, InteractionEvent *); /** * \brief Performs zooming relative to mouse/pointer movement. * * Behavior is determined by \see m_ZoomDirection and \see m_ZoomFactor. * */ virtual void Zoom(StateMachineAction *, InteractionEvent *); /** * \brief Performs scrolling relative to mouse/pointer movement. * * Behavior is determined by \see m_ScrollDirection and \see m_AutoRepeat. * */ virtual void Scroll(StateMachineAction *, InteractionEvent *); /** * \brief Scrolls one layer up */ virtual void ScrollOneDown(StateMachineAction *, InteractionEvent *); /** * \brief Scrolls one layer down */ virtual void ScrollOneUp(StateMachineAction *, InteractionEvent *); /** * \brief Adjusts the level windows relative to mouse/pointer movement. */ virtual void AdjustLevelWindow(StateMachineAction *, InteractionEvent *); /** * \brief Starts crosshair rotation */ virtual void StartRotation(StateMachineAction *, InteractionEvent *); /** * \brief Ends crosshair rotation */ virtual void EndRotation(StateMachineAction *, InteractionEvent *); /** * \brief */ virtual void Rotate(StateMachineAction *, InteractionEvent *event); virtual void Swivel(StateMachineAction *, InteractionEvent *event); /** * \brief Updates the Statusbar information with the information about the clicked position */ +<<<<<<< HEAD virtual void UpdateStatusbar(StateMachineAction *, InteractionEvent *event); +======= + virtual void SetStatusBarInfoFromClickedPosition(StateMachineAction*, InteractionEvent* event); +>>>>>>> T19609-integration-branch /** * \brief Method to retrieve bool-value for given property from string-property * in given propertylist. */ bool GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char *propertyName, bool defaultValue); // Typedefs typedef std::vector SNCVector; private: mitk::DataNode::Pointer GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, mitk::Point3D worldposition, BaseRenderer *ren); + /** + * @brief UpdateStatusBar + * @param image3D + * @param idx + * @param time + * @param component If the PixelType of image3D is a vector (for example a 2D velocity vector), then only one of the vector components can be + * displayed at once. Setting this parameter will determine which of the vector's components will be used to determine the displayed PixelValue. + * Set this to 0 for scalar images + */ + void UpdateStatusBar(itk::SmartPointer image3D, itk::Index<3> idx, TimeStepType time=0, int component=0); + + /** * \brief Coordinate of the pointer at begin of an interaction translated to mm unit */ mitk::Point2D m_StartCoordinateInMM; /** * \brief Coordinate of the pointer in the last step within an interaction. */ mitk::Point2D m_LastDisplayCoordinate; /** * \brief Coordinate of the pointer in the last step within an interaction translated to mm unit */ mitk::Point2D m_LastCoordinateInMM; /** * \brief Current coordinates of the pointer. */ mitk::Point2D m_CurrentDisplayCoordinate; + + /** * \brief Modifier that defines how many slices are scrolled per pixel that the mouse has moved * * This modifier defines how many slices the scene is scrolled per pixel that the mouse cursor has moved. * By default the modifier is 4. This means that when the user moves the cursor by 4 pixels in Y-direction * the scene is scrolled by one slice. If the user has moved the the cursor by 20 pixels, the scene is * scrolled by 5 slices. * * If the cursor has moved less than m_IndexToSliceModifier pixels the scene is scrolled by one slice. */ int m_IndexToSliceModifier; /** Defines behavior at end of data set. * If set to true it will restart at end of data set from the beginning. */ bool m_AutoRepeat; /** * Defines scroll behavior. * Default is up/down movement of pointer performs scrolling */ std::string m_ScrollDirection; /** * Defines how the axis of interaction influences scroll behavior. */ bool m_InvertScrollDirection; /** * Defines scroll behavior. * Default is up/down movement of pointer performs zooming */ std::string m_ZoomDirection; /** * Defines how the axis of interaction influences zoom behavior. */ bool m_InvertZoomDirection; /** * Defines how the axis of interaction influences move behavior. */ bool m_InvertMoveDirection; /** * Defines level/window behavior. * Default is left/right movement of pointer modifies the level. */ std::string m_LevelDirection; /** * Defines how the axis of interaction influences level/window behavior. */ bool m_InvertLevelWindowDirection; /** * Determines if the Observer reacts to events that already have been processed by a DataInteractor. * The default value is false. */ bool m_AlwaysReact; /** * Factor to adjust zooming speed. */ float m_ZoomFactor; ///// Members to deal with rotating slices /** * @brief m_LinkPlanes Determines if angle between crosshair remains fixed when rotating */ bool m_LinkPlanes; SNCVector m_RotatableSNCs; /// all SNCs that currently have CreatedWorldGeometries, that can be rotated. SNCVector m_SNCsToBeRotated; /// all SNCs that will be rotated (exceptions are the ones parallel to the one being clicked) Point3D m_LastCursorPosition; /// used for calculation of the rotation angle Point3D m_CenterOfRotation; /// used for calculation of the rotation angle Point2D m_ReferenceCursor; Vector3D m_RotationPlaneNormal; Vector3D m_RotationPlaneXVector; Vector3D m_RotationPlaneYVector; Vector3D m_PreviousRotationAxis; ScalarType m_PreviousRotationAngle; }; } #endif diff --git a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp index e32d8b95c8..e47c383f0d 100644 --- a/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayInteractor.cpp @@ -1,928 +1,1028 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDisplayInteractor.h" #include "mitkBaseRenderer.h" #include "mitkCameraController.h" #include "mitkInteractionPositionEvent.h" #include "mitkPropertyList.h" #include #include #include // level window #include "mitkLevelWindow.h" #include "mitkLevelWindowProperty.h" #include "mitkLine.h" #include "mitkNodePredicateDataType.h" #include "mitkStandaloneDataStorage.h" #include "vtkRenderWindowInteractor.h" // Rotation #include "mitkInteractionConst.h" #include "rotate_cursor.xpm" #include #include // #include "mitkImage.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkStatusBar.h" void mitk::DisplayInteractor::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled || m_AlwaysReact) { this->HandleEvent(interactionEvent, NULL); } } void mitk::DisplayInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("check_position_event", CheckPositionEvent); CONNECT_CONDITION("check_can_rotate", CheckRotationPossible); CONNECT_CONDITION("check_can_swivel", CheckSwivelPossible); CONNECT_FUNCTION("init", Init); CONNECT_FUNCTION("move", Move); CONNECT_FUNCTION("zoom", Zoom); CONNECT_FUNCTION("scroll", Scroll); CONNECT_FUNCTION("ScrollOneDown", ScrollOneDown); CONNECT_FUNCTION("ScrollOneUp", ScrollOneUp); CONNECT_FUNCTION("levelWindow", AdjustLevelWindow); CONNECT_FUNCTION("setCrosshair", SetCrosshair); +<<<<<<< HEAD CONNECT_FUNCTION("updateStatusbar", UpdateStatusbar) +======= + + CONNECT_FUNCTION ("updateStatusbar", SetStatusBarInfoFromClickedPosition) +>>>>>>> T19609-integration-branch CONNECT_FUNCTION("startRotation", StartRotation); CONNECT_FUNCTION("endRotation", EndRotation); CONNECT_FUNCTION("rotate", Rotate); CONNECT_FUNCTION("swivel", Swivel); } mitk::DisplayInteractor::DisplayInteractor() : m_IndexToSliceModifier(4), m_AutoRepeat(false), m_InvertScrollDirection(false), m_InvertZoomDirection(false), m_InvertMoveDirection(false), m_InvertLevelWindowDirection(false), m_AlwaysReact(false), m_ZoomFactor(2), m_LinkPlanes(true) { m_StartCoordinateInMM.Fill(0); m_LastDisplayCoordinate.Fill(0); m_LastCoordinateInMM.Fill(0); m_CurrentDisplayCoordinate.Fill(0); } mitk::DisplayInteractor::~DisplayInteractor() { } bool mitk::DisplayInteractor::CheckPositionEvent(const InteractionEvent *interactionEvent) { const InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == NULL) { return false; } return true; } bool mitk::DisplayInteractor::CheckRotationPossible(const mitk::InteractionEvent *interactionEvent) { // Decide between moving and rotation slices. /* Detailed logic: 1. Find the SliceNavigationController that has sent the event: this one defines our rendering plane and will NOT be rotated. Needs not even be counted or checked. 2. Inspect every other SliceNavigationController - calculate the line intersection of this SliceNavigationController's plane with our rendering plane - if there is NO interesection, ignore and continue - IF there is an intersection - check the mouse cursor's distance from that line. 0. if the line is NOT near the cursor, remember the plane as "one of the other planes" (which can be rotated in "locked" mode) 1. on first line near the cursor, just remember this intersection line as THE other plane that we want to rotate 2. on every consecutive line near the cursor, check if the line is geometrically identical to the line that we want to rotate - if yes, we just push this line to the "other" lines and rotate it along - if no, then we have a situation where the mouse is near two other lines (e.g. crossing point) and don't want to rotate */ const InteractionPositionEvent *posEvent = dynamic_cast(interactionEvent); if (posEvent == nullptr) return false; BaseRenderer *clickedRenderer = posEvent->GetSender(); const PlaneGeometry *ourViewportGeometry = (clickedRenderer->GetCurrentWorldPlaneGeometry()); if (!ourViewportGeometry) return false; Point3D cursorPosition = posEvent->GetPositionInWorld(); const PlaneGeometry *geometryToBeRotated = NULL; // this one is under the mouse cursor const PlaneGeometry *anyOtherGeometry = NULL; // this is also visible (for calculation of intersection ONLY) Line3D intersectionLineWithGeometryToBeRotated; bool hitMultipleLines(false); m_SNCsToBeRotated.clear(); const double threshholdDistancePixels = 12.0; auto renWindows = interactionEvent->GetSender()->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; const PlaneGeometry *otherRenderersRenderPlane = snc->GetCurrentPlaneGeometry(); if (otherRenderersRenderPlane == NULL) continue; // ignore, we don't see a plane // check if there is an intersection Line3D intersectionLine; // between rendered/clicked geometry and the one being analyzed if (!ourViewportGeometry->IntersectionLine(otherRenderersRenderPlane, intersectionLine)) { continue; // we ignore this plane, it's parallel to our plane } // check distance from intersection line double distanceFromIntersectionLine = intersectionLine.Distance(cursorPosition); // far away line, only remember for linked rotation if necessary if (distanceFromIntersectionLine > threshholdDistancePixels) { anyOtherGeometry = otherRenderersRenderPlane; // we just take the last one, so overwrite each iteration (we just // need some crossing point) // TODO what about multiple crossings? NOW we have undefined behavior / random crossing point is used if (m_LinkPlanes) { m_SNCsToBeRotated.push_back(snc); } } else // close to cursor { if (geometryToBeRotated == NULL) // first one close to the cursor { geometryToBeRotated = otherRenderersRenderPlane; intersectionLineWithGeometryToBeRotated = intersectionLine; m_SNCsToBeRotated.push_back(snc); } else { // compare to the line defined by geometryToBeRotated: if identical, just rotate this otherRenderersRenderPlane // together with the primary one // if different, DON'T rotate if (intersectionLine.IsParallel(intersectionLineWithGeometryToBeRotated) && intersectionLine.Distance(intersectionLineWithGeometryToBeRotated.GetPoint1()) < mitk::eps) { m_SNCsToBeRotated.push_back(snc); } else { hitMultipleLines = true; } } } } bool moveSlices(true); if (geometryToBeRotated && anyOtherGeometry && ourViewportGeometry && !hitMultipleLines) { // assure all three are valid, so calculation of center of rotation can be done moveSlices = false; } // question in state machine is: "rotate?" if (moveSlices) // i.e. NOT rotate { return false; } else { // we DO have enough information for rotation m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project( cursorPosition); // remember where the last cursor position ON THE LINE has been observed if (anyOtherGeometry->IntersectionPoint( intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) // find center of rotation by intersection with any of the OTHER lines { return true; } else { return false; } } return false; } bool mitk::DisplayInteractor::CheckSwivelPossible(const mitk::InteractionEvent *interactionEvent) { const ScalarType ThresholdDistancePixels = 6.0; // Decide between moving and rotation: if we're close to the crossing // point of the planes, moving mode is entered, otherwise // rotation/swivel mode const InteractionPositionEvent *posEvent = dynamic_cast(interactionEvent); BaseRenderer *renderer = interactionEvent->GetSender(); if (!posEvent || !renderer) return false; const Point3D &cursor = posEvent->GetPositionInWorld(); m_SNCsToBeRotated.clear(); const PlaneGeometry *clickedGeometry(NULL); const PlaneGeometry *otherGeometry1(NULL); const PlaneGeometry *otherGeometry2(NULL); auto renWindows = interactionEvent->GetSender()->GetRenderingManager()->GetAllRegisteredRenderWindows(); for (auto renWin : renWindows) { SliceNavigationController *snc = BaseRenderer::GetInstance(renWin)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard3D) continue; // unsigned int slice = (*iter)->GetSlice()->GetPos(); // unsigned int time = (*iter)->GetTime()->GetPos(); const PlaneGeometry *planeGeometry = snc->GetCurrentPlaneGeometry(); if (!planeGeometry) continue; if (snc == renderer->GetSliceNavigationController()) { clickedGeometry = planeGeometry; m_SNCsToBeRotated.push_back(snc); } else { if (otherGeometry1 == NULL) { otherGeometry1 = planeGeometry; } else { otherGeometry2 = planeGeometry; } if (m_LinkPlanes) { // If planes are linked, apply rotation to all planes m_SNCsToBeRotated.push_back(snc); } } } mitk::Line3D line; mitk::Point3D point; if ((clickedGeometry != NULL) && (otherGeometry1 != NULL) && (otherGeometry2 != NULL) && clickedGeometry->IntersectionLine(otherGeometry1, line) && otherGeometry2->IntersectionPoint(line, point)) { m_CenterOfRotation = point; if (m_CenterOfRotation.EuclideanDistanceTo(cursor) < ThresholdDistancePixels) { return false; } else { m_ReferenceCursor = posEvent->GetPointerPositionOnScreen(); // Get main axes of rotation plane and store it for rotation step m_RotationPlaneNormal = clickedGeometry->GetNormal(); ScalarType xVector[] = {1.0, 0.0, 0.0}; ScalarType yVector[] = {0.0, 1.0, 0.0}; clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(xVector), m_RotationPlaneXVector); clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(yVector), m_RotationPlaneYVector); m_RotationPlaneNormal.Normalize(); m_RotationPlaneXVector.Normalize(); m_RotationPlaneYVector.Normalize(); m_PreviousRotationAxis.Fill(0.0); m_PreviousRotationAxis[2] = 1.0; m_PreviousRotationAngle = 0.0; return true; } } else { return false; } return false; } void mitk::DisplayInteractor::Init(StateMachineAction *, InteractionEvent *interactionEvent) { InteractionPositionEvent *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); m_CurrentDisplayCoordinate = m_LastDisplayCoordinate; positionEvent->GetSender()->DisplayToPlane(m_LastDisplayCoordinate, m_StartCoordinateInMM); m_LastCoordinateInMM = m_StartCoordinateInMM; } void mitk::DisplayInteractor::Move(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer *sender = interactionEvent->GetSender(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); float invertModifier = -1.0; if (m_InvertMoveDirection) { invertModifier = 1.0; } // perform translation Vector2D moveVector = (positionEvent->GetPointerPositionOnScreen() - m_LastDisplayCoordinate) * invertModifier; moveVector *= sender->GetScaleFactorMMPerDisplayUnit(); sender->GetCameraController()->MoveBy(moveVector); sender->GetRenderingManager()->RequestUpdate(sender->GetRenderWindow()); m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); } void mitk::DisplayInteractor::SetCrosshair(mitk::StateMachineAction *, mitk::InteractionEvent *interactionEvent) { const BaseRenderer::Pointer sender = interactionEvent->GetSender(); auto renWindows = sender->GetRenderingManager()->GetAllRegisteredRenderWindows(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); Point3D pos = positionEvent->GetPositionInWorld(); for (auto renWin : renWindows) { if (BaseRenderer::GetInstance(renWin)->GetMapperID() == BaseRenderer::Standard2D && renWin != sender->GetRenderWindow()) BaseRenderer::GetInstance(renWin)->GetSliceNavigationController()->SelectSliceByPoint(pos); } } void mitk::DisplayInteractor::Zoom(StateMachineAction *, InteractionEvent *interactionEvent) { const BaseRenderer::Pointer sender = interactionEvent->GetSender(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); float factor = 1.0; float distance = 0; if (m_ZoomDirection == "updown") { distance = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { distance = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertZoomDirection) { distance *= -1.0; } // set zooming speed if (distance < 0.0) { factor = 1.0 / m_ZoomFactor; } else if (distance > 0.0) { factor = 1.0 * m_ZoomFactor; } if (factor != 1.0) { sender->GetCameraController()->Zoom(factor, m_StartCoordinateInMM); sender->GetRenderingManager()->RequestUpdate(sender->GetRenderWindow()); } m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); } void mitk::DisplayInteractor::Scroll(StateMachineAction *, InteractionEvent *interactionEvent) { InteractionPositionEvent *positionEvent = static_cast(interactionEvent); mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (sliceNaviController) { int delta = 0; // Scrolling direction if (m_ScrollDirection == "updown") { delta = static_cast(m_LastDisplayCoordinate[1] - positionEvent->GetPointerPositionOnScreen()[1]); } else { delta = static_cast(m_LastDisplayCoordinate[0] - positionEvent->GetPointerPositionOnScreen()[0]); } if (m_InvertScrollDirection) { delta *= -1; } // Set how many pixels the mouse has to be moved to scroll one slice // if we moved less than 'm_IndexToSliceModifier' pixels slice ONE slice only if (delta > 0 && delta < m_IndexToSliceModifier) { delta = m_IndexToSliceModifier; } else if (delta < 0 && delta > -m_IndexToSliceModifier) { delta = -m_IndexToSliceModifier; } delta /= m_IndexToSliceModifier; int newPos = sliceNaviController->GetSlice()->GetPos() + delta; // if auto repeat is on, start at first slice if you reach the last slice and vice versa int maxSlices = sliceNaviController->GetSlice()->GetSteps(); if (m_AutoRepeat) { while (newPos < 0) { newPos += maxSlices; } while (newPos >= maxSlices) { newPos -= maxSlices; } } else { // if the new slice is below 0 we still show slice 0 // due to the stepper using unsigned int we have to do this ourselves if (newPos < 1) { newPos = 0; } } // set the new position sliceNaviController->GetSlice()->SetPos(newPos); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); + MITK_INFO << "using mitk::DisplayInteractor::Scroll"; } } void mitk::DisplayInteractor::ScrollOneDown(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Next(); + MITK_INFO << "using mitk::DisplayInteractor::ScrollOneDown"; } } void mitk::DisplayInteractor::ScrollOneUp(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::SliceNavigationController::Pointer sliceNaviController = interactionEvent->GetSender()->GetSliceNavigationController(); if (!sliceNaviController->GetSliceLocked()) { mitk::Stepper *stepper = sliceNaviController->GetSlice(); if (stepper->GetSteps() <= 1) { stepper = sliceNaviController->GetTime(); } stepper->Previous(); + MITK_INFO << "using mitk::DisplayInteractor::ScrollOneUp"; } } void mitk::DisplayInteractor::AdjustLevelWindow(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer::Pointer sender = interactionEvent->GetSender(); InteractionPositionEvent *positionEvent = static_cast(interactionEvent); m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // search for active image mitk::DataStorage::Pointer storage = sender->GetDataStorage(); mitk::DataNode::Pointer node = NULL; mitk::DataStorage::SetOfObjects::ConstPointer allImageNodes = storage->GetSubset(mitk::NodePredicateDataType::New("Image")); for (unsigned int i = 0; i < allImageNodes->size(); i++) { bool isActiveImage = false; bool propFound = allImageNodes->at(i)->GetBoolProperty("imageForLevelWindow", isActiveImage); if (propFound && isActiveImage) { node = allImageNodes->at(i); continue; } } if (node.IsNull()) { node = storage->GetNode(mitk::NodePredicateDataType::New("Image")); } if (node.IsNull()) { return; } mitk::LevelWindow lv = mitk::LevelWindow(); node->GetLevelWindow(lv); ScalarType level = lv.GetLevel(); ScalarType window = lv.GetWindow(); int levelIndex = 0; int windowIndex = 1; if (m_LevelDirection != "leftright") { levelIndex = 1; windowIndex = 0; } int directionModifier = 1; if (m_InvertLevelWindowDirection) { directionModifier = -1; } // calculate adjustments from mouse movements level += (m_CurrentDisplayCoordinate[levelIndex] - m_LastDisplayCoordinate[levelIndex]) * static_cast(2) * directionModifier; window += (m_CurrentDisplayCoordinate[windowIndex] - m_LastDisplayCoordinate[windowIndex]) * static_cast(2) * directionModifier; lv.SetLevelWindow(level, window); dynamic_cast(node->GetProperty("levelwindow"))->SetLevelWindow(lv); sender->GetRenderingManager()->RequestUpdateAll(); } void mitk::DisplayInteractor::StartRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->SetMouseCursor(rotate_cursor_xpm, 0, 0); } void mitk::DisplayInteractor::EndRotation(mitk::StateMachineAction *, mitk::InteractionEvent *) { this->ResetMouseCursor(); } void mitk::DisplayInteractor::Rotate(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const InteractionPositionEvent *posEvent = dynamic_cast(event); if (posEvent == nullptr) return; Point3D cursor = posEvent->GetPositionInWorld(); Vector3D toProjected = m_LastCursorPosition - m_CenterOfRotation; Vector3D toCursor = cursor - m_CenterOfRotation; // cross product: | A x B | = |A| * |B| * sin(angle) Vector3D axisOfRotation; vnl_vector_fixed vnlDirection = vnl_cross_3d(toCursor.GetVnlVector(), toProjected.GetVnlVector()); axisOfRotation.SetVnlVector(vnlDirection); // scalar product: A * B = |A| * |B| * cos(angle) // tan = sin / cos ScalarType angle = -atan2((double)(axisOfRotation.GetNorm()), (double)(toCursor * toProjected)); angle *= 180.0 / vnl_math::pi; m_LastCursorPosition = cursor; // create RotationOperation and apply to all SNCs that should be rotated RotationOperation rotationOperation(OpROTATE, m_CenterOfRotation, axisOfRotation, angle); // iterate the OTHER slice navigation controllers: these are filled in DoDecideBetweenRotationAndSliceSelection for (SNCVector::iterator iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&rotationOperation); (*iter)->SendCreatedWorldGeometryUpdate(); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::DisplayInteractor::Swivel(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const InteractionPositionEvent *posEvent = dynamic_cast(event); if (!posEvent) return; // Determine relative mouse movement projected onto world space Point2D cursor = posEvent->GetPointerPositionOnScreen(); Vector2D relativeCursor = cursor - m_ReferenceCursor; Vector3D relativeCursorAxis = m_RotationPlaneXVector * relativeCursor[0] + m_RotationPlaneYVector * relativeCursor[1]; // Determine rotation axis (perpendicular to rotation plane and cursor // movement) Vector3D rotationAxis = itk::CrossProduct(m_RotationPlaneNormal, relativeCursorAxis); ScalarType rotationAngle = relativeCursor.GetNorm() / 2.0; // Restore the initial plane pose by undoing the previous rotation // operation RotationOperation op(OpROTATE, m_CenterOfRotation, m_PreviousRotationAxis, -m_PreviousRotationAngle); SNCVector::iterator iter; for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; timeGeometry->ExecuteOperation(&op); (*iter)->SendCreatedWorldGeometryUpdate(); } } // Apply new rotation operation to all relevant SNCs RotationOperation op2(OpROTATE, m_CenterOfRotation, rotationAxis, rotationAngle); for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { // Retrieve the TimeGeometry of this SliceNavigationController TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (!timeGeometry) continue; // Execute the new rotation timeGeometry->ExecuteOperation(&op2); // Notify listeners (*iter)->SendCreatedWorldGeometryUpdate(); } } m_PreviousRotationAxis = rotationAxis; m_PreviousRotationAngle = rotationAngle; RenderingManager::GetInstance()->RequestUpdateAll(); return; } -void mitk::DisplayInteractor::UpdateStatusbar(mitk::StateMachineAction *, mitk::InteractionEvent *event) +void mitk::DisplayInteractor::SetStatusBarInfoFromClickedPosition(mitk::StateMachineAction *, mitk::InteractionEvent *event) { const InteractionPositionEvent *posEvent = dynamic_cast(event); if (!posEvent) return; std::string statusText; TNodePredicateDataType::Pointer isImageData = TNodePredicateDataType::New(); mitk::DataStorage::SetOfObjects::ConstPointer nodes = posEvent->GetSender()->GetDataStorage()->GetSubset(isImageData).GetPointer(); mitk::Point3D worldposition = posEvent->GetPositionInWorld(); mitk::Image::Pointer image3D; mitk::DataNode::Pointer node; mitk::DataNode::Pointer topSourceNode; int component = 0; node = this->GetTopLayerNode(nodes, worldposition, posEvent->GetSender()); if (node.IsNotNull()) { bool isBinary(false); node->GetBoolProperty("binary", isBinary); if (isBinary) { mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = posEvent->GetSender()->GetDataStorage()->GetSources(node, NULL, true); if (!sourcenodes->empty()) { topSourceNode = this->GetTopLayerNode(sourcenodes, worldposition, posEvent->GetSender()); } if (topSourceNode.IsNotNull()) { image3D = dynamic_cast(topSourceNode->GetData()); topSourceNode->GetIntProperty("Image.Displayed Component", component); } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } else { image3D = dynamic_cast(node->GetData()); node->GetIntProperty("Image.Displayed Component", component); } } +<<<<<<< HEAD // get the position and gray value from the image and build up status bar text if (image3D.IsNotNull()) { itk::Index<3> p; image3D->GetGeometry()->WorldToIndex(worldposition, p); mitk::ScalarType pixelValue; mitkPixelTypeMultiplex5(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, image3D->GetVolumeData(posEvent->GetSender()->GetTimeStep()), p, pixelValue, component); mitk::StatusBar::GetInstance()->DisplayImageInfo(worldposition, p, posEvent->GetSender()->GetTime(), pixelValue); } else { mitk::StatusBar::GetInstance()->DisplayImageInfoInvalid(); } +======= +// std::stringstream stream; +// stream.imbue(std::locale::classic()); + +// // get the position and gray value from the image and build up status bar text + if(image3D.IsNotNull()) + { + itk::Index<3> p; + image3D->GetGeometry()->WorldToIndex(worldposition, p); +// stream.precision(2); +// stream<<"Position: <" << std::fixed < mm"; +// stream<<"; Index: <"< "; + +// mitk::ScalarType pixelValue; + +// mitkPixelTypeMultiplex5( +// mitk::FastSinglePixelAccess, +// image3D->GetChannelDescriptor().GetPixelType(), +// image3D, +// image3D->GetVolumeData(posEvent->GetSender()->GetTimeStep()), +// p, +// pixelValue, +// component); + + + +// if (fabs(pixelValue)>1000000 || fabs(pixelValue) < 0.01) +// { +// stream<<"; Time: " << posEvent->GetSender()->GetTime() << " ms; Pixelvalue: " << std::scientific<< pixelValue <<" "; +// } +// else +// { +// stream<<"; Time: " << posEvent->GetSender()->GetTime() << " ms; Pixelvalue: "<< pixelValue <<" "; +// } +// } +// else +// { +// stream << "No image information at this position!"; +// } + +// statusText = stream.str(); +// mitk::StatusBar::GetInstance()->DisplayGreyValueText(statusText.c_str()); + TimeStepType time = posEvent->GetSender()->GetTimeStep(); + UpdateStatusBar(image3D, p, time, component); + } +} + +void mitk::DisplayInteractor::UpdateStatusBar(itk::SmartPointer image3D, itk::Index<3> idx, TimeStepType time, int component) +{ + std::stringstream stream; + stream.imbue(std::locale::classic()); + + // get the position and gray value from the image and build up status bar text + if(image3D.IsNotNull()) + { + mitk::Point3D worldposition; + image3D->GetGeometry()->IndexToWorld(idx, worldposition); + stream.precision(2); + stream<<"Position: <" << std::fixed < mm"; + stream<<"; Index: <"< "; + + mitk::ScalarType pixelValue; + + mitkPixelTypeMultiplex5( + mitk::FastSinglePixelAccess, + image3D->GetChannelDescriptor().GetPixelType(), + image3D, + image3D->GetVolumeData(time), + idx, + pixelValue, + component); + + + + if (fabs(pixelValue)>1000000 || fabs(pixelValue) < 0.01) + { + stream<<"; Pixelvalue: " << std::scientific<< pixelValue <<" "; + } + else + { + stream<<"; Pixelvalue: "<< pixelValue <<" "; + } + } + else + { + stream << "No image information at this position!"; + } + + std::string statusText = stream.str(); + mitk::StatusBar::GetInstance()->DisplayGreyValueText(statusText.c_str()); +>>>>>>> T19609-integration-branch } void mitk::DisplayInteractor::ConfigurationChanged() { mitk::PropertyList::Pointer properties = GetAttributes(); // auto repeat std::string strAutoRepeat = ""; if (properties->GetStringProperty("autoRepeat", strAutoRepeat)) { if (strAutoRepeat == "true") { m_AutoRepeat = true; } else { m_AutoRepeat = false; } } // pixel movement for scrolling one slice std::string strPixelPerSlice = ""; if (properties->GetStringProperty("pixelPerSlice", strPixelPerSlice)) { m_IndexToSliceModifier = atoi(strPixelPerSlice.c_str()); } else { m_IndexToSliceModifier = 4; } // scroll direction if (!properties->GetStringProperty("scrollDirection", m_ScrollDirection)) { m_ScrollDirection = "updown"; } m_InvertScrollDirection = GetBoolProperty(properties, "invertScrollDirection", false); // zoom direction if (!properties->GetStringProperty("zoomDirection", m_ZoomDirection)) { m_ZoomDirection = "updown"; } m_InvertZoomDirection = GetBoolProperty(properties, "invertZoomDirection", false); m_InvertMoveDirection = GetBoolProperty(properties, "invertMoveDirection", false); if (!properties->GetStringProperty("levelWindowDirection", m_LevelDirection)) { m_LevelDirection = "leftright"; } m_InvertLevelWindowDirection = GetBoolProperty(properties, "invertLevelWindowDirection", false); // coupled rotation std::string strCoupled = ""; if (properties->GetStringProperty("coupled", strCoupled)) { if (strCoupled == "true") m_LinkPlanes = true; else m_LinkPlanes = false; } // zoom factor std::string strZoomFactor = ""; properties->GetStringProperty("zoomFactor", strZoomFactor); m_ZoomFactor = .05; if (atoi(strZoomFactor.c_str()) > 0) { m_ZoomFactor = 1.0 + (atoi(strZoomFactor.c_str()) / 100.0); } // allwaysReact std::string strAlwaysReact = ""; if (properties->GetStringProperty("alwaysReact", strAlwaysReact)) { if (strAlwaysReact == "true") { m_AlwaysReact = true; } else { m_AlwaysReact = false; } } else { m_AlwaysReact = false; } } bool mitk::DisplayInteractor::FilterEvents(InteractionEvent *interactionEvent, DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } bool mitk::DisplayInteractor::GetBoolProperty(mitk::PropertyList::Pointer propertyList, const char *propertyName, bool defaultValue) { std::string valueAsString; if (!propertyList->GetStringProperty(propertyName, valueAsString)) { return defaultValue; } else { if (valueAsString == "true") { return true; } else { return false; } } } mitk::DataNode::Pointer mitk::DisplayInteractor::GetTopLayerNode(mitk::DataStorage::SetOfObjects::ConstPointer nodes, mitk::Point3D worldposition, BaseRenderer *ren) { mitk::DataNode::Pointer node; if (nodes.IsNotNull()) { int maxlayer = -32768; bool isHelper(false); for (unsigned int x = 0; x < nodes->size(); x++) { nodes->at(x)->GetBoolProperty("helper object", isHelper); if (nodes->at(x)->GetData()->GetGeometry()->IsInside(worldposition) && isHelper == false) { int layer = 0; if (!(nodes->at(x)->GetIntProperty("layer", layer))) continue; if (layer > maxlayer) { if (static_cast(nodes->at(x))->IsVisible(ren)) { node = nodes->at(x); maxlayer = layer; } } } } } return node; } diff --git a/Modules/DiffusionImaging/MiniApps/CMakeLists.txt b/Modules/DiffusionImaging/MiniApps/CMakeLists.txt index 5b02b7e213..742c5e1dc4 100755 --- a/Modules/DiffusionImaging/MiniApps/CMakeLists.txt +++ b/Modules/DiffusionImaging/MiniApps/CMakeLists.txt @@ -1,57 +1,58 @@ if(BUILD_DiffusionMiniApps OR MITK_BUILD_ALL_APPS) # needed include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # list of diffusion miniapps # if an app requires additional dependencies # they are added after a "^^" and separated by "_" set( diffusionminiapps NetworkCreation^^MitkFiberTracking_MitkConnectomics NetworkStatistics^^MitkConnectomics + Fiberfox^^MitkFiberTracking MultishellMethods^^MitkFiberTracking PeaksAngularError^^MitkFiberTracking PeakExtraction^^MitkFiberTracking FiberExtraction^^MitkFiberTracking FiberProcessing^^MitkFiberTracking FiberDirectionExtraction^^MitkFiberTracking LocalDirectionalFiberPlausibility^^MitkFiberTracking StreamlineTracking^^MitkFiberTracking GibbsTracking^^MitkFiberTracking TractometerMetrics^^MitkFiberTracking FileFormatConverter^^MitkFiberTracking DFTraining^^MitkFiberTracking DFTracking^^MitkFiberTracking ) foreach(diffusionminiapp ${diffusionminiapps}) # extract mini app name and dependencies string(REPLACE "^^" "\\;" miniapp_info ${diffusionminiapp}) set(miniapp_info_list ${miniapp_info}) list(GET miniapp_info_list 0 appname) list(GET miniapp_info_list 1 raw_dependencies) string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") set(dependencies_list ${dependencies}) mitkFunctionCreateCommandLineApp( NAME ${appname} DEPENDS MitkCore MitkDiffusionCore ${dependencies_list} PACKAGE_DEPENDS ITK ) endforeach() # This mini app does not depend on mitkDiffusionImaging at all mitkFunctionCreateCommandLineApp( NAME Dicom2Nrrd DEPENDS MitkCore ${dependencies_list} ) if(EXECUTABLE_IS_ENABLED) MITK_INSTALL_TARGETS(EXECUTABLES ${EXECUTABLE_TARGET}) endif() endif() diff --git a/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp b/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp index e30bbfa158..b89d0995b8 100644 --- a/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp +++ b/Modules/DiffusionImaging/MiniApps/ExtractImageStatistics.cpp @@ -1,127 +1,129 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#include "MiniAppManager.h" +//#include "MiniAppManager.h" -#include "ctkCommandLineParser.h" +//#include "ctkCommandLineParser.h" +#include "mitkCommandLineParser.cpp" #include "mitkImage.h" #include "mitkImageStatisticsCalculator.h" #include "mitkIOUtil.h" #include #include #include int ExtractImageStatistics(int argc, char* argv[]) { mitkCommandLineParser parser; parser.setTitle("Extract Image Statistics"); parser.setCategory("Preprocessing Tools"); parser.setDescription(""); parser.setContributor("MBI"); parser.setArgumentPrefix("--", "-"); parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); parser.addArgument("mask", "m", mitkCommandLineParser::InputFile, "Mask:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),false); parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: filenameOfRoi.nrrd_statistics.txt)", us::Any()); map parsedArgs = parser.parseArguments(argc, argv); if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << "\n\n MiniApp Description: \nCalculates statistics on the supplied image using given mask." << endl; std::cout << "Output is written to the designated output file in this order:" << endl; std::cout << "Mean, Standard Deviation, RMS, Max, Min, Number of Voxels, Volume [mm3]" << endl; std::cout << "\n\n Parameters:"<< endl; std::cout << parser.helpText(); return EXIT_SUCCESS; } + // Parameters: bool ignoreZeroValues = false; unsigned int timeStep = 0; std::string inputImageFile = us::any_cast(parsedArgs["input"]); std::string maskImageFile = us::any_cast(parsedArgs["mask"]); std::string outFile; if (parsedArgs.count("out") || parsedArgs.count("o") ) outFile = us::any_cast(parsedArgs["out"]); else outFile = inputImageFile + "_statistics.txt"; // Load image and mask mitk::Image::Pointer maskImage = mitk::IOUtil::LoadImage(maskImageFile); mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); // Calculate statistics mitk::ImageStatisticsCalculator::Statistics statisticsStruct; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); try { calculator->SetImage(inputImage); calculator->SetImageMask(maskImage); calculator->SetMaskingModeToImage(); } catch( const itk::ExceptionObject& e) { MITK_ERROR << "Statistic Calculation Failed - ITK Exception:" << e.what(); return -1; } calculator->SetDoIgnorePixelValue(ignoreZeroValues); calculator->SetIgnorePixelValue(0); try { calculator->ComputeStatistics(timeStep); } catch ( mitk::Exception& e) { MITK_ERROR<< "MITK Exception: " << e.what(); return -1; } statisticsStruct = calculator->GetStatistics(timeStep); // Calculate Volume double volume = 0; const mitk::BaseGeometry *geometry = inputImage->GetGeometry(); if ( geometry != NULL ) { const mitk::Vector3D &spacing = inputImage->GetGeometry()->GetSpacing(); volume = spacing[0] * spacing[1] * spacing[2] * (double) statisticsStruct.GetN(); } // Write Results to file std::ofstream output; output.open(outFile.c_str()); output << statisticsStruct.GetMean() << " , "; output << statisticsStruct.GetSigma() << " , "; output << statisticsStruct.GetRMS() << " , "; output << statisticsStruct.GetMax() << " , "; output << statisticsStruct.GetMin() << " , "; output << statisticsStruct.GetN() << " , "; output << volume << "\n"; output.flush(); output.close(); return 0; } RegisterDiffusionMiniApp(ExtractImageStatistics); diff --git a/Modules/DiffusionImaging/MiniApps/HotspotMiniApp.cpp b/Modules/DiffusionImaging/MiniApps/HotspotMiniApp.cpp new file mode 100644 index 0000000000..3dae227d76 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/HotspotMiniApp.cpp @@ -0,0 +1,103 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkCommandLineParser.h" +#include "mitkImage.h" +#include "mitkIOUtil.h" +#include +#include +#include +#include + + +int main( int argc, char* argv[] ) +{ + mitkCommandLineParser parser; + + parser.setTitle("Test basic hotspot functionality"); + parser.setCategory("Preprocessing Tools"); + parser.setDescription(""); + parser.setContributor("MBI"); + + parser.setArgumentPrefix("--", "-"); + parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); + parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); + parser.addArgument("mask", "m", mitkCommandLineParser::InputFile, "Mask:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),true); + parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: hotspotMiniApp_output.nrrd)", us::Any()); + + std::cout << "test...." << std::endl; + + std::map parsedArgs = parser.parseArguments(argc, argv); + std::cout << "parsedArgs.size()= " << parsedArgs.size() << std::endl; + if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) + { +// std::cout << "\n\n MiniApp Description: \nCalculates and saves the hotspot of an image." << endl; +// std::cout << "Output is written to the designated output file" << endl; +// std::cout << parser.helpText(); +// return EXIT_SUCCESS; + } + + // Parameters: + std::string inputImageFile; + if (!parsedArgs.count("i") && !parsedArgs.count("input")) + { + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + } + else + { + inputImageFile = us::any_cast(parsedArgs["input"]); + } + + mitk::Image::Pointer maskImage; + if (parsedArgs.count("mask") || parsedArgs.count("m")) + { + std::string maskImageFile = us::any_cast(parsedArgs["mask"]); + maskImage = mitk::IOUtil::LoadImage(maskImageFile); + } + + std::string outFile; + if (parsedArgs.count("out") || parsedArgs.count("o") ) + outFile = us::any_cast(parsedArgs["out"]); + else + outFile = "hotspotMiniApp_output.nrrd"; + + // Load image and mask + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + // Calculate statistics + mitk::HotspotMaskGenerator::Pointer hotspotCalc = mitk::HotspotMaskGenerator::New(); + hotspotCalc->SetInputImage(inputImage); + hotspotCalc->SetHotspotRadiusInMM(10); + hotspotCalc->SetTimeStep(0); + mitk::Image::Pointer outImage; + try + { + outImage = hotspotCalc->GetMask(); + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed - ITK Exception:" << e.what(); + return -1; + } + + if (outImage != nullptr) + { + mitk::IOUtil::SaveImage(outImage, outFile); + } + + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniApp.cpp b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniApp.cpp new file mode 100644 index 0000000000..f2bef60001 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniApp.cpp @@ -0,0 +1,218 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkCommandLineParser.h" +#include "mitkImage.h" +#include "mitkImageStatisticsCalculator.h" +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct statistics_res{ + double mean, variance, min, max, count, moment; +}; + +void printstats(statistics_res s) +{ + std::cout << "mean: " << s.mean << std::endl + << "variance: " << s.variance << std::endl + << "min: " << s.min << std::endl + << "max: " << s.max << std::endl + << "count: " << s.count << std::endl + << "moment: " << s.moment << std::endl; +} + +template < typename TPixel, unsigned int VImageDimension > +void get_statistics_boost(itk::Image* itkImage, statistics_res& res){ + typedef itk::Image ImageType; + + itk::ImageRegionConstIterator it(itkImage, itkImage->GetLargestPossibleRegion()); + + TPixel currentPixel; + int ctr=0; + double sum=0; + + boost::accumulators::accumulator_set> > acc; + + for (it.GoToBegin(); !it.IsAtEnd(); ++it) + { + acc(it.Get()); +// currentPixel = it.Get(); +// sum+=currentPixel; +// ctr+=1; + } + +// res.mean=(double)sum/(double)ctr; + res.mean = boost::accumulators::mean(acc); + res.variance = boost::accumulators::variance(acc); + res.min = boost::accumulators::min(acc); + res.max = boost::accumulators::max(acc); + res.count = boost::accumulators::count(acc); + res.moment = boost::accumulators::moment<2>(acc); + + std::cout << "sum: " << sum << " N: " << ctr << " mean: " << res.mean << std::endl; +} + +int main( int argc, char* argv[] ) +{ + mitkCommandLineParser parser; + + parser.setTitle("Extract Image Statistics"); + parser.setCategory("Preprocessing Tools"); + parser.setDescription(""); + parser.setContributor("MBI"); + + parser.setArgumentPrefix("--", "-"); + parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); + parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); + parser.addArgument("mask", "m", mitkCommandLineParser::InputFile, "Mask:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),true); + parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: filenameOfRoi.nrrd_statistics.txt)", us::Any()); + + std::cout << "test...." << std::endl; + + std::map parsedArgs = parser.parseArguments(argc, argv); + std::cout << "parsedArgs.size()= " << parsedArgs.size() << std::endl; + if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) + { + std::cout << "\n\n MiniApp Description: \nCalculates statistics on the supplied image using given mask." << endl; + std::cout << "Output is written to the designated output file in this order:" << endl; + std::cout << "Mean, Standard Deviation, RMS, Max, Min, Number of Voxels, Volume [mm3]" << endl; + std::cout << "\n\n Parameters:"<< endl; + std::cout << parser.helpText(); + return EXIT_SUCCESS; + } + + + // Parameters: + bool ignoreZeroValues = false; + unsigned int timeStep = 0; + + std::string inputImageFile = us::any_cast(parsedArgs["input"]); + mitk::Image::Pointer maskImage; + if (parsedArgs.count("mask") || parsedArgs.count("m")) + { + std::string maskImageFile = us::any_cast(parsedArgs["mask"]); + maskImage = mitk::IOUtil::LoadImage(maskImageFile); + } + + std::string outFile; + if (parsedArgs.count("out") || parsedArgs.count("o") ) + outFile = us::any_cast(parsedArgs["out"]); + else + outFile = inputImageFile + "_statistics.txt"; + + // Load image and mask + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + // Calculate statistics + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer statisticsStruct; + mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); + try + { + calculator->SetInputImage(inputImage); + if (parsedArgs.count("mask") || parsedArgs.count("m")) + { + mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); + imgMask->SetImageMask(maskImage); + imgMask->SetTimeStep(timeStep); + calculator->SetMask(imgMask.GetPointer()); + } + else + { + calculator->SetMask(nullptr); + } + + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Statistic Calculation Failed - ITK Exception:" << e.what(); + return -1; + } + + + if (ignoreZeroValues) + { + // TODO, cannot have more than one mask, using ignore pixel value will override the image mask :-c + mitk::IgnorePixelMaskGenerator::Pointer ignorePixelMask = mitk::IgnorePixelMaskGenerator::New(); + ignorePixelMask->SetInputImage(inputImage); + ignorePixelMask->SetTimeStep(timeStep); + ignorePixelMask->SetIgnoredPixelValue(0); + calculator->SetMask(ignorePixelMask.GetPointer()); + } + + std::cout << "calculating statistics itk: " << std::endl; + try + { + statisticsStruct = calculator->GetStatistics(timeStep); + } + catch ( mitk::Exception& e) + { + MITK_ERROR<< "MITK Exception: " << e.what(); + return -1; + } + + + // Calculate Volume + double volume = 0; + const mitk::BaseGeometry *geometry = inputImage->GetGeometry(); + if ( geometry != NULL ) + { + const mitk::Vector3D &spacing = inputImage->GetGeometry()->GetSpacing(); + volume = spacing[0] * spacing[1] * spacing[2] * (double) statisticsStruct->GetN(); + } + + // Write Results to file + std::ofstream output; + output.open(outFile.c_str()); + output << statisticsStruct->GetMean() << " , "; + output << statisticsStruct->GetStd() << " , "; + output << statisticsStruct->GetRMS() << " , "; + output << statisticsStruct->GetMax() << " , "; + output << statisticsStruct->GetMin() << " , "; + output << statisticsStruct->GetN() << " , "; + output << volume << "\n"; + + output.flush(); + output.close(); + + std::cout << "calculating statistics boost: " << std::endl; + + statistics_res res; + AccessByItk_n(inputImage, get_statistics_boost, (res)); + + printstats(res); + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_3.cpp b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_3.cpp new file mode 100644 index 0000000000..ed343d9861 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_3.cpp @@ -0,0 +1,84 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkCommandLineParser.h" +#include "mitkImage.h" +#include +#include +#include +#include +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +int main( int argc, char* argv[] ) +{ + unsigned int timeStep = 0; + std::string inputImageFile, maskImageFile; + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_someSegmentation.nrrd"; + // Load image + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + mitk::Image::Pointer maskImage = mitk::IOUtil::LoadImage(maskImageFile); + + + // Calculate statistics + mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); + + std::cout << "calculating statistics (unmasked) itk: " << std::endl; + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result; + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(maskImage); + + calculator->SetMask(imgMaskGen.GetPointer()); + calculator->SetInputImage(inputImage); + calculator->SetNBinsForHistogramStatistics(100); + + for (unsigned int i=0; i < inputImage->GetTimeSteps(); i++) + { + std::cout << "Results for time step " << i << ":" << std::endl; + result = calculator->GetStatistics(i, 1); + result->Print(); + std::cout << std::endl; + } + MITK_INFO << "-------------"; + mitk::Image::Pointer test = imgMaskGen->GetMask(); + MITK_INFO << "-------------"; + + mitk::Image::Pointer test2 = imgMaskGen->GetMask(); + MITK_INFO << "-------------"; + imgMaskGen->SetTimeStep(2); + mitk::Image::Pointer test3 = imgMaskGen->GetMask(); + + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_v2.cpp b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_v2.cpp new file mode 100644 index 0000000000..a81d680524 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/ImageStatisticsMiniapp_v2.cpp @@ -0,0 +1,360 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkCommandLineParser.h" +#include "mitkImage.h" +#include +#include +#include +#include +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct statistics_res{ + double mean, variance, min, max, count, moment; +}; + +long getTimeInMs() +{ + std::chrono::milliseconds ms = std::chrono::duration_cast< std::chrono::milliseconds > (std::chrono::system_clock::now().time_since_epoch()); + long time = ms.count(); + return time; +} + +//std::string printstats(mitk::ImageStatisticsCalculator::Statistics statisticsStruct) +//{ +// std::string res = ""; +// res += std::string("Entropy ") + std::to_string(statisticsStruct.GetEntropy()) + std::string("\n"); +// res += std::string("Kurtosis ") + std::to_string(statisticsStruct.GetKurtosis()) + std::string("\n"); +// res += std::string("MPP ") + std::to_string(statisticsStruct.GetMPP()) + std::string("\n"); +// res += std::string("Max ") + std::to_string(statisticsStruct.GetMax()) + std::string("\n"); +// res += std::string("Mean ") + std::to_string(statisticsStruct.GetMean()) + std::string("\n"); +// res += std::string("Median ") + std::to_string(statisticsStruct.GetMedian()) + std::string("\n"); +// res += std::string("Min ") + std::to_string(statisticsStruct.GetMin()) + std::string("\n"); +// res += std::string("N ") + std::to_string(statisticsStruct.GetN()) + std::string("\n"); +// res += std::string("RMS ") + std::to_string(statisticsStruct.GetRMS()) + std::string("\n"); +// res += std::string("Skewness ") + std::to_string(statisticsStruct.GetSkewness()) + std::string("\n"); +// res += std::string("Std ") + std::to_string(statisticsStruct.GetSigma()) + std::string("\n"); +// res += std::string("UPP ") + std::to_string(statisticsStruct.GetUPP()) + std::string("\n"); +// res += std::string("Uniformity ") + std::to_string(statisticsStruct.GetUniformity()) + std::string("\n"); +// res += std::string("Variance ") + std::to_string(statisticsStruct.GetVariance()) + std::string("\n"); +// vnl_vector minIndex = statisticsStruct.GetMinIndex(); +// vnl_vector maxIndex = statisticsStruct.GetMaxIndex(); + +// res += "Min Index: "; +// for (auto it = minIndex.begin(); it != minIndex.end(); it++) +// { +// res += std::to_string(*it) + " "; +// } +// res += std::string("\n"); + +// res += "Max Index: "; +// for (auto it = maxIndex.begin(); it != maxIndex.end(); it++) +// { +// res += std::to_string(*it) + " "; +// } +// res += std::string("\n"); +// return res; +//} + +template +void printMap(std::map input) +{ + for (auto it = input.begin(); it != input.end(); ++it) + { + std::cout << it->first<< ": " << it->second<< std::endl; + } + std::cout << std::endl; +} + +template < typename TPixel, unsigned int VImageDimension > +void get_statistics_boost(itk::Image* itkImage, statistics_res& res){ + typedef itk::Image ImageType; + + itk::ImageRegionConstIterator it(itkImage, itkImage->GetLargestPossibleRegion()); + + int ctr=0; + double sum=0; + + boost::accumulators::accumulator_set> > acc; + + for (it.GoToBegin(); !it.IsAtEnd(); ++it) + { + acc(it.Get()); +// currentPixel = it.Get(); +// sum+=currentPixel; +// ctr+=1; + } + +// res.mean=(double)sum/(double)ctr; + res.mean = boost::accumulators::mean(acc); + res.variance = boost::accumulators::variance(acc); + res.min = boost::accumulators::min(acc); + res.max = boost::accumulators::max(acc); + res.count = boost::accumulators::count(acc); + res.moment = boost::accumulators::moment<2>(acc); + + std::cout << "sum: " << sum << " N: " << ctr << " mean: " << res.mean << std::endl; +} + +void compute_statistics(std::string inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd", + std::string outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_new.txt", + std::string outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_old.txt", + std::string maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_someSegmentation.nrrd", + std::string pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_rectangle.pf" ) +{ + ofstream outFile; + outFile.open(outfname); + + unsigned int timeStep = 0; + + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + mitk::Image::Pointer maskImage; + maskImage = mitk::IOUtil::LoadImage(maskImageFile); + + std::vector loadedObjects; + loadedObjects = mitk::IOUtil::Load(pFfile); + mitk::BaseData::Pointer pf = loadedObjects[0]; + mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(pf.GetPointer()); + + mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result; + calculator->SetInputImage(inputImage); + calculator->SetNBinsForHistogramStatistics(100); + + long start_time; + + outFile << "Calculating Statistics on Pic3D" << std::endl << std::endl; + + outFile << "New Image Statistics Calculator" << std::endl; + + start_time = getTimeInMs(); + outFile << "1) unmasked: " << std::endl; + calculator->SetMask(nullptr); + result = calculator->GetStatistics(timeStep); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "2) planarfigure: " << std::endl; + mitk::PlanarFigureMaskGenerator::Pointer planarFigMaskExtr = mitk::PlanarFigureMaskGenerator::New(); + planarFigMaskExtr->SetPlanarFigure(planarFigure); + planarFigMaskExtr->SetInputImage(inputImage); + calculator->SetMask(planarFigMaskExtr.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "3) ignore pixel value mask: " << std::endl; + mitk::IgnorePixelMaskGenerator::Pointer ignPixVal = mitk::IgnorePixelMaskGenerator::New(); + ignPixVal->SetInputImage(inputImage); + ignPixVal->SetIgnoredPixelValue(0); + calculator->SetMask(ignPixVal.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "4) image mask: " << std::endl; + mitk::ImageMaskGenerator::Pointer binaryImageMaskGen = mitk::ImageMaskGenerator::New(); + binaryImageMaskGen->SetImageMask(maskImage); + calculator->SetMask(binaryImageMaskGen.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + + start_time = getTimeInMs(); + outFile << "5) hotspot mask: " << std::endl; + mitk::HotspotMaskGenerator::Pointer hotSpotMaskGen = mitk::HotspotMaskGenerator::New(); + hotSpotMaskGen->SetInputImage(inputImage); + hotSpotMaskGen->SetHotspotRadiusInMM(10); + hotSpotMaskGen->SetTimeStep(0); + calculator->SetMask(hotSpotMaskGen.GetPointer()); + result = calculator->GetStatistics(timeStep, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + //mitk::IOUtil::SaveImage(hotSpotMaskGen->GetMask(), "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusTHotspot.nrrd"); + + outFile << "6) all time steps (image mask): " << std::endl; + if (inputImage->GetTimeSteps() > 1) + { + start_time = getTimeInMs(); + for (unsigned int i=0; i < inputImage->GetTimeSteps(); i++) + { + outFile << "timestep: " << i << std::endl; + calculator->SetMask(binaryImageMaskGen.GetPointer()); + result = calculator->GetStatistics(i, 1); + outFile << result->GetAsString() << std::endl; + outFile << "new image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + } + } + else + { + outFile << "Input image has only 1 time step!" << std::endl; + } + + outFile.flush(); + outFile.close(); + + // ----------------------------------------------------------------------------------------------------------------------------------------------------------- +// outFile.open(outfname2); + +// std::cout << std::endl << std::endl << "calculating statistics old imgstatcalc: " << std::endl; +// mitk::ImageStatisticsCalculator::Statistics statisticsStruct; +// mitk::ImageStatisticsCalculator::Pointer calculator_old; + + + +// start_time = getTimeInMs(); +// outFile << "1) unmasked: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetMaskingModeToNone(); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + +// start_time = getTimeInMs(); +// outFile << "2) planarFigure: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetPlanarFigure(planarFigure); +// calculator_old->SetMaskingModeToPlanarFigure(); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + +// start_time = getTimeInMs(); +// outFile << "3) IgnorePixelValue: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetMaskingModeToNone(); +// calculator_old->SetDoIgnorePixelValue(true); +// calculator_old->SetIgnorePixelValue(0); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; +// calculator_old->SetDoIgnorePixelValue(false); + +// start_time = getTimeInMs(); +// outFile << "4) Image Mask: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetImageMask(maskImage); +// calculator_old->SetMaskingModeToImage(); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; + +// start_time = getTimeInMs(); +// outFile << "5) Hotspot Mask: " << std::endl; +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetMaskingModeToNone(); +// calculator_old->SetCalculateHotspot(true); +// calculator_old->SetHotspotRadiusInMM(10); +// calculator_old->ComputeStatistics(timeStep); +// statisticsStruct = calculator_old->GetStatistics(timeStep).GetHotspotStatistics(); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; +// calculator_old->SetCalculateHotspot(false); + +// outFile << "6) all time steps (image mask): " << std::endl; +// if (inputImage->GetTimeSteps() > 1) +// { +// start_time = getTimeInMs(); +// calculator_old = mitk::ImageStatisticsCalculator::New(); +// calculator_old->SetHistogramBinSize(100); +// calculator_old->SetImage(inputImage); +// calculator_old->SetImageMask(maskImage); +// calculator_old->SetMaskingModeToImage(); +// for (unsigned int i=0; i < inputImage->GetTimeSteps(); i++) +// { +// calculator_old->ComputeStatistics(i); +// statisticsStruct = calculator_old->GetStatistics(i); +// outFile << printstats(statisticsStruct) << std::endl; +// outFile << "old image statistics calculator took: " << getTimeInMs()-start_time << " ms." << std::endl << std::endl; +// } +// } +// else +// { +// outFile << "Input image has only 1 time step!" << std::endl; +// } + +// outFile.flush(); +// outFile.close(); +} + +int main( int argc, char* argv[] ) +{ + std::string inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + std::string outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_new.txt"; + std::string outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_statistics_old.txt"; + std::string maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_someSegmentation.nrrd"; + std::string pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_rectangle.pf"; + compute_statistics(inputImageFile, outfname, outfname2, maskImageFile, pFfile); + + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT.nrrd"; + outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_statistics_new.txt"; + outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_statistics_old.txt"; + maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_someSegmentation.nrrd"; + pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic2DplusT_ellipse.pf"; + compute_statistics(inputImageFile, outfname, outfname2, maskImageFile, pFfile); + + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct.pic"; + outfname = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_statistics_new.txt"; + outfname2 = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_statistics_old.txt"; + maskImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_segmentation.nrrd"; + pFfile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/leber_ct_PF.pf"; + compute_statistics(inputImageFile, outfname, outfname2, maskImageFile, pFfile); + + + return EXIT_SUCCESS; +} diff --git a/Modules/DiffusionImaging/MiniApps/PlanarFigureMaskCreatorMiniapp.cpp b/Modules/DiffusionImaging/MiniApps/PlanarFigureMaskCreatorMiniapp.cpp new file mode 100644 index 0000000000..ec163dc9e1 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/PlanarFigureMaskCreatorMiniapp.cpp @@ -0,0 +1,131 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkCommandLineParser.h" +#include "mitkImage.h" +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include +#include + + +int main( int argc, char* argv[] ) +{ + mitkCommandLineParser parser; + + parser.setTitle("Test basic hotspot functionality"); + parser.setCategory("Preprocessing Tools"); + parser.setDescription(""); + parser.setContributor("MBI"); + + parser.setArgumentPrefix("--", "-"); + parser.addArgument("help", "h", mitkCommandLineParser::String, "Help:", "Show this help text"); + parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image", us::Any(),false); + parser.addArgument("planarfigure", "p", mitkCommandLineParser::InputFile, "PlanarFigure:", "mask image / roi image denotin area on which statistics are calculated", us::Any(),false); + parser.addArgument("out", "o", mitkCommandLineParser::OutputFile, "Output", "output file (default: hotspotMiniApp_output.nrrd)", us::Any()); + + std::cout << "test...." << std::endl; + + std::map parsedArgs = parser.parseArguments(argc, argv); + std::cout << "parsedArgs.size()= " << parsedArgs.size() << std::endl; + if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) + { +// std::cout << "\n\n MiniApp Description: \nCalculates and saves the hotspot of an image." << endl; +// std::cout << "Output is written to the designated output file" << endl; +// std::cout << parser.helpText(); +// return EXIT_SUCCESS; + } + + // Parameters: + std::string inputImageFile; + if (!parsedArgs.count("i") && !parsedArgs.count("input")) + { + inputImageFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D.nrrd"; + } + else + { + inputImageFile = us::any_cast(parsedArgs["input"]); + } + + std::string inputPFFile; + if (parsedArgs.count("planarfigure") || parsedArgs.count("p")) + { + inputPFFile = us::any_cast(parsedArgs["planarfigure"]); + } + else + { + inputPFFile = "/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/Pic3D_rectangle.pf"; + } + + + mitk::PlanarFigure::Pointer planarFigure; + try + { + std::vector loadedObjects; + // try except + loadedObjects = mitk::IOUtil::Load(inputPFFile); + mitk::BaseData::Pointer pf = loadedObjects[0]; + planarFigure = dynamic_cast(pf.GetPointer()); + if (planarFigure.IsNull()) + { + MITK_ERROR << "something went wrong"; + return -1; + } + } + catch (const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed: " << e.what(); + return -1; + } + + std::string outFile; + if (parsedArgs.count("out") || parsedArgs.count("o") ) + outFile = us::any_cast(parsedArgs["out"]); + else + outFile = "planarFigureExtraction_output.nrrd"; + + // Load image and mask + mitk::Image::Pointer inputImage = mitk::IOUtil::LoadImage(inputImageFile); + + // Calculate statistics + mitk::PlanarFigureMaskGenerator::Pointer planarFigMaskExtr = mitk::PlanarFigureMaskGenerator::New(); + planarFigMaskExtr->SetPlanarFigure(planarFigure); + planarFigMaskExtr->SetInputImage(inputImage); + + mitk::Image::Pointer outImage; + try + { + outImage = planarFigMaskExtr->GetMask(); + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed - ITK Exception:" << e.what(); + return -1; + } + + mitk::BaseGeometry *imageGeo = outImage->GetGeometry(); + std::cout << "origin: " << imageGeo->GetOrigin()[0] << " " << imageGeo->GetOrigin()[1] << " " << imageGeo->GetOrigin()[2] << std::endl; + if (outImage != nullptr) + { + mitk::IOUtil::SaveImage(outImage, outFile); + } + + + return EXIT_SUCCESS; +} diff --git a/Modules/GraphAlgorithms/CMakeLists.txt b/Modules/GraphAlgorithms/CMakeLists.txt index d5f4be6021..64c609e707 100644 --- a/Modules/GraphAlgorithms/CMakeLists.txt +++ b/Modules/GraphAlgorithms/CMakeLists.txt @@ -1,4 +1,4 @@ MITK_CREATE_MODULE( - DEPENDS MitkImageStatistics +# DEPENDS MitkImageStatistics WARNINGS_AS_ERRORS ) diff --git a/Modules/ImageStatistics/CMakeLists.txt b/Modules/ImageStatistics/CMakeLists.txt index 5264c1c320..fa0a7cf973 100644 --- a/Modules/ImageStatistics/CMakeLists.txt +++ b/Modules/ImageStatistics/CMakeLists.txt @@ -1,13 +1,13 @@ MITK_CREATE_MODULE( - DEPENDS MitkImageExtraction MitkPlanarFigure + DEPENDS MitkImageExtraction MitkPlanarFigure MitkMultilabel PACKAGE_DEPENDS PUBLIC ITK|ITKIOXML PRIVATE ITK|ITKVTK+ITKConvolution # WARNINGS_AS_ERRORS ) if(BUILD_TESTING) add_subdirectory(Testing) endif(BUILD_TESTING) diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp index 1cec357532..1eb1a62353 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsCalculatorTest.cpp @@ -1,507 +1,1709 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkImageStatisticsCalculator.h" #include #include #include #include #include #include #include #include +#include +#include +#include + /** * \brief Test class for mitkImageStatisticsCalculator * * This test covers: * - instantiation of an ImageStatisticsCalculator class * - correctness of statistics when using PlanarFigures for masking */ class mitkImageStatisticsCalculatorTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkImageStatisticsCalculatorTestSuite); MITK_TEST(TestUninitializedImage); MITK_TEST(TestCase1); MITK_TEST(TestCase2); MITK_TEST(TestCase3); MITK_TEST(TestCase4); MITK_TEST(TestCase5); MITK_TEST(TestCase6); MITK_TEST(TestCase7); MITK_TEST(TestCase8); MITK_TEST(TestCase9); MITK_TEST(TestCase10); MITK_TEST(TestCase11); MITK_TEST(TestCase12); MITK_TEST(TestImageMaskingEmpty); MITK_TEST(TestImageMaskingNonEmpty); MITK_TEST(TestRecomputeOnModifiedMask); + MITK_TEST(TestPic3DStatistics); + MITK_TEST(TestPic3DAxialPlanarFigureMaskStatistics); + MITK_TEST(TestPic3DSagittalPlanarFigureMaskStatistics); + MITK_TEST(TestPic3DCoronalPlanarFigureMaskStatistics); + MITK_TEST(TestPic3DImageMaskStatistics_label1); + MITK_TEST(TestPic3DImageMaskStatistics_label2); + MITK_TEST(TestPic3DIgnorePixelValueMaskStatistics); + MITK_TEST(TestPic3DSecondaryMaskStatistics); + MITK_TEST(TestUS4DCylStatistics_time1); + MITK_TEST(TestUS4DCylAxialPlanarFigureMaskStatistics_time1); + MITK_TEST(TestUS4DCylSagittalPlanarFigureMaskStatistics_time1); + MITK_TEST(TestUS4DCylCoronalPlanarFigureMaskStatistics_time1); + MITK_TEST(TestUS4DCylImageMaskStatistics_time1_label_1); + MITK_TEST(TestUS4DCylImageMaskStatistics_time2_label_1); + MITK_TEST(TestUS4DCylImageMaskStatistics_time1_label_2); + MITK_TEST(TestUS4DCylIgnorePixelValueMaskStatistics_time1); + MITK_TEST(TestUS4DCylSecondaryMaskStatistics_time1); CPPUNIT_TEST_SUITE_END(); public: void setUp() override; void TestUninitializedImage(); void TestCase1(); void TestCase2(); void TestCase3(); void TestCase4(); void TestCase5(); void TestCase6(); void TestCase7(); void TestCase8(); void TestCase9(); void TestCase10(); void TestCase11(); void TestCase12(); void TestImageMaskingEmpty(); void TestImageMaskingNonEmpty(); void TestRecomputeOnModifiedMask(); + void TestPic3DStatistics(); + void TestPic3DAxialPlanarFigureMaskStatistics(); + void TestPic3DSagittalPlanarFigureMaskStatistics(); + void TestPic3DCoronalPlanarFigureMaskStatistics(); + void TestPic3DImageMaskStatistics_label1(); + void TestPic3DImageMaskStatistics_label2(); + void TestPic3DIgnorePixelValueMaskStatistics(); + void TestPic3DSecondaryMaskStatistics(); + + void TestUS4DCylStatistics_time1(); + void TestUS4DCylAxialPlanarFigureMaskStatistics_time1(); + void TestUS4DCylSagittalPlanarFigureMaskStatistics_time1(); + void TestUS4DCylCoronalPlanarFigureMaskStatistics_time1(); + void TestUS4DCylImageMaskStatistics_time1_label_1(); + void TestUS4DCylImageMaskStatistics_time2_label_1(); + void TestUS4DCylImageMaskStatistics_time1_label_2(); + void TestUS4DCylIgnorePixelValueMaskStatistics_time1(); + void TestUS4DCylSecondaryMaskStatistics_time1(); + + void TestDifferentNBinsForHistogramStatistics(); + void TestDifferentBinSizeForHistogramStatistic(); + + void TestSwitchFromBinSizeToNBins(); + void TestSwitchFromNBinsToBinSize(); + private: - mitk::Image::Pointer m_Image; + mitk::Image::Pointer m_TestImage; + + mitk::Image::Pointer m_Pic3DImage; + mitk::Image::Pointer m_Pic3DImageMask; + mitk::Image::Pointer m_Pic3DImageMask2; + mitk::PlanarFigure::Pointer m_Pic3DPlanarFigureAxial; + mitk::PlanarFigure::Pointer m_Pic3DPlanarFigureSagittal; + mitk::PlanarFigure::Pointer m_Pic3DPlanarFigureCoronal; + + mitk::Image::Pointer m_US4DImage; + mitk::Image::Pointer m_US4DImageMask; + mitk::Image::Pointer m_US4DImageMask2; + mitk::PlanarFigure::Pointer m_US4DPlanarFigureAxial; + mitk::PlanarFigure::Pointer m_US4DPlanarFigureSagittal; + mitk::PlanarFigure::Pointer m_US4DPlanarFigureCoronal; + mitk::PlaneGeometry::Pointer m_Geometry; // calculate statistics for the given image and planarpolygon - const mitk::ImageStatisticsCalculator::Statistics ComputeStatistics( mitk::Image::Pointer image, + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer ComputeStatistics( mitk::Image::Pointer image, mitk::PlanarFigure::Pointer polygon ); - // calculate statistics for the given image and planarpolygon - const mitk::ImageStatisticsCalculator::Statistics ComputeStatistics( mitk::Image::Pointer image, + // calculate statistics for the given image and mask + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer ComputeStatistics( mitk::Image::Pointer image, mitk::Image::Pointer image_mask ); - void VerifyStatistics(const mitk::ImageStatisticsCalculator::Statistics& stats, + // universal function to calculate statistics + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer ComputeStatisticsNew(mitk::Image::Pointer image, + int timeStep=0, + mitk::MaskGenerator::Pointer maskGen=nullptr, + mitk::MaskGenerator::Pointer secondardMaskGen=nullptr, + unsigned short label=1); + + void VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, double testMean, double testSD, double testMedian=0); + + void VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, + long N, + double mean, + double MPP, + double median, + double skewness, + double kurtosis, + double uniformity, + double UPP, + double variance, + double stdev, + double min, + double max, + double RMS, + double entropy, + vnl_vector minIndex, + vnl_vector maxIndex); }; void mitkImageStatisticsCalculatorTestSuite::setUp() { - std::string filename = this->GetTestDataFilePath("ImageStatistics/testimage.dcm"); - if (filename.empty()) + std::string filename = this->GetTestDataFilePath("ImageStatistics/testimage.dcm"); + + std::string Pic3DFile = this->GetTestDataFilePath("Pic3D.nrrd"); + std::string Pic3DImageMaskFile = this->GetTestDataFilePath("ImageStatistics/Pic3D-labels.nrrd"); + std::string Pic3DImageMaskFile2 = this->GetTestDataFilePath("ImageStatistics/Pic3D-labels2.nrrd"); + std::string Pic3DAxialPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/Pic3DAxialPlanarFigure.pf"); + std::string Pic3DSagittalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/Pic3DSagittalPlanarFigure.pf"); + std::string Pic3DCoronalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/Pic3DCoronalPlanarFigure.pf"); + + std::string US4DFile = this->GetTestDataFilePath("US4DCyl.nrrd"); + std::string US4DImageMaskFile = this->GetTestDataFilePath("ImageStatistics/US4D-labels.nrrd"); + std::string US4DImageMaskFile2 = this->GetTestDataFilePath("ImageStatistics/US4D-labels2.nrrd"); + std::string US4DAxialPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/US4DAxialPlanarFigure.pf"); + std::string US4DSagittalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/US4DSagittalPlanarFigure.pf"); + std::string US4DCoronalPlanarFigureFile = this->GetTestDataFilePath("ImageStatistics/US4DCoronalPlanarFigure.pf"); + + if (filename.empty() || + Pic3DFile.empty() || Pic3DImageMaskFile.empty() || + Pic3DAxialPlanarFigureFile.empty() || Pic3DSagittalPlanarFigureFile.empty() || Pic3DCoronalPlanarFigureFile.empty() || + US4DFile.empty() || US4DImageMaskFile.empty() || + US4DAxialPlanarFigureFile.empty() || US4DSagittalPlanarFigureFile.empty() || US4DCoronalPlanarFigureFile.empty()) { MITK_TEST_FAILED_MSG( << "Could not find test file" ) } MITK_TEST_OUTPUT(<< "Loading test image '" << filename << "'") - m_Image = mitk::IOUtil::LoadImage(filename); - MITK_TEST_CONDITION_REQUIRED( m_Image.IsNotNull(), "Loaded an mitk::Image" ); + m_TestImage = mitk::IOUtil::LoadImage(filename); + MITK_TEST_CONDITION_REQUIRED( m_TestImage.IsNotNull(), "Loaded an mitk::Image" ); + + m_Geometry = m_TestImage->GetSlicedGeometry()->GetPlaneGeometry(0); + MITK_TEST_CONDITION_REQUIRED( m_Geometry.IsNotNull(), "Getting image geometry" ); + + m_Pic3DImage = mitk::IOUtil::LoadImage(Pic3DFile); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DImage.IsNotNull(), "Loaded Pic3D" ); + m_Pic3DImageMask = mitk::IOUtil::LoadImage(Pic3DImageMaskFile); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DImageMask.IsNotNull(), "Loaded Pic3D image mask" ); + m_Pic3DImageMask2 = mitk::IOUtil::LoadImage(Pic3DImageMaskFile2); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DImageMask2.IsNotNull(), "Loaded Pic3D image secondary mask" ); + m_Pic3DPlanarFigureAxial = dynamic_cast(mitk::IOUtil::Load(Pic3DAxialPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DPlanarFigureAxial.IsNotNull(), "Loaded Pic3D axial planarFigure" ); + m_Pic3DPlanarFigureSagittal = dynamic_cast(mitk::IOUtil::Load(Pic3DSagittalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DPlanarFigureSagittal.IsNotNull(), "Loaded Pic3D sagittal planarFigure" ); + m_Pic3DPlanarFigureCoronal = dynamic_cast(mitk::IOUtil::Load(Pic3DCoronalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_Pic3DPlanarFigureCoronal.IsNotNull(), "Loaded Pic3D coronal planarFigure" ); + + m_US4DImage = mitk::IOUtil::LoadImage(US4DFile); + MITK_TEST_CONDITION_REQUIRED( m_US4DImage.IsNotNull(), "Loaded US4D" ); + m_US4DImageMask = mitk::IOUtil::LoadImage(US4DImageMaskFile); + MITK_TEST_CONDITION_REQUIRED( m_US4DImageMask.IsNotNull(), "Loaded US4D image mask" ); + m_US4DImageMask2 = mitk::IOUtil::LoadImage(US4DImageMaskFile2); + MITK_TEST_CONDITION_REQUIRED( m_US4DImageMask2.IsNotNull(), "Loaded US4D image mask2" ); + m_US4DPlanarFigureAxial = dynamic_cast(mitk::IOUtil::Load(US4DAxialPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_US4DPlanarFigureAxial.IsNotNull(), "Loaded US4D axial planarFigure" ); + m_US4DPlanarFigureSagittal = dynamic_cast(mitk::IOUtil::Load(US4DSagittalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_US4DPlanarFigureSagittal.IsNotNull(), "Loaded US4D sagittal planarFigure" ); + m_US4DPlanarFigureCoronal = dynamic_cast(mitk::IOUtil::Load(US4DCoronalPlanarFigureFile)[0].GetPointer()); + MITK_TEST_CONDITION_REQUIRED( m_US4DPlanarFigureCoronal.IsNotNull(), "Loaded US4D coronal planarFigure" ); - m_Geometry = m_Image->GetSlicedGeometry()->GetPlaneGeometry(0); - MITK_TEST_CONDITION_REQUIRED( m_Geometry.IsNotNull(), "Getting image geometry" ) } void mitkImageStatisticsCalculatorTestSuite::TestCase1() { /***************************** * one whole white pixel * -> mean of 255 expected ******************************/ + MITK_INFO << std::endl << "Test case 1:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 10.5 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 10.5; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 255.0, 0.0, 255.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 255.0, 0.0, 255.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase2() { /***************************** * half pixel in x-direction (white) * -> mean of 255 expected ******************************/ + MITK_INFO << std::endl << "Test case 2:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 10.0 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 10.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 255.0, 0.0, 255.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 255.0, 0.0, 255.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase3() { /***************************** * half pixel in diagonal-direction (white) * -> mean of 255 expected ******************************/ + MITK_INFO << std::endl << "Test case 3:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 10.5 ; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 255.0, 0.0, 255.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 255.0, 0.0, 255.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase4() { /***************************** * one pixel (white) + 2 half pixels (white) + 1 half pixel (black) * -> mean of 191.25 expected ******************************/ + MITK_INFO << std::endl << "Test case 4:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 1.1; pnt1[1] = 1.1; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 2.0; pnt2[1] = 2.0; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 3.0; pnt3[1] = 1.0; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 2.0; pnt4[1] = 0.0; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 191.25, 127.5, 254.50); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 191.25, 110.41, 242.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase5() { /***************************** * whole pixel (white) + half pixel (gray) in x-direction * -> mean of 191.5 expected ******************************/ + MITK_INFO << std::endl << "Test case 5:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.5; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 191.50, 89.80, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 191.50, 63.50, 134.340); } void mitkImageStatisticsCalculatorTestSuite::TestCase6() { /***************************** * quarter pixel (black) + whole pixel (white) + half pixel (gray) in x-direction * -> mean of 191.5 expected ******************************/ + MITK_INFO << std::endl << "Test case 6:-----------------------------------------------------------------------------------"; mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.25; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.25; pnt3[1] = 4.5; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.5; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 191.5, 89.80, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 191.5, 63.50, 134.340); } void mitkImageStatisticsCalculatorTestSuite::TestCase7() { /***************************** * half pixel (black) + whole pixel (white) + half pixel (gray) in x-direction * -> mean of 127.66 expected ******************************/ + MITK_INFO << std::endl << "Test case 7:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure1 = mitk::PlanarPolygon::New(); figure1->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.0; pnt1[1] = 3.5; figure1->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.0; pnt2[1] = 3.5; figure1->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 9.0; pnt3[1] = 4.0; figure1->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 11.0; pnt4[1] = 4.0; figure1->SetControlPoint( 3, pnt4, true ); figure1->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure1.GetPointer()), 127.66, 127.5, 128.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure1.GetPointer()), 127.66, 104.1, 140.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase8() { /***************************** * whole pixel (gray) * -> mean of 128 expected ******************************/ + MITK_INFO << std::endl << "Test case 8:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 11.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 11.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 128.0, 0.0, 128.0); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 128.0, 0.0, 128.0); } void mitkImageStatisticsCalculatorTestSuite::TestCase9() { /***************************** * whole pixel (gray) + half pixel (white) in y-direction * -> mean of 191.5 expected ******************************/ + MITK_INFO << std::endl << "Test case 9:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 12.0; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 12.0; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 191.5, 89.80, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 191.5, 63.50, 134.340); } void mitkImageStatisticsCalculatorTestSuite::TestCase10() { /***************************** * 2 whole pixel (white) + 2 whole pixel (black) in y-direction * -> mean of 127.66 expected ******************************/ + MITK_INFO << std::endl << "Test case 10:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 11.5; pnt1[1] = 10.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 11.5; pnt2[1] = 13.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 12.5; pnt3[1] = 13.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 12.5; pnt4[1] = 10.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 127.66, 127.5, 128.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 127.66, 104.1, 140.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase11() { /***************************** * 9 whole pixels (white) + 3 half pixels (white) * + 3 whole pixel (black) [ + 3 slightly less than half pixels (black)] * -> mean of 204.0 expected ******************************/ + MITK_INFO << std::endl << "Test case 11:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 0.5; pnt1[1] = 0.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 3.5; pnt2[1] = 3.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 8.4999; pnt3[1] = 3.5; figure2->SetControlPoint( 2, pnt3, true ); mitk::Point2D pnt4; pnt4[0] = 5.4999; pnt4[1] = 0.5; figure2->SetControlPoint( 3, pnt4, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 204.0, 105.58, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 204.0, 102.00, 242.250); } void mitkImageStatisticsCalculatorTestSuite::TestCase12() { /***************************** * half pixel (white) + whole pixel (white) + half pixel (black) * -> mean of 212.66 expected ******************************/ + MITK_INFO << std::endl << "Test case 12:-----------------------------------------------------------------------------------"; + mitk::PlanarPolygon::Pointer figure2 = mitk::PlanarPolygon::New(); figure2->SetPlaneGeometry( m_Geometry ); mitk::Point2D pnt1; pnt1[0] = 9.5; pnt1[1] = 0.5; figure2->PlaceFigure( pnt1 ); mitk::Point2D pnt2; pnt2[0] = 9.5; pnt2[1] = 2.5; figure2->SetControlPoint( 1, pnt2, true ); mitk::Point2D pnt3; pnt3[0] = 11.5; pnt3[1] = 2.5; figure2->SetControlPoint( 2, pnt3, true ); figure2->GetPolyLine(0); - this->VerifyStatistics(ComputeStatistics(m_Image, figure2.GetPointer()), 212.66, 73.32, 254.5); + this->VerifyStatistics(ComputeStatistics(m_TestImage, figure2.GetPointer()), 212.66, 59.860, 248.640); } void mitkImageStatisticsCalculatorTestSuite::TestImageMaskingEmpty() { - mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_Image, 0 ); + MITK_INFO << std::endl << "TestImageMaskingEmpty:-----------------------------------------------------------------------------------"; - this->VerifyStatistics( ComputeStatistics( m_Image, mask_image ), 0.0, 0.0, 0.0); + mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_TestImage, 0 ); + + this->VerifyStatistics( ComputeStatistics( m_TestImage, mask_image ), -21474836.480, -21474836.480, -21474836.480); // empty statisticsContainer (default values) } void mitkImageStatisticsCalculatorTestSuite::TestImageMaskingNonEmpty() { - mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_Image, 0 ); + MITK_INFO << std::endl << "TestImageMaskingNonEmpty:-----------------------------------------------------------------------------------"; + + mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_TestImage, 0 ); std::vector< itk::Index<3U> > activated_indices; itk::Index<3U> index = {{10, 8, 0}}; activated_indices.push_back( index ); index[0] = 9; index[1] = 8; index[2] = 0; activated_indices.push_back( index ); index[0] = 9; index[1] = 7; index[2] = 0; activated_indices.push_back( index ); index[0] = 10; index[1] = 7; index[2] = 0; activated_indices.push_back( index ); std::vector< itk::Index<3U> >::const_iterator indexIter = activated_indices.begin(); // activate voxel in the mask image mitk::ImagePixelWriteAccessor< unsigned char, 3> writeAccess( mask_image ); while( indexIter != activated_indices.end() ) { writeAccess.SetPixelByIndex( (*indexIter++), 1); } - this->VerifyStatistics( ComputeStatistics( m_Image, mask_image ), 127.5, 147.22, 254.5); + this->VerifyStatistics( ComputeStatistics( m_TestImage, mask_image ), 127.5, 127.5, 12.750); } void mitkImageStatisticsCalculatorTestSuite::TestRecomputeOnModifiedMask() { - mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_Image, 0 ); + MITK_INFO << std::endl << "TestRecomputeOnModifiedMask:-----------------------------------------------------------------------------------"; + mitk::Image::Pointer mask_image = mitk::ImageGenerator::GenerateImageFromReference( m_TestImage, 0 ); mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage( m_Image ); - statisticsCalculator->SetImageMask( mask_image ); - statisticsCalculator->SetMaskingModeToImage(); + statisticsCalculator->SetInputImage( m_TestImage ); + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(mask_image); - statisticsCalculator->ComputeStatistics(); - this->VerifyStatistics( statisticsCalculator->GetStatistics(), 0.0, 0.0, 0.0); + statisticsCalculator->SetMask(imgMaskGen.GetPointer()); + + this->VerifyStatistics( statisticsCalculator->GetStatistics(), -21474836.480, -21474836.480, -21474836.480); // activate voxel in the mask image itk::Index<3U> test_index = {11, 8, 0}; mitk::ImagePixelWriteAccessor< unsigned char, 3> writeAccess( mask_image ); writeAccess.SetPixelByIndex( test_index, 1); mask_image->Modified(); - statisticsCalculator->ComputeStatistics(); - const mitk::ImageStatisticsCalculator::Statistics stat = statisticsCalculator->GetStatistics(); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stat = statisticsCalculator->GetStatistics(); this->VerifyStatistics( stat, 128.0, 0.0, 128.0); - MITK_TEST_CONDITION( stat.GetN() == 1, "Calculated mask voxel count '" << stat.GetN() << "' is equal to the desired value '" << 1 << "'" ); + MITK_TEST_CONDITION( stat->GetN() == 1, "Calculated mask voxel count '" << stat->GetN() << "' is equal to the desired value '" << 1 << "'" ); + +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DStatistics() +{ + MITK_INFO << std::endl << "Test plain Pic3D:-----------------------------------------------------------------------------------"; + long expected_N = 3211264; + double expected_mean = -365.80015345982144; + double expected_MPP = 111.80226129535752; + double expected_median = -105.16000366210938; + double expected_skewness = -0.26976612134147004; + double expected_kurtosis = 1.4655017209571437; + double expected_uniformity = 0.06087994379480554; + double expected_UPP = 0.011227934437026977; + double expected_variance = 224036.80150510342; + double expected_standarddev = 473.32525973700518; + double expected_min = -1023; + double expected_max = 1361; + double expected_RMS = 598.20276978323352; + double expected_entropy = 4.6727423654570357; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 0; + expected_minIndex[1] = 0; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 139; + expected_maxIndex[1] = 182; + expected_maxIndex[2] = 43; + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DAxialPlanarFigureMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D axial pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.6719817476387417; + double expected_kurtosis = 5.8846935191205221; + double expected_MPP = 230.43933685003768; + double expected_max = 1206; + double expected_mean = 182.30282131661443; + double expected_median = 95.970001220703125; + double expected_min = -156; + long expected_N = 3190; + double expected_RMS = 301.93844376702253; + double expected_skewness = 1.6400489794326298; + double expected_standarddev = 240.69172225993557; + double expected_UPP = 0.024889790784288681; + double expected_uniformity = 0.027579917650180332; + double expected_variance = 57932.505164453964; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 156; + expected_minIndex[1] = 133; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 125; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 24; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_Pic3DImage); + pfMaskGen->SetPlanarFigure(m_Pic3DPlanarFigureAxial); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, pfMaskGen.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DSagittalPlanarFigureMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D sagittal pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.6051911962074286; + double expected_kurtosis = 6.5814062739142338; + double expected_MPP = 249.03202846975088; + double expected_max = 1240; + double expected_mean = 233.93602693602693; + double expected_median = 174.9849853515625; + double expected_min = -83; + long expected_N = 1188; + double expected_RMS = 332.03230188484594; + double expected_skewness = 1.7489809015501814; + double expected_standarddev = 235.62551813489128; + double expected_UPP = 0.026837539253364174; + double expected_uniformity = 0.027346982734188126; + double expected_variance = 55519.384796335973; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 128; + expected_minIndex[1] = 119; + expected_minIndex[2] = 22; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 128; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 22; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_Pic3DImage); + pfMaskGen->SetPlanarFigure(m_Pic3DPlanarFigureSagittal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, pfMaskGen.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DCoronalPlanarFigureMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D coronal pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 6.0677398647867449; + double expected_kurtosis = 1.6242929941303372; + double expected_MPP = 76.649350649350652; + double expected_max = 156; + double expected_mean = -482.14807692307693; + double expected_median = -660.07501220703125; + double expected_min = -897; + long expected_N = 520; + double expected_RMS = 595.09446729069839; + double expected_skewness = 0.51691492278851858; + double expected_standarddev = 348.81321207686312; + double expected_UPP = 0.0021560650887573964; + double expected_uniformity = 0.020295857988165685; + double expected_variance = 121670.6569193787; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 217; + expected_minIndex[1] = 127; + expected_minIndex[2] = 43; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 209; + expected_maxIndex[1] = 127; + expected_maxIndex[2] = 39; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_Pic3DImage); + pfMaskGen->SetPlanarFigure(m_Pic3DPlanarFigureCoronal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, pfMaskGen.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DImageMaskStatistics_label1() +{ + MITK_INFO << std::endl << "Test Pic3D image mask label 1 pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.695858251095868; + double expected_kurtosis = 4.2728827997815717; + double expected_MPP = 413.52408256880733; + double expected_max = 1206; + double expected_mean = 413.52408256880733; + double expected_median = 324; + double expected_min = 6; + long expected_N = 872; + double expected_RMS = 472.02024695145235; + double expected_skewness = 1.3396074364415382; + double expected_standarddev = 227.59821323493802; + double expected_UPP = 0.029758648261930806; + double expected_uniformity = 0.029758648261930806; + double expected_variance = 51800.946667736309; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 135; + expected_minIndex[1] = 158; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 125; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 24; + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(m_Pic3DImageMask); + imgMaskGen->SetInputImage(m_Pic3DImage); + imgMaskGen->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, imgMaskGen.GetPointer(), nullptr, 1); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DImageMaskStatistics_label2() +{ + MITK_INFO << std::endl << "Test Pic3D image mask label 2 pf:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.3685781901212764; + double expected_kurtosis = 9.7999112757587934; + double expected_MPP = -nan(""); + double expected_max = -145; + double expected_mean = -897.92833876221493; + double expected_median = -969.16499900817871; + double expected_min = -1008; + long expected_N = 307; + double expected_RMS = 913.01496468179471; + double expected_skewness = 2.6658524648889736; + double expected_standarddev = 165.29072623903585; + double expected_UPP = 0; + double expected_uniformity = 0.087544695434434425; + double expected_variance = 27321.024180627897; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 170; + expected_minIndex[1] = 60; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 173; + expected_maxIndex[1] = 57; + expected_maxIndex[2] = 24; + + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(m_Pic3DImageMask); + imgMaskGen->SetInputImage(m_Pic3DImage); + imgMaskGen->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, imgMaskGen.GetPointer(), nullptr, 2); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DIgnorePixelValueMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D ignore zero pixels:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.671045011438645; + double expected_kurtosis = 1.4638176488404484; + double expected_MPP = 111.80226129535752; + double expected_max = 1361; + double expected_mean = -366.48547402877585; + double expected_median = -105.16000366210938; + double expected_min = -1023; + long expected_N = 3205259; + double expected_RMS = 598.76286909522139; + double expected_skewness = -0.26648854845130782; + double expected_standarddev = 473.50329537717545; + double expected_UPP = 0.011270044547276429; + double expected_uniformity = 0.061029773286547614; + double expected_variance = 224205.37073304466; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 0; + expected_minIndex[1] = 0; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 139; + expected_maxIndex[1] = 182; + expected_maxIndex[2] = 43; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_Pic3DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + ignPixelValMask->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, ignPixelValMask.GetPointer()); + //std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestPic3DSecondaryMaskStatistics() +{ + MITK_INFO << std::endl << "Test Pic3D ignore zero pixels AND Image mask 2:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.9741637167320176; + double expected_kurtosis = 3.490663358061596; + double expected_MPP = 332.43534482758622; + double expected_max = 1206; + double expected_mean = 320.63333333333333; + double expected_median = 265.06500244140625; + double expected_min = -57; + long expected_N = 720; + double expected_RMS = 433.57749531594055; + double expected_skewness = 1.1047775627624981; + double expected_standarddev = 291.86248474238687; + double expected_UPP = 0.020628858024691339; + double expected_uniformity = 0.021377314814814797; + double expected_variance = 85183.710000000006; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 116; + expected_minIndex[1] = 170; + expected_minIndex[2] = 24; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 125; + expected_maxIndex[1] = 167; + expected_maxIndex[2] = 24; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_Pic3DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + ignPixelValMask->SetTimeStep(0); + + mitk::ImageMaskGenerator::Pointer imgMaskGen2 = mitk::ImageMaskGenerator::New(); + imgMaskGen2->SetImageMask(m_Pic3DImageMask2); + imgMaskGen2->SetInputImage(m_Pic3DImage); + imgMaskGen2->SetTimeStep(0); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_Pic3DImage, 0, imgMaskGen2.GetPointer(), ignPixelValMask.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylStatistics_time1() +{ + MITK_INFO << std::endl << "Test plain US4D timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.8272774900452502; + double expected_kurtosis = 6.1336513352934432; + double expected_MPP = 53.395358640738536; + double expected_max = 199; + double expected_mean = 35.771298153622375; + double expected_median = 20.894999504089355; + double expected_min = 0; + long expected_N = 3409920; + double expected_RMS = 59.244523377028408; + double expected_skewness = 1.8734292240015058; + double expected_standarddev = 47.226346233600559; + double expected_UPP = 0.12098731125004937; + double expected_uniformity = 0.12098731125004937; + double expected_variance = 2230.3277785759178; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 0; + expected_minIndex[1] = 0; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 268; + expected_maxIndex[1] = 101; + expected_maxIndex[2] = 0; + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylAxialPlanarFigureMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D axial pf timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 6.218151288002292; + double expected_kurtosis = 1.7322676370242023; + double expected_MPP = 121.11663807890223; + double expected_max = 199; + double expected_mean = 121.11663807890223; + double expected_median = 120.14999771118164; + double expected_min = 9; + long expected_N = 2332; + double expected_RMS = 134.41895158590751; + double expected_skewness = -0.1454808104597369; + double expected_standarddev = 58.30278317472294; + double expected_UPP = 0.021354765820606133; + double expected_uniformity = 0.021354765820606133; + double expected_variance = 3399.214525918756; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 129; + expected_minIndex[1] = 131; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 126; + expected_maxIndex[1] = 137; + expected_maxIndex[2] = 19; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_US4DImage); + pfMaskGen->SetPlanarFigure(m_US4DPlanarFigureAxial); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, pfMaskGen.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylSagittalPlanarFigureMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D sagittal pf timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.2003987046387508; + double expected_kurtosis = 2.7574491062430142; + double expected_MPP = 26.212534059945504; + double expected_max = 59; + double expected_mean = 26.176870748299319; + double expected_median = 26.254999160766602; + double expected_min = 0; + long expected_N = 735; + double expected_RMS = 28.084905283121476; + double expected_skewness = 0.18245181360752327; + double expected_standarddev = 10.175133541567705; + double expected_UPP = 0.032921467906890628; + double expected_uniformity = 0.032921467906890628; + double expected_variance = 103.53334258873615; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 147; + expected_minIndex[1] = 94; + expected_minIndex[2] = 21; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 147; + expected_maxIndex[1] = 77; + expected_maxIndex[2] = 24; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_US4DImage); + pfMaskGen->SetPlanarFigure(m_US4DPlanarFigureSagittal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, pfMaskGen.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylCoronalPlanarFigureMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D coronal pf timeStep1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.8892941136639161; + double expected_kurtosis = 4.6434920707409564; + double expected_MPP = 55.486426346239433; + double expected_max = 199; + double expected_mean = 55.118479221927501; + double expected_median = 36.815000534057617; + double expected_min = 0; + long expected_N = 2262; + double expected_RMS = 71.98149752438627; + double expected_skewness = 1.4988288344523237; + double expected_standarddev = 46.29567187238105; + double expected_UPP = 0.023286748110675673; + double expected_uniformity = 0.023286748110675673; + double expected_variance = 2143.2892341151742; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 214; + expected_minIndex[1] = 169; + expected_minIndex[2] = 10; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 99; + expected_maxIndex[1] = 169; + expected_maxIndex[2] = 17; + + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_US4DImage); + pfMaskGen->SetPlanarFigure(m_US4DPlanarFigureCoronal); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, pfMaskGen.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylImageMaskStatistics_time1_label_1() +{ + MITK_INFO << std::endl << "Test US4D image mask time 1 label 1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.0082903903398677; + double expected_kurtosis = 3.6266994778237809; + double expected_MPP = 169.58938547486034; + double expected_max = 199; + double expected_mean = 169.58938547486034; + double expected_median = 187.44000244140625; + double expected_min = 63; + long expected_N = 716; + double expected_RMS = 173.09843164831432; + double expected_skewness = -1.2248969838579555; + double expected_standarddev = 34.677188083311712; + double expected_UPP = 0.076601073624418703; + double expected_uniformity = 0.076601073624418703; + double expected_variance = 1202.5073733653758; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 82; + expected_minIndex[1] = 158; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 126; + expected_maxIndex[1] = 140; + expected_maxIndex[2] = 19; + + mitk::ImageMaskGenerator::Pointer imgMask1 = mitk::ImageMaskGenerator::New(); + imgMask1->SetInputImage(m_US4DImage); + imgMask1->SetImageMask(m_US4DImageMask); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, imgMask1.GetPointer(), nullptr, 1); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylImageMaskStatistics_time2_label_1() +{ + MITK_INFO << std::endl << "Test US4D image mask time 2 label 1:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.1857604214916506; + double expected_kurtosis = 3.0692303858330683; + double expected_MPP = 167.97194163860831; + double expected_max = 199; + double expected_mean = 167.97194163860831; + double expected_median = 184.39499664306641; + double expected_min = 72; + long expected_N = 891; + double expected_RMS = 171.67986611998634; + double expected_skewness = -1.1221651136259736; + double expected_standarddev = 35.488071983870803; + double expected_UPP = 0.063124070232188439; + double expected_uniformity = 0.063124070232188439; + double expected_variance = 1259.4032531323958; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 103; + expected_minIndex[1] = 212; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 102; + expected_maxIndex[1] = 168; + expected_maxIndex[2] = 19; + + mitk::ImageMaskGenerator::Pointer imgMask1 = mitk::ImageMaskGenerator::New(); + imgMask1->SetInputImage(m_US4DImage); + imgMask1->SetImageMask(m_US4DImageMask); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 2, imgMask1.GetPointer(), nullptr, 1); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylImageMaskStatistics_time1_label_2() +{ + MITK_INFO << std::endl << "Test US4D image mask time 1 label 2:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.0822234230119001; + double expected_kurtosis = 2.4346603343623747; + double expected_MPP = 20.733626373626375; + double expected_max = 46; + double expected_mean = 20.624836029733274; + double expected_median = 20.010000228881836; + double expected_min = 0; + long expected_N = 2287; + double expected_RMS = 22.508347574573804; + double expected_skewness = 0.13837218490626488; + double expected_standarddev = 9.0134260569684965; + double expected_UPP = 0.034783970308787; + double expected_uniformity = 0.034783970308787; + double expected_variance = 81.241849284438644; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 178; + expected_minIndex[1] = 76; + expected_minIndex[2] = 19; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 198; + expected_maxIndex[1] = 90; + expected_maxIndex[2] = 19; + + mitk::ImageMaskGenerator::Pointer imgMask1 = mitk::ImageMaskGenerator::New(); + imgMask1->SetInputImage(m_US4DImage); + imgMask1->SetImageMask(m_US4DImageMask); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, imgMask1.GetPointer(), nullptr, 2); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylIgnorePixelValueMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4D ignore zero pixels:-----------------------------------------------------------------------------------"; + + double expected_entropy = 5.8609813848087962; + double expected_kurtosis = 4.7556214582883651; + double expected_MPP = 53.395358640738536; + double expected_max = 199; + double expected_mean = 53.395358640738536; + double expected_median = 35.649999618530273; + double expected_min = 1; + long expected_N = 2284417; + double expected_RMS = 72.382339046507084; + double expected_skewness = 1.588289859859108; + double expected_standarddev = 48.868585834566694; + double expected_UPP = 0.023927063695115193; + double expected_uniformity = 0.023927063695115193; + double expected_variance = 2388.1386814704128; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 187; + expected_minIndex[1] = 19; + expected_minIndex[2] = 0; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 268; + expected_maxIndex[1] = 101; + expected_maxIndex[2] = 0; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_US4DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + ignPixelValMask->SetTimeStep(1); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, ignPixelValMask.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); } -const mitk::ImageStatisticsCalculator::Statistics + +void mitkImageStatisticsCalculatorTestSuite::TestUS4DCylSecondaryMaskStatistics_time1() +{ + MITK_INFO << std::endl << "Test US4d ignore zero pixels AND Image mask 2:-----------------------------------------------------------------------------------"; + + double expected_entropy = 4.9955858614274558; + double expected_kurtosis = 17.471042803365179; + double expected_MPP = 32.791403286978507; + double expected_max = 199; + double expected_mean = 32.791403286978507; + double expected_median = 25.75; + double expected_min = 1; + long expected_N = 17402; + double expected_RMS = 42.776697859745241; + double expected_skewness = 3.3991813038552596; + double expected_standarddev = 27.469433016621732; + double expected_UPP = 0.043040554251756687; + double expected_uniformity = 0.043040554251756687; + double expected_variance = 754.56975025466807; + vnl_vector expected_minIndex; + expected_minIndex.set_size(3); + expected_minIndex[0] = 177; + expected_minIndex[1] = 27; + expected_minIndex[2] = 36; + + vnl_vector expected_maxIndex; + expected_maxIndex.set_size(3); + expected_maxIndex[0] = 109; + expected_maxIndex[1] = 116; + expected_maxIndex[2] = 36; + + mitk::IgnorePixelMaskGenerator::Pointer ignPixelValMask = mitk::IgnorePixelMaskGenerator::New(); + ignPixelValMask->SetInputImage(m_US4DImage); + ignPixelValMask->SetIgnoredPixelValue(0); + + mitk::ImageMaskGenerator::Pointer imgMaskGen2 = mitk::ImageMaskGenerator::New(); + imgMaskGen2->SetImageMask(m_US4DImageMask2); + imgMaskGen2->SetInputImage(m_US4DImage); + + const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result = ComputeStatisticsNew(m_US4DImage, 1, imgMaskGen2.GetPointer(), ignPixelValMask.GetPointer()); + std::cout << result->GetAsString(); + + VerifyStatistics(result, + expected_N, + expected_mean, + expected_MPP, + expected_median, + expected_skewness, + expected_kurtosis, + expected_uniformity, + expected_UPP, + expected_variance, + expected_standarddev, + expected_min, + expected_max, + expected_RMS, + expected_entropy, + expected_minIndex, + expected_maxIndex); +} + + +const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer mitkImageStatisticsCalculatorTestSuite::ComputeStatistics( mitk::Image::Pointer image, mitk::PlanarFigure::Pointer polygon ) { mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage( image ); - statisticsCalculator->SetMaskingModeToPlanarFigure(); - statisticsCalculator->SetPlanarFigure( polygon ); + statisticsCalculator->SetInputImage( image ); + + statisticsCalculator->SetNBinsForHistogramStatistics(10); + + mitk::PlanarFigureMaskGenerator::Pointer planFigMaskGen = mitk::PlanarFigureMaskGenerator::New(); + planFigMaskGen->SetInputImage(image); + planFigMaskGen->SetPlanarFigure(polygon); + + statisticsCalculator->SetMask(planFigMaskGen.GetPointer()); try { - statisticsCalculator->ComputeStatistics(); return statisticsCalculator->GetStatistics(); } catch( ... ) { } - return mitk::ImageStatisticsCalculator::Statistics(); + return mitk::ImageStatisticsCalculator::StatisticsContainer::New(); } -const mitk::ImageStatisticsCalculator::Statistics +const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer mitkImageStatisticsCalculatorTestSuite::ComputeStatistics(mitk::Image::Pointer image, mitk::Image::Pointer image_mask ) { mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage( image ); - statisticsCalculator->SetImageMask( image_mask ); - statisticsCalculator->SetMaskingModeToImage(); + statisticsCalculator->SetInputImage(image); + + statisticsCalculator->SetNBinsForHistogramStatistics(10); - statisticsCalculator->ComputeStatistics(); + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(image_mask); + statisticsCalculator->SetMask(imgMaskGen.GetPointer()); return statisticsCalculator->GetStatistics(); } +const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer +mitkImageStatisticsCalculatorTestSuite::ComputeStatisticsNew(mitk::Image::Pointer image, + int timeStep, + mitk::MaskGenerator::Pointer maskGen, + mitk::MaskGenerator::Pointer secondardMaskGen, + unsigned short label) +{ + mitk::ImageStatisticsCalculator::Pointer imgStatCalc = mitk::ImageStatisticsCalculator::New(); + imgStatCalc->SetInputImage(image); + + if (maskGen.IsNotNull()) + { + imgStatCalc->SetMask(maskGen.GetPointer()); + if (secondardMaskGen.IsNotNull()) + { + imgStatCalc->SetSecondaryMask(secondardMaskGen.GetPointer()); + } + } + + return imgStatCalc->GetStatistics(timeStep, label); +} -void mitkImageStatisticsCalculatorTestSuite::VerifyStatistics(const mitk::ImageStatisticsCalculator::Statistics& stats, +void mitkImageStatisticsCalculatorTestSuite::VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, double testMean, double testSD, double testMedian) { - int tmpMean = stats.GetMean() * 100; + int tmpMean = stats->GetMean() * 100; double calculatedMean = tmpMean / 100.0; MITK_TEST_CONDITION( calculatedMean == testMean, "Calculated mean grayvalue '" << calculatedMean << "' is equal to the desired value '" << testMean << "'" ); - int tmpSD = stats.GetSigma() * 100; + int tmpSD = stats->GetStd() * 100; double calculatedSD = tmpSD / 100.0; MITK_TEST_CONDITION( calculatedSD == testSD, "Calculated grayvalue sd '" << calculatedSD << "' is equal to the desired value '" << testSD <<"'" ); - int tmpMedian = stats.GetMedian() * 100; + int tmpMedian = stats->GetMedian() * 100; double calculatedMedian = tmpMedian / 100.0; MITK_TEST_CONDITION( testMedian == calculatedMedian, "Calculated median grayvalue '" << calculatedMedian << "' is equal to the desired value '" << testMedian << "'"); } +void mitkImageStatisticsCalculatorTestSuite::VerifyStatistics(mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats, + long N, + double mean, + double MPP, + double median, + double skewness, + double kurtosis, + double uniformity, + double UPP, + double variance, + double stdev, + double min, + double max, + double RMS, + double entropy, + vnl_vector minIndex, + vnl_vector maxIndex) +{ + MITK_TEST_CONDITION(std::abs(stats->GetN() - N) < mitk::eps, "calculated N: " << stats->GetN() << " expected N: " << N); + MITK_TEST_CONDITION(std::abs(stats->GetMean() - mean) < mitk::eps, "calculated mean: " << stats->GetMean() << " expected mean: " << mean); + // in one test case MPP is None because the roi has no positive pixels + if (!std::isnan(stats->GetMPP())) + { + MITK_TEST_CONDITION(std::abs(stats->GetMPP() - MPP) < mitk::eps, "calculated MPP: " << stats->GetMPP() << " expected MPP: " << MPP); + } + MITK_TEST_CONDITION(std::abs(stats->GetMedian() - median) < mitk::eps, "calculated median: " << stats->GetMedian() << " expected median: " << median); + MITK_TEST_CONDITION(std::abs(stats->GetSkewness() - skewness) < mitk::eps, "calculated skewness: " << stats->GetSkewness() << " expected skewness: " << skewness); + MITK_TEST_CONDITION(std::abs(stats->GetKurtosis() - kurtosis) < mitk::eps, "calculated kurtosis: " << stats->GetKurtosis() << " expected kurtosis: " << kurtosis); + MITK_TEST_CONDITION(std::abs(stats->GetUniformity() - uniformity) < mitk::eps, "calculated uniformity: " << stats->GetUniformity() << " expected uniformity: " << uniformity); + MITK_TEST_CONDITION(std::abs(stats->GetUPP() - UPP) < mitk::eps, "calculated UPP: " << stats->GetUPP() << " expected UPP: " << UPP); + MITK_TEST_CONDITION(std::abs(stats->GetVariance() - variance) < mitk::eps, "calculated variance: " << stats->GetVariance() << " expected variance: " << variance); + MITK_TEST_CONDITION(std::abs(stats->GetStd() - stdev) < mitk::eps, "calculated stdev: " << stats->GetStd() << " expected stdev: " << stdev); + MITK_TEST_CONDITION(std::abs(stats->GetMin() - min) < mitk::eps, "calculated min: " << stats->GetMin() << " expected min: " << min); + MITK_TEST_CONDITION(std::abs(stats->GetMax() - max) < mitk::eps, "calculated max: " << stats->GetMax() << " expected max: " << max); + MITK_TEST_CONDITION(std::abs(stats->GetRMS() - RMS) < mitk::eps, "calculated RMS: " << stats->GetRMS() << " expected RMS: " << RMS); + MITK_TEST_CONDITION(std::abs(stats->GetEntropy() - entropy) < mitk::eps, "calculated entropy: " << stats->GetEntropy() << " expected entropy: " << entropy); + for (unsigned int i = 0; i < minIndex.size(); ++i) + { + MITK_TEST_CONDITION(std::abs(stats->GetMinIndex()[i] - minIndex[i]) < mitk::eps, "minIndex [" << i << "] = " << stats->GetMinIndex()[i] << " expected: " << minIndex[i]); + } + for (unsigned int i = 0; i < maxIndex.size(); ++i) + { + MITK_TEST_CONDITION(std::abs(stats->GetMaxIndex()[i] - maxIndex[i]) < mitk::eps, "maxIndex [" << i << "] = " << stats->GetMaxIndex()[i] << " expected: " << maxIndex[i]); + } +} + void mitkImageStatisticsCalculatorTestSuite::TestUninitializedImage() { /***************************** * loading uninitialized image to datastorage ******************************/ + MITK_INFO << std::endl << "Test uninitialized image: -----------------------------------------------------------------------------------"; MITK_TEST_FOR_EXCEPTION_BEGIN(mitk::Exception) mitk::Image::Pointer image = mitk::Image::New(); mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(image); mitk::ImageStatisticsCalculator::Pointer is = mitk::ImageStatisticsCalculator::New(); - is->ComputeStatistics(); + is->GetStatistics(); MITK_TEST_FOR_EXCEPTION_END(mitk::Exception) } MITK_TEST_SUITE_REGISTRATION(mitkImageStatisticsCalculator) diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp index baa2df1120..5372b3b769 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsHotspotTest.cpp @@ -1,632 +1,649 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkImageStatisticsCalculator.h" #include "itkMultiGaussianImageSource.h" #include "mitkTestingMacros.h" #include "mitkImageCast.h" #include #include #include #include +#include +#include + /** \section hotspotCalculationTestCases Testcases To see the different Hotspot-Testcases have a look at the \ref hotspottestdoc. Note from an intensive session of checking the test results: - itk::MultiGaussianImageSource needs a review - the test idea is ok, but the combination of XML files for parameters and MultiGaussianImageSource has serious flaws - the XML file should contain exactly the parameters that MultiGaussianImageSource requires - in contrast, now the XML file mentions index coordinates for gaussian centers while the MultiGaussianImageSource expects world coordinates - this requires a transformation (index * spacing assuming no rotation) that was actually broken until recently */ struct mitkImageStatisticsHotspotTestClass { /** \brief Test parameters for one test case. Describes all aspects of a single test case: - parameters to generate a test image - parameters of a ROI that describes where to calculate statistics - expected statistics results */ struct Parameters { public: // XML-Tag /** \brief XML-Tag "image-rows": size of x-dimension */ int m_ImageRows; /** \brief XML-Tag "image-columns": size of y-dimension */ int m_ImageColumns; /** \brief XML-Tag "image-slices": size of z-dimension */ int m_ImageSlices; /** \brief XML-Tag "numberOfGaussians": number of used gauss-functions */ int m_NumberOfGaussian; /** \brief XML-Tags "spacingX", "spacingY", "spacingZ": spacing of image in every direction */ double m_Spacing[3]; /** \brief XML-Tag "entireHotSpotInImage" */ unsigned int m_EntireHotspotInImage; // XML-Tag /** \brief XML-Tag "centerIndexX: gaussian parameter \warning This parameter READS the centerIndexX parameter from file and is THEN MISUSED to calculate some position in world coordinates, so we require double. */ std::vector m_CenterX; /** \brief XML-Tag "centerIndexY: gaussian parameter \warning This parameter READS the centerIndexX parameter from file and is THEN MISUSED to calculate some position in world coordinates, so we require double. */ std::vector m_CenterY; /** \brief XML-Tag "centerIndexZ: gaussian parameter \warning This parameter READS the centerIndexX parameter from file and is THEN MISUSED to calculate some position in world coordinates, so we require double. */ std::vector m_CenterZ; /** \brief XML-Tag "deviationX: gaussian parameter */ std::vector m_SigmaX; /** \brief XML-Tag "deviationY: gaussian parameter */ std::vector m_SigmaY; /** \brief XML-Tag "deviationZ: gaussian parameter */ std::vector m_SigmaZ; /** \brief XML-Tag "altitude: gaussian parameter */ std::vector m_Altitude; // XML-Tag /** \brief XML-Tag "numberOfLabels": number of different labels which appear in the mask */ unsigned int m_NumberOfLabels; /** \brief XML-Tag "hotspotRadiusInMM": radius of hotspot */ double m_HotspotRadiusInMM; // XML-Tag /** \brief XML-Tag "maximumSizeX": maximum position of ROI in x-dimension */ vnl_vector m_MaxIndexX; /** \brief XML-Tag "minimumSizeX": minimum position of ROI in x-dimension */ vnl_vector m_MinIndexX; /** \brief XML-Tag "maximumSizeX": maximum position of ROI in y-dimension */ vnl_vector m_MaxIndexY; /** \brief XML-Tag "minimumSizeX": minimum position of ROI in y-dimension */ vnl_vector m_MinIndexY; /** \brief XML-Tag "maximumSizeX": maximum position of ROI in z-dimension */ vnl_vector m_MaxIndexZ; /** \brief XML-Tag "minimumSizeX": minimum position of ROI in z-dimension */ vnl_vector m_MinIndexZ; /** \brief XML-Tag "label": value of label */ vnl_vector m_Label; //XML-Tag /** \brief XML-Tag "minimum": minimum inside hotspot */ vnl_vector m_HotspotMin; /** \brief XML-Tag "maximum": maximum inside hotspot */ vnl_vector m_HotspotMax; /** \brief XML-Tag "mean": mean value of hotspot */ vnl_vector m_HotspotMean; /** \brief XML-Tag "maximumIndexX": x-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMaxIndexX; /** \brief XML-Tag "maximumIndexX": y-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMaxIndexY; /** \brief XML-Tag "maximumIndexX": z-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMaxIndexZ; /** \brief XML-Tag "maximumIndexX": x-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMinIndexX; /** \brief XML-Tag "maximumIndexX": y-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMinIndexY; /** \brief XML-Tag "maximumIndexX": z-coordinate of maximum-location inside hotspot */ vnl_vector m_HotspotMinIndexZ; /** \brief XML-Tag "maximumIndexX": x-coordinate of hotspot-location */ vnl_vector m_HotspotIndexX; /** \brief XML-Tag "maximumIndexX": y-coordinate of hotspot-location */ vnl_vector m_HotspotIndexY; /** \brief XML-Tag "maximumIndexX": z-coordinate of hotspot-location */ vnl_vector m_HotspotIndexZ; }; /** \brief Find/Convert integer attribute in itk::DOMNode. */ static int GetIntegerAttribute(itk::DOMNode* domNode, const std::string& tag) { assert(domNode); MITK_TEST_CONDITION_REQUIRED( domNode->HasAttribute(tag), "Tag '" << tag << "' is defined in test parameters" ); std::string attributeValue = domNode->GetAttribute(tag); int resultValue; try { //MITK_TEST_OUTPUT( << "Converting tag value '" << attributeValue << "' for tag '" << tag << "' to integer"); std::stringstream(attributeValue) >> resultValue; return resultValue; } catch(std::exception& /*e*/) { MITK_TEST_CONDITION_REQUIRED(false, "Convert tag value '" << attributeValue << "' for tag '" << tag << "' to integer"); return 0; // just to satisfy compiler } } /** \brief Find/Convert double attribute in itk::DOMNode. */ static double GetDoubleAttribute(itk::DOMNode* domNode, const std::string& tag) { assert(domNode); MITK_TEST_CONDITION_REQUIRED( domNode->HasAttribute(tag), "Tag '" << tag << "' is defined in test parameters" ); std::string attributeValue = domNode->GetAttribute(tag); double resultValue; try { //MITK_TEST_OUTPUT( << "Converting tag value '" << attributeValue << "' for tag '" << tag << "' to double"); std::stringstream(attributeValue) >> resultValue; return resultValue; } catch(std::exception& /*e*/) { MITK_TEST_CONDITION_REQUIRED(false, "Convert tag value '" << attributeValue << "' for tag '" << tag << "' to double"); return 0.0; // just to satisfy compiler } } /** \brief Read XML file describing the test parameters. Reads XML file given in first commandline parameter in order to construct a Parameters structure. The XML file should be structurs as the following example, i.e. we describe the three test aspects of Parameters in four different tags, with all the details described as tag attributes. */ /** \verbatim \endverbatim */ static Parameters ParseParameters(int argc, char* argv[]) { MITK_TEST_CONDITION_REQUIRED(argc == 2, "Test is invoked with exactly 1 parameter (XML parameters file)"); MITK_INFO << "Reading parameters from file '" << argv[1] << "'"; std::string filename = argv[1]; Parameters result; itk::DOMNodeXMLReader::Pointer xmlReader = itk::DOMNodeXMLReader::New(); xmlReader->SetFileName( filename ); try { xmlReader->Update(); itk::DOMNode::Pointer domRoot = xmlReader->GetOutput(); typedef std::vector NodeList; NodeList testimages; domRoot->GetChildren("testimage", testimages); MITK_TEST_CONDITION_REQUIRED( testimages.size() == 1, "One test image defined" ) itk::DOMNode* testimage = testimages[0]; result.m_ImageRows = GetIntegerAttribute( testimage, "image-rows" ); result.m_ImageColumns = GetIntegerAttribute( testimage, "image-columns" ); result.m_ImageSlices = GetIntegerAttribute( testimage, "image-slices" ); result.m_NumberOfGaussian = GetIntegerAttribute( testimage, "numberOfGaussians" ); result.m_Spacing[0] = GetDoubleAttribute(testimage, "spacingX"); result.m_Spacing[1] = GetDoubleAttribute(testimage, "spacingY"); result.m_Spacing[2] = GetDoubleAttribute(testimage, "spacingZ"); result.m_EntireHotspotInImage = GetIntegerAttribute( testimage, "entireHotSpotInImage" ); MITK_TEST_OUTPUT( << "Read size parameters (x,y,z): " << result.m_ImageRows << "," << result.m_ImageColumns << "," << result.m_ImageSlices); MITK_TEST_OUTPUT( << "Read spacing parameters (x,y,z): " << result.m_Spacing[0] << "," << result.m_Spacing[1] << "," << result.m_Spacing[2]); NodeList gaussians; testimage->GetChildren("gaussian", gaussians); MITK_TEST_CONDITION_REQUIRED( gaussians.size() >= 1, "At least one gaussian is defined" ) result.m_CenterX.resize(result.m_NumberOfGaussian); result.m_CenterY.resize(result.m_NumberOfGaussian); result.m_CenterZ.resize(result.m_NumberOfGaussian); result.m_SigmaX.resize(result.m_NumberOfGaussian); result.m_SigmaY.resize(result.m_NumberOfGaussian); result.m_SigmaZ.resize(result.m_NumberOfGaussian); result.m_Altitude.resize(result.m_NumberOfGaussian); for(int i = 0; i < result.m_NumberOfGaussian ; ++i) { itk::DOMNode* gaussian = gaussians[i]; result.m_CenterX[i] = GetIntegerAttribute(gaussian, "centerIndexX"); result.m_CenterY[i] = GetIntegerAttribute(gaussian, "centerIndexY"); result.m_CenterZ[i] = GetIntegerAttribute(gaussian, "centerIndexZ"); result.m_SigmaX[i] = GetDoubleAttribute(gaussian, "deviationX"); result.m_SigmaY[i] = GetDoubleAttribute(gaussian, "deviationY"); result.m_SigmaZ[i] = GetDoubleAttribute(gaussian, "deviationZ"); result.m_Altitude[i] = GetDoubleAttribute(gaussian, "altitude"); result.m_CenterX[i] = result.m_CenterX[i] * result.m_Spacing[0]; result.m_CenterY[i] = result.m_CenterY[i] * result.m_Spacing[1]; result.m_CenterZ[i] = result.m_CenterZ[i] * result.m_Spacing[2]; result.m_SigmaX[i] = result.m_SigmaX[i] * result.m_Spacing[0]; result.m_SigmaY[i] = result.m_SigmaY[i] * result.m_Spacing[1]; result.m_SigmaZ[i] = result.m_SigmaZ[i] * result.m_Spacing[2]; } NodeList segmentations; domRoot->GetChildren("segmentation", segmentations); MITK_TEST_CONDITION_REQUIRED( segmentations.size() == 1, "One segmentation defined"); itk::DOMNode* segmentation = segmentations[0]; result.m_NumberOfLabels = GetIntegerAttribute(segmentation, "numberOfLabels"); result.m_HotspotRadiusInMM = GetDoubleAttribute(segmentation, "hotspotRadiusInMM"); // read ROI parameters, fill result structure NodeList rois; segmentation->GetChildren("roi", rois); MITK_TEST_CONDITION_REQUIRED( rois.size() >= 1, "At least one ROI defined" ) result.m_MaxIndexX.set_size(result.m_NumberOfLabels); result.m_MinIndexX.set_size(result.m_NumberOfLabels); result.m_MaxIndexY.set_size(result.m_NumberOfLabels); result.m_MinIndexY.set_size(result.m_NumberOfLabels); result.m_MaxIndexZ.set_size(result.m_NumberOfLabels); result.m_MinIndexZ.set_size(result.m_NumberOfLabels); result.m_Label.set_size(result.m_NumberOfLabels); for(unsigned int i = 0; i < rois.size(); ++i) { result.m_MaxIndexX[i] = GetIntegerAttribute(rois[i], "maximumIndexX"); result.m_MinIndexX[i] = GetIntegerAttribute(rois[i], "minimumIndexX"); result.m_MaxIndexY[i] = GetIntegerAttribute(rois[i], "maximumIndexY"); result.m_MinIndexY[i] = GetIntegerAttribute(rois[i], "minimumIndexY"); result.m_MaxIndexZ[i] = GetIntegerAttribute(rois[i], "maximumIndexZ"); result.m_MinIndexZ[i] = GetIntegerAttribute(rois[i], "minimumIndexZ"); result.m_Label[i] = GetIntegerAttribute(rois[i], "label"); } // read statistic parameters, fill result structure NodeList statistics; domRoot->GetChildren("statistic", statistics); MITK_TEST_CONDITION_REQUIRED( statistics.size() >= 1 , "At least one statistic defined" ) MITK_TEST_CONDITION_REQUIRED( statistics.size() == rois.size(), "Same number of rois and corresponding statistics defined"); result.m_HotspotMin.set_size(statistics.size()); result.m_HotspotMax.set_size(statistics.size()); result.m_HotspotMean.set_size(statistics.size()); result.m_HotspotMinIndexX.set_size(statistics.size()); result.m_HotspotMinIndexY.set_size(statistics.size()); result.m_HotspotMinIndexZ.set_size(statistics.size()); result.m_HotspotMaxIndexX.set_size(statistics.size()); result.m_HotspotMaxIndexY.set_size(statistics.size()); result.m_HotspotMaxIndexZ.set_size(statistics.size()); result.m_HotspotIndexX.set_size(statistics.size()); result.m_HotspotIndexY.set_size(statistics.size()); result.m_HotspotIndexZ.set_size(statistics.size()); for(unsigned int i = 0; i < statistics.size(); ++i) { result.m_HotspotMin[i] = GetDoubleAttribute(statistics[i], "minimum"); result.m_HotspotMax[i] = GetDoubleAttribute(statistics[i], "maximum"); result.m_HotspotMean[i] = GetDoubleAttribute(statistics[i], "mean"); result.m_HotspotMinIndexX[i] = GetIntegerAttribute(statistics[i], "minimumIndexX"); result.m_HotspotMinIndexY[i] = GetIntegerAttribute(statistics[i], "minimumIndexY"); result.m_HotspotMinIndexZ[i] = GetIntegerAttribute(statistics[i], "minimumIndexZ"); result.m_HotspotMaxIndexX[i] = GetIntegerAttribute(statistics[i], "maximumIndexX"); result.m_HotspotMaxIndexY[i] = GetIntegerAttribute(statistics[i], "maximumIndexY"); result.m_HotspotMaxIndexZ[i] = GetIntegerAttribute(statistics[i], "maximumIndexZ"); result.m_HotspotIndexX[i] = GetIntegerAttribute(statistics[i], "hotspotIndexX"); result.m_HotspotIndexY[i] = GetIntegerAttribute(statistics[i], "hotspotIndexY"); result.m_HotspotIndexZ[i] = GetIntegerAttribute(statistics[i], "hotspotIndexZ"); } } catch (std::exception& e) { MITK_TEST_CONDITION_REQUIRED(false, "Reading test parameters from XML file. Error message: " << e.what()); } return result; } /** \brief Generate an image that contains a couple of 3D gaussian distributions. Uses the given parameters to produce a test image using class MultiGaussianImageSource. */ static mitk::Image::Pointer BuildTestImage(const Parameters& testParameters) { mitk::Image::Pointer result; typedef double PixelType; const int Dimension = 3; typedef itk::Image ImageType; ImageType::Pointer image = ImageType::New(); typedef itk::MultiGaussianImageSource< ImageType > MultiGaussianImageSource; MultiGaussianImageSource::Pointer gaussianGenerator = MultiGaussianImageSource::New(); ImageType::SizeValueType size[3]; size[0] = testParameters.m_ImageColumns; size[1] = testParameters.m_ImageRows; size[2] = testParameters.m_ImageSlices; itk::MultiGaussianImageSource::VectorType centerXVec, centerYVec, centerZVec, sigmaXVec, sigmaYVec, sigmaZVec, altitudeVec; for(int i = 0; i < testParameters.m_NumberOfGaussian; ++i) { centerXVec.push_back(testParameters.m_CenterX[i]); centerYVec.push_back(testParameters.m_CenterY[i]); centerZVec.push_back(testParameters.m_CenterZ[i]); sigmaXVec.push_back(testParameters.m_SigmaX[i]); sigmaYVec.push_back(testParameters.m_SigmaY[i]); sigmaZVec.push_back(testParameters.m_SigmaZ[i]); altitudeVec.push_back(testParameters.m_Altitude[i]); } ImageType::SpacingType spacing; for( int i = 0; i < Dimension; ++i ) spacing[i] = testParameters.m_Spacing[i]; gaussianGenerator->SetSize( size ); gaussianGenerator->SetSpacing( spacing ); gaussianGenerator->SetRadius(testParameters.m_HotspotRadiusInMM); gaussianGenerator->SetNumberOfGausssians(testParameters.m_NumberOfGaussian); gaussianGenerator->AddGaussian(centerXVec, centerYVec, centerZVec, sigmaXVec, sigmaYVec, sigmaZVec, altitudeVec); gaussianGenerator->Update(); image = gaussianGenerator->GetOutput(); mitk::CastToMitkImage(image, result); return result; } /** \brief Calculates hotspot statistics for given test image and ROI parameters. Uses ImageStatisticsCalculator to find a hotspot in a defined ROI within the given image. */ - static mitk::ImageStatisticsCalculator::Statistics CalculateStatistics(mitk::Image* image, const Parameters& testParameters, unsigned int label) + static mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer CalculateStatistics(mitk::Image* image, const Parameters& testParameters, unsigned int label) { - mitk::ImageStatisticsCalculator::Statistics result; + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer result; const unsigned int Dimension = 3; typedef itk::Image MaskImageType; MaskImageType::Pointer mask = MaskImageType::New(); MaskImageType::SizeType size; MaskImageType::SpacingType spacing; MaskImageType::IndexType start; mitk::ImageStatisticsCalculator::Pointer statisticsCalculator = mitk::ImageStatisticsCalculator::New(); - statisticsCalculator->SetImage(image); + statisticsCalculator->SetInputImage(image); mitk::Image::Pointer mitkMaskImage; if((testParameters.m_MaxIndexX[label] > testParameters.m_MinIndexX[label] && testParameters.m_MinIndexX[label] >= 0) && (testParameters.m_MaxIndexY[label] > testParameters.m_MinIndexY[label] && testParameters.m_MinIndexY[label] >= 0) && (testParameters.m_MaxIndexZ[label] > testParameters.m_MinIndexZ[label] && testParameters.m_MinIndexZ[label] >= 0)) { for(unsigned int i = 0; i < Dimension; ++i) { start[i] = 0; spacing[i] = testParameters.m_Spacing[i]; } size[0] = testParameters.m_ImageColumns; size[1] = testParameters.m_ImageRows; size[2] = testParameters.m_ImageSlices; MaskImageType::RegionType region; region.SetIndex(start); region.SetSize(size); mask->SetSpacing(spacing); mask->SetRegions(region); mask->Allocate(); typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; MaskImageIteratorType maskIt(mask, region); for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { maskIt.Set(0); } for(unsigned int i = 0; i < testParameters.m_NumberOfLabels; ++i) { for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { MaskImageType::IndexType index = maskIt.GetIndex(); if((index[0] >= testParameters.m_MinIndexX[i] && index[0] <= testParameters.m_MaxIndexX[i] ) && (index[1] >= testParameters.m_MinIndexY[i] && index[1] <= testParameters.m_MaxIndexY[i] ) && (index[2] >= testParameters.m_MinIndexZ[i] && index[2] <= testParameters.m_MaxIndexZ[i] )) { maskIt.Set(testParameters.m_Label[i]); } } } - MITK_DEBUG << "Masking mode has set to image"; mitk::CastToMitkImage(mask, mitkMaskImage); - statisticsCalculator->SetImageMask(mitkMaskImage); - statisticsCalculator->SetMaskingModeToImage(); - } - else - { - MITK_DEBUG << "Masking mode has set to none"; - statisticsCalculator->SetMaskingModeToNone(); - } - - statisticsCalculator->SetHotspotRadiusInMM(testParameters.m_HotspotRadiusInMM); - statisticsCalculator->SetCalculateHotspot(true); + mitk::ImageMaskGenerator::Pointer imgMaskGen = mitk::ImageMaskGenerator::New(); + imgMaskGen->SetImageMask(mitkMaskImage); + + mitk::HotspotMaskGenerator::Pointer hotspotMaskGen = mitk::HotspotMaskGenerator::New(); + hotspotMaskGen->SetInputImage(image); + hotspotMaskGen->SetMask(imgMaskGen.GetPointer()); + hotspotMaskGen->SetHotspotRadiusInMM(testParameters.m_HotspotRadiusInMM); + if(testParameters.m_EntireHotspotInImage == 1) + { + MITK_INFO << "Hotspot must be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(true); + } + else + { + MITK_INFO << "Hotspot must not be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(false); + } - if(testParameters.m_EntireHotspotInImage == 1) - { - MITK_INFO << "Hotspot must be completly inside image"; - statisticsCalculator->SetHotspotMustBeCompletlyInsideImage(true); + statisticsCalculator->SetMask(hotspotMaskGen.GetPointer()); + MITK_DEBUG << "Masking is set to hotspot+image mask"; } else { - MITK_INFO << "Hotspot must not be completly inside image"; - statisticsCalculator->SetHotspotMustBeCompletlyInsideImage(false); + mitk::HotspotMaskGenerator::Pointer hotspotMaskGen = mitk::HotspotMaskGenerator::New(); + hotspotMaskGen->SetInputImage(image); + hotspotMaskGen->SetHotspotRadiusInMM(testParameters.m_HotspotRadiusInMM); + if(testParameters.m_EntireHotspotInImage == 1) + { + MITK_INFO << "Hotspot must be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(true); + } + else + { + MITK_INFO << "Hotspot must not be completly inside image"; + hotspotMaskGen->SetHotspotMustBeCompletelyInsideImage(false); + } + MITK_DEBUG << "Masking is set to hotspot only"; } - statisticsCalculator->ComputeStatistics(); result = statisticsCalculator->GetStatistics(0, label); return result; } static void ValidateStatisticsItem(const std::string& label, double testvalue, double reference, double tolerance) { double diff = ::fabs(reference - testvalue); MITK_TEST_CONDITION( diff < tolerance, "'" << label << "' value close enough to reference value " "(value=" << testvalue << ", reference=" << reference << ", diff=" << diff << ")" ); } static void ValidateStatisticsItem(const std::string& label, const vnl_vector& testvalue, const vnl_vector& reference) { double diffX = ::fabs(double(testvalue[0] - reference[0])); double diffY = ::fabs(double(testvalue[1] - reference[1])); double diffZ = ::fabs(double(testvalue[2] - reference[2])); std::stringstream testPosition; testPosition << testvalue[0] << "," << testvalue[1] << "," << testvalue[2]; std::stringstream referencePosition; referencePosition << reference[0] << "," << reference[1] << "," << reference[2]; MITK_TEST_CONDITION( diffX < mitk::eps && diffY < mitk::eps && diffZ < mitk::eps, "'" << label << "' close enough to reference value " << "(value=[" << testPosition.str() << "]," << " reference=[" << referencePosition.str() << "]"); } /** \brief Compares calculated against actual statistics values. Checks validness of all statistics aspects. Lets test fail if any aspect is not sufficiently equal. */ - static void ValidateStatistics(const mitk::ImageStatisticsCalculator::Statistics& statistics, const Parameters& testParameters, unsigned int label) + static void ValidateStatistics(const mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer hotspotStatistics, const Parameters& testParameters, unsigned int label) { // check all expected test result against actual results double eps = 0.25; // value above the largest tested difference - ValidateStatisticsItem("Hotspot mean", statistics.GetHotspotStatistics().GetMean(), testParameters.m_HotspotMean[label], eps); - ValidateStatisticsItem("Hotspot maximum", statistics.GetHotspotStatistics().GetMax(), testParameters.m_HotspotMax[label], eps); - ValidateStatisticsItem("Hotspot minimum", statistics.GetHotspotStatistics().GetMin(), testParameters.m_HotspotMin[label], eps); + ValidateStatisticsItem("Hotspot mean", hotspotStatistics->GetMean(), testParameters.m_HotspotMean[label], eps); + ValidateStatisticsItem("Hotspot maximum", hotspotStatistics->GetMax(), testParameters.m_HotspotMax[label], eps); + ValidateStatisticsItem("Hotspot minimum", hotspotStatistics->GetMin(), testParameters.m_HotspotMin[label], eps); vnl_vector referenceHotspotCenterIndex; referenceHotspotCenterIndex.set_size(3); referenceHotspotCenterIndex[0] = testParameters.m_HotspotIndexX[label]; referenceHotspotCenterIndex[1] = testParameters.m_HotspotIndexY[label]; referenceHotspotCenterIndex[2] = testParameters.m_HotspotIndexZ[label]; - ValidateStatisticsItem("Hotspot center position", statistics.GetHotspotStatistics().GetHotspotIndex(), referenceHotspotCenterIndex); + // ValidateStatisticsItem("Hotspot center position", statistics.GetHotspotStatistics().GetHotspotIndex(), referenceHotspotCenterIndex); TODO: new image statistics calculator does not give hotspot position // TODO we do not test minimum/maximum positions within the peak/hotspot region, because // these positions are not unique, i.e. there are multiple valid minima/maxima positions. // One solution would be to modify the test cases in order to achive clear positions. // The BETTER/CORRECT solution would be to change the singular position into a set of positions / a region } }; /** \brief Verifies that hotspot statistics part of ImageStatisticsCalculator. The test reads parameters from an XML-file to generate a test-image, calculates the hotspot statistics of the image and checks if the calculated statistics are the same as the specified values of the XML-file. */ int mitkImageStatisticsHotspotTest(int argc, char* argv[]) { MITK_TEST_BEGIN("mitkImageStatisticsHotspotTest") try { mitkImageStatisticsHotspotTestClass::Parameters parameters = mitkImageStatisticsHotspotTestClass::ParseParameters(argc,argv); mitk::Image::Pointer image = mitkImageStatisticsHotspotTestClass::BuildTestImage(parameters); MITK_TEST_CONDITION_REQUIRED( image.IsNotNull(), "Generate test image" ); for(unsigned int label = 0; label < parameters.m_NumberOfLabels; ++label) { - mitk::ImageStatisticsCalculator::Statistics statistics = mitkImageStatisticsHotspotTestClass::CalculateStatistics(image, parameters, label); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer statistics = mitkImageStatisticsHotspotTestClass::CalculateStatistics(image, parameters, label); mitkImageStatisticsHotspotTestClass::ValidateStatistics(statistics, parameters, label); std::cout << std::endl; } } catch (std::exception& e) { std::cout << "Error: " << e.what() << std::endl; MITK_TEST_CONDITION_REQUIRED( false, "Exception occurred during test execution: " << e.what() ); } catch(...) { MITK_TEST_CONDITION_REQUIRED( false, "Exception occurred during test execution." ); } MITK_TEST_END() } diff --git a/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp b/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp index 1907147299..d5d888d586 100644 --- a/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp +++ b/Modules/ImageStatistics/Testing/mitkImageStatisticsTextureAnalysisTest.cpp @@ -1,235 +1,235 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkTestingMacros.h" #include "itkImage.h" #include "mitkExtendedLabelStatisticsImageFilter.h" #include "mitkExtendedStatisticsImageFilter.h" #include "mitkNumericConstants.h" /** \section Testing of Skewness and Kurtosis * This test class is for testing the added coefficients Skewness and Kurtosis * for the mitkExtendedLabelStatisticsImageFilter (Masked Images) and for the * mitkExtendedStatisticsImageFilter (Unmasked Images). Both filter will be tested * against two pictures. */ class mitkImageStatisticsTextureAnalysisTestClass { /** * \brief Explanation of the mitkImageStatisticsTextureAnalysisTestClass test class * * this test class produce test images and masking images with the method CreatingTestImageForDifferentLabelSize. * TestInstanceFortheMaskedStatisticsFilter and TestInstanceFortheUnmaskedStatisticsFilter are the two Instances * for the filters of masking and unmasking images. * TestofSkewnessAndKurtosisForMaskedImagesand TestofSkewnessAndKurtosisForUnmaskedImages are the correlated test * for the checking the values. */ public: typedef itk::Image< int,3 >ImageType; typedef ImageType::Pointer PointerOfImage; typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, ImageType > LabelStatisticsFilterType; typedef LabelStatisticsFilterType::Pointer labelStatisticsFilterPointer; typedef itk::ExtendedStatisticsImageFilter< ImageType > StatisticsFilterType; typedef StatisticsFilterType::Pointer StatisticsFilterPointer; ImageType::Pointer CreatingTestImageForDifferentLabelSize( int factorOfDividingThePicture, int bufferValue, int labelValue) { ImageType::Pointer image = ImageType :: New(); ImageType::IndexType start; ImageType::SizeType size; start[0] = 0; start[1] = 0; start[2] = 0; size[0] = 100; size[1] = 100; size[2] = 100; ImageType:: RegionType region; region.SetSize( size ); region.SetIndex( start ); image->SetRegions(region); image->Allocate(); image->FillBuffer(bufferValue); int i = 0; for(unsigned int r = 0; r < 50; r++) { for(unsigned int c = 0; c < factorOfDividingThePicture; c++) { for(unsigned int l = 0; l < 100; l++) { ImageType::IndexType pixelIndex; pixelIndex[0] = r; pixelIndex[1] = c; pixelIndex[2] = l; image->SetPixel(pixelIndex, labelValue); } } } return image; } LabelStatisticsFilterType::Pointer TestInstanceFortheMaskedStatisticsFilter(ImageType::Pointer image, ImageType::Pointer maskImage) { LabelStatisticsFilterType::Pointer labelStatisticsFilter; labelStatisticsFilter = LabelStatisticsFilterType::New(); labelStatisticsFilter->SetInput( image ); labelStatisticsFilter->UseHistogramsOn(); labelStatisticsFilter->SetHistogramParameters( 20, -10, 10); labelStatisticsFilter->SetLabelInput( maskImage ); labelStatisticsFilter->Update(); return labelStatisticsFilter; } StatisticsFilterType::Pointer TestInstanceFortheUnmaskedStatisticsFilter(ImageType::Pointer image ) { StatisticsFilterType::Pointer StatisticsFilter; StatisticsFilter = StatisticsFilterType::New(); StatisticsFilter->SetInput( image ); - StatisticsFilter->SetBinSize( 20 ); + StatisticsFilter->SetHistogramParameters( 20, -10, 10 ); StatisticsFilter->Update(); return StatisticsFilter; } //test for Skewness,Kurtosis and MPP for masked Images void TestofSkewnessKurtosisAndMPPForMaskedImages(LabelStatisticsFilterType::Pointer labelStatisticsFilter, double expectedSkewness, double expectedKurtosis, double expectedMPP) { // let's create an object of our class bool isSkewsnessLowerlimitCorrect = labelStatisticsFilter->GetSkewness( 1 )- expectedKurtosis+ std::pow(10,-3) <= expectedSkewness; bool isSkewsnessUpperlimitCorrect = labelStatisticsFilter->GetSkewness( 1 )+ expectedKurtosis+ std::pow(10,-3) >= expectedSkewness; MITK_TEST_CONDITION( isSkewsnessLowerlimitCorrect && isSkewsnessUpperlimitCorrect,"expectedSkewness: " << expectedSkewness << " actual Value: " << labelStatisticsFilter->GetSkewness( 1 ) ); bool isKurtosisUpperlimitCorrect = labelStatisticsFilter->GetKurtosis( 1 ) <= expectedKurtosis+ std::pow(10,-3); bool isKurtosisLowerlimitCorrect = expectedKurtosis- std::pow(10,-3) <= labelStatisticsFilter->GetKurtosis( 1 ); MITK_TEST_CONDITION( isKurtosisUpperlimitCorrect && isKurtosisLowerlimitCorrect,"expectedKurtosis: " << expectedKurtosis << " actual Value: " << labelStatisticsFilter->GetKurtosis( 1 ) ); MITK_TEST_CONDITION( ( expectedMPP - labelStatisticsFilter->GetMPP( 1 ) ) < 1, "expected MPP: " << expectedMPP << " actual Value: " << labelStatisticsFilter->GetMPP( 1 ) ); } //test for Entropy,Uniformity and UPP for masked Images void TestofEntropyUniformityAndUppForMaskedImages(LabelStatisticsFilterType::Pointer labelStatisticsFilter, double expectedEntropy, double expectedUniformity, double expectedUPP) { bool calculatedEntropyLowerLimit = labelStatisticsFilter->GetEntropy( 1 ) >= expectedEntropy - std::pow(10,-3); bool calculatedUniformityLowerLimit = labelStatisticsFilter->GetUniformity( 1 ) >= expectedUniformity - std::pow(10,-3); bool calculatedUppLowerLimit = labelStatisticsFilter->GetUPP( 1 ) >= expectedUPP - std::pow(10,-3); bool calculatedEntropyUpperLimit = labelStatisticsFilter->GetEntropy( 1 ) <= expectedEntropy + std::pow(10,-3); bool calculatedUniformityUpperLimit = labelStatisticsFilter->GetUniformity( 1 ) <= expectedUniformity + std::pow(10,-3); bool calculatedUppUpperLimit = labelStatisticsFilter->GetUPP( 1 ) <= expectedUPP + std::pow(10,-3); MITK_TEST_CONDITION( calculatedEntropyLowerLimit && calculatedEntropyUpperLimit, "expected Entropy: " << expectedEntropy << " actual Value: " << labelStatisticsFilter->GetEntropy( 1 ) ); MITK_TEST_CONDITION( calculatedUniformityLowerLimit && calculatedUniformityUpperLimit, "expected Uniformity: " << expectedUniformity << " actual Value: " << labelStatisticsFilter->GetUniformity( 1 ) ); MITK_TEST_CONDITION( calculatedUppLowerLimit && calculatedUppUpperLimit, "expected UPP: " << expectedUPP << " actual Value: " << labelStatisticsFilter->GetUPP( 1 ) ); } //test for Skewness,Kurtosis and MPP for unmasked Images void TestofSkewnessKurtosisAndMPPForUnmaskedImages(StatisticsFilterType::Pointer StatisticsFilter, double expectedSkewness, double expectedKurtosis, double expectedMPP) { // let's create an object of our class bool isSkewsnessLowerlimitCorrect = StatisticsFilter->GetSkewness()- expectedKurtosis+ std::pow(10,-3) <= expectedSkewness; bool isSkewsnessUpperlimitCorrect = StatisticsFilter->GetSkewness()+ expectedKurtosis+ std::pow(10,-3) >= expectedSkewness; MITK_TEST_CONDITION( isSkewsnessLowerlimitCorrect && isSkewsnessUpperlimitCorrect,"expectedSkewness: " << expectedSkewness << " actual Value: " << StatisticsFilter->GetSkewness() ); bool isKurtosisUpperlimitCorrect = StatisticsFilter->GetKurtosis() <= expectedKurtosis+ std::pow(10,-3); bool isKurtosisLowerlimitCorrect = expectedKurtosis- std::pow(10,-3) <= StatisticsFilter->GetKurtosis(); MITK_TEST_CONDITION( isKurtosisUpperlimitCorrect && isKurtosisLowerlimitCorrect,"expectedKurtosis: " << expectedKurtosis << " actual Value: " << StatisticsFilter->GetKurtosis() ); MITK_TEST_CONDITION( ( expectedMPP - StatisticsFilter->GetMPP() ) < mitk::eps, "expected MPP: " << expectedMPP << " actual Value: " << StatisticsFilter->GetMPP() ); } //test for Entropy,Uniformity and UPP for unmasked Images void TestofEntropyUniformityAndUppForUnmaskedImages(StatisticsFilterType::Pointer StatisticsFilter, double expectedEntropy, double expectedUniformity, double expectedUPP) { bool calculatedEntropyLowerLimit = StatisticsFilter->GetEntropy() >= expectedEntropy - std::pow(10,-3); bool calculatedUniformityLowerLimit = StatisticsFilter->GetUniformity() >= expectedUniformity - std::pow(10,-3); bool calculatedUppLowerLimit = StatisticsFilter->GetUPP() >= expectedUPP - std::pow(10,-3); bool calculatedEntropyUpperLimit = StatisticsFilter->GetEntropy() <= expectedEntropy + std::pow(10,-3); bool calculatedUniformityUpperLimit = StatisticsFilter->GetUniformity() <= expectedUniformity + std::pow(10,-3); bool calculatedUppUpperLimit = StatisticsFilter->GetUPP() <= expectedUPP + std::pow(10,-3); MITK_TEST_CONDITION( calculatedEntropyLowerLimit && calculatedEntropyUpperLimit, "expected Entropy: " << expectedEntropy << " actual Value: " << StatisticsFilter->GetEntropy() ); MITK_TEST_CONDITION( calculatedUniformityLowerLimit && calculatedUniformityUpperLimit, "expected Uniformity: " << expectedUniformity << " actual Value: " << StatisticsFilter->GetUniformity() ); MITK_TEST_CONDITION( calculatedUppLowerLimit && calculatedUppUpperLimit, "expected UPP: " << expectedUPP << " actual Value: " << StatisticsFilter->GetUPP() ); } }; int mitkImageStatisticsTextureAnalysisTest(int, char* []) { // always start with this! MITK_TEST_BEGIN("mitkImageStatisticsTextureAnalysisTest") mitkImageStatisticsTextureAnalysisTestClass testclassInstance; mitkImageStatisticsTextureAnalysisTestClass::PointerOfImage labelImage = testclassInstance.CreatingTestImageForDifferentLabelSize(100, 1, 1); mitkImageStatisticsTextureAnalysisTestClass::PointerOfImage image = testclassInstance.CreatingTestImageForDifferentLabelSize(100, 3, 2); mitkImageStatisticsTextureAnalysisTestClass::PointerOfImage image2 = testclassInstance.CreatingTestImageForDifferentLabelSize(50, 3, 2); //test for masked images mitkImageStatisticsTextureAnalysisTestClass::labelStatisticsFilterPointer mitkLabelFilter= testclassInstance.TestInstanceFortheMaskedStatisticsFilter( image,labelImage); testclassInstance.TestofSkewnessKurtosisAndMPPForMaskedImages(mitkLabelFilter, 0, 0.999998, 2.5); testclassInstance.TestofEntropyUniformityAndUppForMaskedImages(mitkLabelFilter, 1, 0.5, 0.5); mitkImageStatisticsTextureAnalysisTestClass::labelStatisticsFilterPointer mitkLabelFilter2= testclassInstance.TestInstanceFortheMaskedStatisticsFilter( image2,labelImage); testclassInstance.TestofSkewnessKurtosisAndMPPForMaskedImages(mitkLabelFilter2, -1.1547, 2.33333, 2.75); testclassInstance.TestofEntropyUniformityAndUppForMaskedImages(mitkLabelFilter2, 0.811278, 0.625, 0.625); //test for unmasked images mitkImageStatisticsTextureAnalysisTestClass::StatisticsFilterPointer mitkFilter= testclassInstance.TestInstanceFortheUnmaskedStatisticsFilter( image); testclassInstance.TestofSkewnessKurtosisAndMPPForUnmaskedImages(mitkFilter, 0, 0.999998, 2.5); testclassInstance.TestofEntropyUniformityAndUppForUnmaskedImages(mitkFilter, 1, 0.5, 0.5); mitkImageStatisticsTextureAnalysisTestClass::StatisticsFilterPointer mitkFilter2= testclassInstance.TestInstanceFortheUnmaskedStatisticsFilter( image2); testclassInstance.TestofSkewnessKurtosisAndMPPForUnmaskedImages(mitkFilter2, -1.1547, 2.33333, 2.75); testclassInstance.TestofEntropyUniformityAndUppForUnmaskedImages( mitkFilter2, 0.811278, 0.625, 0.625); MITK_TEST_END() } diff --git a/Modules/ImageStatistics/files.cmake b/Modules/ImageStatistics/files.cmake index 29d3c3547a..5904c1fdfb 100644 --- a/Modules/ImageStatistics/files.cmake +++ b/Modules/ImageStatistics/files.cmake @@ -1,14 +1,33 @@ set(CPP_FILES mitkImageStatisticsCalculator.cpp mitkPointSetStatisticsCalculator.cpp mitkPointSetDifferenceStatisticsCalculator.cpp mitkIntensityProfile.cpp + mitkHotspotMaskGenerator.cpp + mitkMaskGenerator.cpp + mitkPlanarFigureMaskGenerator.cpp + mitkMultiLabelMaskGenerator.cpp + mitkImageMaskGenerator.cpp + mitkHistogramStatisticsCalculator.cpp + mitkMaskUtilities.cpp + mitkIgnorePixelMaskGenerator.cpp ) set(H_FILES mitkImageStatisticsCalculator.h mitkPointSetDifferenceStatisticsCalculator.h mitkPointSetStatisticsCalculator.h mitkExtendedStatisticsImageFilter.h mitkExtendedLabelStatisticsImageFilter.h + mitkHotspotMaskGenerator.h + mitkMaskGenerator.h + mitkPlanarFigureMaskGenerator.h + mitkMultiLabelMaskGenerator.h + mitkImageMaskGenerator.h + mitkHistogramStatisticsCalculator.h + mitkMaskUtilities.h + mitkitkMaskImageFilter.h + mitkIgnorePixelMaskGenerator.h + mitkMinMaxImageFilterWithIndex.h + mitkMinMaxLabelmageFilterWithIndex.h ) diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h index ae449d14c8..54d3d46f47 100644 --- a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h +++ b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h @@ -1,130 +1,348 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#ifndef __mitkExtendedLabelStatisticsImageFilter_h -#define __mitkExtendedLabelStatisticsImageFilter_h +#ifndef __mitkExtendedLabelStatisticsImageFilter +#define __mitkExtendedLabelStatisticsImageFilter #include "itkLabelStatisticsImageFilter.h" namespace itk { /** * \class ExtendedLabelStatisticsImageFilter * \brief Extension of the itkLabelStatisticsImageFilter that also calculates the Skewness,Kurtosis,Entropy,Uniformity. * * This class inherits from the itkLabelStatisticsImageFilter and - * uses its results for the calculation of six additional coefficients: - * the Skewness,Kurtosis,Uniformity,UPP,MPP,Entropy + * uses its results for the calculation of seven additional coefficients: + * the Skewness, Kurtosis, Uniformity, UPP, MPP, Entropy and Median * - * As these coefficient are based on the mean and the sigma which are both calculated - * by the LabelStatisticsImageFilter, the method AfterThreadedGenerateData() is overwritten - * and calls ComputeSkewnessKurtosisAndMPP() and ComputeEntropyUniformityAndUPP after the AfterThreadedGenerateData() - * while the second coefficient Method is only called when the the method useHistogram is on!!! - * implementation of the superclass is called. * */ template< class TInputImage, class TLabelImage > class ExtendedLabelStatisticsImageFilter : public LabelStatisticsImageFilter< TInputImage, TLabelImage > { public: typedef ExtendedLabelStatisticsImageFilter Self; typedef LabelStatisticsImageFilter < TInputImage, TLabelImage > Superclass; typedef SmartPointer< Self > Pointer; typedef SmartPointer< const Self > ConstPointer; typedef typename Superclass::LabelPixelType LabelPixelType; typedef typename Superclass::RealType RealType; typedef typename Superclass::PixelType PixelType; typedef typename Superclass::MapIterator MapIterator; typedef itk::Statistics::Histogram HistogramType; itkFactorylessNewMacro( Self ); itkCloneMacro( Self ); itkTypeMacro(ExtendedLabelStatisticsImageFilter, LabelStatisticsImageFilter); - /** - * \brief Internal class which stores the calculated coefficients Skewness,Kurtosis,Uniformity,UPP,MPP,Entropy. - */ - class CoefficientsClass + + /** \class LabelStatistics + * \brief Statistics stored per label + * \ingroup ITKImageStatistics + */ + class LabelStatistics { - public: + public: + + // default constructor + LabelStatistics() + { + // initialized to the default values + m_Count = NumericTraits< IdentifierType >::ZeroValue(); + m_PositivePixelCount = NumericTraits< IdentifierType >::ZeroValue(); + m_Sum = NumericTraits< RealType >::ZeroValue(); + m_SumOfPositivePixels = NumericTraits< RealType >::ZeroValue(); + + m_SumOfSquares = NumericTraits< RealType >::ZeroValue(); + m_SumOfCubes = NumericTraits< RealType >::ZeroValue(); + m_SumOfQuadruples = NumericTraits< RealType >::ZeroValue(); + + // Set such that the first pixel encountered can be compared + m_Minimum = NumericTraits< RealType >::max(); + m_Maximum = NumericTraits< RealType >::NonpositiveMin(); + + // Default these to zero + m_Mean = NumericTraits< RealType >::ZeroValue(); + m_Sigma = NumericTraits< RealType >::ZeroValue(); + m_Variance = NumericTraits< RealType >::ZeroValue(); + m_MPP = NumericTraits< RealType >::ZeroValue(); + m_Median = NumericTraits< RealType >::ZeroValue(); + m_Uniformity = NumericTraits< RealType >::ZeroValue(); + m_UPP = NumericTraits< RealType >::ZeroValue(); + m_Entropy = NumericTraits< RealType >::ZeroValue(); + m_Skewness = NumericTraits< RealType >::ZeroValue(); + m_Kurtosis = NumericTraits< RealType >::ZeroValue(); + + unsigned int imageDimension = itkGetStaticConstMacro(ImageDimension); + m_BoundingBox.resize(imageDimension * 2); + for ( unsigned int i = 0; i < imageDimension * 2; i += 2 ) + { + m_BoundingBox[i] = NumericTraits< IndexValueType >::max(); + m_BoundingBox[i + 1] = NumericTraits< IndexValueType >::NonpositiveMin(); + } + m_Histogram = ITK_NULLPTR; + } + + // constructor with histogram enabled + LabelStatistics(int size, RealType lowerBound, RealType upperBound) + { + // initialized to the default values + m_Count = NumericTraits< IdentifierType >::ZeroValue(); + m_PositivePixelCount = NumericTraits< IdentifierType >::ZeroValue(); + m_Sum = NumericTraits< RealType >::ZeroValue(); + m_SumOfPositivePixels = NumericTraits< RealType >::ZeroValue(); + + m_SumOfSquares = NumericTraits< RealType >::ZeroValue(); + m_SumOfCubes = NumericTraits< RealType >::ZeroValue(); + m_SumOfQuadruples = NumericTraits< RealType >::ZeroValue(); - CoefficientsClass() + // Set such that the first pixel encountered can be compared + m_Minimum = NumericTraits< RealType >::max(); + m_Maximum = NumericTraits< RealType >::NonpositiveMin(); + + // Default these to zero + m_Mean = NumericTraits< RealType >::ZeroValue(); + m_Sigma = NumericTraits< RealType >::ZeroValue(); + m_Variance = NumericTraits< RealType >::ZeroValue(); + m_MPP = NumericTraits< RealType >::ZeroValue(); + m_Median = NumericTraits< RealType >::ZeroValue(); + m_Uniformity = NumericTraits< RealType >::ZeroValue(); + m_UPP = NumericTraits< RealType >::ZeroValue(); + m_Entropy = NumericTraits< RealType >::ZeroValue(); + m_Skewness = NumericTraits< RealType >::ZeroValue(); + m_Kurtosis = NumericTraits< RealType >::ZeroValue(); + + + unsigned int imageDimension = itkGetStaticConstMacro(ImageDimension); + m_BoundingBox.resize(imageDimension * 2); + for ( unsigned int i = 0; i < imageDimension * 2; i += 2 ) + { + m_BoundingBox[i] = NumericTraits< IndexValueType >::max(); + m_BoundingBox[i + 1] = NumericTraits< IndexValueType >::NonpositiveMin(); + } + + // Histogram + m_Histogram = HistogramType::New(); + typename HistogramType::SizeType hsize; + typename HistogramType::MeasurementVectorType lb; + typename HistogramType::MeasurementVectorType ub; + hsize.SetSize(1); + lb.SetSize(1); + ub.SetSize(1); + m_Histogram->SetMeasurementVectorSize(1); + hsize[0] = size; + lb[0] = lowerBound; + ub[0] = upperBound; + m_Histogram->Initialize(hsize, lb, ub); + } + + // need copy constructor because of smart pointer to histogram + LabelStatistics(const LabelStatistics & l) { - m_Kurtosis = 0.0; - m_Skewness = 0.0; - m_Entropy = -1.0; - m_Uniformity = 0.0; - m_MPP = 0.0; - m_UPP = 0.0; - }; - - ~CoefficientsClass(){}; - - /* the new member coefficients*/ - RealType m_Kurtosis; - RealType m_Skewness; - RealType m_Entropy; - RealType m_Uniformity; - RealType m_MPP; - RealType m_UPP; + m_Count = l.m_Count; + m_Minimum = l.m_Minimum; + m_Maximum = l.m_Maximum; + m_Mean = l.m_Mean; + m_Sum = l.m_Sum; + m_SumOfSquares = l.m_SumOfSquares; + m_Sigma = l.m_Sigma; + m_Variance = l.m_Variance; + m_MPP = l.m_MPP; + m_Median = l.m_Median; + m_Uniformity = l.m_Uniformity; + m_UPP = l.m_UPP; + m_Entropy = l.m_Entropy; + m_Skewness = l.m_Skewness; + m_Kurtosis = l.m_Kurtosis; + m_BoundingBox = l.m_BoundingBox; + m_Histogram = l.m_Histogram; + m_SumOfPositivePixels = l.m_SumOfPositivePixels; + m_PositivePixelCount = l.m_PositivePixelCount; + m_SumOfCubes = l.m_SumOfCubes; + m_SumOfQuadruples = l.m_SumOfQuadruples; + } + + // added for completeness + LabelStatistics &operator= (const LabelStatistics& l) + { + if(this != &l) + { + m_Count = l.m_Count; + m_Minimum = l.m_Minimum; + m_Maximum = l.m_Maximum; + m_Mean = l.m_Mean; + m_Sum = l.m_Sum; + m_SumOfSquares = l.m_SumOfSquares; + m_Sigma = l.m_Sigma; + m_Variance = l.m_Variance; + m_MPP = l.m_MPP; + m_Median = l.m_Median; + m_Uniformity = l.m_Uniformity; + m_UPP = l.m_UPP; + m_Entropy = l.m_Entropy; + m_Skewness = l.m_Skewness; + m_Kurtosis = l.m_Kurtosis; + m_BoundingBox = l.m_BoundingBox; + m_Histogram = l.m_Histogram; + m_SumOfPositivePixels = l.m_SumOfPositivePixels; + m_PositivePixelCount = l.m_PositivePixelCount; + m_SumOfCubes = l.m_SumOfCubes; + m_SumOfQuadruples = l.m_SumOfQuadruples; + } + return *this; + } + + IdentifierType m_Count; + RealType m_Minimum; + RealType m_Maximum; + RealType m_Mean; + RealType m_Sum; + RealType m_SumOfSquares; + RealType m_Sigma; + RealType m_Variance; + RealType m_MPP; + RealType m_Median; + RealType m_Uniformity; + RealType m_UPP; + RealType m_Entropy; + RealType m_Skewness; + RealType m_Kurtosis; + IdentifierType m_PositivePixelCount; + RealType m_SumOfPositivePixels; + RealType m_SumOfCubes; + RealType m_SumOfQuadruples; + typename Superclass::BoundingBoxType m_BoundingBox; + typename HistogramType::Pointer m_Histogram; }; + /** Type of the map used to store data per label */ + typedef itksys::hash_map< LabelPixelType, LabelStatistics > MapType; + typedef typename itksys::hash_map< LabelPixelType, LabelStatistics >::const_iterator StatisticsMapConstIterator; + typedef typename itksys::hash_map< LabelPixelType, LabelStatistics >::iterator StatisticsMapIterator; + typedef IdentifierType MapSizeType; + + /** Type of the container used to store valid label values */ + typedef std::vector ValidLabelValuesContainerType; + + /** Return the computed Minimum for a label. */ + RealType GetMinimum(LabelPixelType label) const; + + /** Return the computed Maximum for a label. */ + RealType GetMaximum(LabelPixelType label) const; - /*getter method for the new coefficients*/ + /** Return the computed Mean for a label. */ + RealType GetMean(LabelPixelType label) const; + + /** Return the computed Standard Deviation for a label. */ + RealType GetSigma(LabelPixelType label) const; + + /** Return the computed Variance for a label. */ + RealType GetVariance(LabelPixelType label) const; + + /** Return the computed bounding box for a label. */ + typename Superclass::BoundingBoxType GetBoundingBox(LabelPixelType label) const; + + /** Return the computed region. */ + typename Superclass::RegionType GetRegion(LabelPixelType label) const; + + /** Return the compute Sum for a label. */ + RealType GetSum(LabelPixelType label) const; + + /** Return the number of pixels for a label. */ + MapSizeType GetCount(LabelPixelType label) const; + + /** Return the histogram for a label */ + HistogramType::Pointer GetHistogram(LabelPixelType label) const; + + /*getter method for the new statistics*/ RealType GetSkewness(LabelPixelType label) const; RealType GetKurtosis(LabelPixelType label) const; RealType GetUniformity( LabelPixelType label) const; + RealType GetMedian( LabelPixelType label) const; RealType GetEntropy( LabelPixelType label) const; RealType GetMPP( LabelPixelType label) const; RealType GetUPP( LabelPixelType label) const; - std::list< int> GetRelevantLabels() const; bool GetMaskingNonEmpty() const; - protected: + std::list GetRelevantLabels() const; - typedef std::map< LabelPixelType, CoefficientsClass > CoefficientsMap; - typedef typename CoefficientsMap::const_iterator CoefficientsMapConstIterator; - ExtendedLabelStatisticsImageFilter(); + /** specify global Histogram parameters. If the histogram parameters are set with this function, the same min and max value are used for all histograms. */ + void SetHistogramParameters(const int numBins, RealType lowerBound, + RealType upperBound); - virtual ~ExtendedLabelStatisticsImageFilter(){}; + /** specify Histogram parameters for each label individually. Labels in the label image that are not represented in the std::maps here will receive global parameters (if available) */ + void SetHistogramParametersForLabels(std::map numBins, std::map lowerBound, + std::map upperBound); + + protected: + ExtendedLabelStatisticsImageFilter(): + m_GlobalHistogramParametersSet(false), + m_LabelHistogramParametersSet(false), + m_PreferGlobalHistogramParameters(false) + { + m_NumBins.set_size(1); + } + + virtual ~ExtendedLabelStatisticsImageFilter(){} - /** - * \brief ComputeSkewnessKurtosisAndMPP(),ComputeEntropyUniformityAndUPP() will be called after superclass - * both methods are seprated because one is build up on the pixel values and one is build up a step after on a - * histogram - */ - void ComputeSkewnessKurtosisAndMPP(); void AfterThreadedGenerateData(); - void ComputeEntropyUniformityAndUPP(); - void CalculateSettingsForLabels(); + + /** Initialize some accumulators before the threads run. */ + void BeforeThreadedGenerateData(); + + /** Multi-thread version GenerateData. */ + void ThreadedGenerateData(const typename TInputImage::RegionType & + outputRegionForThread, + ThreadIdType threadId); + + /** Does the specified label exist? Can only be called after a call + * a call to Update(). */ + bool HasLabel(LabelPixelType label) const + { + return m_LabelStatistics.find(label) != m_LabelStatistics.end(); + } private: + std::vector< MapType > m_LabelStatisticsPerThread; + MapType m_LabelStatistics; + ValidLabelValuesContainerType m_ValidLabelValues; + + bool m_GlobalHistogramParametersSet; + + typename HistogramType::SizeType m_NumBins; + + RealType m_LowerBound; + RealType m_UpperBound; - CoefficientsMap m_LabelStatisticsCoefficients; - std::list< int> m_RelevantLabels; bool m_MaskNonEmpty; + bool m_LabelHistogramParametersSet; + std::map m_LabelMin, m_LabelMax; + std::map m_LabelNBins; + bool m_PreferGlobalHistogramParameters; + }; // end of class } // end namespace itk #ifndef ITK_MANUAL_INSTANTIATION #include "mitkExtendedLabelStatisticsImageFilter.hxx" #endif #endif diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx index 42865e425c..78ea7b98a6 100644 --- a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx @@ -1,324 +1,765 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _mitkExtendedLabelStatisticsImageFilter_hxx #define _mitkExtendedLabelStatisticsImageFilter_hxx #include "mitkExtendedLabelStatisticsImageFilter.h" #include "itkImageRegionConstIteratorWithIndex.h" #include "itkImageRegionConstIterator.h" #include #include #include "mitkNumericConstants.h" #include "mitkLogMacros.h" +#include namespace itk { - template< class TInputImage , class TLabelImage> - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::ExtendedLabelStatisticsImageFilter() - : LabelStatisticsImageFilter< TInputImage, TLabelImage >() + template< class TInputImage, class TLabelImage > + bool + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMaskingNonEmpty() const { - CalculateSettingsForLabels(); + return m_MaskNonEmpty; } - - template< class TInputImage, class TLabelImage > - std::list - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::GetRelevantLabels() const + template< typename TInputImage, typename TLabelImage > + void + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::SetHistogramParameters(const int numBins, RealType lowerBound, RealType upperBound) { - return m_RelevantLabels; + m_NumBins[0] = numBins; + m_LowerBound = lowerBound; + m_UpperBound = upperBound; + m_GlobalHistogramParametersSet = true; + m_PreferGlobalHistogramParameters = true; + this->Modified(); } + template< typename TInputImage, typename TLabelImage > + void + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::SetHistogramParametersForLabels(std::map numBins, std::map lowerBound, + std::map upperBound) + { + m_LabelMin = lowerBound; + m_LabelMax = upperBound; + m_LabelNBins = numBins; + m_LabelHistogramParametersSet = true; + m_PreferGlobalHistogramParameters = false; + this->Modified(); + } template< class TInputImage, class TLabelImage > - bool + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::GetMaskingNonEmpty() const + ::GetUniformity(LabelPixelType label) const { - return m_MaskNonEmpty; + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::Zero; + } + else + { + return ( *mapIt ).second.m_Uniformity; + } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > - ::GetUniformity(LabelPixelType label) const + ::GetMedian(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { - return ( *mapIt ).second.m_Uniformity; + return ( *mapIt ).second.m_Median; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetEntropy(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Entropy; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetUPP(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_UPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetMPP(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_MPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetKurtosis(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Kurtosis; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetSkewness(LabelPixelType label) const { - CoefficientsMapConstIterator mapIt; + StatisticsMapConstIterator mapIt; - mapIt = m_LabelStatisticsCoefficients.find(label); - if ( mapIt == m_LabelStatisticsCoefficients.end() ) + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Skewness; } } - template< class TInputImage, class TLabelImage > - void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: - CalculateSettingsForLabels() + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMinimum(LabelPixelType label) const { - LabelPixelType i; - m_MaskNonEmpty = false; - for ( i = 1; i < 4096; ++i ) - { - if ( this->HasLabel( i ) ) + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { - m_RelevantLabels.push_back( i ); - m_MaskNonEmpty = true; - m_LabelStatisticsCoefficients.insert( std::make_pair(i, CoefficientsClass()) ); + // label does not exist, return a default value + return NumericTraits< PixelType >::max(); + } + else + { + return ( *mapIt ).second.m_Minimum; } - } } + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMaximum(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; - template< class TInputImage, class TLabelImage > - void - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: - ComputeEntropyUniformityAndUPP() + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::NonpositiveMin(); + } + else + { + return ( *mapIt ).second.m_Maximum; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetMean(LabelPixelType label) const { - double baseChange = std::log10(2); - RealType partialProbability( 0.0 ); - RealType uniformity( 0.0 ); - RealType entropy( 0.0 ); - RealType upp( 0.0 ); - - LabelPixelType i; - if ( m_MaskNonEmpty ) - { - typename std::list< int >::const_iterator it; - for ( it = m_RelevantLabels.cbegin(), i = 0; - it != m_RelevantLabels.cend(); - ++it, ++i ) + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) { - HistogramType::Pointer histogramForEntropy = this->GetHistogram(*it); - for (int i = 0; i < histogramForEntropy->Size(); i++) - { - partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Mean; + } + } - if( partialProbability != 0) - { - entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; - uniformity += std::pow(partialProbability,2); + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetSum(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Sum; + } + } + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetSigma(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; - if(histogramForEntropy->GetMeasurement(i,0) > 0) - { - upp += std::pow(partialProbability,2); - } - } + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Sigma; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetVariance(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return NumericTraits< PixelType >::ZeroValue(); + } + else + { + return ( *mapIt ).second.m_Variance; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::Superclass::BoundingBoxType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetBoundingBox(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + typename Superclass::BoundingBoxType emptyBox; + // label does not exist, return a default value + return emptyBox; + } + else + { + return ( *mapIt ).second.m_BoundingBox; + } + } + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::Superclass::RegionType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetRegion(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + + if ( mapIt == m_LabelStatistics.end() ) + { + typename Superclass::RegionType emptyRegion; + // label does not exist, return a default value + return emptyRegion; + } + else + { + typename Superclass::BoundingBoxType bbox = this->GetBoundingBox(label); + typename Superclass::IndexType index; + typename Superclass::SizeType size; + + unsigned int dimension = bbox.size() / 2; + + for ( unsigned int i = 0; i < dimension; i++ ) + { + index[i] = bbox[2 * i]; + size[i] = bbox[2 * i + 1] - bbox[2 * i] + 1; } - m_LabelStatisticsCoefficients[*it].m_Entropy = entropy; - m_LabelStatisticsCoefficients[*it].m_Uniformity = uniformity; - m_LabelStatisticsCoefficients[*it].m_UPP = upp; + typename Superclass::RegionType region; + region.SetSize(size); + region.SetIndex(index); + + return region; } - } } - template< class TInputImage, class TLabelImage > + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::MapSizeType + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetCount(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return 0; + } + else + { + return ( *mapIt ).second.m_Count; + } + } + + + template< typename TInputImage, typename TLabelImage > + typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::HistogramType::Pointer + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetHistogram(LabelPixelType label) const + { + StatisticsMapConstIterator mapIt; + + mapIt = m_LabelStatistics.find(label); + if ( mapIt == m_LabelStatistics.end() ) + { + // label does not exist, return a default value + return ITK_NULLPTR; + } + else + { + // this will be zero if histograms have not been enabled + return ( *mapIt ).second.m_Histogram; + } + } + + + + template< typename TInputImage, typename TLabelImage > void - ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: - ComputeSkewnessKurtosisAndMPP() + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::BeforeThreadedGenerateData() { - typename TLabelImage::RegionType Subregion; + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + // Resize the thread temporaries + m_LabelStatisticsPerThread.resize(numberOfThreads); - RealType baseOfSkewnessAndCurtosis( 0.0 ); - RealType kurtosis( 0.0 ); - RealType skewness( 0.0 ); - RealType mpp( 0.0 ); - RealType currentPixel( 0.0 ); + // Initialize the temporaries + for ( ThreadIdType i = 0; i < numberOfThreads; ++i ) + { + m_LabelStatisticsPerThread[i].clear(); + } - std::list< LabelPixelType> relevantLabels; - LabelPixelType i; - if ( m_MaskNonEmpty ) + // Initialize the final map + m_LabelStatistics.clear(); + } + + template< typename TInputImage, typename TLabelImage > + std::list + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::GetRelevantLabels() const + { + std::list< int> relevantLabels; + for (int i = 0; i < 4096; ++i ) { - typename std::list< int >::const_iterator it; - for ( it = m_RelevantLabels.cbegin(), i = 0; - it != m_RelevantLabels.cend(); - ++it ) + if ( this->HasLabel( i ) ) { - RealType sigma = this->GetSigma( *it ); - RealType mean = this->GetMean( *it ); - Subregion = Superclass::GetRegion(*it); + relevantLabels.push_back( i ); + } + } + return relevantLabels; + } - int count( this->GetCount(*it) ); - if ( count == 0 || sigma < mitk::eps) - { - throw std::logic_error( "Empty segmentation" ); - } + template< typename TInputImage, typename TLabelImage > + void + ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > + ::ThreadedGenerateData(const typename TInputImage::RegionType & outputRegionForThread, + ThreadIdType threadId) + { - if ( fabs( sigma ) < mitk::sqrteps ) - { - throw std::logic_error( "Sigma == 0" ); - } + typename HistogramType::IndexType histogramIndex(1); + typename HistogramType::MeasurementVectorType histogramMeasurement(1); + + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) + { + return; + } + + ImageLinearConstIteratorWithIndex< TInputImage > it (this->GetInput(), + outputRegionForThread); + ImageScanlineConstIterator< TLabelImage > labelIt (this->GetLabelInput(), + outputRegionForThread); - ImageRegionConstIteratorWithIndex< TInputImage > it1 (this->GetInput(), - Subregion); - ImageRegionConstIterator< TLabelImage > labelIt (this->GetLabelInput(), - Subregion); + StatisticsMapIterator mapIt; - for (it1.GoToBegin(); !it1.IsAtEnd(); ++it1, ++labelIt) + // support progress methods/callbacks + const size_t numberOfLinesToProcess = outputRegionForThread.GetNumberOfPixels() / size0; + ProgressReporter progress( this, threadId, numberOfLinesToProcess ); + + typedef typename MapType::value_type MapValueType; + + // do the work + while ( !it.IsAtEnd() ) + { + while ( !it.IsAtEndOfLine() ) { - if (labelIt.Get() == *it) + const RealType & value = static_cast< RealType >( it.Get() ); + + const LabelPixelType & label = labelIt.Get(); + + // is the label already in this thread? + mapIt = m_LabelStatisticsPerThread[threadId].find(label); + if ( mapIt == m_LabelStatisticsPerThread[threadId].end() ) { - currentPixel = it1.Get(); - baseOfSkewnessAndCurtosis = (currentPixel -mean) / sigma; - kurtosis += std::pow( baseOfSkewnessAndCurtosis, 4.0 ); - skewness += std::pow( baseOfSkewnessAndCurtosis, 3.0 ); + // if global histogram parameters are set and preferred then use them + if ( m_PreferGlobalHistogramParameters && m_GlobalHistogramParametersSet ) + { + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics(m_NumBins[0], m_LowerBound, + m_UpperBound) ) ).first; + } + // if we have label histogram parameters then use them. If we encounter a label that has no parameters then use global settings if available + else if(!m_PreferGlobalHistogramParameters && m_LabelHistogramParametersSet) + { + typename std::map::iterator lbIt, ubIt; + typename std::map::iterator nbIt; + + lbIt = m_LabelMin.find(label); + ubIt = m_LabelMax.find(label); + nbIt = m_LabelNBins.find(label); - if(currentPixel > 0) + // if any of the parameters is lacking for the current label but global histogram params are available, use the global parameters + if ((lbIt == m_LabelMin.end() || ubIt == m_LabelMax.end() || nbIt == m_LabelNBins.end()) && m_GlobalHistogramParametersSet) { - mpp+= currentPixel; + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics(m_NumBins[0], m_LowerBound, + m_UpperBound) ) ).first; } + // if any of the parameters is lacking for the current label and global histogram params are not available, dont use histograms for this label + else if ((lbIt == m_LabelMin.end() || ubIt == m_LabelMax.end() || nbIt == m_LabelNBins.end()) && !m_GlobalHistogramParametersSet) + { + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics() ) ).first; + } + // label histogram parameters are available, use them! + else + { + PixelType lowerBound, upperBound; + unsigned int nBins; + lowerBound = (*lbIt).second; + upperBound = (*ubIt).second; + nBins = (*nbIt).second; + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics(nBins, lowerBound, upperBound) ) ).first; + } + } + // neither global nor label specific histogram parameters are set -> don't use histograms + else + { + mapIt = m_LabelStatisticsPerThread[threadId].insert( MapValueType( label, + LabelStatistics() ) ).first; + } + } + + typename MapType::mapped_type &labelStats = ( *mapIt ).second; + // update the values for this label and this thread + if ( value < labelStats.m_Minimum ) + { + labelStats.m_Minimum = value; + } + if ( value > labelStats.m_Maximum ) + { + labelStats.m_Maximum = value; } + + // bounding box is min,max pairs + for ( unsigned int i = 0; i < ( 2 * TInputImage::ImageDimension ); i += 2 ) + { + const typename TInputImage::IndexType & index = it.GetIndex(); + if ( labelStats.m_BoundingBox[i] > index[i / 2] ) + { + labelStats.m_BoundingBox[i] = index[i / 2]; + } + if ( labelStats.m_BoundingBox[i + 1] < index[i / 2] ) + { + labelStats.m_BoundingBox[i + 1] = index[i / 2]; + } + } + + labelStats.m_Sum += value; + labelStats.m_SumOfSquares += ( value * value ); + labelStats.m_Count++; + labelStats.m_SumOfCubes += std::pow(value, 3.); + labelStats.m_SumOfQuadruples += std::pow(value, 4.); + + if (value > 0) + { + labelStats.m_PositivePixelCount++; + labelStats.m_SumOfPositivePixels += value; + } + + // if enabled, update the histogram for this label + if ( labelStats.m_Histogram.IsNotNull() ) + { + histogramMeasurement[0] = value; + labelStats.m_Histogram->GetIndex(histogramMeasurement, histogramIndex); + labelStats.m_Histogram->IncreaseFrequencyOfIndex(histogramIndex, 1); + } + else + { + int x = 0; } - m_LabelStatisticsCoefficients[*it].m_Skewness = RealType(skewness/count); - m_LabelStatisticsCoefficients[*it].m_Kurtosis = RealType(kurtosis/count); - m_LabelStatisticsCoefficients[*it].m_MPP = RealType(mpp/count); + + + ++labelIt; + ++it; + } + labelIt.NextLine(); + it.NextLine(); + progress.CompletedPixel(); } - } - } + } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: AfterThreadedGenerateData() { - Superclass::AfterThreadedGenerateData(); + StatisticsMapIterator mapIt; + StatisticsMapConstIterator threadIt; + ThreadIdType i; + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + // Run through the map for each thread and accumulate the count, + // sum, and sumofsquares + for ( i = 0; i < numberOfThreads; i++ ) + { + // iterate over the map for this thread + for ( threadIt = m_LabelStatisticsPerThread[i].begin(); + threadIt != m_LabelStatisticsPerThread[i].end(); + ++threadIt ) + { + // does this label exist in the cumulative structure yet? + mapIt = m_LabelStatistics.find( ( *threadIt ).first ); + if ( mapIt == m_LabelStatistics.end() ) + { + // create a new entry + typedef typename MapType::value_type MapValueType; + if ( m_GlobalHistogramParametersSet || m_LabelHistogramParametersSet ) + { +// mapIt = m_LabelStatistics.insert( MapValueType( ( *threadIt ).first, +// LabelStatistics(m_NumBins[0], m_LowerBound, +// m_UpperBound) ) ).first; + mapIt = m_LabelStatistics.insert( MapValueType( *threadIt ) ).first; + continue; + } + else + { + mapIt = m_LabelStatistics.insert( MapValueType( ( *threadIt ).first, + LabelStatistics() ) ).first; + } + } - CalculateSettingsForLabels(); - ComputeSkewnessKurtosisAndMPP(); + typename MapType::mapped_type &labelStats = ( *mapIt ).second; - if(this->GetUseHistograms()) - { - ComputeEntropyUniformityAndUPP(); - } - else - { - MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; - } + // accumulate the information from this thread + labelStats.m_Count += ( *threadIt ).second.m_Count; + labelStats.m_Sum += ( *threadIt ).second.m_Sum; + labelStats.m_SumOfSquares += ( *threadIt ).second.m_SumOfSquares; + labelStats.m_SumOfPositivePixels += ( *threadIt ).second.m_SumOfPositivePixels; + labelStats.m_PositivePixelCount += ( *threadIt ).second.m_PositivePixelCount; + labelStats.m_SumOfCubes += ( *threadIt ).second.m_SumOfCubes; + labelStats.m_SumOfQuadruples += ( *threadIt ).second.m_SumOfQuadruples; + + if ( labelStats.m_Minimum > ( *threadIt ).second.m_Minimum ) + { + labelStats.m_Minimum = ( *threadIt ).second.m_Minimum; + } + if ( labelStats.m_Maximum < ( *threadIt ).second.m_Maximum ) + { + labelStats.m_Maximum = ( *threadIt ).second.m_Maximum; + } + + //bounding box is min,max pairs + int dimension = labelStats.m_BoundingBox.size() / 2; + for ( int ii = 0; ii < ( dimension * 2 ); ii += 2 ) + { + if ( labelStats.m_BoundingBox[ii] > ( *threadIt ).second.m_BoundingBox[ii] ) + { + labelStats.m_BoundingBox[ii] = ( *threadIt ).second.m_BoundingBox[ii]; + } + if ( labelStats.m_BoundingBox[ii + 1] < ( *threadIt ).second.m_BoundingBox[ii + 1] ) + { + labelStats.m_BoundingBox[ii + 1] = ( *threadIt ).second.m_BoundingBox[ii + 1]; + } + } + + // if enabled, update the histogram for this label + if ( m_GlobalHistogramParametersSet || m_LabelHistogramParametersSet ) + { + typename HistogramType::IndexType index; + index.SetSize(1); + for ( unsigned int bin = 0; bin < labelStats.m_Histogram->Size(); bin++ ) + { + index[0] = bin; + labelStats.m_Histogram->IncreaseFrequency( bin, ( *threadIt ).second.m_Histogram->GetFrequency(bin) ); + } + } + } // end of thread map iterator loop + } // end of thread loop + + // compute the remainder of the statistics + for ( mapIt = m_LabelStatistics.begin(); + mapIt != m_LabelStatistics.end(); + ++mapIt ) + { + typename MapType::mapped_type &labelStats = ( *mapIt ).second; + + // mean + labelStats.m_Mean = labelStats.m_Sum + / static_cast< RealType >( labelStats.m_Count ); + + // MPP + labelStats.m_MPP = labelStats.m_SumOfPositivePixels + / static_cast< RealType >( labelStats.m_PositivePixelCount ); + + // variance + if ( labelStats.m_Count > 0 ) + { + // unbiased estimate of variance + LabelStatistics & ls = mapIt->second; + const RealType sumSquared = ls.m_Sum * ls.m_Sum; + const RealType count = static_cast< RealType >( ls.m_Count ); + + ls.m_Variance = ( ls.m_SumOfSquares - sumSquared / count ) / ( count ); + + RealType secondMoment = ls.m_SumOfSquares / count; + RealType thirdMoment = ls.m_SumOfCubes / count; + RealType fourthMoment = ls.m_SumOfQuadruples / count; + + ls.m_Skewness = (thirdMoment - 3. * secondMoment * ls.m_Mean + 2. * std::pow(ls.m_Mean, 3.)) / std::pow(secondMoment - std::pow(ls.m_Mean, 2.), 1.5); // see http://www.boost.org/doc/libs/1_51_0/doc/html/boost/accumulators/impl/skewness_impl.html + ls.m_Kurtosis = (fourthMoment - 4. * thirdMoment * ls.m_Mean + 6. * secondMoment * std::pow(ls.m_Mean, 2.) - 3. * std::pow(ls.m_Mean, 4.)) / std::pow(secondMoment - std::pow(ls.m_Mean, 2.), 2.); // see http://www.boost.org/doc/libs/1_51_0/doc/html/boost/accumulators/impl/kurtosis_impl.html, dropped -3 + } + else + { + labelStats.m_Variance = NumericTraits< RealType >::ZeroValue(); + labelStats.m_Skewness = NumericTraits< RealType >::ZeroValue(); + labelStats.m_Kurtosis = NumericTraits< RealType >::ZeroValue(); + } + + // sigma + labelStats.m_Sigma = std::sqrt( labelStats.m_Variance ); + + // histogram statistics + if (labelStats.m_Histogram.IsNotNull()) + { + mitk::HistogramStatisticsCalculator histStatCalc; + histStatCalc.SetHistogram(labelStats.m_Histogram); + histStatCalc.CalculateStatistics(); + labelStats.m_Median = histStatCalc.GetMedian(); + labelStats.m_Entropy = histStatCalc.GetEntropy(); + labelStats.m_Uniformity = histStatCalc.GetUniformity(); + labelStats.m_UPP = histStatCalc.GetUPP(); + } + + } + + { + //Now update the cached vector of valid labels. + m_ValidLabelValues.resize(0); + m_ValidLabelValues.reserve(m_LabelStatistics.size()); + for ( mapIt = m_LabelStatistics.begin(); + mapIt != m_LabelStatistics.end(); + ++mapIt ) + { + m_ValidLabelValues.push_back(mapIt->first); + } + } } } // end namespace itk #endif diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h index ea0927d30c..912bec98dd 100644 --- a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h +++ b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h @@ -1,205 +1,210 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#ifndef __mitkExtendedStatisticsImageFilter_h -#define __mitkExtendedStatisticsImageFilter_h +#ifndef __mitkExtendedStatisticsImageFilter +#define __mitkExtendedStatisticsImageFilter #include "itkStatisticsImageFilter.h" -#include "itkScalarImageToHistogramGenerator.h" #include #include namespace itk { /** * \class ExtendedStatisticsImageFilter * \brief Extension of the itkStatisticsImageFilter that also calculates the Skewness and Kurtosis. * * This class inherits from the itkStatisticsImageFilter and * uses its results for the calculation of other additional coefficients: * the Skewness and Kurtosis. * - * As these coefficient are based on the mean and the sigma which are both calculated - * by the StatisticsImageFilter, the method AfterThreadedGenerateData() is overwritten - * and calls ComputeSkewnessKurtosisAndMPP() and ComputeEntropyUniformityAndUPP - * after the AfterThreadedGenerateData() - * implementation of the superclass is called. - * * As the StatisticsImageFilter stores the statistics in the outputs 1 to 6 by the - * StatisticsImageFilter, the skewness, kurtosis,MPP,UPP,Uniformity and Entropy are stored in the outputs - * 7 to 13 by this filter. + * StatisticsImageFilter, the skewness, kurtosis, MPP, UPP, Uniformity, Entropy and Median are stored in the outputs + * 7 to 14 by this filter. */ template< class TInputImage > class ExtendedStatisticsImageFilter : public StatisticsImageFilter< TInputImage > { public: /** Standard Self typedef */ typedef ExtendedStatisticsImageFilter Self; typedef StatisticsImageFilter< TInputImage > Superclass; typedef SmartPointer< Self > Pointer; typedef SmartPointer< const Self > ConstPointer; typedef typename Superclass::RealType RealType; typedef typename Superclass::RealObjectType RealObjectType; typedef typename Superclass::PixelType PixelType; + /** Histogram-related typedefs */ + typedef itk::Statistics::Histogram< RealType > HistogramType; + typedef typename HistogramType::Pointer HistogramPointer; + itkFactorylessNewMacro( Self ); itkCloneMacro( Self ); itkTypeMacro( ExtendedStatisticsImageFilter, StatisticsImageFilter ); - - typedef itk::Statistics::ScalarImageToHistogramGenerator< TInputImage > - HistogramGeneratorType; - /** * \brief Return the computed Skewness. */ double GetSkewness() const { return this->GetSkewnessOutput()->Get(); } /** * \brief Return the computed Median */ double GetMedian() const { return this->GetMedianOutput()->Get(); } /** * \brief Return the computed Kurtosis. */ double GetKurtosis() const { return this->GetKurtosisOutput()->Get(); } /* \brief Return the computed MPP. */ double GetMPP() const { return this->GetMPPOutput()->Get(); } /** * \brief Return the computed Uniformity. */ double GetUniformity() const { return this->GetUniformityOutput()->Get(); } /** *\brief Return the computed Entropy. */ double GetEntropy() const { return this->GetEntropyOutput()->Get(); } /** * \brief Return the computed UPP. */ double GetUPP() const { return this->GetUPPOutput()->Get(); } /** * \brief Return the computed Histogram. */ - const typename HistogramGeneratorType::HistogramType* + const typename HistogramType::Pointer GetHistogram() { - return m_HistogramGenerator->GetOutput(); + if (m_HistogramCalculated) + { + return m_Histogram; + } + else + { + return ITK_NULLPTR; + } } - /** - * \brief Set the Binsize for the Histogram. - */ - void SetBinSize(int size); + + /** specify Histogram parameters */ + void SetHistogramParameters(const int numBins, RealType lowerBound, + RealType upperBound); protected: ExtendedStatisticsImageFilter(); virtual ~ExtendedStatisticsImageFilter(){}; + void BeforeThreadedGenerateData(); + + /** Multi-thread version GenerateData. */ + void ThreadedGenerateData(const typename StatisticsImageFilter::RegionType & + outputRegionForThread, + ThreadIdType threadId); + /** * brief Calls AfterThreadedGenerateData() of the superclass and the main methods */ void AfterThreadedGenerateData(); - void CalculateHistogram(); - - /** - * \brief Compute Entropy,uniformity,MPP,UPP, Median. - * - * The Entropy,uniformity,MPP, Median and UPP will be calculated with the Sigma, Histogram and Mean Value of the - * itkStatisticsImageFilter which comes out of the threadedGenerateData(). - */ - void ComputeSkewnessKurtosisAndMPP(); - void ComputeEntropyUniformityMedianAndUPP(); - - /** - * \brief Histogram. - * - * new members for setting and calculating the hisotgram for those coefficients which depends on this - */ - typename HistogramGeneratorType::Pointer m_HistogramGenerator; - int m_BinSize; - bool m_HistogramCalculated; RealObjectType* GetSkewnessOutput(); const RealObjectType* GetSkewnessOutput() const; RealObjectType* GetKurtosisOutput(); const RealObjectType* GetKurtosisOutput() const; RealObjectType* GetMPPOutput(); const RealObjectType* GetMPPOutput() const; RealObjectType* GetEntropyOutput(); const RealObjectType* GetEntropyOutput() const; RealObjectType* GetUniformityOutput(); const RealObjectType* GetUniformityOutput() const; RealObjectType* GetUPPOutput(); const RealObjectType* GetUPPOutput() const; RealObjectType* GetMedianOutput(); const RealObjectType* GetMedianOutput() const; virtual DataObject::Pointer MakeOutput( ProcessObject::DataObjectPointerArraySizeType idx ); +private: + Array< RealType > m_ThreadSum; + Array< RealType > m_SumOfSquares; + Array< RealType > m_SumOfCubes; + Array< RealType > m_SumOfQuadruples; + Array< SizeValueType > m_Count; + Array< SizeValueType > m_PositivePixelCount; + Array< RealType > m_ThreadSumOfPositivePixels; + Array< PixelType > m_ThreadMin; + Array< PixelType > m_ThreadMax; + std::vector< HistogramPointer > m_HistogramPerThread; + HistogramPointer m_Histogram; + bool m_UseHistogram; + bool m_HistogramCalculated; + RealType m_LowerBound, m_UpperBound; + int m_NumBins; + }; // end of class } // end namespace itk #ifndef ITK_MANUAL_INSTANTIATION #include "mitkExtendedStatisticsImageFilter.hxx" #endif #endif diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx index 004cf1a90b..9505222c7d 100644 --- a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx @@ -1,344 +1,477 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef __mitkExtendedStatisticsImageFilter_hxx #define __mitkExtendedStatisticsImageFilter_hxx #include "mitkExtendedStatisticsImageFilter.h" +#include namespace itk { template< class TInputImage > ExtendedStatisticsImageFilter< TInputImage >::ExtendedStatisticsImageFilter() - : StatisticsImageFilter< TInputImage >() + : StatisticsImageFilter< TInputImage >(), + m_ThreadSum(1), + m_SumOfSquares(1), + m_SumOfCubes(1), + m_SumOfQuadruples(1), + m_Count(1), + m_ThreadMin(1), + m_ThreadMax(1), + m_PositivePixelCount(1), + m_ThreadSumOfPositivePixels(1), + m_UseHistogram(false), + m_HistogramCalculated(false) { /* * add the Skewness,Kurtosis,Entropy,Uniformity,MPP, UPP, Median to the other statistical calculated Values * of the mitkStatisticsImageFilter as the 7th to the 13th Output */ for ( int i = 7; i < 14; ++i ) { typename RealObjectType::Pointer output = static_cast< RealObjectType * >( this->MakeOutput(i).GetPointer() ); this->ProcessObject::SetNthOutput( i, output.GetPointer() ); } this->GetSkewnessOutput()->Set( 0.0 ); this->GetKurtosisOutput()->Set( 0.0 ); this->GetEntropyOutput()->Set( -1.0 ); this->GetUniformityOutput()->Set( 0.0 ); this->GetUPPOutput()->Set( 0.0 ); this->GetMPPOutput()->Set( 0.0 ); this->GetMedianOutput()->Set( 0.0 ); - this->m_HistogramCalculated = false; - this->m_HistogramGenerator = HistogramGeneratorType::New(); + m_Histogram = ITK_NULLPTR; + m_HistogramPerThread.resize(0); } template< class TInputImage > DataObject::Pointer ExtendedStatisticsImageFilter< TInputImage >::MakeOutput( ProcessObject::DataObjectPointerArraySizeType output) { switch ( output ) { case 7: case 8: case 9: case 10: case 11: case 12: { return RealObjectType::New().GetPointer(); break; } case 13: { return RealObjectType::New().GetPointer(); break; } default: { // might as well make an image return Superclass::MakeOutput( output ); break; } } } - - template< class TInputImage > - void ExtendedStatisticsImageFilter< TInputImage > - ::SetBinSize( int size ) - { - m_BinSize = size; - } - template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(13) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(13) ); } - - template< class TInputImage > + template< typename TInputImage > void - ExtendedStatisticsImageFilter< TInputImage > - ::AfterThreadedGenerateData() + ExtendedStatisticsImageFilter< TInputImage > + ::BeforeThreadedGenerateData() { - Superclass::AfterThreadedGenerateData(); + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); - ComputeSkewnessKurtosisAndMPP(); - - CalculateHistogram(); - - if(m_HistogramCalculated == true) - { - ComputeEntropyUniformityMedianAndUPP(); - } - else + if (m_UseHistogram) { - MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; + // Histogram + m_Histogram = HistogramType::New(); + typename HistogramType::SizeType hsize; + typename HistogramType::MeasurementVectorType lb; + typename HistogramType::MeasurementVectorType ub; + hsize.SetSize(1); + lb.SetSize(1); + ub.SetSize(1); + m_Histogram->SetMeasurementVectorSize(1); + hsize[0] = m_NumBins; + lb[0] = m_LowerBound; + ub[0] = m_UpperBound; + m_Histogram->Initialize(hsize, lb, ub); + + m_HistogramPerThread.resize(numberOfThreads); + + for (unsigned int i = 0; i < numberOfThreads; i++) + { + typename HistogramType::Pointer hist = HistogramType::New(); + typename HistogramType::SizeType hsize; + typename HistogramType::MeasurementVectorType lb; + typename HistogramType::MeasurementVectorType ub; + hsize.SetSize(1); + lb.SetSize(1); + ub.SetSize(1); + hist->SetMeasurementVectorSize(1); + hsize[0] = m_NumBins; + lb[0] = m_LowerBound; + ub[0] = m_UpperBound; + hist->Initialize(hsize, lb, ub); + m_HistogramPerThread[i] = hist; + } } + + // Resize the thread temporaries + m_Count.SetSize(numberOfThreads); + m_SumOfSquares.SetSize(numberOfThreads); + m_SumOfCubes.SetSize(numberOfThreads); + m_SumOfQuadruples.SetSize(numberOfThreads); + m_ThreadSum.SetSize(numberOfThreads); + m_ThreadMin.SetSize(numberOfThreads); + m_ThreadMax.SetSize(numberOfThreads); + m_PositivePixelCount.SetSize(numberOfThreads); + m_ThreadSumOfPositivePixels.SetSize(numberOfThreads); + + // Initialize the temporaries + m_Count.Fill(NumericTraits< SizeValueType >::Zero); + m_ThreadSum.Fill(NumericTraits< RealType >::Zero); + m_SumOfSquares.Fill(NumericTraits< RealType >::Zero); + m_SumOfCubes.Fill(NumericTraits< RealType >::Zero); + m_SumOfQuadruples.Fill(NumericTraits< RealType >::Zero); + m_ThreadMin.Fill( NumericTraits< PixelType >::max() ); + m_ThreadMax.Fill( NumericTraits< PixelType >::NonpositiveMin() ); + m_PositivePixelCount.Fill(NumericTraits< SizeValueType >::Zero); + m_ThreadSumOfPositivePixels.Fill(NumericTraits< SizeValueType >::Zero); } - template< class TInputImage > + + template< typename TInputImage > void - ExtendedStatisticsImageFilter< TInputImage > - ::ComputeSkewnessKurtosisAndMPP() + ExtendedStatisticsImageFilter< TInputImage > + ::ThreadedGenerateData(const typename StatisticsImageFilter::RegionType & + outputRegionForThread, + ThreadIdType threadId) { - RealType mean = this->GetMean(); - RealType sigma = this->GetSigma(); - RealType baseOfSkewnessAndKurtosis; - RealType kurtosis(0.0); - RealType skewness(0.0); - RealType mpp(0.0); - RealType currentPixel(0.0); - - if ( sigma < mitk::eps ) - { - throw std::logic_error( "Empty segmentation" ); - } - - - ImageRegionConstIterator< TInputImage > it (this->GetInput(), this->GetInput()->GetLargestPossibleRegion() ); - - int counter = 0; - for (it.GoToBegin(); !it.IsAtEnd(); ++it) - { - currentPixel = it.Get(); - - baseOfSkewnessAndKurtosis = (currentPixel - mean) / sigma ; - kurtosis += std::pow( baseOfSkewnessAndKurtosis, 4.0 ); - skewness += std::pow( baseOfSkewnessAndKurtosis, 3.0 ); - - if(currentPixel > 0) + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) { - mpp+= currentPixel; + return; } - counter++; - } - - if ( counter == 0 ) - { - throw std::logic_error( "Empty segmentation" ); - } - - kurtosis = kurtosis / counter; - skewness = skewness / counter; - mpp = mpp/counter; + RealType realValue; + PixelType value; + + typename HistogramType::IndexType histogramIndex(1); + typename HistogramType::MeasurementVectorType histogramMeasurement(1); + + RealType sum = NumericTraits< RealType >::ZeroValue(); + RealType sumOfPositivePixels = NumericTraits< RealType >::ZeroValue(); + RealType sumOfSquares = NumericTraits< RealType >::ZeroValue(); + RealType sumOfCubes = NumericTraits< RealType >::ZeroValue(); + RealType sumOfQuadruples = NumericTraits< RealType >::ZeroValue(); + SizeValueType count = NumericTraits< SizeValueType >::ZeroValue(); + SizeValueType countOfPositivePixels = NumericTraits< SizeValueType >::ZeroValue(); + PixelType min = NumericTraits< PixelType >::max(); + PixelType max = NumericTraits< PixelType >::NonpositiveMin(); + + ImageScanlineConstIterator< TInputImage > it (this->GetInput(), outputRegionForThread); + + // support progress methods/callbacks + const size_t numberOfLinesToProcess = outputRegionForThread.GetNumberOfPixels() / size0; + ProgressReporter progress( this, threadId, numberOfLinesToProcess ); + + // do the work + while ( !it.IsAtEnd() ) + { + while ( !it.IsAtEndOfLine() ) + { + value = it.Get(); + realValue = static_cast< RealType >( value ); - this->GetKurtosisOutput()->Set( kurtosis ); - this->GetSkewnessOutput()->Set( skewness ); - this->GetMPPOutput()->Set( mpp ); - } + if (m_UseHistogram) + { + histogramMeasurement[0] = value; + m_HistogramPerThread[threadId]->GetIndex(histogramMeasurement, histogramIndex); + m_HistogramPerThread[threadId]->IncreaseFrequencyOfIndex(histogramIndex, 1); + } + if ( value < min ) + { + min = value; + } + if ( value > max ) + { + max = value; + } + if (value > 0) + { + sumOfPositivePixels += realValue; + ++countOfPositivePixels; + } - template< class TInputImage > - void - ExtendedStatisticsImageFilter< TInputImage > - ::CalculateHistogram() - { - m_HistogramGenerator->SetInput( this->GetInput() ); - m_HistogramGenerator->SetMarginalScale( 100 ); - m_HistogramGenerator->SetNumberOfBins( m_BinSize ); - m_HistogramGenerator->SetHistogramMin( this->GetMinimum() ); - m_HistogramGenerator->SetHistogramMax( this->GetMaximum() ); - m_HistogramGenerator->Compute(); + sum += realValue; + sumOfSquares += ( realValue * realValue ); + sumOfCubes += std::pow(realValue, 3.); + sumOfQuadruples += std::pow(realValue, 4.); + ++count; + ++it; + } + it.NextLine(); + progress.CompletedPixel(); + } - m_HistogramCalculated = true; + m_ThreadSum[threadId] = sum; + m_SumOfSquares[threadId] = sumOfSquares; + m_Count[threadId] = count; + m_ThreadMin[threadId] = min; + m_ThreadMax[threadId] = max; + m_ThreadSumOfPositivePixels[threadId] = sumOfPositivePixels; + m_PositivePixelCount[threadId] = countOfPositivePixels; + m_SumOfCubes[threadId] = sumOfCubes; + m_SumOfQuadruples[threadId] = sumOfQuadruples; } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > - ::ComputeEntropyUniformityMedianAndUPP() + ::AfterThreadedGenerateData() { - double baseChange = std::log10(2); - RealType partialProbability( 0.0 ); - RealType uniformity( 0.0 ); - RealType entropy( 0.0 ); - RealType upp( 0.0 ); - RealType median( 0.0 ); - - const typename HistogramGeneratorType::HistogramType* histogramForEntropy = GetHistogram(); - - double cumulativeProbability = 0.0; - bool medianFound = false; - - for (int i = 0; i < histogramForEntropy->Size(); i++) - { - partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; - cumulativeProbability += double ( partialProbability ); - - if( partialProbability != 0) + ThreadIdType i; + SizeValueType count; + SizeValueType countOfPositivePixels; + RealType sumOfSquares; + RealType sumOfCubes; + RealType sumOfQuadruples; + RealType sumOfPositivePixels; + + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + PixelType minimum; + PixelType maximum; + RealType mean; + RealType meanOfPositivePixels; + RealType sigma; + RealType variance; + RealType skewness; + RealType kurtosis; + RealType sum; + + sum = sumOfSquares = sumOfCubes = sumOfQuadruples = NumericTraits< RealType >::ZeroValue(); + count = countOfPositivePixels = 0; + + // Find the min/max over all threads and accumulate count, sum and + // sum of squares + minimum = NumericTraits< PixelType >::max(); + maximum = NumericTraits< PixelType >::NonpositiveMin(); + for ( i = 0; i < numberOfThreads; i++ ) { - entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; - uniformity += std::pow(partialProbability,2); - - if(histogramForEntropy->GetMeasurement(i,0) > 0) + count += m_Count[i]; + sum += m_ThreadSum[i]; + sumOfSquares += m_SumOfSquares[i]; + sumOfCubes += m_SumOfCubes[i]; + sumOfQuadruples += m_SumOfQuadruples[i]; + sumOfPositivePixels += m_ThreadSumOfPositivePixels[i]; + countOfPositivePixels += m_PositivePixelCount[i]; + + if ( m_ThreadMin[i] < minimum ) { - upp += std::pow(partialProbability,2); + minimum = m_ThreadMin[i]; + } + if ( m_ThreadMax[i] > maximum ) + { + maximum = m_ThreadMax[i]; + } + // if enabled, update the histogram for this label + if ( m_UseHistogram ) + { + typename HistogramType::IndexType index; + index.SetSize(1); + for ( unsigned int bin = 0; bin < m_NumBins; bin++ ) + { + index[0] = bin; + m_Histogram->IncreaseFrequency( bin, m_HistogramPerThread[i]->GetFrequency(bin) ); + } } } - - if( cumulativeProbability >= 0.5 && !medianFound ) - { - RealType binMin = histogramForEntropy->GetBinMin( 0, i ); - RealType binMax = histogramForEntropy->GetBinMax( 0, i ); - median = ( binMin + binMax ) / 2.0; - medianFound = true; - } + // compute statistics + mean = sum / static_cast< RealType >( count ); + meanOfPositivePixels = sumOfPositivePixels / static_cast< RealType >( countOfPositivePixels ); + + // unbiased estimate + variance = ( sumOfSquares - ( sum * sum / static_cast< RealType >( count ) ) ) + / ( static_cast< RealType >( count ) ); + RealType secondMoment, thirdMoment, fourthMoment; + secondMoment = sumOfSquares / static_cast< RealType >( count ); + thirdMoment = sumOfCubes / static_cast< RealType >( count ); + fourthMoment = sumOfQuadruples / static_cast< RealType >( count ); + + skewness = (thirdMoment - 3. * secondMoment * mean + 2. * std::pow(mean, 3.)) / std::pow(secondMoment - std::pow(mean, 2.), 1.5); // see http://www.boost.org/doc/libs/1_51_0/doc/html/boost/accumulators/impl/skewness_impl.html + kurtosis = (fourthMoment - 4. * thirdMoment * mean + 6. * secondMoment * std::pow(mean, 2.) - 3. * std::pow(mean, 4.)) / std::pow(secondMoment - std::pow(mean, 2.), 2.); // see http://www.boost.org/doc/libs/1_46_1/doc/html/boost/accumulators/impl/kurtosis_impl.html, dropped -3 + sigma = std::sqrt(variance); + + // Set the outputs + this->GetMinimumOutput()->Set(minimum); + this->GetMaximumOutput()->Set(maximum); + this->GetMeanOutput()->Set(mean); + this->GetSigmaOutput()->Set(sigma); + this->GetVarianceOutput()->Set(variance); + this->GetSumOutput()->Set(sum); + this->GetKurtosisOutput()->Set(kurtosis); + this->GetSkewnessOutput()->Set(skewness); + this->GetMPPOutput()->Set(meanOfPositivePixels); + + if (m_UseHistogram) + { + mitk::HistogramStatisticsCalculator histStatCalc; + histStatCalc.SetHistogram(m_Histogram); + histStatCalc.CalculateStatistics(); + this->GetUniformityOutput()->Set(histStatCalc.GetUniformity()); + this->GetUPPOutput()->Set(histStatCalc.GetUPP()); + this->GetEntropyOutput()->Set(histStatCalc.GetEntropy()); + this->GetMedianOutput()->Set(histStatCalc.GetMedian()); } - this->GetEntropyOutput()->Set( entropy ); - this->GetUniformityOutput()->Set( uniformity ); - this->GetUPPOutput()->Set( upp ); - this->GetMedianOutput()->Set( median ); + m_HistogramCalculated = true; + } + template< typename TInputImage > + void + ExtendedStatisticsImageFilter< TInputImage > + ::SetHistogramParameters(const int numBins, RealType lowerBound, RealType upperBound) + { + m_NumBins = numBins; + m_LowerBound = lowerBound; + m_UpperBound = upperBound; + m_UseHistogram = true; } + } #endif diff --git a/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.cpp new file mode 100644 index 0000000000..b7af6a479d --- /dev/null +++ b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.cpp @@ -0,0 +1,108 @@ +#include +#include + +namespace mitk { + +HistogramStatisticsCalculator::HistogramStatisticsCalculator(): + m_StatisticsCalculated(false) +{ + +} + +void HistogramStatisticsCalculator::SetHistogram(HistogramType::Pointer histogram) +{ + if (m_Histogram != histogram) + { + m_Histogram = histogram; + m_StatisticsCalculated = false; + } +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetEntropy() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_Entropy; +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetMedian() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_Median; +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetUniformity() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_Uniformity; +} + +HistogramStatisticsCalculator::MeasurementType HistogramStatisticsCalculator::GetUPP() +{ + if (!m_StatisticsCalculated) + { + MITK_WARN("Statistics have not yet been calculated, running calculation now..."); + CalculateStatistics(); + } + return m_UPP; +} + +void HistogramStatisticsCalculator::CalculateStatistics() +{ + if (m_Histogram.IsNull()) + { + throw std::runtime_error("Histogram not set in HistogramStatisticsCalculator::CalculateStatistics()"); + } + + unsigned int nBins = m_Histogram->GetSize()[0]; + m_Uniformity = 0; + m_Entropy = 0; + m_UPP = 0; + m_Median = 0; + + MeasurementType cumulativeProbability = 0.0; + MeasurementType partialProbability; + bool medianFound(false); + + for (unsigned int i = 0; i < nBins; i++) + { + partialProbability = m_Histogram->GetFrequency(i, 0) / double( m_Histogram->GetTotalFrequency() ); + cumulativeProbability += partialProbability; + + if (partialProbability != 0) + { + m_Entropy -= partialProbability * (std::log2( partialProbability )); + m_Uniformity += std::pow(partialProbability, 2); + + if (m_Histogram->GetMeasurement(i, 0) > 0) + { + m_UPP += std::pow(partialProbability, 2); + } + + } + + if (cumulativeProbability >= 0.5 && !medianFound) + { + MeasurementType binMin = m_Histogram->GetBinMin(0, i); + MeasurementType binMax = m_Histogram->GetBinMax(0, i); + m_Median = (binMax + binMin) / 2.0; + medianFound = true; + } + + } + m_StatisticsCalculated = true; +} + +} + diff --git a/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.h b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.h new file mode 100644 index 0000000000..a7e3e3d450 --- /dev/null +++ b/Modules/ImageStatistics/mitkHistogramStatisticsCalculator.h @@ -0,0 +1,49 @@ +#ifndef MITKHISTOGRAMSTATISTICSCALCULATOR +#define MITKHISTOGRAMSTATISTICSCALCULATOR + +#include +#include +#include + + +namespace mitk +{ +/** + * @brief Computes basic histogram statistics such as Uniformity, UPP (Uniformity of positive entries), Entropy and Median (approximation) + */ + class MITKIMAGESTATISTICS_EXPORT HistogramStatisticsCalculator + { + public: + typedef double MeasurementType; + typedef itk::Statistics::Histogram HistogramType; + + HistogramStatisticsCalculator(); + + /** + * @brief SetHistogram requires a itk::Statistics::Histogram + */ + void SetHistogram(HistogramType::Pointer histogram); + + MeasurementType GetUPP(); + + MeasurementType GetUniformity(); + + MeasurementType GetEntropy(); + + MeasurementType GetMedian(); + + /** + * @brief calculate statistics + */ + void CalculateStatistics(); + + protected: + + private: + HistogramType::Pointer m_Histogram; + MeasurementType m_Uniformity, m_UPP, m_Entropy, m_Median; + bool m_StatisticsCalculated; + }; +} + +#endif diff --git a/Modules/ImageStatistics/mitkHotspotMaskGenerator.cpp b/Modules/ImageStatistics/mitkHotspotMaskGenerator.cpp new file mode 100644 index 0000000000..da07560989 --- /dev/null +++ b/Modules/ImageStatistics/mitkHotspotMaskGenerator.cpp @@ -0,0 +1,595 @@ +#include +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include + +namespace mitk +{ + HotspotMaskGenerator::HotspotMaskGenerator(): + m_HotspotRadiusinMM(6.2035049089940), // radius of a 1cm3 sphere in mm + m_HotspotMustBeCompletelyInsideImage(true), + m_Label(1) + { + m_TimeStep = 0; + m_InternalMask = mitk::Image::New(); + m_InternalMaskUpdateTime = 0; + } + + void HotspotMaskGenerator::SetInputImage(mitk::Image::Pointer inputImage) + { + if (inputImage != m_inputImage) + { + m_inputImage = inputImage; + m_ConvolutionImageMaxIndex.set_size(inputImage->GetDimension()); + m_ConvolutionImageMinIndex.set_size(inputImage->GetDimension()); + this->Modified(); + } + } + + void HotspotMaskGenerator::SetMask(MaskGenerator::Pointer mask) + { + if (mask != m_Mask) + { + m_Mask = mask; + this->Modified(); + } + } + + HotspotMaskGenerator::~HotspotMaskGenerator() + { + } + + void HotspotMaskGenerator::SetHotspotRadiusInMM(double radiusInMillimeter) + { + if(radiusInMillimeter != m_HotspotRadiusinMM) + { + m_HotspotRadiusinMM = radiusInMillimeter; + this->Modified(); + } + } + + const double& HotspotMaskGenerator::GetHotspotRadiusinMM() const + { + return m_HotspotRadiusinMM; + } + + bool HotspotMaskGenerator::GetHotspotMustBeCompletelyInsideImage() const + { + return m_HotspotMustBeCompletelyInsideImage; + } + + void HotspotMaskGenerator::SetHotspotMustBeCompletelyInsideImage(bool mustBeCompletelyInImage) + { + if (m_HotspotMustBeCompletelyInsideImage != mustBeCompletelyInImage) + { + m_HotspotMustBeCompletelyInsideImage = mustBeCompletelyInImage; + this->Modified(); + } + } + + + mitk::Image::Pointer HotspotMaskGenerator::GetMask() + { + if (IsUpdateRequired()) + { + if ( m_inputImage.IsNull() ) + { + throw std::runtime_error( "Error: image empty!" ); + } + + if ( m_TimeStep >= m_inputImage->GetTimeSteps() ) + { + throw std::runtime_error( "Error: invalid time step!" ); + } + + mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); + imageTimeSelector->SetInput( m_inputImage ); + imageTimeSelector->SetTimeNr( m_TimeStep ); + imageTimeSelector->UpdateLargestPossibleRegion(); + mitk::Image::Pointer timeSliceImage = imageTimeSelector->GetOutput(); + + m_internalImage = timeSliceImage; + m_internalMask2D = nullptr; // is this correct when this variable holds a smart pointer? + m_internalMask3D = nullptr; + + if ( m_Mask != nullptr ) + { + m_Mask->SetTimeStep(m_TimeStep); + mitk::Image::Pointer timeSliceMask = m_Mask->GetMask(); + + if ( m_internalImage->GetDimension() == 3 ) + { + CastToItkImage(timeSliceMask, m_internalMask3D); + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 3, m_internalMask3D, m_Label); + } + else if ( m_internalImage->GetDimension() == 2 ) + { + CastToItkImage(timeSliceMask, m_internalMask2D); + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 2, m_internalMask2D, m_Label); + } + else + { + throw std::runtime_error( "Error: invalid image dimension" ); + } + } + else + { + + if ( m_internalImage->GetDimension() == 3 ) + { + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 3, m_internalMask3D, m_Label); + } + else if ( m_internalImage->GetDimension() == 2 ) + { + AccessFixedDimensionByItk_2(m_internalImage, CalculateHotspotMask, 2, m_internalMask2D, m_Label); + } + else + { + throw std::runtime_error( "Error: invalid image dimension" ); + } + } + this->Modified(); + } + + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; + } + + void HotspotMaskGenerator::SetLabel(unsigned short label) + { + if (label != m_Label) + { + m_Label = label; + this->Modified(); + } + } + + vnl_vector HotspotMaskGenerator::GetConvolutionImageMinIndex() + { + this->GetMask(); // make sure we are up to date + return m_ConvolutionImageMinIndex; + } + + vnl_vector HotspotMaskGenerator::GetHotspotIndex() + { + this->GetMask(); // make sure we are up to date + return m_ConvolutionImageMaxIndex; + } + + template + HotspotMaskGenerator::ImageExtrema + HotspotMaskGenerator::CalculateExtremaWorld( const itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + double neccessaryDistanceToImageBorderInMM, + unsigned int label ) + { + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< unsigned short, VImageDimension > MaskImageType; + + typedef itk::ImageRegionConstIteratorWithIndex MaskImageIteratorType; + typedef itk::ImageRegionConstIteratorWithIndex InputImageIndexIteratorType; + + typename ImageType::SpacingType spacing = inputImage->GetSpacing(); + + ImageExtrema minMax; + minMax.Defined = false; + minMax.MaxIndex.set_size(VImageDimension); + minMax.MaxIndex.set_size(VImageDimension); + + typename ImageType::RegionType allowedExtremaRegion = inputImage->GetLargestPossibleRegion(); + + bool keepDistanceToImageBorders( neccessaryDistanceToImageBorderInMM > 0 ); + if (keepDistanceToImageBorders) + { + long distanceInPixels[VImageDimension]; + for(unsigned short dimension = 0; dimension < VImageDimension; ++dimension) + { + // To confirm that the whole hotspot is inside the image we have to keep a specific distance to the image-borders, which is as long as + // the radius. To get the amount of indices we divide the radius by spacing and add 0.5 because voxels are center based: + // For example with a radius of 2.2 and a spacing of 1 two indices are enough because 2.2 / 1 + 0.5 = 2.7 => 2. + // But with a radius of 2.7 we need 3 indices because 2.7 / 1 + 0.5 = 3.2 => 3 + distanceInPixels[dimension] = int( neccessaryDistanceToImageBorderInMM / spacing[dimension] + 0.5); + } + + allowedExtremaRegion.ShrinkByRadius(distanceInPixels); + } + + InputImageIndexIteratorType imageIndexIt(inputImage, allowedExtremaRegion); + + float maxValue = itk::NumericTraits::min(); + float minValue = itk::NumericTraits::max(); + + typename ImageType::IndexType maxIndex; + typename ImageType::IndexType minIndex; + + for(unsigned short i = 0; i < VImageDimension; ++i) + { + maxIndex[i] = 0; + minIndex[i] = 0; + } + + if (maskImage != nullptr) + { + MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); + typename ImageType::IndexType imageIndex; + typename ImageType::PointType worldPosition; + typename ImageType::IndexType maskIndex; + + for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) + { + imageIndex = maskIndex = maskIt.GetIndex(); + + if(maskIt.Get() == label) + { + if( allowedExtremaRegion.IsInside(imageIndex) ) + { + imageIndexIt.SetIndex( imageIndex ); + double value = imageIndexIt.Get(); + minMax.Defined = true; + + //Calculate minimum, maximum and corresponding index-values + if( value > maxValue ) + { + maxIndex = imageIndexIt.GetIndex(); + maxValue = value; + } + + if(value < minValue ) + { + minIndex = imageIndexIt.GetIndex(); + minValue = value; + } + } + } + } + } + else + { + for(imageIndexIt.GoToBegin(); !imageIndexIt.IsAtEnd(); ++imageIndexIt) + { + double value = imageIndexIt.Get(); + minMax.Defined = true; + + //Calculate minimum, maximum and corresponding index-values + if( value > maxValue ) + { + maxIndex = imageIndexIt.GetIndex(); + maxValue = value; + } + + if(value < minValue ) + { + minIndex = imageIndexIt.GetIndex(); + minValue = value; + } + } + } + + minMax.MaxIndex.set_size(VImageDimension); + minMax.MinIndex.set_size(VImageDimension); + + for(unsigned int i = 0; i < minMax.MaxIndex.size(); ++i) + { + minMax.MaxIndex[i] = maxIndex[i]; + } + + for(unsigned int i = 0; i < minMax.MinIndex.size(); ++i) + { + minMax.MinIndex[i] = minIndex[i]; + } + + minMax.Max = maxValue; + minMax.Min = minValue; + + return minMax; + } + + template + itk::Size + HotspotMaskGenerator::CalculateConvolutionKernelSize( double spacing[VImageDimension], + double radiusInMM ) + { + typedef itk::Image< float, VImageDimension > KernelImageType; + typedef typename KernelImageType::SizeType SizeType; + SizeType maskSize; + + for(unsigned int i = 0; i < VImageDimension; ++i) + { + maskSize[i] = static_cast( 2 * radiusInMM / spacing[i]); + + // We always want an uneven size to have a clear center point in the convolution mask + if(maskSize[i] % 2 == 0 ) + { + ++maskSize[i]; + } + } + return maskSize; + } + + template + itk::SmartPointer< itk::Image > + HotspotMaskGenerator::GenerateHotspotSearchConvolutionKernel(double mmPerPixel[VImageDimension], + double radiusInMM ) + { + std::stringstream ss; + for (unsigned int i = 0; i < VImageDimension; ++i) + { + ss << mmPerPixel[i]; + if (i < VImageDimension -1) + ss << ","; + } + MITK_DEBUG << "Update convolution kernel for spacing (" << ss.str() << ") and radius " << radiusInMM << "mm"; + + + double radiusInMMSquared = radiusInMM * radiusInMM; + typedef itk::Image< float, VImageDimension > KernelImageType; + typename KernelImageType::Pointer convolutionKernel = KernelImageType::New(); + + // Calculate size and allocate mask image + typedef typename KernelImageType::SizeType SizeType; + SizeType maskSize = this->CalculateConvolutionKernelSize(mmPerPixel, radiusInMM); + + mitk::Point3D convolutionMaskCenterIndex; + convolutionMaskCenterIndex.Fill(0.0); + for(unsigned int i = 0; i < VImageDimension; ++i) + { + convolutionMaskCenterIndex[i] = 0.5 * (double)(maskSize[i]-1); + } + + typedef typename KernelImageType::IndexType IndexType; + IndexType maskIndex; + maskIndex.Fill(0); + + typedef typename KernelImageType::RegionType RegionType; + RegionType maskRegion; + maskRegion.SetSize(maskSize); + maskRegion.SetIndex(maskIndex); + + convolutionKernel->SetRegions(maskRegion); + convolutionKernel->SetSpacing(mmPerPixel); + convolutionKernel->Allocate(); + + // Fill mask image values by subsampling the image grid + typedef itk::ImageRegionIteratorWithIndex MaskIteratorType; + MaskIteratorType maskIt(convolutionKernel,maskRegion); + + int numberOfSubVoxelsPerDimension = 2; // per dimension! + int numberOfSubVoxels = ::pow( static_cast(numberOfSubVoxelsPerDimension), static_cast(VImageDimension) ); + double subVoxelSizeInPixels = 1.0 / (double)numberOfSubVoxelsPerDimension; + double valueOfOneSubVoxel = 1.0 / (double)numberOfSubVoxels; + double maskValue = 0.0; + mitk::Point3D subVoxelIndexPosition; + double distanceSquared = 0.0; + + typedef itk::ContinuousIndex ContinuousIndexType; + for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) + { + ContinuousIndexType indexPoint(maskIt.GetIndex()); + mitk::Point3D voxelPosition; + for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) + { + voxelPosition[dimension] = indexPoint[dimension]; + } + + maskValue = 0.0; + mitk::Vector3D subVoxelOffset; subVoxelOffset.Fill(0.0); + // iterate sub-voxels by iterating all possible offsets + for (subVoxelOffset[0] = -0.5 + subVoxelSizeInPixels / 2.0; + subVoxelOffset[0] < +0.5; + subVoxelOffset[0] += subVoxelSizeInPixels) + { + for (subVoxelOffset[1] = -0.5 + subVoxelSizeInPixels / 2.0; + subVoxelOffset[1] < +0.5; + subVoxelOffset[1] += subVoxelSizeInPixels) + { + for (subVoxelOffset[2] = -0.5 + subVoxelSizeInPixels / 2.0; + subVoxelOffset[2] < +0.5; + subVoxelOffset[2] += subVoxelSizeInPixels) + { + subVoxelIndexPosition = voxelPosition + subVoxelOffset; // this COULD be integrated into the for-loops if neccessary (add voxelPosition to initializer and end condition) + distanceSquared = + (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] * (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] + + (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] * (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] + + (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2] * (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2]; + + if (distanceSquared <= radiusInMMSquared) + { + maskValue += valueOfOneSubVoxel; + } + } + } + } + maskIt.Set( maskValue ); + } + + return convolutionKernel; + } + + template + itk::SmartPointer > + HotspotMaskGenerator::GenerateConvolutionImage( const itk::Image* inputImage ) + { + double mmPerPixel[VImageDimension]; + for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) + { + mmPerPixel[dimension] = inputImage->GetSpacing()[dimension]; + } + + // update convolution kernel + typedef itk::Image< float, VImageDimension > KernelImageType; + typename KernelImageType::Pointer convolutionKernel = this->GenerateHotspotSearchConvolutionKernel(mmPerPixel, m_HotspotRadiusinMM); + + // update convolution image + typedef itk::Image< TPixel, VImageDimension > InputImageType; + typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; + typedef itk::FFTConvolutionImageFilter ConvolutionFilterType; + + typename ConvolutionFilterType::Pointer convolutionFilter = ConvolutionFilterType::New(); + typedef itk::ConstantBoundaryCondition BoundaryConditionType; + BoundaryConditionType boundaryCondition; + boundaryCondition.SetConstant(0.0); + + if (m_HotspotMustBeCompletelyInsideImage) + { + // overwrite default boundary condition + convolutionFilter->SetBoundaryCondition(&boundaryCondition); + } + + convolutionFilter->SetInput(inputImage); + convolutionFilter->SetKernelImage(convolutionKernel); + convolutionFilter->SetNormalize(true); + MITK_DEBUG << "Update Convolution image for hotspot search"; + convolutionFilter->UpdateLargestPossibleRegion(); + + typename ConvolutionImageType::Pointer convolutionImage = convolutionFilter->GetOutput(); + convolutionImage->SetSpacing( inputImage->GetSpacing() ); // only workaround because convolution filter seems to ignore spacing of input image + + return convolutionImage; + } + + template < typename TPixel, unsigned int VImageDimension> + void + HotspotMaskGenerator::FillHotspotMaskPixels( itk::Image* maskImage, + itk::Point sphereCenter, + double sphereRadiusInMM ) + { + typedef itk::Image< TPixel, VImageDimension > MaskImageType; + typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; + + MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); + + typename MaskImageType::IndexType maskIndex; + typename MaskImageType::PointType worldPosition; + + // this is not very smart. I would rather use a 0 initialized mask (not the case here -> blame CalculateHotspotMask) and find the region where I need to iterate over, then iterate only over the small region + for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) + { + maskIndex = maskIt.GetIndex(); + maskImage->TransformIndexToPhysicalPoint(maskIndex, worldPosition); + maskIt.Set( worldPosition.EuclideanDistanceTo(sphereCenter) <= sphereRadiusInMM ? 1 : 0 ); + } + } + + template + void + HotspotMaskGenerator::CalculateHotspotMask(itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + unsigned int label) + { + typedef itk::Image< TPixel, VImageDimension > InputImageType; + typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; + typedef itk::Image< float, VImageDimension > KernelImageType; + typedef itk::Image< unsigned short, VImageDimension > MaskImageType; + + typename ConvolutionImageType::Pointer convolutionImage = this->GenerateConvolutionImage(inputImage); + + if (convolutionImage.IsNull()) + { + MITK_ERROR << "Empty convolution image in CalculateHotspotStatistics(). We should never reach this state (logic error)."; + throw std::logic_error("Empty convolution image in CalculateHotspotStatistics()"); + } + + // if mask image is not defined, create an image of the same size as inputImage and fill it with 1's + // there is maybe a better way to do this!? + if (maskImage == nullptr) + { + maskImage = MaskImageType::New(); + typename MaskImageType::RegionType maskRegion = inputImage->GetLargestPossibleRegion(); + typename MaskImageType::SpacingType maskSpacing = inputImage->GetSpacing(); + typename MaskImageType::PointType maskOrigin = inputImage->GetOrigin(); + typename MaskImageType::DirectionType maskDirection = inputImage->GetDirection(); + maskImage->SetRegions(maskRegion); + maskImage->Allocate(); + maskImage->SetOrigin(maskOrigin); + maskImage->SetSpacing(maskSpacing); + maskImage->SetDirection(maskDirection); + + maskImage->FillBuffer(1); + + label = 1; + } + + // find maximum in convolution image, given the current mask + double requiredDistanceToBorder = m_HotspotMustBeCompletelyInsideImage ? m_HotspotRadiusinMM : -1.0; + ImageExtrema convolutionImageInformation = CalculateExtremaWorld(convolutionImage.GetPointer(), maskImage, requiredDistanceToBorder, label); + + bool isHotspotDefined = convolutionImageInformation.Defined; + + if (!isHotspotDefined) + { + MITK_ERROR << "No origin of hotspot-sphere was calculated!"; + m_InternalMask = nullptr; + } + else + { + // create a binary mask around the "hotspot" region, fill the shape of a sphere around our hotspot center +// typename DuplicatorType::Pointer copyMachine = DuplicatorType::New(); +// copyMachine->SetInputImage(inputImage); +// copyMachine->Update(); + +// typename CastFilterType::Pointer caster = CastFilterType::New(); +// caster->SetInput( copyMachine->GetOutput() ); +// caster->Update(); + typename MaskImageType::Pointer hotspotMaskITK = MaskImageType::New(); + hotspotMaskITK->SetOrigin(inputImage->GetOrigin()); + hotspotMaskITK->SetSpacing(inputImage->GetSpacing()); + hotspotMaskITK->SetLargestPossibleRegion(inputImage->GetLargestPossibleRegion()); + hotspotMaskITK->SetBufferedRegion(inputImage->GetBufferedRegion()); + hotspotMaskITK->SetDirection(inputImage->GetDirection()); + hotspotMaskITK->SetNumberOfComponentsPerPixel(inputImage->GetNumberOfComponentsPerPixel()); + hotspotMaskITK->Allocate(); + hotspotMaskITK->FillBuffer(1); + + typedef typename InputImageType::IndexType IndexType; + IndexType maskCenterIndex; + for (unsigned int d =0; d< VImageDimension;++d) + { + maskCenterIndex[d]=convolutionImageInformation.MaxIndex[d]; + } + + typename ConvolutionImageType::PointType maskCenter; + inputImage->TransformIndexToPhysicalPoint(maskCenterIndex,maskCenter); + + FillHotspotMaskPixels(hotspotMaskITK.GetPointer(), maskCenter, m_HotspotRadiusinMM); + + //obtain mitk::Image::Pointer from itk::Image + mitk::Image::Pointer hotspotMaskAsMITKImage = mitk::GrabItkImageMemory(hotspotMaskITK); + + m_InternalMask = hotspotMaskAsMITKImage; + m_ConvolutionImageMaxIndex = convolutionImageInformation.MaxIndex; + m_ConvolutionImageMinIndex = convolutionImageInformation.MinIndex; + } + } + + bool HotspotMaskGenerator::IsUpdateRequired() const + { + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long maskGeneratorTimeStamp = m_Mask->GetMTime(); + unsigned long inputImageTimeStamp = m_inputImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < maskGeneratorTimeStamp || m_InternalMaskUpdateTime < inputImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; + } +} diff --git a/Modules/ImageStatistics/mitkHotspotMaskGenerator.h b/Modules/ImageStatistics/mitkHotspotMaskGenerator.h new file mode 100644 index 0000000000..909fb6b1a6 --- /dev/null +++ b/Modules/ImageStatistics/mitkHotspotMaskGenerator.h @@ -0,0 +1,164 @@ +#ifndef MITKHOTSPOTCALCULATOR_H +#define MITKHOTSPOTCALCULATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace mitk +{ +/** + * @brief The HotspotMaskGenerator class is used when a hotspot has to be found in an image. A hotspot is + * the region of the image where the mean intensity is maximal (=brightest spot). It is usually used in PET scans. + * The identification of the hotspot is done as follows: First a cubic (or circular, if image is 2d) + * mask of predefined size is generated. This mask is then convolved with the input image (in fourier domain). + * The maximum value of the convolved image then corresponds to the hotspot. + * If a maskGenerator is set, only the pixels of the convolved image where the corresponding mask is == @a label + * are searched for the maximum value. + */ + class MITKIMAGESTATISTICS_EXPORT HotspotMaskGenerator: public MaskGenerator + { + public: + /** Standard Self typedef */ + typedef HotspotMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(HotspotMaskGenerator, MaskGenerator) + + /** + @brief Set the input image. Required for this class + */ + void SetInputImage(mitk::Image::Pointer inputImage); + + /** + @brief Set a mask (can be nullptr if no mask is desired) + */ + void SetMask(MaskGenerator::Pointer mask); + + /** + @brief Set the radius of the hotspot (in MM) + */ + void SetHotspotRadiusInMM(double radiusInMillimeter); + + const double& GetHotspotRadiusinMM() const; + + /** + @brief Define whether the hotspot must be completely inside the image. Default is true + */ + void SetHotspotMustBeCompletelyInsideImage(bool hotspotCompletelyInsideImage); + + bool GetHotspotMustBeCompletelyInsideImage() const; + + /** + @brief If a maskGenerator is set, this detemines which mask value is used + */ + void SetLabel(unsigned short label); + + /** + @brief Computes and returns the hotspot mask. The hotspot mask has the same size as the input image. The hopspot has value 1, the remaining pixels are set to 0 + */ + mitk::Image::Pointer GetMask(); + + /** + @brief Returns the image index where the hotspot is located + */ + vnl_vector GetHotspotIndex(); + + /** + @brief Returns the index where the convolution image is minimal (darkest spot in image) + */ + vnl_vector GetConvolutionImageMinIndex(); + + protected: + HotspotMaskGenerator(); + + ~HotspotMaskGenerator(); + + class ImageExtrema + { + public: + bool Defined; + double Max; + double Min; + vnl_vector MaxIndex; + vnl_vector MinIndex; + + ImageExtrema() + :Defined(false) + ,Max(itk::NumericTraits::min()) + ,Min(itk::NumericTraits::max()) + { + } + }; + + private: + /** \brief Returns size of convolution kernel depending on spacing and radius. */ + template + itk::Size + CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM); + + /** \brief Generates image of kernel which is needed for convolution. */ + template + itk::SmartPointer< itk::Image > + GenerateHotspotSearchConvolutionKernel(double spacing[VImageDimension], double radiusInMM); + + /** \brief Convolves image with spherical kernel image. Used for hotspot calculation. */ + template + itk::SmartPointer< itk::Image > + GenerateConvolutionImage( const itk::Image* inputImage ); + + + /** \brief Fills pixels of the spherical hotspot mask. */ + template < typename TPixel, unsigned int VImageDimension> + void + FillHotspotMaskPixels( itk::Image* maskImage, + itk::Point sphereCenter, + double sphereRadiusInMM); + + + /** \brief */ + template + void + CalculateHotspotMask(itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + unsigned int label); + + + template + ImageExtrema CalculateExtremaWorld( const itk::Image* inputImage, + typename itk::Image::Pointer maskImage, + double neccessaryDistanceToImageBorderInMM, + unsigned int label); + + bool IsUpdateRequired() const; + + HotspotMaskGenerator(const HotspotMaskGenerator &); + HotspotMaskGenerator & operator=(const HotspotMaskGenerator &); + + MaskGenerator::Pointer m_Mask; + mitk::Image::Pointer m_internalImage; + itk::Image::Pointer m_internalMask2D; + itk::Image::Pointer m_internalMask3D; + double m_HotspotRadiusinMM; + bool m_HotspotMustBeCompletelyInsideImage; + bool m_HotspotParamsChanged; + unsigned short m_Label; + vnl_vector m_ConvolutionImageMinIndex, m_ConvolutionImageMaxIndex; + unsigned long m_InternalMaskUpdateTime; + }; +} +#endif // MITKHOTSPOTCALCULATOR + + diff --git a/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.cpp b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.cpp new file mode 100644 index 0000000000..ba068667dd --- /dev/null +++ b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include + +namespace mitk +{ +void IgnorePixelMaskGenerator::SetIgnoredPixelValue(RealType pixelValue) +{ + if (pixelValue != m_IgnoredPixelValue) + { + m_IgnoredPixelValue = pixelValue; + this->Modified(); + } +} + +mitk::Image::Pointer IgnorePixelMaskGenerator::GetMask() +{ + if (IsUpdateRequired()) + { + if (m_inputImage.IsNull()) + { + MITK_ERROR << "Image not set!"; + } + + if (m_IgnoredPixelValue == std::numeric_limits::min()) + { + MITK_ERROR << "IgnotePixelValue not set!"; + } + + if (m_TimeStep > (m_inputImage->GetTimeSteps() - 1)) + { + MITK_ERROR << "Invalid time step: " << m_TimeStep << ". The image has " << m_inputImage->GetTimeSteps() << " timeSteps!"; + } + + // extractimage time slice + ImageTimeSelector::Pointer imgTimeSel = ImageTimeSelector::New(); + imgTimeSel->SetInput(m_inputImage); + imgTimeSel->SetTimeNr(m_TimeStep); + imgTimeSel->UpdateLargestPossibleRegion(); + + mitk::Image::Pointer timeSliceImage = imgTimeSel->GetOutput(); + + // update m_InternalMask + AccessByItk(timeSliceImage, InternalCalculateMask); + m_InternalMask->SetGeometry(timeSliceImage->GetGeometry()); + + this->Modified(); + } + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; +} + +template +void IgnorePixelMaskGenerator::InternalCalculateMask(typename itk::Image* image) +{ + typedef itk::Image ImageType; + typedef itk::Image MaskType; + + typename MaskType::Pointer mask = MaskType::New(); + mask->SetOrigin(image->GetOrigin()); + mask->SetSpacing(image->GetSpacing()); + mask->SetLargestPossibleRegion(image->GetLargestPossibleRegion()); + mask->SetBufferedRegion(image->GetBufferedRegion()); + mask->SetDirection(image->GetDirection()); + mask->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); + mask->Allocate(); + mask->FillBuffer(1); + + // iterate over image and mask and set mask=1 if image=m_IgnorePixelValue + itk::ImageRegionConstIterator imageIterator(image, image->GetLargestPossibleRegion()); + itk::ImageRegionIterator maskIterator(mask, mask->GetLargestPossibleRegion()); + + + for (imageIterator.GoToBegin(); !imageIterator.IsAtEnd(); ++imageIterator, ++maskIterator) + { + if (imageIterator.Value() == static_cast(m_IgnoredPixelValue)) + { + maskIterator.Set(0); + } + } + + m_InternalMask = GrabItkImageMemory(mask); +} + +bool IgnorePixelMaskGenerator::IsUpdateRequired() const +{ + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long inputImageTimeStamp = m_inputImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < inputImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; +} + +} // end namespace diff --git a/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.h b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.h new file mode 100644 index 0000000000..d8eecde982 --- /dev/null +++ b/Modules/ImageStatistics/mitkIgnorePixelMaskGenerator.h @@ -0,0 +1,67 @@ +#ifndef MITKIGNOREPIXELMASKGEN_ +#define MITKIGNOREPIXELMASKGEN_ + +#include +#include +#include +#include +#include + + +namespace mitk +{ +/** + * @brief The IgnorePixelMaskGenerator class is used to generate a mask that is zero for specific pixel values in the input image. This class requires an input image. + */ +class MITKIMAGESTATISTICS_EXPORT IgnorePixelMaskGenerator: public MaskGenerator +{ +public: + /** Standard Self typedef */ + typedef IgnorePixelMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + typedef double RealType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(IgnorePixelMaskGenerator, MaskGenerator) + + /** + * @brief The mask will be 0 there inputImage==pixelValue and 1 otherwise + */ + void SetIgnoredPixelValue(RealType pixelValue); + + /** + * @brief Computes and returns the mask + */ + mitk::Image::Pointer GetMask(); + +protected: + IgnorePixelMaskGenerator(): + m_IgnoredPixelValue(std::numeric_limits::min()) + { + m_TimeStep = 0; + m_InternalMaskUpdateTime = 0; + m_InternalMask = mitk::Image::New(); + } + + ~IgnorePixelMaskGenerator(){} + + template + void InternalCalculateMask(typename itk::Image* image); + +private: + bool IsUpdateRequired() const; + + RealType m_IgnoredPixelValue; + unsigned long m_InternalMaskUpdateTime; + + +}; + +} + +#endif diff --git a/Modules/ImageStatistics/mitkImageMaskGenerator.cpp b/Modules/ImageStatistics/mitkImageMaskGenerator.cpp new file mode 100644 index 0000000000..a96916aebb --- /dev/null +++ b/Modules/ImageStatistics/mitkImageMaskGenerator.cpp @@ -0,0 +1,82 @@ + + +#include +#include +#include + +namespace mitk { + +void ImageMaskGenerator::SetImageMask(Image::Pointer maskImage) +{ + if (m_internalMaskImage != maskImage) + { + m_internalMaskImage = maskImage; + this->Modified(); + } +} + +mitk::Image::Pointer ImageMaskGenerator::GetMask() +{ + if (m_internalMaskImage.IsNull()) + { + MITK_ERROR << "Mask Image is nullptr"; + } + + if (this->IsUpdateRequired()) + { + unsigned int timeStepForExtraction; + + if (m_TimeStep >= m_internalMaskImage->GetTimeSteps()) + { + MITK_WARN << "Warning: time step > number of time steps in mask image, using last time step"; + timeStepForExtraction = m_internalMaskImage->GetTimeSteps() - 1; + } + else + { + timeStepForExtraction = m_TimeStep; + } + ImageTimeSelector::Pointer imageTimeSelector = ImageTimeSelector::New(); + imageTimeSelector->SetInput(m_internalMaskImage); + imageTimeSelector->SetTimeNr(timeStepForExtraction); + imageTimeSelector->UpdateLargestPossibleRegion(); + + m_InternalMask = mitk::Image::New(); + m_InternalMask = imageTimeSelector->GetOutput(); + this->Modified(); + } + + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; +} + +bool ImageMaskGenerator::IsUpdateRequired() const +{ + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long maskImageTimeStamp = m_internalMaskImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < maskImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; +} + +} + + + + + + diff --git a/Modules/ImageStatistics/mitkImageMaskGenerator.h b/Modules/ImageStatistics/mitkImageMaskGenerator.h new file mode 100644 index 0000000000..0d6d12c826 --- /dev/null +++ b/Modules/ImageStatistics/mitkImageMaskGenerator.h @@ -0,0 +1,48 @@ +#ifndef mitkBinaryMaskGenerator +#define mitkBinaryMaskGenerator + +#include +#include +#include +#include +#include + +namespace mitk +{ +class MITKIMAGESTATISTICS_EXPORT ImageMaskGenerator: public MaskGenerator +{ +public: + /** Standard Self typedef */ + typedef ImageMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(BinaryImageMaskGenerator, MaskGenerator) + + mitk::Image::Pointer GetMask(); + + void SetImageMask(mitk::Image::Pointer maskImage); + +protected: + ImageMaskGenerator():Superclass(){ + m_InternalMaskUpdateTime = 0; + m_InternalMask = mitk::Image::New(); + } + +private: + bool IsUpdateRequired() const; + + mitk::Image::Pointer m_internalMaskImage; + unsigned long m_InternalMaskUpdateTime; + +}; + + +} + +#endif diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp index b88432606f..4551c82546 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp @@ -1,2249 +1,642 @@ -/*=================================================================== -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center, -Division of Medical and Biological Informatics. -All rights reserved. - -This software is distributed WITHOUT ANY WARRANTY; without -even the implied warranty of MERCHANTABILITY or FITNESS FOR -A PARTICULAR PURPOSE. - -See LICENSE.txt or http://www.mitk.org for details. - -===================================================================*/ - - -#include "mitkImageStatisticsCalculator.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkExtractImageFilter.h" -#include "mitkImageTimeSelector.h" -#include "mitkITKImageImport.h" - -#include -#include - -#include +#include +#include -#include +#include +#include +#include #include +#include +#include #include -#include -#include +#include #include -#include -#include +#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "itkImage.h" -//#define DEBUG_HOTSPOTSEARCH -#define _USE_MATH_DEFINES -#include - -#include "vtkLassoStencilSource.h" +#include +#include "itkImageFileWriter.h" namespace mitk { - ImageStatisticsCalculator::ImageStatisticsCalculator() - : m_MaskingMode( MASKING_MODE_NONE ), - m_MaskingModeChanged( false ), - m_IgnorePixelValue(0.0), - m_DoIgnorePixelValue(false), - m_IgnorePixelValueChanged(false), - m_PlanarFigureAxis (0), - m_PlanarFigureSlice (0), - m_PlanarFigureCoordinate0 (0), - m_PlanarFigureCoordinate1 (0), - m_HistogramBinSize(1.0), - m_UseDefaultBinSize(true), - m_UseBinSizeBasedOnVOIRegion(false), - m_HotspotRadiusInMM(6.2035049089940), // radius of a 1cm3 sphere in mm - m_CalculateHotspot(false), - m_HotspotRadiusInMMChanged(false), - m_HotspotMustBeCompletelyInsideImage(true) - { - m_EmptyHistogram = HistogramType::New(); - m_EmptyHistogram->SetMeasurementVectorSize(1); - HistogramType::SizeType histogramSize(1); - histogramSize.Fill( 256 ); - m_EmptyHistogram->Initialize( histogramSize ); - - m_EmptyStatistics.Reset(); - } - - ImageStatisticsCalculator::~ImageStatisticsCalculator() - { - } - - - void ImageStatisticsCalculator::SetUseDefaultBinSize(bool useDefault) - { - m_UseDefaultBinSize = useDefault; - } - - - - - ImageStatisticsCalculator::Statistics::Statistics(bool withHotspotStatistics) - :m_HotspotStatistics(withHotspotStatistics ? new Statistics(false) : nullptr) - { - Reset(); - } - - ImageStatisticsCalculator::Statistics::Statistics(const Statistics& other) - :m_HotspotStatistics( nullptr) - { - this->SetLabel( other.GetLabel() ); - this->SetN( other.GetN() ); - this->SetMin( other.GetMin() ); - this->SetMax( other.GetMax() ); - this->SetMedian( other.GetMedian() ); - this->SetMean( other.GetMean() ); - this->SetVariance( other.GetVariance() ); - this->SetKurtosis( other.GetKurtosis() ); - this->SetSkewness( other.GetSkewness() ); - this->SetUniformity( other.GetUniformity() ); - this->SetEntropy( other.GetEntropy() ); - this->SetUPP( other.GetUPP() ); - this->SetMPP( other.GetMPP() ); - this->SetSigma( other.GetSigma() ); - this->SetRMS( other.GetRMS() ); - this->SetMaxIndex( other.GetMaxIndex() ); - this->SetMinIndex( other.GetMinIndex() ); - this->SetHotspotIndex( other.GetHotspotIndex() ); - - if (other.m_HotspotStatistics) - { - this->m_HotspotStatistics = new Statistics(false); - *this->m_HotspotStatistics = *other.m_HotspotStatistics; - } - } - - bool ImageStatisticsCalculator::Statistics::HasHotspotStatistics() const - { - return m_HotspotStatistics != nullptr; - } - - void ImageStatisticsCalculator::Statistics::SetHasHotspotStatistics(bool hasHotspotStatistics) - { - m_HasHotspotStatistics = hasHotspotStatistics; - } - - - ImageStatisticsCalculator::Statistics::~Statistics() - { - delete m_HotspotStatistics; - } - - double ImageStatisticsCalculator::Statistics::GetVariance() const - { - return this->Variance; - } - - void ImageStatisticsCalculator::Statistics::SetVariance( const double value ) - { - if( this->Variance != value ) - { - if( value < 0.0 ) - { - this->Variance = 0.0; // if given value is negative set variance to 0.0 - } - else - { - this->Variance = value; - } - } - } - - double ImageStatisticsCalculator::Statistics::GetSigma() const - { - return this->Sigma; - } - - void ImageStatisticsCalculator::Statistics::SetSigma( const double value ) - { - if( this->Sigma != value ) - { - // for some compiler the value != value works to check for NaN but not for all - // but we can always be sure that the standard deviation is a positive value - if( value != value || value < 0.0 ) - { - // if standard deviation is NaN we just assume 0.0 - this->Sigma = 0.0; - } - else - { - this->Sigma = value; - } - } - } - - void ImageStatisticsCalculator::Statistics::Reset(unsigned int dimension) - { - SetLabel(0); - SetN( 0 ); - SetMin( 0.0 ); - SetMax( 0.0 ); - SetMedian( 0.0 ); - SetVariance( 0.0 ); - SetMean( 0.0 ); - SetSigma( 0.0 ); - SetRMS( 0.0 ); - - SetSkewness( 0.0 ); - SetKurtosis( 0.0 ); - SetUniformity( 0.0 ); - SetEntropy( 0.0 ); - SetMPP( 0.0 ); - SetUPP( 0.0 ); - - vnl_vector zero; - zero.set_size(dimension); - for(unsigned int i = 0; i < dimension; ++i) - { - zero[i] = 0; - } - - SetMaxIndex(zero); - SetMinIndex(zero); - SetHotspotIndex(zero); - - if (m_HotspotStatistics != nullptr) - { - m_HotspotStatistics->Reset(dimension); - } - } - - const ImageStatisticsCalculator::Statistics& - ImageStatisticsCalculator::Statistics::GetHotspotStatistics() const - { - if (m_HotspotStatistics) - { - return *m_HotspotStatistics; - } - else - { - throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); - } - } - - ImageStatisticsCalculator::Statistics& - ImageStatisticsCalculator::Statistics::GetHotspotStatistics() - { - if (m_HotspotStatistics) - { - return *m_HotspotStatistics; - } - else - { - throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); - } - } - - ImageStatisticsCalculator::Statistics& - ImageStatisticsCalculator::Statistics::operator=(ImageStatisticsCalculator::Statistics const& other) - { - if (this == &other) - return *this; - - this->SetLabel( other.GetLabel() ); - this->SetN( other.GetN() ); - this->SetMin( other.GetMin() ); - this->SetMax( other.GetMax() ); - this->SetMean( other.GetMean() ); - this->SetMedian( other.GetMedian() ); - this->SetVariance( other.GetVariance() ); - this->SetSigma( other.GetSigma() ); - this->SetRMS( other.GetRMS() ); - this->SetMinIndex( other.GetMinIndex() ); - this->SetMaxIndex( other.GetMaxIndex() ); - this->SetHotspotIndex( other.GetHotspotIndex() ); - this->SetSkewness( other.GetSkewness() ); - this->SetKurtosis( other.GetKurtosis() ); - this->SetUniformity( other.GetUniformity() ); - this->SetEntropy( other.GetEntropy() ); - this->SetUPP( other.GetUPP() ); - this->SetMPP( other.GetMPP() ); - - - delete this->m_HotspotStatistics; - this->m_HotspotStatistics = nullptr; - - if (other.m_HotspotStatistics) - { - this->m_HotspotStatistics = new Statistics(false); - *this->m_HotspotStatistics = *other.m_HotspotStatistics; - } - - return *this; - - } - - void ImageStatisticsCalculator::SetImage( const mitk::Image *image ) - { - if ( m_Image != image ) - { - m_Image = image; - this->Modified(); - unsigned int numberOfTimeSteps = image->GetTimeSteps(); - - // Initialize vectors to time-size of this image - m_ImageHistogramVector.resize( numberOfTimeSteps ); - m_MaskedImageHistogramVector.resize( numberOfTimeSteps ); - m_PlanarFigureHistogramVector.resize( numberOfTimeSteps ); - - m_ImageStatisticsVector.resize( numberOfTimeSteps ); - m_MaskedImageStatisticsVector.resize( numberOfTimeSteps ); - m_PlanarFigureStatisticsVector.resize( numberOfTimeSteps ); - - m_ImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); - m_MaskedImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); - m_PlanarFigureStatisticsTimeStampVector.resize( numberOfTimeSteps ); - - m_ImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); - m_MaskedImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); - m_PlanarFigureStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); - - for ( unsigned int t = 0; t < image->GetTimeSteps(); ++t ) - { - m_ImageStatisticsTimeStampVector[t].Modified(); - m_ImageStatisticsCalculationTriggerVector[t] = true; - } - } - } - - - void ImageStatisticsCalculator::SetImageMask( const mitk::Image *imageMask ) - { - if ( m_Image.IsNull() ) - { - itkExceptionMacro( << "Image needs to be set first!" ); - } - - if ( m_ImageMask != imageMask ) - { - m_ImageMask = imageMask; - this->Modified(); - - for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) - { - m_MaskedImageStatisticsTimeStampVector[t].Modified(); - m_MaskedImageStatisticsCalculationTriggerVector[t] = true; - } - } - } - - void ImageStatisticsCalculator::SetPlanarFigure( mitk::PlanarFigure *planarFigure ) - { - if ( m_Image.IsNull() ) + void ImageStatisticsCalculator::SetInputImage(mitk::Image::Pointer image) { - itkExceptionMacro( << "Image needs to be set first!" ); + if (image != m_Image) + { + m_Image = image; + m_StatisticsByTimeStep.resize(m_Image->GetTimeSteps()); + m_StatisticsUpdateTimePerTimeStep.resize(m_Image->GetTimeSteps()); + std::fill(m_StatisticsUpdateTimePerTimeStep.begin(), m_StatisticsUpdateTimePerTimeStep.end(), 0); + this->Modified(); + } } - if ( m_PlanarFigure != planarFigure ) + void ImageStatisticsCalculator::SetMask(mitk::MaskGenerator::Pointer mask) { - m_PlanarFigure = planarFigure; - this->Modified(); - - for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) - { - m_PlanarFigureStatisticsTimeStampVector[t].Modified(); - m_PlanarFigureStatisticsCalculationTriggerVector[t] = true; - } - } - } + if (mask != m_MaskGenerator) + { + m_MaskGenerator = mask; + this->Modified(); + } - void ImageStatisticsCalculator::SetMaskingMode( unsigned int mode ) - { - if ( m_MaskingMode != mode ) - { - m_MaskingMode = mode; - m_MaskingModeChanged = true; - this->Modified(); } - } - - void ImageStatisticsCalculator::SetMaskingModeToNone() - { - if ( m_MaskingMode != MASKING_MODE_NONE ) + void ImageStatisticsCalculator::SetSecondaryMask(mitk::MaskGenerator::Pointer mask) { - m_MaskingMode = MASKING_MODE_NONE; - m_MaskingModeChanged = true; - this->Modified(); - } - } + if (mask != m_SecondaryMaskGenerator) + { + m_SecondaryMaskGenerator = mask; + this->Modified(); + } - void ImageStatisticsCalculator::SetMaskingModeToImage() - { - if ( m_MaskingMode != MASKING_MODE_IMAGE ) - { - m_MaskingMode = MASKING_MODE_IMAGE; - m_MaskingModeChanged = true; - this->Modified(); } - } - void ImageStatisticsCalculator::SetMaskingModeToPlanarFigure() - { - if ( m_MaskingMode != MASKING_MODE_PLANARFIGURE ) + void ImageStatisticsCalculator::SetNBinsForHistogramStatistics(unsigned int nBins) { - m_MaskingMode = MASKING_MODE_PLANARFIGURE; - m_MaskingModeChanged = true; - this->Modified(); - } - } - - void ImageStatisticsCalculator::SetIgnorePixelValue(double value) - { - if ( m_IgnorePixelValue != value ) - { - m_IgnorePixelValue = value; - if(m_DoIgnorePixelValue) - { - m_IgnorePixelValueChanged = true; - } - this->Modified(); + if (nBins != m_nBinsForHistogramStatistics) + { + m_nBinsForHistogramStatistics = nBins; + this->Modified(); + this->m_UseBinSizeOverNBins = false; + } + if (m_UseBinSizeOverNBins) + { + this->Modified(); + this->m_UseBinSizeOverNBins = false; + } } - } - - double ImageStatisticsCalculator::GetIgnorePixelValue() - { - return m_IgnorePixelValue; - } - void ImageStatisticsCalculator::SetDoIgnorePixelValue(bool value) - { - if ( m_DoIgnorePixelValue != value ) + unsigned int ImageStatisticsCalculator::GetNBinsForHistogramStatistics() const { - m_DoIgnorePixelValue = value; - m_IgnorePixelValueChanged = true; - this->Modified(); + return m_nBinsForHistogramStatistics; } - } - - bool ImageStatisticsCalculator::GetDoIgnorePixelValue() - { - return m_DoIgnorePixelValue; - } - - void ImageStatisticsCalculator::SetHistogramBinSize(double size) - { - this->m_HistogramBinSize = size; - } - double ImageStatisticsCalculator::GetHistogramBinSize() - { - return this->m_HistogramBinSize; - } - - void ImageStatisticsCalculator::SetHotspotRadiusInMM(double value) - { - if ( m_HotspotRadiusInMM != value ) - { - m_HotspotRadiusInMM = value; - if(m_CalculateHotspot) - { - m_HotspotRadiusInMMChanged = true; - //MITK_INFO <<"Hotspot radius changed, new convolution required"; - } - this->Modified(); - } - } - double ImageStatisticsCalculator::GetHotspotRadiusInMM() - { - return m_HotspotRadiusInMM; - } - - void ImageStatisticsCalculator::SetCalculateHotspot(bool on) - { - if ( m_CalculateHotspot != on ) + void ImageStatisticsCalculator::SetBinSizeForHistogramStatistics(double binSize) { - m_CalculateHotspot = on; - m_HotspotRadiusInMMChanged = true; - //MITK_INFO <<"Hotspot calculation changed, new convolution required"; - this->Modified(); + if (binSize != m_binSizeForHistogramStatistics) + { + m_binSizeForHistogramStatistics = binSize; + this->Modified(); + this->m_UseBinSizeOverNBins = true; + } + if (!m_UseBinSizeOverNBins) + { + this->Modified(); + this->m_UseBinSizeOverNBins = true; + } } - } - bool ImageStatisticsCalculator::IsHotspotCalculated() - { - return m_CalculateHotspot; - } - - void ImageStatisticsCalculator::SetHotspotMustBeCompletlyInsideImage(bool hotspotMustBeCompletelyInsideImage, bool warn) - { - m_HotspotMustBeCompletelyInsideImage = hotspotMustBeCompletelyInsideImage; - if (!m_HotspotMustBeCompletelyInsideImage && warn) + double ImageStatisticsCalculator::GetBinSizeForHistogramStatistics() const { - MITK_WARN << "Hotspot calculation will extrapolate pixels at image borders. Be aware of the consequences for the hotspot location."; + return m_binSizeForHistogramStatistics; } - } - - bool ImageStatisticsCalculator::GetHotspotMustBeCompletlyInsideImage() const - { - return m_HotspotMustBeCompletelyInsideImage; - } - - - /* Implementation of the min max values for setting the range of the histogram */ - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::GetMinAndMaxValue( double &min, - double &max, int &counter, double &sigma, - const itk::Image< TPixel, VImageDimension > *InputImage, - itk::Image< unsigned short, VImageDimension > *MaskedImage ) - { - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - typedef itk::Image< TPixel, VImageDimension > ImageType; - - typedef itk::ImageRegionConstIteratorWithIndex Imageie; - typedef itk::ImageRegionConstIteratorWithIndex Imageie2; - - Imageie2 labelIterator2( MaskedImage, MaskedImage->GetRequestedRegion() ); - Imageie labelIterator3( InputImage, InputImage->GetRequestedRegion() ); - - max = 0; - min = 0; - counter = 0; - sigma = 0; - double SumOfSquares = 0; - double sumSquared = 0; - double actualPielValue = 0; - int counterOfPixelsInROI = 0; - - - for( labelIterator2.GoToBegin(); !labelIterator2.IsAtEnd(); ++labelIterator2, ++labelIterator3) + ImageStatisticsCalculator::StatisticsContainer::Pointer ImageStatisticsCalculator::GetStatistics(unsigned int timeStep, unsigned int label) { - if( labelIterator2.Value()== 1.0) - { - counter++; - - counterOfPixelsInROI++; - actualPielValue = labelIterator3.Value(); - sumSquared = sumSquared + actualPielValue; - SumOfSquares = SumOfSquares + std::pow(actualPielValue,2); - - if(counterOfPixelsInROI == 1) + if (timeStep >= m_StatisticsByTimeStep.size()) { - max = actualPielValue; - min = actualPielValue; + mitkThrow() << "invalid timeStep in ImageStatisticsCalculator_v2::GetStatistics"; } - if(actualPielValue >= max) + if (m_Image.IsNull()) { - max = actualPielValue; + mitkThrow() << "no image"; } - else if(actualPielValue <= min) + + if (!m_Image->IsInitialized()) { - min = actualPielValue; + mitkThrow() << "Image not initialized!"; } - } - } - - if (counter > 1) - { - sigma = ( SumOfSquares - std::pow( sumSquared, 2) / counter ) / ( counter-1 ); - } - else - { - sigma = 0; - } - - } - - - bool ImageStatisticsCalculator::ComputeStatistics( unsigned int timeStep ) - { - - if (m_Image.IsNull() ) - { - mitkThrow() << "Image not set!"; - } - - if (!m_Image->IsInitialized()) - { - mitkThrow() << "Image not initialized!"; - } - - if ( m_Image->GetReferenceCount() == 1 ) - { - // Image no longer valid; we are the only ones to still hold a reference on it - return false; - } - - if ( timeStep >= m_Image->GetTimeSteps() ) - { - throw std::runtime_error( "Error: invalid time step!" ); - } - - // If a mask was set but we are the only ones to still hold a reference on - // it, delete it. - if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() == 1) ) - { - m_ImageMask = nullptr; - } - - // Check if statistics is already up-to-date - unsigned long imageMTime = m_ImageStatisticsTimeStampVector[timeStep].GetMTime(); - unsigned long maskedImageMTime = m_MaskedImageStatisticsTimeStampVector[timeStep].GetMTime(); - unsigned long planarFigureMTime = m_PlanarFigureStatisticsTimeStampVector[timeStep].GetMTime(); - - bool imageStatisticsCalculationTrigger = m_ImageStatisticsCalculationTriggerVector[timeStep]; - bool maskedImageStatisticsCalculationTrigger = m_MaskedImageStatisticsCalculationTriggerVector[timeStep]; - bool planarFigureStatisticsCalculationTrigger = m_PlanarFigureStatisticsCalculationTriggerVector[timeStep]; - - if ( !m_IgnorePixelValueChanged - && !m_HotspotRadiusInMMChanged - && ((m_MaskingMode != MASKING_MODE_NONE) || (imageMTime > m_Image->GetMTime() && !imageStatisticsCalculationTrigger)) - && ((m_MaskingMode != MASKING_MODE_IMAGE) || (maskedImageMTime > m_ImageMask->GetMTime() && !maskedImageStatisticsCalculationTrigger)) - && ((m_MaskingMode != MASKING_MODE_PLANARFIGURE) || (planarFigureMTime > m_PlanarFigure->GetMTime() && !planarFigureStatisticsCalculationTrigger)) ) - { - // Statistics is up to date! - if ( m_MaskingModeChanged ) - { - m_MaskingModeChanged = false; - } - else - { - return false; - } - } - - // Reset state changed flag - m_MaskingModeChanged = false; - m_IgnorePixelValueChanged = false; - - // Depending on masking mode, extract and/or generate the required image - // and mask data from the user input - this->ExtractImageAndMask( timeStep ); - - - StatisticsContainer *statisticsContainer; - HistogramContainer *histogramContainer; - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - if(!m_DoIgnorePixelValue) - { - statisticsContainer = &m_ImageStatisticsVector[timeStep]; - histogramContainer = &m_ImageHistogramVector[timeStep]; - - m_ImageStatisticsTimeStampVector[timeStep].Modified(); - m_ImageStatisticsCalculationTriggerVector[timeStep] = false; - } - else - { - statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; - histogramContainer = &m_MaskedImageHistogramVector[timeStep]; - - m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); - m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; - } - break; - - case MASKING_MODE_IMAGE: - statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; - histogramContainer = &m_MaskedImageHistogramVector[timeStep]; - - m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); - m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; - break; - - case MASKING_MODE_PLANARFIGURE: - statisticsContainer = &m_PlanarFigureStatisticsVector[timeStep]; - histogramContainer = &m_PlanarFigureHistogramVector[timeStep]; - - m_PlanarFigureStatisticsTimeStampVector[timeStep].Modified(); - m_PlanarFigureStatisticsCalculationTriggerVector[timeStep] = false; - break; - } - - // Calculate statistics and histogram(s) - if ( m_InternalImage->GetDimension() == 3 ) - { - if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) - { - AccessFixedDimensionByItk_2( - m_InternalImage, - InternalCalculateStatisticsUnmasked, - 3, - statisticsContainer, - histogramContainer ); - } - else - { - AccessFixedDimensionByItk_3( - m_InternalImage, - InternalCalculateStatisticsMasked, - 3, - m_InternalImageMask3D.GetPointer(), - statisticsContainer, - histogramContainer ); - } - } - else if ( m_InternalImage->GetDimension() == 2 ) - { - if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) - { - AccessFixedDimensionByItk_2( - m_InternalImage, - InternalCalculateStatisticsUnmasked, - 2, - statisticsContainer, - histogramContainer ); - } - else - { - AccessFixedDimensionByItk_3( - m_InternalImage, - InternalCalculateStatisticsMasked, - 2, - m_InternalImageMask2D.GetPointer(), - statisticsContainer, - histogramContainer ); - } - } - else - { - MITK_ERROR << "ImageStatistics: Image dimension not supported!"; - } - - - // Release unused image smart pointers to free memory - m_InternalImage = mitk::Image::ConstPointer(); - m_InternalImageMask3D = MaskImage3DType::Pointer(); - m_InternalImageMask2D = MaskImage2DType::Pointer(); - - - - return true; - } - - - ImageStatisticsCalculator::BinFrequencyType - ImageStatisticsCalculator::GetBinsAndFreuqencyForHistograms( unsigned int timeStep , unsigned int label ) const - { - const HistogramType *binsAndFrequencyToCalculate = this->GetHistogram(0); - - // ToDo: map should be created on stack not on heap - std::map returnedHistogramMap; - - unsigned int size = binsAndFrequencyToCalculate->Size(); - for( unsigned int bin=0; bin < size; ++bin ) - { - double frequency = binsAndFrequencyToCalculate->GetFrequency( bin, 0 ); - //if( frequency > mitk::eps ) - { - returnedHistogramMap.insert( std::pair(binsAndFrequencyToCalculate->GetMeasurement( bin, 0 ), binsAndFrequencyToCalculate->GetFrequency( bin, 0 ) ) ); - } - } - - return returnedHistogramMap; - } - - const ImageStatisticsCalculator::HistogramType * - ImageStatisticsCalculator::GetHistogram( unsigned int timeStep, unsigned int label ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) - { - return nullptr; - } - - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageHistogramVector[timeStep][label]; - - return m_ImageHistogramVector[timeStep][label]; - } + if (IsUpdateRequired(timeStep)) + { + if (m_MaskGenerator.IsNotNull()) + { + m_MaskGenerator->SetTimeStep(timeStep); + m_InternalMask = m_MaskGenerator->GetMask(); + if (m_MaskGenerator->GetReferenceImage().IsNotNull()) + { + m_InternalImageForStatistics = m_MaskGenerator->GetReferenceImage(); + } + else + { + m_InternalImageForStatistics = m_Image; + } + } + else + { + m_InternalImageForStatistics = m_Image; + } - case MASKING_MODE_IMAGE: - return m_MaskedImageHistogramVector[timeStep][label]; + if (m_SecondaryMaskGenerator.IsNotNull()) + { + m_SecondaryMaskGenerator->SetTimeStep(timeStep); + m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); + } - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureHistogramVector[timeStep][label]; - } - } + ImageTimeSelector::Pointer imgTimeSel = ImageTimeSelector::New(); + imgTimeSel->SetInput(m_InternalImageForStatistics); + imgTimeSel->SetTimeNr(timeStep); + imgTimeSel->UpdateLargestPossibleRegion(); + m_ImageTimeSlice = imgTimeSel->GetOutput(); - const ImageStatisticsCalculator::HistogramContainer & - ImageStatisticsCalculator::GetHistogramVector( unsigned int timeStep ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) - { - return m_EmptyHistogramContainer; - } - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageHistogramVector[timeStep]; + // Calculate statistics with/without mask + if (m_MaskGenerator.IsNull() && m_SecondaryMaskGenerator.IsNull()) + { + // 1) calculate statistics unmasked: + AccessByItk_1(m_ImageTimeSlice, InternalCalculateStatisticsUnmasked, timeStep) - return m_ImageHistogramVector[timeStep]; - } + } + else + { + // 2) calculate statistics masked + AccessByItk_1(m_ImageTimeSlice, InternalCalculateStatisticsMasked, timeStep) + } - case MASKING_MODE_IMAGE: - return m_MaskedImageHistogramVector[timeStep]; - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureHistogramVector[timeStep]; - } - } + this->Modified(); + } + m_StatisticsUpdateTimePerTimeStep[timeStep] = m_StatisticsByTimeStep[timeStep][m_StatisticsByTimeStep[timeStep].size()-1]->GetMTime(); - const ImageStatisticsCalculator::Statistics & - ImageStatisticsCalculator::GetStatistics( unsigned int timeStep, unsigned int label ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) - { - return m_EmptyStatistics; - } + for (std::vector::iterator it = m_StatisticsByTimeStep[timeStep].begin(); it != m_StatisticsByTimeStep[timeStep].end(); ++it) + { + StatisticsContainer::Pointer statCont = *it; + if (statCont->GetLabel() == label) + { + return statCont->Clone(); + } + } - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageStatisticsVector[timeStep][label]; - - return m_ImageStatisticsVector[timeStep][label]; - } - case MASKING_MODE_IMAGE: - return m_MaskedImageStatisticsVector[timeStep][label]; - - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureStatisticsVector[timeStep][label]; + // these lines will ony be executed if the requested label could not be found! + MITK_WARN << "Invalid label: " << label << " in time step: " << timeStep; + return StatisticsContainer::New(); } - } - const ImageStatisticsCalculator::StatisticsContainer & - ImageStatisticsCalculator::GetStatisticsVector( unsigned int timeStep ) const - { - if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) + template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( + typename itk::Image< TPixel, VImageDimension >* image, unsigned int timeStep) { - return m_EmptyStatisticsContainer; - } + typedef typename itk::Image< TPixel, VImageDimension > ImageType; + typedef typename itk::ExtendedStatisticsImageFilter ImageStatisticsFilterType; + typedef typename itk::MinMaxImageFilterWithIndex MinMaxFilterType; - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - default: - { - if(m_DoIgnorePixelValue) - return m_MaskedImageStatisticsVector[timeStep]; - - return m_ImageStatisticsVector[timeStep]; - } - case MASKING_MODE_IMAGE: - return m_MaskedImageStatisticsVector[timeStep]; - - case MASKING_MODE_PLANARFIGURE: - return m_PlanarFigureStatisticsVector[timeStep]; - } - } + StatisticsContainer::Pointer statisticsResult = StatisticsContainer::New(); + typename ImageStatisticsFilterType::Pointer statisticsFilter = ImageStatisticsFilterType::New(); + statisticsFilter->SetInput(image); + statisticsFilter->SetCoordinateTolerance(0.001); + statisticsFilter->SetDirectionTolerance(0.001); + // TODO: this is single threaded. Implement our own image filter that does this multi threaded +// typename itk::MinimumMaximumImageCalculator::Pointer imgMinMaxFilter = itk::MinimumMaximumImageCalculator::New(); +// imgMinMaxFilter->SetImage(image); +// imgMinMaxFilter->Compute(); + vnl_vector minIndex, maxIndex; - void ImageStatisticsCalculator::ExtractImageAndMask( unsigned int timeStep ) - { - if ( m_Image.IsNull() ) - { - throw std::runtime_error( "Error: image empty!" ); - } + typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); + minMaxFilter->SetInput(image); + minMaxFilter->UpdateLargestPossibleRegion(); + typename ImageType::PixelType minval = minMaxFilter->GetMin(); + typename ImageType::PixelType maxval = minMaxFilter->GetMax(); - if ( timeStep >= m_Image->GetTimeSteps() ) - { - throw std::runtime_error( "Error: invalid time step!" ); - } + typename ImageType::IndexType tmpMinIndex = minMaxFilter->GetMinIndex(); + typename ImageType::IndexType tmpMaxIndex = minMaxFilter->GetMaxIndex(); - ImageTimeSelector::Pointer imageTimeSelector = ImageTimeSelector::New(); - imageTimeSelector->SetInput( m_Image ); - imageTimeSelector->SetTimeNr( timeStep ); - imageTimeSelector->UpdateLargestPossibleRegion(); - mitk::Image *timeSliceImage = imageTimeSelector->GetOutput(); +// typename ImageType::IndexType tmpMinIndex = imgMinMaxFilter->GetIndexOfMinimum(); +// typename ImageType::IndexType tmpMaxIndex = imgMinMaxFilter->GetIndexOfMaximum(); + minIndex.set_size(tmpMaxIndex.GetIndexDimension()); + maxIndex.set_size(tmpMaxIndex.GetIndexDimension()); - switch ( m_MaskingMode ) - { - case MASKING_MODE_NONE: - { - m_InternalImage = timeSliceImage; - m_InternalImageMask2D = nullptr; - m_InternalImageMask3D = nullptr; - if(m_DoIgnorePixelValue) + for (unsigned int i=0; i < tmpMaxIndex.GetIndexDimension(); i++) { - if( m_InternalImage->GetDimension() == 3 ) - { - if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) - CastToItkImage( timeSliceImage, m_InternalImageMask3D ); - else - CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask3D ); - m_InternalImageMask3D->FillBuffer(1); - } - if( m_InternalImage->GetDimension() == 2 ) - { - if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) - CastToItkImage( timeSliceImage, m_InternalImageMask2D ); - else - CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask2D ); - m_InternalImageMask2D->FillBuffer(1); - } + minIndex[i] = tmpMinIndex[i]; + maxIndex[i] = tmpMaxIndex[i]; } - break; - } - case MASKING_MODE_IMAGE: - { - if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() > 1) ) + statisticsResult->SetMinIndex(minIndex); + statisticsResult->SetMaxIndex(maxIndex); + + //convert m_binSize in m_nBins if necessary + unsigned int nBinsForHistogram; + if (m_UseBinSizeOverNBins) { - if ( timeStep >= m_ImageMask->GetTimeSteps() ) - { - // Use the last mask time step in case the current time step is bigger than the total - // number of mask time steps. - // It makes more sense setting this to the last mask time step than to 0. - // For instance if you have a mask with 2 time steps and an image with 5: - // If time step 0 is selected, the mask will use time step 0. - // If time step 1 is selected, the mask will use time step 1. - // If time step 2+ is selected, the mask will use time step 1. - // If you have a mask with only one time step instead, this will always default to 0. - timeStep = m_ImageMask->GetTimeSteps() - 1; - } - - ImageTimeSelector::Pointer maskedImageTimeSelector = ImageTimeSelector::New(); - maskedImageTimeSelector->SetInput( m_ImageMask ); - maskedImageTimeSelector->SetTimeNr( timeStep ); - maskedImageTimeSelector->UpdateLargestPossibleRegion(); - mitk::Image *timeSliceMaskedImage = maskedImageTimeSelector->GetOutput(); - - m_InternalImage = timeSliceImage; - CastToItkImage( timeSliceMaskedImage, m_InternalImageMask3D ); + nBinsForHistogram = std::max(static_cast(std::ceil(maxval - minval)) / m_binSizeForHistogramStatistics, 10.); // do not allow less than 10 bins } else { - throw std::runtime_error( "Error: image mask empty!" ); + nBinsForHistogram = m_nBinsForHistogramStatistics; } - break; - } - case MASKING_MODE_PLANARFIGURE: - { - m_InternalImageMask2D = nullptr; - if ( m_PlanarFigure.IsNull() ) - { - throw std::runtime_error( "Error: planar figure empty!" ); - } - if ( !m_PlanarFigure->IsClosed() ) - { - throw std::runtime_error( "Masking not possible for non-closed figures" ); - } + statisticsFilter->SetHistogramParameters(nBinsForHistogram, minval, maxval); - const BaseGeometry *imageGeometry = timeSliceImage->GetGeometry(); - if ( imageGeometry == nullptr ) + try { - throw std::runtime_error( "Image geometry invalid!" ); + statisticsFilter->Update(); } - - const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); - if ( planarFigurePlaneGeometry == nullptr ) + catch (const itk::ExceptionObject& e) { - throw std::runtime_error( "Planar-Figure not yet initialized!" ); + mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } - const PlaneGeometry *planarFigureGeometry = - dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); - if ( planarFigureGeometry == nullptr ) + // no mask, therefore just one label = the whole image + m_StatisticsByTimeStep[timeStep].resize(1); + statisticsResult->SetLabel(1); + statisticsResult->SetN(image->GetLargestPossibleRegion().GetNumberOfPixels()); + statisticsResult->SetMean(statisticsFilter->GetMean()); + statisticsResult->SetMin(statisticsFilter->GetMinimum()); + statisticsResult->SetMax(statisticsFilter->GetMaximum()); + statisticsResult->SetVariance(statisticsFilter->GetVariance()); + statisticsResult->SetStd(statisticsFilter->GetSigma()); + statisticsResult->SetSkewness(statisticsFilter->GetSkewness()); + statisticsResult->SetKurtosis(statisticsFilter->GetKurtosis()); + statisticsResult->SetRMS(std::sqrt(std::pow(statisticsFilter->GetMean(), 2.) + statisticsFilter->GetVariance())); // variance = sigma^2 + statisticsResult->SetMPP(statisticsFilter->GetMPP()); + + statisticsResult->SetEntropy(statisticsFilter->GetEntropy()); + statisticsResult->SetMedian(statisticsFilter->GetMedian()); + statisticsResult->SetUniformity(statisticsFilter->GetUniformity()); + statisticsResult->SetUPP(statisticsFilter->GetUPP()); + statisticsResult->SetHistogram(statisticsFilter->GetHistogram()); + + m_StatisticsByTimeStep[timeStep][0] = statisticsResult; + } + + + template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsMasked( + typename itk::Image< TPixel, VImageDimension >* image, + unsigned int timeStep) + { + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< MaskPixelType, VImageDimension > MaskType; + typedef typename MaskType::PixelType LabelPixelType; + typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, MaskType > ImageStatisticsFilterType; + typedef MaskUtilities< TPixel, VImageDimension > MaskUtilType; + typedef typename itk::MinMaxLabelImageFilterWithIndex MinMaxLabelFilterType; + typedef typename ImageType::PixelType InputImgPixelType; + + // workaround: if m_SecondaryMaskGenerator ist not null but m_MaskGenerator is! (this is the case if we request a 'ignore zuero valued pixels' + // mask in the gui but do not define a primary mask) + bool swapMasks = false; + if (m_SecondaryMask.IsNotNull() && m_InternalMask.IsNull()) { - throw std::runtime_error( "Non-planar planar figures not supported!" ); + m_InternalMask = m_SecondaryMask; + m_SecondaryMask = nullptr; + swapMasks = true; } - // Find principal direction of PlanarFigure in input image - unsigned int axis; - if ( !this->GetPrincipalAxis( imageGeometry, - planarFigureGeometry->GetNormal(), axis ) ) - { - throw std::runtime_error( "Non-aligned planar figures not supported!" ); + // maskImage has to have the same dimension as image + typename MaskType::Pointer maskImage = MaskType::New(); + try { + // try to access the pixel values directly (no copying or casting). Only works if mask pixels are of pixelType unsigned short + maskImage = ImageToItkImage< MaskPixelType, VImageDimension >(m_InternalMask); } - m_PlanarFigureAxis = axis; + catch (itk::ExceptionObject & e) - // Find slice number corresponding to PlanarFigure in input image - MaskImage3DType::IndexType index; - imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); - - unsigned int slice = index[axis]; - m_PlanarFigureSlice = slice; - - - // Extract slice with given position and direction from image - unsigned int dimension = timeSliceImage->GetDimension(); - - if (dimension != 2) { - ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); - imageExtractor->SetInput( timeSliceImage ); - imageExtractor->SetSliceDimension( axis ); - imageExtractor->SetSliceIndex( slice ); - imageExtractor->Update(); - m_InternalImage = imageExtractor->GetOutput(); + // if the pixel type of the mask is not short, then we have to make a copy of m_InternalMask (and cast the values) + CastToItkImage(m_InternalMask, maskImage); } - else + + // if we have a secondary mask (say a ignoreZeroPixelMask) we need to combine the masks (corresponds to AND) + if (m_SecondaryMask.IsNotNull()) { - m_InternalImage = timeSliceImage; + // dirty workaround for a bug when pf mask + any other mask is used in conjunction. We need a proper fix for this (Fabian Isensee is responsible and probably working on it!) + if (m_InternalMask->GetDimension() == 2 && (m_SecondaryMask->GetDimension() == 3 || m_SecondaryMask->GetDimension() == 4)) + { + mitk::Image::Pointer old_img = m_SecondaryMaskGenerator->GetReferenceImage(); + m_SecondaryMaskGenerator->SetInputImage(m_MaskGenerator->GetReferenceImage()); + m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); + m_SecondaryMaskGenerator->SetInputImage(old_img); + } + typename MaskType::Pointer secondaryMaskImage = MaskType::New(); + secondaryMaskImage = ImageToItkImage< MaskPixelType, VImageDimension >(m_SecondaryMask); + + // secondary mask should be a ignore zero value pixel mask derived from image. it has to be cropped to the mask region (which may be planar or simply smaller) + typename MaskUtilities::Pointer secondaryMaskMaskUtil = MaskUtilities::New(); + secondaryMaskMaskUtil->SetImage(secondaryMaskImage.GetPointer()); + secondaryMaskMaskUtil->SetMask(maskImage.GetPointer()); + typename MaskType::Pointer adaptedSecondaryMaskImage = secondaryMaskMaskUtil->ExtractMaskImageRegion(); + + typename itk::MaskImageFilter2::Pointer maskFilter = itk::MaskImageFilter2::New(); + maskFilter->SetInput1(maskImage); + maskFilter->SetInput2(adaptedSecondaryMaskImage); + maskFilter->SetMaskingValue(1); // all pixels of maskImage where secondaryMaskImage==1 will be kept, all the others are set to 0 + maskFilter->UpdateLargestPossibleRegion(); + maskImage = maskFilter->GetOutput(); } - // Compute mask from PlanarFigure - AccessFixedDimensionByItk_1( - m_InternalImage, - InternalCalculateMaskFromPlanarFigure, - 2, axis ); - } - } - - if(m_DoIgnorePixelValue) - { - if ( m_InternalImage->GetDimension() == 3 ) - { - AccessFixedDimensionByItk_1( - m_InternalImage, - InternalMaskIgnoredPixels, - 3, - m_InternalImageMask3D.GetPointer() ); - } - else if ( m_InternalImage->GetDimension() == 2 ) - { - AccessFixedDimensionByItk_1( - m_InternalImage, - InternalMaskIgnoredPixels, - 2, - m_InternalImageMask2D.GetPointer() ); - } - } - } - - - bool ImageStatisticsCalculator::GetPrincipalAxis( - const BaseGeometry *geometry, Vector3D vector, - unsigned int &axis ) - { - vector.Normalize(); - for ( unsigned int i = 0; i < 3; ++i ) - { - Vector3D axisVector = geometry->GetAxisVector( i ); - axisVector.Normalize(); - - if ( fabs( fabs( axisVector * vector ) - 1.0) < mitk::eps ) - { - axis = i; - return true; - } - } - - return false; - } - - - unsigned int ImageStatisticsCalculator::calcNumberOfBins(mitk::ScalarType min, mitk::ScalarType max) - { - return std::ceil( ( (max - min ) / m_HistogramBinSize) ); - } - + typename MaskUtilType::Pointer maskUtil = MaskUtilType::New(); + maskUtil->SetImage(image); + maskUtil->SetMask(maskImage.GetPointer()); - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( - const itk::Image< TPixel, VImageDimension > *image, - StatisticsContainer *statisticsContainer, - HistogramContainer* histogramContainer ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef typename ImageType::IndexType IndexType; + // if mask is smaller than image, extract the image region where the mask is + typename ImageType::Pointer adaptedImage = ImageType::New(); - typedef itk::Statistics::ScalarImageToHistogramGenerator< ImageType > - HistogramGeneratorType; + adaptedImage = maskUtil->ExtractMaskImageRegion(); // this also checks mask sanity - statisticsContainer->clear(); - histogramContainer->clear(); + // find min, max, minindex and maxindex + typename MinMaxLabelFilterType::Pointer minMaxFilter = MinMaxLabelFilterType::New(); + minMaxFilter->SetInput(adaptedImage); + minMaxFilter->SetLabelInput(maskImage); + minMaxFilter->UpdateLargestPossibleRegion(); - // Progress listening... - typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; - ITKCommandType::Pointer progressListener; - progressListener = ITKCommandType::New(); - progressListener->SetCallbackFunction( this, - &ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate ); + // set histogram parameters for each label individually (min/max may be different for each label) + typedef typename std::map MapType; + typedef typename std::pair PairType; + std::vector relevantLabels = minMaxFilter->GetRelevantLabels(); + MapType minVals; + MapType maxVals; + std::map nBins; - // Issue 100 artificial progress events since ScalarIMageToHistogramGenerator - // does not (yet?) support progress reporting - this->InvokeEvent( itk::StartEvent() ); - for ( unsigned int i = 0; i < 100; ++i ) - { - this->UnmaskedStatisticsProgressUpdate(); - } - - // Calculate statistics (separate filter) - typedef itk::ExtendedStatisticsImageFilter< ImageType > StatisticsFilterType; - typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); - statisticsFilter->SetInput( image ); - statisticsFilter->SetBinSize( 100 ); - statisticsFilter->SetCoordinateTolerance( 0.001 ); - statisticsFilter->SetDirectionTolerance( 0.001 ); - - unsigned long observerTag = statisticsFilter->AddObserver( itk::ProgressEvent(), progressListener ); - try - { - statisticsFilter->Update(); - } - catch (const itk::ExceptionObject& e) - { - mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } + for (LabelPixelType label:relevantLabels) + { + minVals.insert(PairType(label, minMaxFilter->GetMin(label))); + maxVals.insert(PairType(label, minMaxFilter->GetMax(label))); - statisticsFilter->RemoveObserver( observerTag ); - this->InvokeEvent( itk::EndEvent() ); - - // Calculate minimum and maximum - typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; - typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); - minMaxFilter->SetImage( image ); - unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); - minMaxFilter->Compute(); - minMaxFilter->RemoveObserver( observerTag2 ); - this->InvokeEvent( itk::EndEvent() ); - - Statistics statistics; - statistics.Reset(); - statistics.SetLabel(1); - statistics.SetN(image->GetBufferedRegion().GetNumberOfPixels()); - statistics.SetMin(statisticsFilter->GetMinimum()); - statistics.SetMax(statisticsFilter->GetMaximum()); - statistics.SetMean(statisticsFilter->GetMean()); - statistics.SetMedian(statisticsFilter->GetMedian()); - statistics.SetVariance(statisticsFilter->GetVariance()); - statistics.SetSkewness(statisticsFilter->GetSkewness()); - statistics.SetKurtosis(statisticsFilter->GetKurtosis()); - statistics.SetUniformity( statisticsFilter->GetUniformity()); - statistics.SetEntropy( statisticsFilter->GetEntropy()); - statistics.SetUPP( statisticsFilter->GetUPP()); - statistics.SetMPP( statisticsFilter->GetMPP()); - statistics.SetSigma(statisticsFilter->GetSigma()); - statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() + statistics.GetSigma() * statistics.GetSigma() )); - - statistics.GetMinIndex().set_size(image->GetImageDimension()); - statistics.GetMaxIndex().set_size(image->GetImageDimension()); - - vnl_vector maxIndex; - vnl_vector minIndex; - - maxIndex.set_size( VImageDimension ); - minIndex.set_size( VImageDimension ); - - typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); - typename MinMaxFilterType::IndexType tempMinIndex = minMaxFilter->GetIndexOfMinimum(); - - for (unsigned int i=0; i (std::ceil(minMaxFilter->GetMax(label) - minMaxFilter->GetMin(label))) / m_binSizeForHistogramStatistics, 10.); // do not allow less than 10 bins + } + else + { + nBinsForHistogram = m_nBinsForHistogramStatistics; + } - statistics.SetMaxIndex(maxIndex); - statistics.SetMinIndex(minIndex); + nBins.insert(typename std::pair(label, nBinsForHistogram)); + } - if( IsHotspotCalculated() && VImageDimension == 3 ) - { - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - typename MaskImageType::Pointer nullMask; - bool isHotspotDefined(false); - Statistics hotspotStatistics = this->CalculateHotspotStatistics(image, nullMask.GetPointer(), m_HotspotRadiusInMM, isHotspotDefined, 0 ); - if (isHotspotDefined) - { - statistics.SetHasHotspotStatistics(true); - statistics.GetHotspotStatistics() = hotspotStatistics; - } - else - { - statistics.SetHasHotspotStatistics(false); - } - - if(statistics.GetHotspotStatistics().HasHotspotStatistics() ) - { - MITK_DEBUG << "Hotspot statistics available"; - statistics.SetHotspotIndex(hotspotStatistics.GetHotspotIndex()); - } - else - { - MITK_ERROR << "No hotspot statistics available!"; - } - } + typename ImageStatisticsFilterType::Pointer imageStatisticsFilter = ImageStatisticsFilterType::New(); + imageStatisticsFilter->SetDirectionTolerance(0.001); + imageStatisticsFilter->SetCoordinateTolerance(0.001); + imageStatisticsFilter->SetInput(adaptedImage); + imageStatisticsFilter->SetLabelInput(maskImage); + imageStatisticsFilter->SetHistogramParametersForLabels(nBins, minVals, maxVals); + imageStatisticsFilter->Update(); - statisticsContainer->push_back( statistics ); + std::list labels = imageStatisticsFilter->GetRelevantLabels(); + std::list::iterator it = labels.begin(); + m_StatisticsByTimeStep[timeStep].resize(0); - // Calculate histogram - // calculate bin size or number of bins - unsigned int numberOfBins = 200; // default number of bins - if (m_UseDefaultBinSize) - { - m_HistogramBinSize = std::ceil( (statistics.GetMax() - statistics.GetMin() + 1)/numberOfBins ); - } - else - { - numberOfBins = calcNumberOfBins(statistics.GetMin(), statistics.GetMax()); - } - typename HistogramGeneratorType::Pointer histogramGenerator = HistogramGeneratorType::New(); - histogramGenerator->SetInput( image ); - histogramGenerator->SetMarginalScale( 100 ); - histogramGenerator->SetNumberOfBins( numberOfBins ); - histogramGenerator->SetHistogramMin( statistics.GetMin() ); - histogramGenerator->SetHistogramMax( statistics.GetMax() ); - histogramGenerator->Compute(); - histogramContainer->push_back( histogramGenerator->GetOutput() ); - } - - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalMaskIgnoredPixels( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - - itk::ImageRegionIterator - itmask(maskImage, maskImage->GetLargestPossibleRegion()); - itk::ImageRegionConstIterator - itimage(image, image->GetLargestPossibleRegion()); - - itmask.GoToBegin(); - itimage.GoToBegin(); - - while( !itmask.IsAtEnd() ) - { - if(m_IgnorePixelValue == itimage.Get()) - { - itmask.Set(0); - } + while(it != labels.end()) + { + StatisticsContainer::Pointer statisticsResult = StatisticsContainer::New(); + + // find min, max, minindex and maxindex + // make sure to only look in the masked region, use a masker for this + + vnl_vector minIndex, maxIndex; + mitk::Point3D worldCoordinateMin; + mitk::Point3D worldCoordinateMax; + mitk::Point3D indexCoordinateMin; + mitk::Point3D indexCoordinateMax; + m_InternalImageForStatistics->GetGeometry()->IndexToWorld(minMaxFilter->GetMinIndex(*it), worldCoordinateMin); + m_InternalImageForStatistics->GetGeometry()->IndexToWorld(minMaxFilter->GetMaxIndex(*it), worldCoordinateMax); + m_Image->GetGeometry()->WorldToIndex(worldCoordinateMin, indexCoordinateMin); + m_Image->GetGeometry()->WorldToIndex(worldCoordinateMax, indexCoordinateMax); + + //typename ImageType::IndexType tmpMinIndex = minMaxFilter->GetMinIndex(*it); + //typename ImageType::IndexType tmpMaxIndex = minMaxFilter->GetMaxIndex(*it); + + //minIndex.set_size(tmpMaxIndex.GetIndexDimension()); + //maxIndex.set_size(tmpMaxIndex.GetIndexDimension()); + minIndex.set_size(3); + maxIndex.set_size(3); + + //for (unsigned int i=0; i < tmpMaxIndex.GetIndexDimension(); i++) + for (unsigned int i=0; i < 3; i++) + { + //minIndex[i] = tmpMinIndex[i] + (maskImage->GetOrigin()[i] - image->GetOrigin()[i]) / (double) maskImage->GetSpacing()[i]; + //maxIndex[i] = tmpMaxIndex[i] + (maskImage->GetOrigin()[i] - image->GetOrigin()[i]) / (double) maskImage->GetSpacing()[i]; + minIndex[i] = indexCoordinateMin[i]; + maxIndex[i] = indexCoordinateMax[i]; + } - ++itmask; - ++itimage; - } - } - - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalCalculateStatisticsMasked( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage, - StatisticsContainer* statisticsContainer, - HistogramContainer* histogramContainer ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - typedef typename ImageType::IndexType IndexType; - typedef typename ImageType::PointType PointType; - typedef typename ImageType::SpacingType SpacingType; - typedef typename ImageType::Pointer ImagePointer; - typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, MaskImageType > LabelStatisticsFilterType; - typedef itk::ChangeInformationImageFilter< MaskImageType > ChangeInformationFilterType; - typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; - - statisticsContainer->clear(); - histogramContainer->clear(); - - // Make sure that mask is set - if ( maskImage == nullptr ) - { - itkExceptionMacro( << "Mask image needs to be set!" ); - } + statisticsResult->SetMinIndex(minIndex); + statisticsResult->SetMaxIndex(maxIndex); + + // just debug + TPixel min_Filter = minMaxFilter->GetMin(*it); + TPixel max_Filter = minMaxFilter->GetMax(*it); + TPixel min_Itk = imageStatisticsFilter->GetMinimum(*it); + TPixel max_Itk = imageStatisticsFilter->GetMaximum(*it); + + assert(abs(minMaxFilter->GetMax(*it) - imageStatisticsFilter->GetMaximum(*it)) < mitk::eps); + assert(abs(minMaxFilter->GetMin(*it) - imageStatisticsFilter->GetMinimum(*it)) < mitk::eps); + + + statisticsResult->SetN(imageStatisticsFilter->GetSum(*it) / (double) imageStatisticsFilter->GetMean(*it)); + statisticsResult->SetMean(imageStatisticsFilter->GetMean(*it)); + statisticsResult->SetMin(imageStatisticsFilter->GetMinimum(*it)); + statisticsResult->SetMax(imageStatisticsFilter->GetMaximum(*it)); + statisticsResult->SetVariance(imageStatisticsFilter->GetVariance(*it)); + statisticsResult->SetStd(imageStatisticsFilter->GetSigma(*it)); + statisticsResult->SetSkewness(imageStatisticsFilter->GetSkewness(*it)); + statisticsResult->SetKurtosis(imageStatisticsFilter->GetKurtosis(*it)); + statisticsResult->SetRMS(std::sqrt(std::pow(imageStatisticsFilter->GetMean(*it), 2.) + imageStatisticsFilter->GetVariance(*it))); // variance = sigma^2 + statisticsResult->SetMPP(imageStatisticsFilter->GetMPP(*it)); + statisticsResult->SetLabel(*it); + + statisticsResult->SetEntropy(imageStatisticsFilter->GetEntropy(*it)); + statisticsResult->SetMedian(imageStatisticsFilter->GetMedian(*it)); + statisticsResult->SetUniformity(imageStatisticsFilter->GetUniformity(*it)); + statisticsResult->SetUPP(imageStatisticsFilter->GetUPP(*it)); + statisticsResult->SetHistogram(imageStatisticsFilter->GetHistogram(*it)); + + m_StatisticsByTimeStep[timeStep].push_back(statisticsResult); + ++it; + } - // Make sure that spacing of mask and image are the same - //SpacingType imageSpacing = image->GetSpacing(); - //SpacingType maskSpacing = maskImage->GetSpacing(); - //PointType zeroPoint; zeroPoint.Fill( 0.0 ); - //if ( (zeroPoint + imageSpacing).SquaredEuclideanDistanceTo( (zeroPoint + maskSpacing) ) > mitk::eps ) - //{ - // itkExceptionMacro( << "Mask needs to have same spacing as image! (Image spacing: " << imageSpacing << "; Mask spacing: " << maskSpacing << ")" ); - //} - // Make sure that orientation of mask and image are the same - typedef typename ImageType::DirectionType DirectionType; - DirectionType imageDirection = image->GetDirection(); - DirectionType maskDirection = maskImage->GetDirection(); - for( int i = 0; i < imageDirection.ColumnDimensions; ++i ) - { - for( int j = 0; j < imageDirection.ColumnDimensions; ++j ) - { - double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; - if ( fabs( differenceDirection ) > mitk::eps ) + // swap maskGenerators back + if (swapMasks) { - double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; - if ( fabs( differenceDirection ) > 0.001 /*mitk::eps*/ ) // TODO: temp fix (bug 17121) - { - itkExceptionMacro( << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")" ); - } + m_SecondaryMask = m_InternalMask; + m_InternalMask = nullptr; } - } - } - // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images - PointType imageOrigin = image->GetOrigin(); - PointType maskOrigin = maskImage->GetOrigin(); - long offset[ImageType::ImageDimension]; - - typedef itk::ContinuousIndex ContinousIndexType; - ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; - - image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); - image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); - - for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) - { - double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); - if ( fabs( misalignment ) > mitk::eps ) - { - itkWarningMacro( << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << misalignment << ")" ); - } - - double indexCoordDistance = maskOriginContinousIndex[i] - imageOriginContinousIndex[i]; - offset[i] = int( indexCoordDistance + image->GetBufferedRegion().GetIndex()[i] + 0.5 ); - } - - // Adapt the origin and region (index/size) of the mask so that the origin of both are the same - typename ChangeInformationFilterType::Pointer adaptMaskFilter; - adaptMaskFilter = ChangeInformationFilterType::New(); - adaptMaskFilter->ChangeOriginOn(); - adaptMaskFilter->ChangeRegionOn(); - adaptMaskFilter->SetInput( maskImage ); - adaptMaskFilter->SetOutputOrigin( image->GetOrigin() ); - adaptMaskFilter->SetOutputOffset( offset ); - adaptMaskFilter->SetCoordinateTolerance( 0.001 ); - adaptMaskFilter->SetDirectionTolerance( 0.001 ); - - - typename MaskImageType::Pointer adaptedMaskImage; - try - { - adaptMaskFilter->Update(); - adaptedMaskImage = adaptMaskFilter->GetOutput(); - } - catch( const itk::ExceptionObject &e) - { - mitkThrow() << "Attempt to adapt shifted origin of the mask image failed due to ITK Exception: \n" << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - - - // Make sure that mask region is contained within image region - if ( adaptedMaskImage.IsNotNull() && - !image->GetLargestPossibleRegion().IsInside( adaptedMaskImage->GetLargestPossibleRegion() ) ) - { - itkWarningMacro( << "Mask region needs to be inside of image region! (Image region: " - << image->GetLargestPossibleRegion() << "; Mask region: " << adaptedMaskImage->GetLargestPossibleRegion() << ")" ); - } - - - // If mask region is smaller than image region, extract the sub-sampled region from the original image - typename ImageType::SizeType imageSize = image->GetBufferedRegion().GetSize(); - typename ImageType::SizeType maskSize = maskImage->GetBufferedRegion().GetSize(); - bool maskSmallerImage = false; - for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) - { - if ( maskSize[i] < imageSize[i] ) - { - maskSmallerImage = true; - } - } - - typename ImageType::ConstPointer adaptedImage; - if ( maskSmallerImage ) - { - typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); - extractImageFilter->SetInput( image ); - extractImageFilter->SetExtractionRegion( adaptedMaskImage->GetBufferedRegion() ); - extractImageFilter->SetCoordinateTolerance( 0.001 ); - extractImageFilter->SetDirectionTolerance( 0.001 ); - extractImageFilter->Update(); - adaptedImage = extractImageFilter->GetOutput(); } - else - { - adaptedImage = image; - } - - // Initialize Filter - typedef itk::StatisticsImageFilter< ImageType > StatisticsFilterType; - typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); - statisticsFilter->SetInput( adaptedImage ); - try + bool ImageStatisticsCalculator::IsUpdateRequired(unsigned int timeStep) const { - statisticsFilter->Update(); - } - catch( const itk::ExceptionObject& e) - { - mitkThrow() << "Image statistics initialization computation failed with ITK Exception: \n " << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - - // Calculate bin size or number of bins - unsigned int numberOfBins = 200; // default number of bins - double maximum = 0.0; - double minimum = 0.0; + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long inputImageTimeStamp = m_Image->GetMTime(); + unsigned long statisticsTimeStamp = m_StatisticsUpdateTimePerTimeStep[timeStep]; - if (m_UseBinSizeBasedOnVOIRegion) - { - maximum = statisticsFilter->GetMaximum(); - minimum = statisticsFilter->GetMinimum(); - - if (m_UseDefaultBinSize) - { - m_HistogramBinSize = std::ceil( static_cast((statisticsFilter->GetMaximum() - statisticsFilter->GetMinimum() + 1)/numberOfBins) ); - } - else - { - numberOfBins = calcNumberOfBins(statisticsFilter->GetMinimum(), statisticsFilter->GetMaximum()); - } - } - else - { - double sig = 0.0; - int counter = 0; - - //Find the min and max values for the Roi to set the range for the histogram - GetMinAndMaxValue( minimum, maximum, counter, sig, image, maskImage); - numberOfBins = maximum - minimum; - if(maximum - minimum <= 10) - { - numberOfBins = 100; - } - } - - - typename LabelStatisticsFilterType::Pointer labelStatisticsFilter = LabelStatisticsFilterType::New(); - labelStatisticsFilter->SetInput( adaptedImage ); - labelStatisticsFilter->SetLabelInput( adaptedMaskImage ); - labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); - labelStatisticsFilter->SetDirectionTolerance( 0.001 ); - labelStatisticsFilter->UseHistogramsOn(); - labelStatisticsFilter->SetHistogramParameters( numberOfBins, floor(minimum), ceil(maximum) ); //statisticsFilter->GetMinimum() statisticsFilter->GetMaximum() - - // Add progress listening - typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; - ITKCommandType::Pointer progressListener; - progressListener = ITKCommandType::New(); - progressListener->SetCallbackFunction( this, - &ImageStatisticsCalculator::MaskedStatisticsProgressUpdate ); - unsigned long observerTag = labelStatisticsFilter->AddObserver( - itk::ProgressEvent(), progressListener ); - - // Execute filter - this->InvokeEvent( itk::StartEvent() ); - - // Make sure that only the mask region is considered (otherwise, if the mask region is smaller - // than the image region, the Update() would result in an exception). - labelStatisticsFilter->GetOutput()->SetRequestedRegion( adaptedMaskImage->GetLargestPossibleRegion() ); - - // Execute the filter - try - { - labelStatisticsFilter->Update(); - } - catch( const itk::ExceptionObject& e) - { - mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - catch( const std::exception& e ) - { - //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); - } - - this->InvokeEvent( itk::EndEvent() ); - - if( observerTag ) - labelStatisticsFilter->RemoveObserver( observerTag ); - - // Find all relevant labels of mask (other than 0) - std::list< int > relevantLabels = labelStatisticsFilter->GetRelevantLabels(); - unsigned int i; - - if ( labelStatisticsFilter->GetMaskingNonEmpty() ) - { - std::list< int >::iterator it; - for ( it = relevantLabels.begin(), i = 0; - it != relevantLabels.end(); - ++it, ++i ) - { - Statistics statistics; // restore previous code - labelStatisticsFilter->GetHistogram(*it) ; - histogramContainer->push_back( HistogramType::ConstPointer( labelStatisticsFilter->GetHistogram( (*it) ) ) ); - - statistics.SetLabel (*it); - statistics.SetN(labelStatisticsFilter->GetCount( *it )); - statistics.SetMin(labelStatisticsFilter->GetMinimum( *it )); - statistics.SetMax(labelStatisticsFilter->GetMaximum( *it )); - statistics.SetMean(labelStatisticsFilter->GetMean( *it )); - statistics.SetMedian(labelStatisticsFilter->GetMedian( *it)); - statistics.SetMedian(labelStatisticsFilter->GetMedian( *it )); - statistics.SetVariance(labelStatisticsFilter->GetVariance( *it )); - statistics.SetSigma(labelStatisticsFilter->GetSigma( *it )); - statistics.SetSkewness(labelStatisticsFilter->GetSkewness( *it )); - statistics.SetKurtosis(labelStatisticsFilter->GetKurtosis( *it )); - statistics.SetUniformity( labelStatisticsFilter->GetUniformity( *it )); - statistics.SetEntropy( labelStatisticsFilter->GetEntropy( *it )); - statistics.SetUPP( labelStatisticsFilter->GetUPP( *it)); - statistics.SetMPP( labelStatisticsFilter->GetMPP( *it)); - statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() - + statistics.GetSigma() * statistics.GetSigma() )); - - // restrict image to mask area for min/max index calculation - typedef itk::MaskImageFilter< ImageType, MaskImageType, ImageType > MaskImageFilterType; - typename MaskImageFilterType::Pointer masker = MaskImageFilterType::New(); - bool isMinAndMaxSameValue = (statistics.GetMin() == statistics.GetMax()); - // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here - double outsideValue = (isMinAndMaxSameValue ? (statistics.GetMax()/2) : (statistics.GetMin()+statistics.GetMax())/2); - masker->SetOutsideValue( outsideValue ); - masker->SetInput1(adaptedImage); - masker->SetInput2(adaptedMaskImage); - masker->SetCoordinateTolerance( 0.001 ); - masker->SetDirectionTolerance( 0.001 ); - masker->Update(); - // get index of minimum and maximum - typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; - typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); - minMaxFilter->SetImage( masker->GetOutput() ); - unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); - minMaxFilter->Compute(); - minMaxFilter->RemoveObserver( observerTag2 ); - this->InvokeEvent( itk::EndEvent() ); - - typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); - // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here - typename MinMaxFilterType::IndexType tempMinIndex = - (isMinAndMaxSameValue ? minMaxFilter->GetIndexOfMaximum() : minMaxFilter->GetIndexOfMinimum()); - - // FIX BUG 14644 - //If a PlanarFigure is used for segmentation the - //adaptedImage is a single slice (2D). Adding the - // 3. dimension. - - // FIX Bug 19625 pt. 1 - // m_Image will yield 4 coordinates if it has 4 Dimensions, the min max value is however only searched for in one timeSliceImage (3D) - // the 3D min/max coordinates are then set and the 4th coordinate is blank, causing random numbers to appear in the GUI - // (Fix = replace m_Image with VImageDimension (Dimension of image)) - - vnl_vector maxIndex; - vnl_vector minIndex; - unsigned int imageDimension = m_Image->GetDimension(); - unsigned int maxDimensionsToDisplay = 3; // we do not want to display the time step as part of the coordinates - maxIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); - minIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); - - if (m_MaskingMode == MASKING_MODE_PLANARFIGURE && imageDimension == 3) - { - maxIndex[m_PlanarFigureCoordinate0] = tempMaxIndex[0]; - maxIndex[m_PlanarFigureCoordinate1] = tempMaxIndex[1]; - maxIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; - - minIndex[m_PlanarFigureCoordinate0] = tempMinIndex[0] ; - minIndex[m_PlanarFigureCoordinate1] = tempMinIndex[1]; - minIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; - } else + if (thisClassTimeStamp > statisticsTimeStamp) // inputs have changed { - for (unsigned int i = 0; i statisticsTimeStamp) // image has changed { - bool isDefined(false); - Statistics hotspotStatistics = CalculateHotspotStatistics(adaptedImage.GetPointer(), adaptedMaskImage.GetPointer(),GetHotspotRadiusInMM(), isDefined, *it); - statistics.GetHotspotStatistics() = hotspotStatistics; - if(statistics.GetHotspotStatistics().HasHotspotStatistics()) - { - MITK_DEBUG << "Hotspot statistics available"; - statistics.SetHotspotIndex( hotspotStatistics.GetHotspotIndex() ); - } - else - { - MITK_ERROR << "No hotspot statistics available!"; - } + return true; } - statisticsContainer->push_back( statistics ); - } - } - else - { - histogramContainer->push_back( HistogramType::ConstPointer( m_EmptyHistogram ) ); - statisticsContainer->push_back( Statistics() ); - } - } - - - template - ImageStatisticsCalculator::ImageExtrema - ImageStatisticsCalculator::CalculateExtremaWorld( - const itk::Image *inputImage, - itk::Image *maskImage, - double neccessaryDistanceToImageBorderInMM, - unsigned int label) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - - typedef itk::ImageRegionConstIteratorWithIndex MaskImageIteratorType; - typedef itk::ImageRegionConstIteratorWithIndex InputImageIndexIteratorType; - - typename ImageType::SpacingType spacing = inputImage->GetSpacing(); - - ImageExtrema minMax; - minMax.Defined = false; - minMax.MaxIndex.set_size(VImageDimension); - minMax.MaxIndex.set_size(VImageDimension); - - typename ImageType::RegionType allowedExtremaRegion = inputImage->GetLargestPossibleRegion(); - bool keepDistanceToImageBorders( neccessaryDistanceToImageBorderInMM > 0 ); - if (keepDistanceToImageBorders) - { - long distanceInPixels[VImageDimension]; - for(unsigned short dimension = 0; dimension < VImageDimension; ++dimension) - { - // To confirm that the whole hotspot is inside the image we have to keep a specific distance to the image-borders, which is as long as - // the radius. To get the amount of indices we divide the radius by spacing and add 0.5 because voxels are center based: - // For example with a radius of 2.2 and a spacing of 1 two indices are enough because 2.2 / 1 + 0.5 = 2.7 => 2. - // But with a radius of 2.7 we need 3 indices because 2.7 / 1 + 0.5 = 3.2 => 3 - distanceInPixels[dimension] = int( neccessaryDistanceToImageBorderInMM / spacing[dimension] + 0.5); - } - - allowedExtremaRegion.ShrinkByRadius(distanceInPixels); - } - - InputImageIndexIteratorType imageIndexIt(inputImage, allowedExtremaRegion); - - float maxValue = itk::NumericTraits::min(); - float minValue = itk::NumericTraits::max(); - - typename ImageType::IndexType maxIndex; - typename ImageType::IndexType minIndex; - - for(unsigned short i = 0; i < VImageDimension; ++i) - { - maxIndex[i] = 0; - minIndex[i] = 0; - } - - if (maskImage != nullptr) - { - MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); - typename ImageType::IndexType imageIndex; - typename ImageType::PointType worldPosition; - typename ImageType::IndexType maskIndex; - - for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) - { - imageIndex = maskIndex = maskIt.GetIndex(); - - if(maskIt.Get() == label) + if (m_MaskGenerator.IsNotNull()) { - if( allowedExtremaRegion.IsInside(imageIndex) ) - { - imageIndexIt.SetIndex( imageIndex ); - double value = imageIndexIt.Get(); - minMax.Defined = true; - - //Calculate minimum, maximum and corresponding index-values - if( value > maxValue ) + unsigned long maskGeneratorTimeStamp = m_MaskGenerator->GetMTime(); + if (maskGeneratorTimeStamp > statisticsTimeStamp) // there is a mask generator and it has changed { - maxIndex = imageIndexIt.GetIndex(); - maxValue = value; + return true; } + } - if(value < minValue ) + if (m_SecondaryMaskGenerator.IsNotNull()) + { + unsigned long maskGeneratorTimeStamp = m_SecondaryMaskGenerator->GetMTime(); + if (maskGeneratorTimeStamp > statisticsTimeStamp) // there is a secondary mask generator and it has changed { - minIndex = imageIndexIt.GetIndex(); - minValue = value; + return true; } - } } - } + + return false; } - else - { - for(imageIndexIt.GoToBegin(); !imageIndexIt.IsAtEnd(); ++imageIndexIt) - { - double value = imageIndexIt.Get(); - minMax.Defined = true; - //Calculate minimum, maximum and corresponding index-values - if( value > maxValue ) + + ImageStatisticsCalculator::StatisticsContainer::StatisticsContainer(): + m_N(nan("")), + m_Mean(nan("")), + m_Min(nan("")), + m_Max(nan("")), + m_Std(nan("")), + m_Variance(nan("")), + m_Skewness(nan("")), + m_Kurtosis(nan("")), + m_RMS(nan("")), + m_MPP(nan("")), + m_Median(nan("")), + m_Uniformity(nan("")), + m_UPP(nan("")), + m_Entropy(nan("")) + { + m_minIndex.set_size(0); + m_maxIndex.set_size(0); + } + + ImageStatisticsCalculator::statisticsMapType ImageStatisticsCalculator::StatisticsContainer::GetStatisticsAsMap() + { + ImageStatisticsCalculator::statisticsMapType statisticsAsMap; + + statisticsAsMap["N"] = m_N; + statisticsAsMap["Mean"] = m_Mean; + statisticsAsMap["Min"] = m_Min; + statisticsAsMap["Max"] = m_Max; + statisticsAsMap["StandardDeviation"] = m_Std; + statisticsAsMap["Variance"] = m_Variance; + statisticsAsMap["Skewness"] = m_Skewness; + statisticsAsMap["Kurtosis"] = m_Kurtosis; + statisticsAsMap["RMS"] = m_RMS; + statisticsAsMap["MPP"] = m_MPP; + statisticsAsMap["Median"] = m_Median; + statisticsAsMap["Uniformity"] = m_Uniformity; + statisticsAsMap["UPP"] = m_UPP; + statisticsAsMap["Entropy"] = m_Entropy; + statisticsAsMap["Label"] = m_Label; + + return statisticsAsMap; + } + + + void ImageStatisticsCalculator::StatisticsContainer::Reset() + { + m_N = nan(""); + m_Mean = nan(""); + m_Min = nan(""); + m_Max = nan(""); + m_Std = nan(""); + m_Variance = nan(""); + m_Skewness = nan(""); + m_Kurtosis = nan(""); + m_RMS = nan(""); + m_MPP = nan(""); + m_Median = nan(""); + m_Uniformity = nan(""); + m_UPP = nan(""); + m_Entropy = nan(""); + m_Histogram = HistogramType::New(); + m_minIndex.set_size(0); + m_maxIndex.set_size(0); + m_Label = 0; + } + + void ImageStatisticsCalculator::StatisticsContainer::Print() + { + ImageStatisticsCalculator::statisticsMapType statMap = this->GetStatisticsAsMap(); + // print all map key value pairs + // const auto& val:statMap + for (auto it = statMap.begin(); it != statMap.end(); ++it) { - maxIndex = imageIndexIt.GetIndex(); - maxValue = value; + std::cout << it->first << ": " << it->second << std::endl; } - if(value < minValue ) + // print the min and max index + std::cout << "Min Index:" << std::endl; + for (auto it = this->GetMinIndex().begin(); it != this->GetMinIndex().end(); ++it) { - minIndex = imageIndexIt.GetIndex(); - minValue = value; + std::cout << *it << " "; } - } - } - - minMax.MaxIndex.set_size(VImageDimension); - minMax.MinIndex.set_size(VImageDimension); - - for(unsigned int i = 0; i < minMax.MaxIndex.size(); ++i) - { - minMax.MaxIndex[i] = maxIndex[i]; - } - - for(unsigned int i = 0; i < minMax.MinIndex.size(); ++i) - { - minMax.MinIndex[i] = minIndex[i]; - } - - minMax.Max = maxValue; - minMax.Min = minValue; - - return minMax; - } + std::cout << std::endl; - template - itk::Size - ImageStatisticsCalculator - ::CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM) - { - typedef itk::Image< float, VImageDimension > KernelImageType; - typedef typename KernelImageType::SizeType SizeType; - SizeType maskSize; - - for(unsigned int i = 0; i < VImageDimension; ++i) - { - maskSize[i] = static_cast( 2 * radiusInMM / spacing[i]); - - // We always want an uneven size to have a clear center point in the convolution mask - if(maskSize[i] % 2 == 0 ) - { - ++maskSize[i]; - } - } - return maskSize; - } - - template - itk::SmartPointer< itk::Image > - ImageStatisticsCalculator - ::GenerateHotspotSearchConvolutionKernel(double mmPerPixel[VImageDimension], double radiusInMM) - { - std::stringstream ss; - for (unsigned int i = 0; i < VImageDimension; ++i) - { - ss << mmPerPixel[i]; - if (i < VImageDimension -1) - ss << ","; - } - MITK_DEBUG << "Update convolution kernel for spacing (" << ss.str() << ") and radius " << radiusInMM << "mm"; - - - double radiusInMMSquared = radiusInMM * radiusInMM; - typedef itk::Image< float, VImageDimension > KernelImageType; - typename KernelImageType::Pointer convolutionKernel = KernelImageType::New(); - - // Calculate size and allocate mask image - typedef typename KernelImageType::SizeType SizeType; - SizeType maskSize = this->CalculateConvolutionKernelSize(mmPerPixel, radiusInMM); - - Point3D convolutionMaskCenterIndex; convolutionMaskCenterIndex.Fill(0.0); - for(unsigned int i = 0; i < VImageDimension; ++i) - { - convolutionMaskCenterIndex[i] = 0.5 * (double)(maskSize[i]-1); - } - - typedef typename KernelImageType::IndexType IndexType; - IndexType maskIndex; - maskIndex.Fill(0); - - typedef typename KernelImageType::RegionType RegionType; - RegionType maskRegion; - maskRegion.SetSize(maskSize); - maskRegion.SetIndex(maskIndex); - - convolutionKernel->SetRegions(maskRegion); - convolutionKernel->SetSpacing(mmPerPixel); - convolutionKernel->Allocate(); - - // Fill mask image values by subsampling the image grid - typedef itk::ImageRegionIteratorWithIndex MaskIteratorType; - MaskIteratorType maskIt(convolutionKernel,maskRegion); - - int numberOfSubVoxelsPerDimension = 2; // per dimension! - int numberOfSubVoxels = ::pow( static_cast(numberOfSubVoxelsPerDimension), static_cast(VImageDimension) ); - double subVoxelSizeInPixels = 1.0 / (double)numberOfSubVoxelsPerDimension; - double valueOfOneSubVoxel = 1.0 / (double)numberOfSubVoxels; - double maskValue = 0.0; - Point3D subVoxelIndexPosition; - double distanceSquared = 0.0; - - typedef itk::ContinuousIndex ContinuousIndexType; - for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) - { - ContinuousIndexType indexPoint(maskIt.GetIndex()); - Point3D voxelPosition; - for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) - { - voxelPosition[dimension] = indexPoint[dimension]; - } - - maskValue = 0.0; - Vector3D subVoxelOffset; subVoxelOffset.Fill(0.0); - // iterate sub-voxels by iterating all possible offsets - for (subVoxelOffset[0] = -0.5 + subVoxelSizeInPixels / 2.0; - subVoxelOffset[0] < +0.5; - subVoxelOffset[0] += subVoxelSizeInPixels) - { - for (subVoxelOffset[1] = -0.5 + subVoxelSizeInPixels / 2.0; - subVoxelOffset[1] < +0.5; - subVoxelOffset[1] += subVoxelSizeInPixels) + // print the min and max index + std::cout << "Max Index:" << std::endl; + for (auto it = this->GetMaxIndex().begin(); it != this->GetMaxIndex().end(); ++it) { - for (subVoxelOffset[2] = -0.5 + subVoxelSizeInPixels / 2.0; - subVoxelOffset[2] < +0.5; - subVoxelOffset[2] += subVoxelSizeInPixels) - { - subVoxelIndexPosition = voxelPosition + subVoxelOffset; // this COULD be integrated into the for-loops if neccessary (add voxelPosition to initializer and end condition) - distanceSquared = - (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] * (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] - + (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] * (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] - + (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2] * (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2]; - - if (distanceSquared <= radiusInMMSquared) - { - maskValue += valueOfOneSubVoxel; - } - } + std::cout << *it << " "; } - } - maskIt.Set( maskValue ); - } - - return convolutionKernel; - } - - template - itk::SmartPointer > - ImageStatisticsCalculator::GenerateConvolutionImage( const itk::Image* inputImage ) - { - double mmPerPixel[VImageDimension]; - for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) - { - mmPerPixel[dimension] = inputImage->GetSpacing()[dimension]; - } - - // update convolution kernel - typedef itk::Image< float, VImageDimension > KernelImageType; - typename KernelImageType::Pointer convolutionKernel = this->GenerateHotspotSearchConvolutionKernel(mmPerPixel, m_HotspotRadiusInMM); - - // update convolution image - typedef itk::Image< TPixel, VImageDimension > InputImageType; - typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; - typedef itk::FFTConvolutionImageFilter ConvolutionFilterType; - - typename ConvolutionFilterType::Pointer convolutionFilter = ConvolutionFilterType::New(); - typedef itk::ConstantBoundaryCondition BoundaryConditionType; - BoundaryConditionType boundaryCondition; - boundaryCondition.SetConstant(0.0); - - if (GetHotspotMustBeCompletlyInsideImage()) - { - // overwrite default boundary condition - convolutionFilter->SetBoundaryCondition(&boundaryCondition); - } - - convolutionFilter->SetInput(inputImage); - convolutionFilter->SetKernelImage(convolutionKernel); - convolutionFilter->SetNormalize(true); - MITK_DEBUG << "Update Convolution image for hotspot search"; - convolutionFilter->UpdateLargestPossibleRegion(); - - typename ConvolutionImageType::Pointer convolutionImage = convolutionFilter->GetOutput(); - convolutionImage->SetSpacing( inputImage->GetSpacing() ); // only workaround because convolution filter seems to ignore spacing of input image - - m_HotspotRadiusInMMChanged = false; - return convolutionImage; - } - - template < typename TPixel, unsigned int VImageDimension> - void - ImageStatisticsCalculator - ::FillHotspotMaskPixels( itk::Image* maskImage, - itk::Point sphereCenter, - double sphereRadiusInMM) - { - typedef itk::Image< TPixel, VImageDimension > MaskImageType; - typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; - - MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); - - typename MaskImageType::IndexType maskIndex; - typename MaskImageType::PointType worldPosition; - - for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) - { - maskIndex = maskIt.GetIndex(); - maskImage->TransformIndexToPhysicalPoint(maskIndex, worldPosition); - maskIt.Set( worldPosition.EuclideanDistanceTo(sphereCenter) <= sphereRadiusInMM ? 1 : 0 ); - } - } - - template < typename TPixel, unsigned int VImageDimension> - ImageStatisticsCalculator::Statistics - ImageStatisticsCalculator::CalculateHotspotStatistics( - const itk::Image* inputImage, - itk::Image* maskImage, - double radiusInMM, - bool& isHotspotDefined, - unsigned int label) - { - // get convolution image (updated in GenerateConvolutionImage()) - typedef itk::Image< TPixel, VImageDimension > InputImageType; - typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; - typedef itk::Image< float, VImageDimension > KernelImageType; - typedef itk::Image< unsigned short, VImageDimension > MaskImageType; - - //typename ConvolutionImageType::Pointer convolutionImage = dynamic_cast(this->GenerateConvolutionImage(inputImage)); - typename ConvolutionImageType::Pointer convolutionImage = this->GenerateConvolutionImage(inputImage); - - if (convolutionImage.IsNull()) - { - MITK_ERROR << "Empty convolution image in CalculateHotspotStatistics(). We should never reach this state (logic error)."; - throw std::logic_error("Empty convolution image in CalculateHotspotStatistics()"); - } - - // find maximum in convolution image, given the current mask - double requiredDistanceToBorder = m_HotspotMustBeCompletelyInsideImage ? m_HotspotRadiusInMM : -1.0; - ImageExtrema convolutionImageInformation = CalculateExtremaWorld(convolutionImage.GetPointer(), maskImage, requiredDistanceToBorder, label); - - isHotspotDefined = convolutionImageInformation.Defined; - - if (!isHotspotDefined) - { - m_EmptyStatistics.Reset(VImageDimension); - MITK_ERROR << "No origin of hotspot-sphere was calculated! Returning empty statistics"; - return m_EmptyStatistics; - } - else - { - // create a binary mask around the "hotspot" region, fill the shape of a sphere around our hotspot center - typedef itk::ImageDuplicator< InputImageType > DuplicatorType; - typename DuplicatorType::Pointer copyMachine = DuplicatorType::New(); - copyMachine->SetInputImage(inputImage); - copyMachine->Update(); - - typedef itk::CastImageFilter< InputImageType, MaskImageType > CastFilterType; - typename CastFilterType::Pointer caster = CastFilterType::New(); - caster->SetInput( copyMachine->GetOutput() ); - caster->Update(); - typename MaskImageType::Pointer hotspotMaskITK = caster->GetOutput(); - - typedef typename InputImageType::IndexType IndexType; - IndexType maskCenterIndex; - for (unsigned int d =0; d< VImageDimension;++d) maskCenterIndex[d]=convolutionImageInformation.MaxIndex[d]; - typename ConvolutionImageType::PointType maskCenter; - inputImage->TransformIndexToPhysicalPoint(maskCenterIndex,maskCenter); - - this->FillHotspotMaskPixels(hotspotMaskITK.GetPointer(), maskCenter, radiusInMM); - - // calculate statistics within the binary mask - typedef itk::ExtendedLabelStatisticsImageFilter< InputImageType, MaskImageType> LabelStatisticsFilterType; - typename LabelStatisticsFilterType::Pointer labelStatisticsFilter; - labelStatisticsFilter = LabelStatisticsFilterType::New(); - labelStatisticsFilter->SetInput( inputImage ); - labelStatisticsFilter->SetLabelInput( hotspotMaskITK ); - labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); - labelStatisticsFilter->SetDirectionTolerance( 0.001 ); - - labelStatisticsFilter->Update(); - - Statistics hotspotStatistics; - hotspotStatistics.SetHotspotIndex(convolutionImageInformation.MaxIndex); - hotspotStatistics.SetMean(convolutionImageInformation.Max); - - if ( labelStatisticsFilter->HasLabel( 1 ) ) - { - hotspotStatistics.SetLabel (1); - hotspotStatistics.SetN(labelStatisticsFilter->GetCount(1)); - hotspotStatistics.SetMin(labelStatisticsFilter->GetMinimum(1)); - hotspotStatistics.SetMax(labelStatisticsFilter->GetMaximum(1)); - hotspotStatistics.SetMedian(labelStatisticsFilter->GetMedian(1)); - hotspotStatistics.SetVariance(labelStatisticsFilter->GetVariance(1)); - hotspotStatistics.SetSigma(labelStatisticsFilter->GetSigma(1)); - hotspotStatistics.SetRMS(sqrt( hotspotStatistics.GetMean() * hotspotStatistics.GetMean() - + hotspotStatistics.GetSigma() * hotspotStatistics.GetSigma() )); - - MITK_DEBUG << "Statistics for inside hotspot: Mean " << hotspotStatistics.GetMean() - << ", SD " << hotspotStatistics.GetSigma() - << ", Max " << hotspotStatistics.GetMax() - << ", Min " << hotspotStatistics.GetMin(); - } - else - { - MITK_ERROR << "Uh oh! Unable to calculate statistics for hotspot region..."; - return m_EmptyStatistics; - } - - return hotspotStatistics; - } - } - - template < typename TPixel, unsigned int VImageDimension > - void ImageStatisticsCalculator::InternalCalculateMaskFromPlanarFigure( - const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) - { - typedef itk::Image< TPixel, VImageDimension > ImageType; - - typedef itk::CastImageFilter< ImageType, MaskImage2DType > CastFilterType; - - // Generate mask image as new image with same header as input image and - // initialize with 1. - typename CastFilterType::Pointer castFilter = CastFilterType::New(); - castFilter->SetInput( image ); - castFilter->Update(); - castFilter->GetOutput()->FillBuffer( 1 ); - - // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. - // These points are used by the vtkLassoStencilSource to create - // a vtkImageStencil. - const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); - const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); - const mitk::BaseGeometry *imageGeometry3D = m_Image->GetGeometry( 0 ); - // If there is a second poly line in a closed planar figure, treat it as a hole. - PlanarFigure::PolyLineType planarFigureHolePolyline; - - if (m_PlanarFigure->GetPolyLinesSize() == 2) - planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); - - - // Determine x- and y-dimensions depending on principal axis - int i0, i1; - switch ( axis ) - { - case 0: - i0 = 1; - i1 = 2; - break; - - case 1: - i0 = 0; - i1 = 2; - break; - - case 2: - default: - i0 = 0; - i1 = 1; - break; - } - m_PlanarFigureCoordinate0= i0; - m_PlanarFigureCoordinate1= i1; - - // store the polyline contour as vtkPoints object - bool outOfBounds = false; - vtkSmartPointer points = vtkSmartPointer::New(); - typename PlanarFigure::PolyLineType::const_iterator it; - for ( it = planarFigurePolyline.begin(); - it != planarFigurePolyline.end(); - ++it ) - { - Point3D point3D; - - // Convert 2D point back to the local index coordinates of the selected - // image - planarFigurePlaneGeometry->Map( *it, point3D ); - - // Polygons (partially) outside of the image bounds can not be processed - // further due to a bug in vtkPolyDataToImageStencil - if ( !imageGeometry3D->IsInside( point3D ) ) - { - outOfBounds = true; - } - - imageGeometry3D->WorldToIndex( point3D, point3D ); - - points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); + std::cout << std::endl; } - vtkSmartPointer holePoints = nullptr; - - if (!planarFigureHolePolyline.empty()) + std::string ImageStatisticsCalculator::StatisticsContainer::GetAsString() { - holePoints = vtkSmartPointer::New(); - - Point3D point3D; - PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); - - for (it = planarFigureHolePolyline.begin(); it != end; ++it) - { - planarFigurePlaneGeometry->Map(*it, point3D); - imageGeometry3D->WorldToIndex(point3D, point3D); - holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); - } - } - - // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds - // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero - double bounds[6] = {0, 0, 0, 0, 0, 0}; - points->GetBounds( bounds ); - bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; - bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; - bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; - - // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent - if ( m_PlanarFigure->IsClosed() && - ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) - { - mitkThrow() << "Figure has a zero area and cannot be used for masking."; - } - - if ( outOfBounds ) - { - throw std::runtime_error( "Figure at least partially outside of image bounds!" ); - } - - // create a vtkLassoStencilSource and set the points of the Polygon - vtkSmartPointer lassoStencil = vtkSmartPointer::New(); - lassoStencil->SetShapeToPolygon(); - lassoStencil->SetPoints( points ); - - vtkSmartPointer holeLassoStencil = nullptr; - - if (holePoints.GetPointer() != nullptr) - { - holeLassoStencil = vtkSmartPointer::New(); - holeLassoStencil->SetShapeToPolygon(); - holeLassoStencil->SetPoints(holePoints); - } - - // Export from ITK to VTK (to use a VTK filter) - typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; - typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; - - typename ImageExportType::Pointer itkExporter = ImageExportType::New(); - itkExporter->SetInput( castFilter->GetOutput() ); - - vtkSmartPointer vtkImporter = vtkSmartPointer::New(); - this->ConnectPipelines( itkExporter, vtkImporter ); - - // Apply the generated image stencil to the input image - vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); - imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); - imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); - imageStencilFilter->ReverseStencilOff(); - imageStencilFilter->SetBackgroundValue( 0 ); - imageStencilFilter->Update(); - - vtkSmartPointer holeStencilFilter = nullptr; + std::string res = ""; + ImageStatisticsCalculator::statisticsMapType statMap = this->GetStatisticsAsMap(); + // print all map key value pairs + // const auto& val:statMap + for (auto it = statMap.begin(); it != statMap.end(); ++it) + { + res += std::string(it->first) + ": " + std::to_string(it->second) + "\n"; + } - if (holeLassoStencil.GetPointer() != nullptr) - { - holeStencilFilter = vtkSmartPointer::New(); - holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); - holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); - holeStencilFilter->ReverseStencilOn(); - holeStencilFilter->SetBackgroundValue(0); - holeStencilFilter->Update(); - } + // print the min and max index + res += "Min Index:" + std::string("\n"); + for (auto it = this->GetMinIndex().begin(); it != this->GetMinIndex().end(); it++) + { + res += std::to_string(*it) + std::string(" "); + } + res += "\n"; - // Export from VTK back to ITK - vtkSmartPointer vtkExporter = vtkSmartPointer::New(); - vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr - ? imageStencilFilter->GetOutputPort() - : holeStencilFilter->GetOutputPort()); - vtkExporter->Update(); - - typename ImageImportType::Pointer itkImporter = ImageImportType::New(); - this->ConnectPipelines( vtkExporter, itkImporter ); - itkImporter->Update(); - - typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; - DuplicatorType::Pointer duplicator = DuplicatorType::New(); - duplicator->SetInputImage( itkImporter->GetOutput() ); - duplicator->Update(); - - // Store mask - m_InternalImageMask2D = duplicator->GetOutput(); - } - - - void ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate() - { - // Need to throw away every second progress event to reach a final count of - // 100 since two consecutive filters are used in this case - static int updateCounter = 0; - if ( updateCounter++ % 2 == 0 ) - { - this->InvokeEvent( itk::ProgressEvent() ); + // print the min and max index + res += "Max Index:" + std::string("\n"); + for (auto it = this->GetMaxIndex().begin(); it != this->GetMaxIndex().end(); it++) + { + res += std::to_string(*it) + " "; + } + res += "\n"; + return res; } - } - - void ImageStatisticsCalculator::MaskedStatisticsProgressUpdate() - { - this->InvokeEvent( itk::ProgressEvent() ); - } } diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.h b/Modules/ImageStatistics/mitkImageStatisticsCalculator.h index faaa2cf62c..d21f8a02b1 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.h +++ b/Modules/ImageStatistics/mitkImageStatisticsCalculator.h @@ -1,609 +1,414 @@ -/*=================================================================== -The Medical Imaging Interaction Toolkit (MITK) +#ifndef MITKIMAGESTATISTICSCALCULATOR +#define MITKIMAGESTATISTICSCALCULATOR -Copyright (c) German Cancer Research Center, -Division of Medical and Biological Informatics. -All rights reserved. - -This software is distributed WITHOUT ANY WARRANTY; without -even the implied warranty of MERCHANTABILITY or FITNESS FOR -A PARTICULAR PURPOSE. - -See LICENSE.txt or http://www.mitk.org for details. - -===================================================================*/ - -#ifndef _MITK_IMAGESTATISTICSCALCULATOR_H -#define _MITK_IMAGESTATISTICSCALCULATOR_H - -#include -#include "MitkImageStatisticsExports.h" +#include +#include +#include #include -#include -#include "mitkImage.h" -#include "mitkPlanarFigure.h" - -#ifndef __itkHistogram_h -#include -#endif - -#include - - -#include - -// just a helper to unclutter our code -// to be replaced with references to m_Member (when deprecated public members in Statistics are removed) -#define mitkSetGetConstMacro(name, type) \ - virtual type Get##name() const \ - { \ - return this->name; \ - } \ - \ - virtual void Set##name(const type _arg) \ - { \ - if ( this->name != _arg ) \ - { \ - this->name = _arg; \ - } \ - } +#include +#include +#include namespace mitk { - /** - * \brief Class for calculating statistics and histogram for an (optionally - * masked) image. - * - * Images can be masked by either a label image (of the same dimensions as - * the original image) or by a closed mitk::PlanarFigure, e.g. a circle or - * polygon. When masking with a planar figure, the slice corresponding to the - * plane containing the figure is extracted and then clipped with contour - * defined by the figure. Planar figures need to be aligned along the main axes - * of the image (axial, sagittal, coronal). Planar figures on arbitrary - * rotated planes are not supported. - * - * For each operating mode (no masking, masking by image, masking by planar - * figure), the calculated statistics and histogram are cached so that, when - * switching back and forth between operation modes without modifying mask or - * image, the information doesn't need to be recalculated. - * - * The class also has the possibility to calculate the location and separate - * statistics for a region called "hotspot". The hotspot is a sphere of - * user-defined size and its location is chosen in a way that the average - * pixel value within the sphere is maximized. - * - * \warning Hotspot calculation does not work in case of 2D-images! - * - * Note: currently time-resolved and multi-channel pictures are not properly - * supported. - * - * \section HotspotStatistics_caption Calculation of hotspot statistics - * - * Since calculation of hotspot location and statistics is not - * straight-forward, the following paragraphs will describe it in more detail. - * - * Note: Calculation of hotspot statistics is optional and set to off by default. - * Multilabel-masks are supported. - * - * \subsection HotspotStatistics_description Hotspot Definition - * - * The hotspot of an image is motivated from PET readings. It is defined - * as a spherical region of fixed size which maximizes the average pixel value - * within the region. The following image illustrates the concept: the - * colored areas are different image intensities and the hotspot is located - * in the hottest region of the image. - * - * Note: Only hotspots are calculated for which the whole hotspot-sphere is - * inside the image by default. This behaviour can be changed by - * by calling SetHotspotMustBeCompletlyInsideImage(). - * \warning Note that SetHotspotMustBeCompletlyInsideImage(false) may overrate - * "hot" regions at image borders, because they have a stronger influence on the - * mean value! Think clearly about this fact and make sure this is what you - * want/need in your application, before calling - * SetHotspotMustBeCompletlyInsideImage(false)! - * - * - * \image html hotspotexample.JPG - * - * \subsection HotspotStatistics_calculation Hotspot Calculation - * - * Since only the size of the hotspot is known initially, we need to calculate - * two aspects (both implemented in CalculateHotspotStatistics() ): - * - the hotspot location - * - statistics of the pixels within the hotspot. - * - * Finding the hotspot location requires to calculate the average value at each - * position. This is done by convolution of the image with a sperical kernel - * image which reflects partial volumes (important in the case of low-resolution - * PET images). - * - * Once the hotspot location is known, calculating the actual statistics is a - * simple task which is implemented in CalculateHotspotStatistics() using a second - * instance of the ImageStatisticsCalculator. - * - * Step 1: Finding the hotspot by image convolution - * - * As described above, we use image convolution with a rasterized sphere to - * average the image at each position. To handle coarse resolutions, which would - * normally force us to decide for partially contained voxels whether to count - * them or not, we supersample the kernel image and use non-integer kernel values - * (see GenerateHotspotSearchConvolutionKernel()), which reflect the volume part that is contained in the - * sphere. For example, if three subvoxels are inside the sphere, the corresponding - * kernel voxel gets a value of 0.75 (3 out of 4 subvoxels, see 2D example below). - * - * \image html convolutionkernelsupersampling.jpg - * - * Convolution itself is done by means of the itkFFTConvolutionImageFilter. - * To find the hotspot location, we simply iterate the averaged image and find a - * maximum location (see CalculateExtremaWorld()). In case of images with multiple - * maxima the method returns value and corresponding index of the extrema that is - * found by the iterator first. - * - * Step 2: Computation of hotspot statistics - * - * Once the hotspot location is found, statistics for the region are calculated - * by simply iterating the input image and regarding all pixel centers inside the - * hotspot-sphere for statistics. - * \warning Index positions of maximum/minimum are not provided, because they are not necessarily unique - * \todo If index positions of maximum/minimum are required, output needs to be changed to multiple positions / regions, etc. - * - * \subsection HotspotStatistics_tests Tests - * - * To check the correctness of the hotspot calculation, a special class - * (\ref hotspottestdoc) has been created, which generates images with - * known hotspot location and statistics. A number of unit tests use this class - * to first generate an image of known properites and then verify that - * ImageStatisticsCalculator is able to reproduce the known statistics. - * - */ - class MITKIMAGESTATISTICS_EXPORT ImageStatisticsCalculator : public itk::Object - { - public: - - /** \brief Enum for possible masking modi. */ - enum - { - MASKING_MODE_NONE = 0, - MASKING_MODE_IMAGE = 1, - MASKING_MODE_PLANARFIGURE = 2 - }; - - typedef itk::Statistics::Histogram HistogramType; - typedef HistogramType::ConstIterator HistogramConstIteratorType; - - /** \brief Class for common statistics, includig hotspot properties. */ - class MITKIMAGESTATISTICS_EXPORT Statistics + class MITKIMAGESTATISTICS_EXPORT ImageStatisticsCalculator: public itk::Object { public: + /** Standard Self typedef */ + typedef ImageStatisticsCalculator Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(ImageStatisticsCalculator_v2, itk::Object) + + typedef double statisticsValueType; + typedef std::map statisticsMapType; + typedef itk::Statistics::Histogram HistogramType; + typedef unsigned short MaskPixelType; + + /**Documentation + @brief Container class for storing the computed image statistics. + + Container class for storing the computed image statistics. Stored statistics are: + - N: number of voxels + - Mean + - MPP (Mean of positive pixels) + - Median + - Skewness + - Kurtosis + - Uniformity + - UPP (Uniformity of positive pixels) + - Variance + - Std (Standard Deviation) + - Min + - Max + - RMS (Root Mean Square) + - Label (if applicable, the label (unsigned short) of the mask the statistics belong to) + - Entropy + + It furthermore stores the following: + - MinIndex (Index of Image where the Minimum is located) + - MaxIndex (Index of Image where the Maximum is located) + - Histogram of Pixel Values*/ + class StatisticsContainer: public itk::Object + { + public: + /** Standard Self typedef */ + typedef StatisticsContainer Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(StatisticsContainer, itk::Object) + + typedef double RealType; + + /**Documentation + @brief Returns a std::map containing all real valued statistics stored in this class (= all statistics except minIndex, maxIndex and the histogram)*/ + statisticsMapType GetStatisticsAsMap(); + + /**Documentation + @brief Deletes all stored values*/ + void Reset(); + + void SetN(long n) + { + m_N = n; + } + + const long& GetN() const + { + return m_N; + } + + void SetMean(RealType mean) + { + m_Mean = mean; + } + + const RealType& GetMean() const + { + return m_Mean; + } + + void SetVariance(RealType variance) + { + m_Variance = variance; + } + + const RealType& GetVariance() const + { + return m_Variance; + } + + void SetStd(RealType std) + { + m_Std = std; + } + + const RealType& GetStd() const + { + return m_Std; + } + + void SetMin(RealType minVal) + { + m_Min = minVal; + } + + const RealType& GetMin() const + { + return m_Min; + } + + void SetMax(RealType maxVal) + { + m_Max = maxVal; + } + + const RealType& GetMax() const + { + return m_Max; + } + + void SetRMS(RealType rms) + { + m_RMS = rms; + } + + const RealType& GetRMS() const + { + return m_RMS; + } + + void SetSkewness(RealType skewness) + { + m_Skewness = skewness; + } + + const RealType& GetSkewness() const + { + return m_Skewness; + } + + void SetKurtosis(RealType kurtosis) + { + m_Kurtosis = kurtosis; + } + + const RealType& GetKurtosis() const + { + return m_Kurtosis; + } + + void SetMPP(RealType mpp) + { + m_MPP = mpp; + } + + const RealType& GetMPP() const + { + return m_MPP; + } + + void SetLabel(unsigned int label) + { + m_Label = label; + } + + const unsigned int& GetLabel() const + { + return m_Label; + } + + void SetMinIndex(vnl_vector minIndex) + { + m_minIndex = minIndex; + } + + vnl_vector GetMinIndex() const + { + return m_minIndex; + } + + void SetMaxIndex(vnl_vector maxIndex) + { + m_maxIndex = maxIndex; + } + + vnl_vector GetMaxIndex() const + { + return m_maxIndex; + } + + void SetHistogram(HistogramType::Pointer hist) + { + if (m_Histogram != hist) + { + m_Histogram = hist; + } + } + + const HistogramType::Pointer GetHistogram() const + { + return m_Histogram; + } + + void SetEntropy(RealType entropy) + { + m_Entropy = entropy; + } + + const RealType & GetEntropy() const + { + return m_Entropy; + } + + void SetMedian(RealType median) + { + m_Median = median; + } + + const RealType & GetMedian() const + { + return m_Median; + } + + void SetUniformity(RealType uniformity) + { + m_Uniformity = uniformity; + } + + const RealType & GetUniformity() const + { + return m_Uniformity; + } + + void SetUPP(RealType upp) + { + m_UPP = upp; + } + + const RealType & GetUPP() const + { + return m_UPP; + } + + /**Documentation + @brief Creates a StatisticsMapType containing all real valued statistics stored in this class (= all statistics except minIndex, maxIndex and the histogram) and prints its contents to std::cout*/ + void Print(); + + /**Documentation + @brief Generates a string that contains all real valued statistics stored in this class (= all statistics except minIndex, maxIndex and the histogram)*/ + std::string GetAsString(); + + + protected: + StatisticsContainer(); + + private: + itk::LightObject::Pointer InternalClone() const + { + itk::LightObject::Pointer ioPtr = Superclass::InternalClone(); + Self::Pointer rval = dynamic_cast(ioPtr.GetPointer()); + if (rval.IsNull()) + { + itkExceptionMacro(<< "downcast to type " + << "StatisticsContainer" + << " failed."); + } + + rval->SetEntropy(this->GetEntropy()); + rval->SetKurtosis(this->GetKurtosis()); + rval->SetLabel(this->GetLabel()); + rval->SetMax(this->GetMax()); + rval->SetMin(this->GetMin()); + rval->SetMean(this->GetMean()); + rval->SetMedian(this->GetMedian()); + rval->SetMPP(this->GetMPP()); + rval->SetN(this->GetN()); + rval->SetRMS(this->GetRMS()); + rval->SetSkewness(this->GetSkewness()); + rval->SetStd(this->GetStd()); + rval->SetUniformity(this->GetUniformity()); + rval->SetUPP(this->GetUPP()); + rval->SetVariance(this->GetVariance()); + rval->SetHistogram(this->GetHistogram()); + rval->SetMinIndex(this->GetMinIndex()); + rval->SetMaxIndex(this->GetMaxIndex()); + return ioPtr; + } + + // not pretty, is temporary + long m_N; + RealType m_Mean, m_Min, m_Max, m_Std, m_Variance; + RealType m_Skewness; + RealType m_Kurtosis; + RealType m_RMS; + RealType m_MPP; + vnl_vector m_minIndex, m_maxIndex; + RealType m_Median; + RealType m_Uniformity; + RealType m_UPP; + RealType m_Entropy; + unsigned int m_Label; + HistogramType::Pointer m_Histogram; + + }; + + /**Documentation + @brief Set the image for which the statistics are to be computed.*/ + void SetInputImage(mitk::Image::Pointer image); + + /**Documentation + @brief Set the mask generator that creates the mask which is to be used to calculate statistics. If no more mask is desired simply set @param mask to nullptr*/ + void SetMask(mitk::MaskGenerator::Pointer mask); + + /**Documentation + @brief Set this if more than one mask should be applied (for instance if a IgnorePixelValueMask were to be used alongside with a segmentation). + Both masks are combined using pixel wise AND operation. The secondary mask does not have to be the same size than the primary but they need to have some overlap*/ + void SetSecondaryMask(mitk::MaskGenerator::Pointer mask); + + /**Documentation + @brief Set number of bins to be used for histogram statistics. If Bin size is set after number of bins, bin size will be used instead!*/ + void SetNBinsForHistogramStatistics(unsigned int nBins); + + /**Documentation + @brief Retrieve the number of bins used for histogram statistics. Careful: The return value does not indicate whether NBins or BinSize is used. + That solely depends on which parameter has been set last.*/ + unsigned int GetNBinsForHistogramStatistics() const; + + /**Documentation + @brief Set bin size to be used for histogram statistics. If nbins is set after bin size, nbins will be used instead!*/ + void SetBinSizeForHistogramStatistics(double binSize); + + /**Documentation + @brief Retrieve the bin size for histogram statistics. Careful: The return value does not indicate whether NBins or BinSize is used. + That solely depends on which parameter has been set last.*/ + double GetBinSizeForHistogramStatistics() const; + + /**Documentation + @brief Returns the statistics for label @a label and timeStep @a timeStep. If these requested statistics are not computed yet the computation is done as well. + For performance reasons, statistics for all labels in the image are computed at once. + */ + StatisticsContainer::Pointer GetStatistics(unsigned int timeStep=0, unsigned int label=1); + + protected: + ImageStatisticsCalculator(){ + m_nBinsForHistogramStatistics = 100; + m_binSizeForHistogramStatistics = 10; + m_UseBinSizeOverNBins = false; + }; - Statistics(bool withHotspotStatistics = true); - Statistics(const Statistics& other); - - virtual ~Statistics(); - - Statistics& operator=(Statistics const& stats); - - const Statistics& GetHotspotStatistics() const; // real statistics - Statistics& GetHotspotStatistics(); // real statistics - bool HasHotspotStatistics() const; - void SetHasHotspotStatistics(bool hasHotspotStatistics); // set a flag. if set, return empty hotspotstatistics object - - void Reset(unsigned int dimension = 2); - - mitkSetGetConstMacro(Label, unsigned int) - mitkSetGetConstMacro(N, unsigned int) - mitkSetGetConstMacro(Min, double) - mitkSetGetConstMacro(Max, double) - mitkSetGetConstMacro(Mean, double) - mitkSetGetConstMacro(Median, double) - - double GetVariance() const; - /** \brief Set variance - * - * This method checks whether the variance is negative: - * The reason that the variance may be negative is that the underlying itk::LabelStatisticsImageFilter uses a naïve algorithm - * for calculating the variance ( http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance ) which can lead to negative values - * due to rounding errors. - * - * If the variance is negative the value will be set to 0.0, else the given value will be set. - */ - void SetVariance( const double ); - - double GetSigma() const; - /** \brief Set standard deviation (sigma) - * - * This method checks if the given standard deviation is a positive value. This is done because the underlying itk::LabelStatisticsImageFilter uses - * a naïve algorithm to calculate the variance. This may lead to a negative variance and because the square root of the variance is taken it also - * leads to NaN for sigma. - * - * If the given value is not reasonable the value will be set to 0.0, else the given value will be set. - * - * \see SetVariance() - */ - void SetSigma( const double ); - - mitkSetGetConstMacro(RMS, double) - mitkSetGetConstMacro(MinIndex, vnl_vector) - mitkSetGetConstMacro(MaxIndex, vnl_vector) - mitkSetGetConstMacro(HotspotIndex, vnl_vector) - - mitkSetGetConstMacro(Skewness, double) - mitkSetGetConstMacro(Kurtosis, double) - mitkSetGetConstMacro(Uniformity, double) - mitkSetGetConstMacro(Entropy, double) - mitkSetGetConstMacro(UPP, double); - mitkSetGetConstMacro(MPP, double); private: - unsigned int Label; - unsigned int N; - double Min; - double Max; - double Mean; - double Median; - double Variance; - double Sigma; - double RMS; - vnl_vector MinIndex; - vnl_vector MaxIndex; - - bool m_SkewnessAndKurtosisSuccesfulCalculated; - bool m_ImageStatisticsSuccesfulCalculated; - - - Statistics* m_HotspotStatistics; - - double Skewness; - double Kurtosis; - double Uniformity; - double Entropy; - double UPP; - double MPP; - - bool m_HasHotspotStatistics; - vnl_vector HotspotIndex; //< index of hotspotsphere origin - }; - - typedef std::vector< HistogramType::ConstPointer > HistogramContainer; - typedef std::vector< Statistics > StatisticsContainer; - - mitkClassMacroItkParent( ImageStatisticsCalculator, itk::Object ); - itkFactorylessNewMacro(Self) - itkCloneMacro(Self) - - /** \brief Automatically calculate bin size to obtain 200 bins. */ - void SetUseDefaultBinSize(bool useDefault); - - /** \brief Set image from which to compute statistics. */ - void SetImage( const mitk::Image *image ); - - /** \brief Set image for masking. */ - void SetImageMask( const mitk::Image *imageMask ); - - /** \brief Set planar figure for masking. */ - void SetPlanarFigure( mitk::PlanarFigure *planarFigure ); - - - /** \brief Set/Get operation mode for masking */ - void SetMaskingMode( unsigned int mode ); - - /** \brief Set/Get operation mode for masking */ - itkGetMacro( MaskingMode, unsigned int ); - - /** \brief Set/Get operation mode for masking */ - void SetMaskingModeToNone(); - - /** \brief Set/Get operation mode for masking */ - void SetMaskingModeToImage(); - - /** \brief Set/Get operation mode for masking */ - void SetMaskingModeToPlanarFigure(); - - /** \brief Set a pixel value for pixels that will be ignored in the statistics */ - void SetIgnorePixelValue(double value); - - /** \brief Get the pixel value for pixels that will be ignored in the statistics */ - double GetIgnorePixelValue(); - - /** \brief Set whether a pixel value should be ignored in the statistics */ - void SetDoIgnorePixelValue(bool doit); - - /** \brief Get whether a pixel value will be ignored in the statistics */ - bool GetDoIgnorePixelValue(); - - /** \brief Set bin size for histogram resolution.*/ - void SetHistogramBinSize( double size); - - /** \brief Get bin size for histogram resolution.*/ - double GetHistogramBinSize(); - - /** \brief Sets the radius for the hotspot */ - void SetHotspotRadiusInMM (double hotspotRadiusInMM); - - /** \brief Returns the radius of the hotspot */ - double GetHotspotRadiusInMM(); - - /** \brief Sets whether the hotspot should be calculated */ - void SetCalculateHotspot(bool calculateHotspot); - - /** \brief Returns true whether the hotspot should be calculated, otherwise false */ - bool IsHotspotCalculated(); - - /** \brief Sets flag whether hotspot is completly inside the image. Please note that if set to false - it can be possible that statistics are calculated for which the whole hotspot is not inside the image! - - \warning regarding positions at the image centers may produce unexpected hotspot locations, please see \ref HotspotStatistics_description - */ - void SetHotspotMustBeCompletlyInsideImage(bool hotspotIsCompletlyInsideImage, bool warn = true); - - /** \brief Returns true if hotspot has to be completly inside the image. */ - bool GetHotspotMustBeCompletlyInsideImage() const; - - /** \brief Compute statistics (together with histogram) for the current - * masking mode. - * - * Computation is not executed if statistics is already up to date. In this - * case, false is returned; otherwise, true.*/ - virtual bool ComputeStatistics( unsigned int timeStep = 0 ); - - - /** \brief Retrieve the histogram depending on the current masking mode. - * - * \param label The label for which to retrieve the histogram in multi-label situations (ascending order). - */ - const HistogramType *GetHistogram( unsigned int timeStep = 0, unsigned int label = 0 ) const; - - /** \brief Retrieve the histogram depending on the current masking mode (for all image labels. */ - const HistogramContainer &GetHistogramVector( unsigned int timeStep = 0 ) const; - - typedef std::map BinFrequencyType; - - /* Returning a map including bin and Frequency*/ - BinFrequencyType GetBinsAndFreuqencyForHistograms( unsigned int timeStep = 0, unsigned int label = 0) const; - - /** \brief Retrieve statistics depending on the current masking mode. - * - - * \param label The label for which to retrieve the statistics in multi-label situations (ascending order). - */ - const Statistics &GetStatistics( unsigned int timeStep = 0, unsigned int label = 0 ) const; + template < typename TPixel, unsigned int VImageDimension > void InternalCalculateStatisticsUnmasked( + typename itk::Image< TPixel, VImageDimension >* image, + unsigned int timeStep); - /** \brief Retrieve statistics depending on the current masking mode (for all image labels). */ - const StatisticsContainer &GetStatisticsVector( unsigned int timeStep = 0 ) const; + template < typename TPixel, unsigned int VImageDimension > typename HistogramType::Pointer InternalCalculateHistogramUnmasked( + typename itk::Image< TPixel, VImageDimension >* image, + double minVal, + double maxVal); + template < typename TPixel, unsigned int VImageDimension > void InternalCalculateStatisticsMasked( + typename itk::Image< TPixel, VImageDimension >* image, + unsigned int timeStep); + bool IsUpdateRequired(unsigned int timeStep) const; + std::string GetNameOfClass() + { + return std::string("ImageStatisticsCalculator_v2"); + } - protected: - typedef std::vector< HistogramContainer > HistogramVector; - typedef std::vector< StatisticsContainer > StatisticsVector; + mitk::Image::Pointer m_Image; + mitk::Image::Pointer m_ImageTimeSlice; + mitk::Image::Pointer m_InternalImageForStatistics; - typedef std::vector< itk::TimeStamp > TimeStampVectorType; - typedef std::vector< bool > BoolVectorType; + mitk::MaskGenerator::Pointer m_MaskGenerator; + mitk::Image::Pointer m_InternalMask; - typedef itk::Image< unsigned short, 3 > MaskImage3DType; - typedef itk::Image< unsigned short, 2 > MaskImage2DType; + mitk::MaskGenerator::Pointer m_SecondaryMaskGenerator; + mitk::Image::Pointer m_SecondaryMask; - ImageStatisticsCalculator(); + unsigned int m_nBinsForHistogramStatistics; + double m_binSizeForHistogramStatistics; + bool m_UseBinSizeOverNBins; - virtual ~ImageStatisticsCalculator(); - - /** \brief Depending on the masking mode, the image and mask from which to - * calculate statistics is extracted from the original input image and mask - * data. - * - * For example, a when using a PlanarFigure as mask, the 2D image slice - * corresponding to the PlanarFigure will be extracted from the original - * image. If masking is disabled, the original image is simply passed - * through. */ - void ExtractImageAndMask( unsigned int timeStep = 0 ); - - /*calculate the min and max value, this is done because we need the min and max value before execution the statistics filter to have the wright range for the histogramm*/ - template < typename TPixel, unsigned int VImageDimension > - void GetMinAndMaxValue(double &minimum, double &maximum, int &counter, double &sigma, const itk::Image< TPixel, VImageDimension > *InputImage, - itk::Image< unsigned short, VImageDimension > *MaskImageType); - - /** \brief If the passed vector matches any of the three principal axes - * of the passed geometry, the ínteger value corresponding to the axis - * is set and true is returned. */ - bool GetPrincipalAxis( const BaseGeometry *geometry, Vector3D vector, - unsigned int &axis ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalCalculateStatisticsUnmasked( - const itk::Image< TPixel, VImageDimension > *image, - StatisticsContainer* statisticsContainer, - HistogramContainer *histogramContainer ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalCalculateStatisticsMasked( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage, - StatisticsContainer* statisticsContainer, - HistogramContainer* histogramContainer ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalCalculateMaskFromPlanarFigure( - const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ); - - template < typename TPixel, unsigned int VImageDimension > - void InternalMaskIgnoredPixels( - const itk::Image< TPixel, VImageDimension > *image, - itk::Image< unsigned short, VImageDimension > *maskImage ); - - class ImageExtrema - { - public: - bool Defined; - double Max; - double Min; - vnl_vector MaxIndex; - vnl_vector MinIndex; - - ImageExtrema() - :Defined(false) - ,Max(itk::NumericTraits::min()) - ,Min(itk::NumericTraits::max()) - { - } + std::vector> m_StatisticsByTimeStep; + std::vector m_StatisticsUpdateTimePerTimeStep; }; +} +#endif // MITKIMAGESTATISTICSCALCULATOR - /** \brief Calculates minimum, maximum, mean value and their - * corresponding indices in a given ROI. As input the function - * needs an image and a mask. Returns an ImageExtrema object. */ - template - ImageExtrema CalculateExtremaWorld( - const itk::Image *inputImage, - itk::Image *maskImage, - double neccessaryDistanceToImageBorderInMM, - unsigned int label); - - - /** \brief Calculates the hotspot statistics depending on - * masking mode. Hotspot statistics are calculated for a - * hotspot which is completly located inside the image by default. */ - template < typename TPixel, unsigned int VImageDimension> - Statistics CalculateHotspotStatistics( - const itk::Image *inputImage, - itk::Image *maskImage, - double radiusInMM, - bool& isHotspotDefined, - unsigned int label); - - /** Connection from ITK to VTK */ - template - void ConnectPipelines(ITK_Exporter exporter, vtkSmartPointer importer) - { - importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); - - importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); - importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); - importer->SetSpacingCallback(exporter->GetSpacingCallback()); - importer->SetOriginCallback(exporter->GetOriginCallback()); - importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); - - importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); - - importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); - importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); - importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); - importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); - importer->SetCallbackUserData(exporter->GetCallbackUserData()); - } - - /** Connection from VTK to ITK */ - template - void ConnectPipelines(vtkSmartPointer exporter, ITK_Importer importer) - { - importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); - - importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); - importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); - importer->SetSpacingCallback(exporter->GetSpacingCallback()); - importer->SetOriginCallback(exporter->GetOriginCallback()); - importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); - - importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); - - importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); - importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); - importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); - importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); - importer->SetCallbackUserData(exporter->GetCallbackUserData()); - } - - - void UnmaskedStatisticsProgressUpdate(); - - void MaskedStatisticsProgressUpdate(); - - /** \brief Returns size of convolution kernel depending on spacing and radius. */ - template - itk::Size - CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM); - - /** \brief Generates image of kernel which is needed for convolution. */ - template - itk::SmartPointer< itk::Image > - GenerateHotspotSearchConvolutionKernel(double spacing[VImageDimension], double radiusInMM); - - /** \brief Convolves image with spherical kernel image. Used for hotspot calculation. */ - template - itk::SmartPointer< itk::Image > - GenerateConvolutionImage( const itk::Image* inputImage ); - - /** \brief Fills pixels of the spherical hotspot mask. */ - template < typename TPixel, unsigned int VImageDimension> - void - FillHotspotMaskPixels( itk::Image* maskImage, - itk::Point sphereCenter, - double sphereRadiusInMM); - - /** m_Image contains the input image (e.g. 2D, 3D, 3D+t)*/ - mitk::Image::ConstPointer m_Image; - - mitk::Image::ConstPointer m_ImageMask; - - mitk::PlanarFigure::Pointer m_PlanarFigure; - - HistogramVector m_ImageHistogramVector; - HistogramVector m_MaskedImageHistogramVector; - HistogramVector m_PlanarFigureHistogramVector; - - HistogramType::Pointer m_EmptyHistogram; - HistogramContainer m_EmptyHistogramContainer; - - - StatisticsVector m_ImageStatisticsVector; - StatisticsVector m_MaskedImageStatisticsVector; - StatisticsVector m_PlanarFigureStatisticsVector; - StatisticsVector m_MaskedImageHotspotStatisticsVector; - - Statistics m_EmptyStatistics; - StatisticsContainer m_EmptyStatisticsContainer; - - unsigned int m_MaskingMode; - bool m_MaskingModeChanged; - - /** m_InternalImage contains a image volume at one time step (e.g. 2D, 3D)*/ - mitk::Image::ConstPointer m_InternalImage; - MaskImage3DType::Pointer m_InternalImageMask3D; - MaskImage2DType::Pointer m_InternalImageMask2D; - - TimeStampVectorType m_ImageStatisticsTimeStampVector; - TimeStampVectorType m_MaskedImageStatisticsTimeStampVector; - TimeStampVectorType m_PlanarFigureStatisticsTimeStampVector; - - BoolVectorType m_ImageStatisticsCalculationTriggerVector; - BoolVectorType m_MaskedImageStatisticsCalculationTriggerVector; - BoolVectorType m_PlanarFigureStatisticsCalculationTriggerVector; - - double m_IgnorePixelValue; - bool m_DoIgnorePixelValue; - bool m_IgnorePixelValueChanged; - - unsigned int m_PlanarFigureAxis; // Normal axis for PlanarFigure - unsigned int m_PlanarFigureSlice; // Slice which contains PlanarFigure - int m_PlanarFigureCoordinate0; // First plane-axis for PlanarFigure - int m_PlanarFigureCoordinate1; // Second plane-axis for PlanarFigure - - double m_HistogramBinSize; ///Bin size for histogram resoluion. - bool m_UseDefaultBinSize; - bool m_UseBinSizeBasedOnVOIRegion; - double m_HotspotRadiusInMM; - bool m_CalculateHotspot; - bool m_HotspotRadiusInMMChanged; - bool m_HotspotMustBeCompletelyInsideImage; - - - private: - - unsigned int calcNumberOfBins(mitk::ScalarType min, mitk::ScalarType max); - - - }; - -} // namespace - -#endif diff --git a/Modules/ImageStatistics/mitkIntensityProfile.cpp b/Modules/ImageStatistics/mitkIntensityProfile.cpp index e8e5ca066d..795e1750c1 100644 --- a/Modules/ImageStatistics/mitkIntensityProfile.cpp +++ b/Modules/ImageStatistics/mitkIntensityProfile.cpp @@ -1,380 +1,380 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include #include #include #include "mitkIntensityProfile.h" using namespace mitk; template static void ReadPixel(const PixelType&, Image::Pointer image, const itk::Index<3>& index, ScalarType* returnValue) { switch (image->GetDimension()) { case 2: { ImagePixelReadAccessor readAccess(image, image->GetSliceData(0)); *returnValue = readAccess.GetPixelByIndex(reinterpret_cast&>(index)); break; } case 3: { ImagePixelReadAccessor readAccess(image, image->GetVolumeData(0)); *returnValue = readAccess.GetPixelByIndex(index); break; } default: *returnValue = 0; break; } } static IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, itk::PolyLineParametricPath<3>::Pointer path) { IntensityProfile::Pointer intensityProfile = IntensityProfile::New(); itk::PolyLineParametricPath<3>::InputType input = path->StartOfInput(); BaseGeometry* imageGeometry = image->GetGeometry(); const PixelType pixelType = image->GetPixelType(); IntensityProfile::MeasurementVectorType measurementVector; itk::PolyLineParametricPath<3>::OffsetType offset; Point3D worldPoint; itk::Index<3> index; do { imageGeometry->IndexToWorld(path->Evaluate(input), worldPoint); imageGeometry->WorldToIndex(worldPoint, index); mitkPixelTypeMultiplex3(ReadPixel, pixelType, image, index, measurementVector.GetDataPointer()); intensityProfile->PushBack(measurementVector); offset = path->IncrementInput(input); } while ((offset[0] | offset[1] | offset[2]) != 0); return intensityProfile; } template static typename itk::InterpolateImageFunction::Pointer CreateInterpolateImageFunction(InterpolateImageFunction::Enum interpolator) { switch (interpolator) { case InterpolateImageFunction::NearestNeighbor: return itk::NearestNeighborInterpolateImageFunction::New().GetPointer(); case InterpolateImageFunction::Linear: return itk::LinearInterpolateImageFunction::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Blackman_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Blackman_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Blackman_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Cosine_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Cosine_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Cosine_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Hamming_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Hamming_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Hamming_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Lanczos_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Lanczos_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Lanczos_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Welch_3: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Welch_4: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); case InterpolateImageFunction::WindowedSinc_Welch_5: return itk::WindowedSincInterpolateImageFunction >::New().GetPointer(); default: return itk::NearestNeighborInterpolateImageFunction::New().GetPointer(); } } template static void ComputeIntensityProfile(itk::Image* image, itk::PolyLineParametricPath<3>::Pointer path, unsigned int numSamples, InterpolateImageFunction::Enum interpolator, IntensityProfile::Pointer intensityProfile) { typename itk::InterpolateImageFunction >::Pointer interpolateImageFunction = CreateInterpolateImageFunction >(interpolator); interpolateImageFunction->SetInputImage(image); const itk::PolyLineParametricPath<3>::InputType startOfInput = path->StartOfInput(); const itk::PolyLineParametricPath<3>::InputType delta = 1.0 / (numSamples - 1); IntensityProfile::MeasurementVectorType measurementVector; for (unsigned int i = 0; i < numSamples; ++i) { measurementVector[0] = interpolateImageFunction->EvaluateAtContinuousIndex(path->Evaluate(startOfInput + i * delta)); intensityProfile->PushBack(measurementVector); } } static IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, itk::PolyLineParametricPath<3>::Pointer path, unsigned int numSamples, InterpolateImageFunction::Enum interpolator) { IntensityProfile::Pointer intensityProfile = IntensityProfile::New(); AccessFixedDimensionByItk_n(image, ComputeIntensityProfile, 3, (path, numSamples, interpolator, intensityProfile)); return intensityProfile; } class AddPolyLineElementToPath { public: AddPolyLineElementToPath(const PlaneGeometry* planarFigureGeometry, const BaseGeometry* imageGeometry, itk::PolyLineParametricPath<3>::Pointer path) : m_PlanarFigureGeometry(planarFigureGeometry), m_ImageGeometry(imageGeometry), m_Path(path) { } void operator()(const PlanarFigure::PolyLineElement& polyLineElement) { m_PlanarFigureGeometry->Map(polyLineElement, m_WorldPoint); m_ImageGeometry->WorldToIndex(m_WorldPoint, m_ContinuousIndexPoint); m_Vertex.CastFrom(m_ContinuousIndexPoint); m_Path->AddVertex(m_Vertex); } private: const PlaneGeometry* m_PlanarFigureGeometry; const BaseGeometry* m_ImageGeometry; itk::PolyLineParametricPath<3>::Pointer m_Path; Point3D m_WorldPoint; Point3D m_ContinuousIndexPoint; itk::PolyLineParametricPath<3>::ContinuousIndexType m_Vertex; }; static itk::PolyLineParametricPath<3>::Pointer CreatePathFromPlanarFigure(BaseGeometry* imageGeometry, PlanarFigure* planarFigure) { itk::PolyLineParametricPath<3>::Pointer path = itk::PolyLineParametricPath<3>::New(); const PlanarFigure::PolyLineType polyLine = planarFigure->GetPolyLine(0); std::for_each(polyLine.begin(), polyLine.end(), AddPolyLineElementToPath(planarFigure->GetPlaneGeometry(), imageGeometry, path)); return path; } static void AddPointToPath(const BaseGeometry* imageGeometry, const Point3D& point, itk::PolyLineParametricPath<3>::Pointer path) { Point3D continuousIndexPoint; imageGeometry->WorldToIndex(point, continuousIndexPoint); itk::PolyLineParametricPath<3>::ContinuousIndexType vertex; vertex.CastFrom(continuousIndexPoint); path->AddVertex(vertex); } static itk::PolyLineParametricPath<3>::Pointer CreatePathFromPoints(BaseGeometry* imageGeometry, const Point3D& startPoint, const Point3D& endPoint) { itk::PolyLineParametricPath<3>::Pointer path = itk::PolyLineParametricPath<3>::New(); AddPointToPath(imageGeometry, startPoint, path); AddPointToPath(imageGeometry, endPoint, path); return path; } IntensityProfile::Pointer mitk::ComputeIntensityProfile(Image::Pointer image, PlanarFigure::Pointer planarFigure) { return ::ComputeIntensityProfile(image, CreatePathFromPlanarFigure(image->GetGeometry(), planarFigure)); } IntensityProfile::Pointer mitk::ComputeIntensityProfile(Image::Pointer image, PlanarLine::Pointer planarLine, unsigned int numSamples, InterpolateImageFunction::Enum interpolator) { return ::ComputeIntensityProfile(image, CreatePathFromPlanarFigure(image->GetGeometry(), planarLine.GetPointer()), numSamples, interpolator); } IntensityProfile::Pointer mitk::ComputeIntensityProfile(Image::Pointer image, const Point3D& startPoint, const Point3D& endPoint, unsigned int numSamples, InterpolateImageFunction::Enum interpolator) { return ::ComputeIntensityProfile(image, CreatePathFromPoints(image->GetGeometry(), startPoint, endPoint), numSamples, interpolator); } IntensityProfile::InstanceIdentifier mitk::ComputeGlobalMaximum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &max) { max = -vcl_numeric_limits::min(); IntensityProfile::InstanceIdentifier maxIndex = 0; IntensityProfile::ConstIterator end = intensityProfile->End(); IntensityProfile::MeasurementType measurement; for (IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { measurement = it.GetMeasurementVector()[0]; if (measurement > max) { max = measurement; maxIndex = it.GetInstanceIdentifier(); } } return maxIndex; } IntensityProfile::InstanceIdentifier mitk::ComputeGlobalMinimum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &min) { min = vcl_numeric_limits::max(); IntensityProfile::InstanceIdentifier minIndex = 0; IntensityProfile::ConstIterator end = intensityProfile->End(); IntensityProfile::MeasurementType measurement; for (IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { measurement = it.GetMeasurementVector()[0]; if (measurement < min) { min = measurement; minIndex = it.GetInstanceIdentifier(); } } return minIndex; } IntensityProfile::InstanceIdentifier mitk::ComputeCenterOfMaximumArea(IntensityProfile::Pointer intensityProfile, IntensityProfile::InstanceIdentifier radius) { //const IntensityProfile::MeasurementType min = intensityProfile->GetMeasurementVector(ComputeGlobalMinimum(intensityProfile))[0]; IntensityProfile::MeasurementType min; ComputeGlobalMinimum(intensityProfile, min); const IntensityProfile::InstanceIdentifier areaWidth = 1 + 2 * radius; IntensityProfile::MeasurementType maxArea = 0; for (IntensityProfile::InstanceIdentifier i = 0; i < areaWidth; ++i) maxArea += intensityProfile->GetMeasurementVector(i)[0] - min; const IntensityProfile::InstanceIdentifier lastIndex = intensityProfile->Size() - areaWidth; IntensityProfile::InstanceIdentifier centerOfMaxArea = radius; IntensityProfile::MeasurementType area = maxArea; for (IntensityProfile::InstanceIdentifier i = 1; i <= lastIndex; ++i) { area += intensityProfile->GetMeasurementVector(i + areaWidth - 1)[0] - min; area -= intensityProfile->GetMeasurementVector(i - 1)[0] - min; if (area > maxArea) { maxArea = area; centerOfMaxArea = i + radius; // TODO: If multiple areas in the neighborhood have the same intensity chose the middle one instead of the first one. } } return centerOfMaxArea; } std::vector mitk::CreateVectorFromIntensityProfile(IntensityProfile::Pointer intensityProfile) { std::vector result; result.reserve(intensityProfile->Size()); IntensityProfile::ConstIterator end = intensityProfile->End(); for (IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) result.push_back(it.GetMeasurementVector()[0]); return result; } IntensityProfile::Pointer mitk::CreateIntensityProfileFromVector(const std::vector& vector) { const IntensityProfile::InstanceIdentifier size = vector.size(); IntensityProfile::Pointer result = IntensityProfile::New(); result->Resize(size); for (IntensityProfile::InstanceIdentifier i = 0; i < size; ++i) result->SetMeasurement(i, 0, vector[i]); return result; } -void mitk::ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::Statistics &stats) +void mitk::ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::StatisticsContainer::Pointer stats) { typedef std::vector StatsVecType; StatsVecType statsVec = mitk::CreateVectorFromIntensityProfile( intensityProfile ); IntensityProfile::MeasurementType min; IntensityProfile::MeasurementType max; mitk::ComputeGlobalMinimum( intensityProfile, min ); mitk::ComputeGlobalMaximum( intensityProfile, max ); StatsVecType::size_type numSamples = statsVec.size(); double mean = 0.0; double rms = 0.0; for ( StatsVecType::const_iterator it = statsVec.begin(); it != statsVec.end(); ++it ) { double val = *it; mean += val; rms += val*val; } mean /= numSamples; rms /= numSamples; double var = 0.0; for ( StatsVecType::const_iterator it = statsVec.begin(); it != statsVec.end(); ++it ) { double diff = *it - mean; var += diff*diff; } var /= ( numSamples - 1 ); rms = sqrt( rms ); - stats.SetMin( static_cast( min ) ); - stats.SetMax( static_cast( max ) ); - stats.SetN( numSamples ); - stats.SetMean( mean ); - stats.SetVariance( var ); - stats.SetRMS( rms ); + stats->SetMin( static_cast( min ) ); + stats->SetMax( static_cast( max ) ); + stats->SetN( numSamples ); + stats->SetMean( mean ); + stats->SetVariance( var ); + stats->SetRMS( rms ); } diff --git a/Modules/ImageStatistics/mitkIntensityProfile.h b/Modules/ImageStatistics/mitkIntensityProfile.h index c0ba4bb96d..04afd64c47 100644 --- a/Modules/ImageStatistics/mitkIntensityProfile.h +++ b/Modules/ImageStatistics/mitkIntensityProfile.h @@ -1,137 +1,137 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkIntensityProfile_h #define mitkIntensityProfile_h #include #include #include #include #include namespace mitk { typedef itk::Statistics::ListSample::MeasurementVectorType> IntensityProfile; /** \brief Compute intensity profile of an image for each pixel along the first PolyLine of a given planar figure. * * \param[in] image A two or three-dimensional image which consists of single component pixels. * \param[in] planarFigure A planar figure from which the first PolyLine is used to evaluate the intensity profile. * * \return The computed intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, PlanarFigure::Pointer planarFigure); namespace InterpolateImageFunction { enum Enum { NearestNeighbor, Linear, WindowedSinc_Blackman_3, WindowedSinc_Blackman_4, WindowedSinc_Blackman_5, WindowedSinc_Cosine_3, WindowedSinc_Cosine_4, WindowedSinc_Cosine_5, WindowedSinc_Hamming_3, WindowedSinc_Hamming_4, WindowedSinc_Hamming_5, WindowedSinc_Lanczos_3, WindowedSinc_Lanczos_4, WindowedSinc_Lanczos_5, WindowedSinc_Welch_3, WindowedSinc_Welch_4, WindowedSinc_Welch_5 }; } /** \brief Compute intensity profile of an image for each sample along a planar line. * * \param[in] image A three-dimensional image which consists of single component pixels. * \param[in] planarLine A planar line along which the intensity profile will be evaluated. * \param[in] numSamples Number of samples along the planar line (must be at least 2). * \param[in] interpolator Image interpolation function which is used to read each sample. * * \return The computed intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, PlanarLine::Pointer planarLine, unsigned int numSamples, InterpolateImageFunction::Enum interpolator = InterpolateImageFunction::NearestNeighbor); /** \brief Compute intensity profile of an image for each sample between two points. * * \param[in] image A three-dimensional image which consists of single component pixels. * \param[in] startPoint A point at which the first sample is to be read. * \param[in] endPoint A point at which the last sample is to be read. * \param[in] numSamples Number of samples between startPoint and endPoint (must be at least 2). * \param[in] interpolator Image interpolation function which is used to read each sample. * * \return The computed intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer ComputeIntensityProfile(Image::Pointer image, const Point3D& startPoint, const Point3D& endPoint, unsigned int numSamples, InterpolateImageFunction::Enum interpolator = InterpolateImageFunction::NearestNeighbor); /** \brief Compute global maximum of an intensity profile. * * \param[in] intensityProfile An intensity profile. * * \return Index of the global maximum. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::InstanceIdentifier ComputeGlobalMaximum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &max); /** \brief Compute global minimum of an intensity profile. * * \param[in] intensityProfile An intensity profile. * * \return Index of the global minimum. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::InstanceIdentifier ComputeGlobalMinimum(IntensityProfile::Pointer intensityProfile, IntensityProfile::MeasurementType &min); /** \brief Compute statistics of an intensity profile. * * \param[in] intensityProfile An intensity profile. * * \param[in] stats An ImageStatisticsCalculator::Statistics object to hold the calculated statistics. * */ - MITKIMAGESTATISTICS_EXPORT void ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::Statistics &stats); + MITKIMAGESTATISTICS_EXPORT void ComputeIntensityProfileStatistics(IntensityProfile::Pointer intensityProfile, ImageStatisticsCalculator::StatisticsContainer::Pointer stats); /** \brief Compute center of maximum area under the curve of an intensity profile. * * \param[in] intensityProfile An intensity profile. * \param[in] radius Radius of the area (width of area equals 1 + 2 * radius). * * \return Index of the maximum area center. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::InstanceIdentifier ComputeCenterOfMaximumArea(IntensityProfile::Pointer intensityProfile, IntensityProfile::InstanceIdentifier radius); /** \brief Convert an intensity profile to a standard library vector. * * \param[in] intensityProfile An intensity profile. * * \return Standard library vector which contains the input intensity profile measurements. */ MITKIMAGESTATISTICS_EXPORT std::vector CreateVectorFromIntensityProfile(IntensityProfile::Pointer intensityProfile); /** \brief Convert a standard library vector to an intensity profile. * * \param[in] vector An standard library vector which contains intensity profile measurements. * * \return An intensity profile. */ MITKIMAGESTATISTICS_EXPORT IntensityProfile::Pointer CreateIntensityProfileFromVector(const std::vector& vector); } #endif diff --git a/Modules/ImageStatistics/mitkMaskGenerator.cpp b/Modules/ImageStatistics/mitkMaskGenerator.cpp new file mode 100644 index 0000000000..81c3e3a7aa --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskGenerator.cpp @@ -0,0 +1,51 @@ +#include + +namespace mitk +{ +void MaskGenerator::SetTimeStep(unsigned int timeStep) +{ + if (m_TimeStep != timeStep) + { + m_TimeStep = timeStep; + this->Modified(); + } +} + +MaskGenerator::MaskGenerator(): + m_TimeStep(0) +{ + m_inputImage = nullptr; +} + +mitk::Image::Pointer MaskGenerator::GetMask() +{ + return mitk::Image::New(); +} + +//typename itk::Region<3>::Pointer MaskGenerator::GetImageRegionOfMask(Image::Pointer image) +//{ +// if (m_InternalMask.IsNull() || m_Modified) +// { +// MITK_ERROR << "Update MaskGenerator first!"; +// } + +// mitk::BaseGeometry::Pointer imageGeometry = image->GetGeometry(); +// mitk::BaseGeometry::Pointer maskGeometry = m_InternalMask->GetGeometry(); + + +//} + +void MaskGenerator::SetInputImage(mitk::Image::Pointer inputImg) +{ + if (inputImg != m_inputImage) + { + m_inputImage = inputImg; + this->Modified(); + } +} + +mitk::Image::Pointer MaskGenerator::GetReferenceImage() +{ + return m_inputImage; +} +} diff --git a/Modules/ImageStatistics/mitkMaskGenerator.h b/Modules/ImageStatistics/mitkMaskGenerator.h new file mode 100644 index 0000000000..627c7bc4e1 --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskGenerator.h @@ -0,0 +1,70 @@ +#ifndef MITKMASKGENERATOR +#define MITKMASKGENERATOR + +#include +#include +#include +#include +#include + +namespace mitk +{ +/** +* \class MaskGenerator +* \brief Base Class for all Mask Generators. Mask generators are classes that provide functionality for the +* creation of binary (or unsigned short) masks that can be applied to an image. See dervied classes for more +* information. +*/ +class MITKIMAGESTATISTICS_EXPORT MaskGenerator: public itk::Object +{ +public: + /** Standard Self typedef */ + typedef MaskGenerator Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(MaskGenerator, itk::Object) + + //~MaskGenerator(); + + /** + * @brief GetMask must be overridden by derived classes. + * @return mitk::Image::Pointer of generated mask + */ + virtual mitk::Image::Pointer GetMask(); + + /** + * @brief SetTimeStep is used to set the time step for which the mask is to be generated + * @param timeStep + */ + void SetTimeStep(unsigned int timeStep); + + /** + * @brief GetReferenceImage per default returns the inputImage (as set by SetInputImage). If no input image is set it will return a nullptr. + */ + virtual mitk::Image::Pointer GetReferenceImage(); + + /** + * @brief SetInputImage is used to set the input image to the mask generator. Some subclasses require an input image, others don't. See the documentation of the specific Mask Generator for more information. + */ + void SetInputImage(mitk::Image::Pointer inputImg); + +protected: + MaskGenerator(); + + unsigned int m_TimeStep; + mitk::Image::Pointer m_InternalMask; + mitk::Image::Pointer m_inputImage; + +private: + +}; +} + +#endif // MITKMASKGENERATOR + diff --git a/Modules/ImageStatistics/mitkMaskUtilities.cpp b/Modules/ImageStatistics/mitkMaskUtilities.cpp new file mode 100644 index 0000000000..38f82022ac --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskUtilities.cpp @@ -0,0 +1,189 @@ +#ifndef MITKMASKUTIL_CPP +#define MITKMASKUTIL_CPP + +#include +//#include +#include +#include +#include +#include + +namespace mitk +{ + template + void MaskUtilities::SetImage(ImageType* image) + { + if (image != m_Image) + { + m_Image = image; + } + } + + template + void MaskUtilities::SetMask(MaskType* mask) + { + if (mask != m_Mask) + { + m_Mask = mask; + } + } + + template + bool MaskUtilities::CheckMaskSanity() + { + if (m_Mask==nullptr || m_Image==nullptr) + { + MITK_ERROR << "Set an image and a mask first"; + } + + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef typename ImageType::PointType PointType; + typedef typename ImageType::DirectionType DirectionType; + + bool maskSanity = true; + + + if (m_Mask==nullptr) + { + MITK_ERROR << "Something went wrong when casting the mitk mask image to an itk mask image. Do the mask and the input image have the same dimension?"; + // note to self: We could try to convert say a 2d mask to a 3d mask if the image is 3d. (mask and image dimension have to match.) + } + // check direction + DirectionType imageDirection = m_Image->GetDirection(); + DirectionType maskDirection = m_Mask->GetDirection(); + for( int i = 0; i < imageDirection.ColumnDimensions; ++i ) + { + for( int j = 0; j < imageDirection.ColumnDimensions; ++j ) + { + double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; + if ( fabs( differenceDirection ) > mitk::eps ) + { + double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; + if ( fabs( differenceDirection ) > 0.001 /*mitk::eps*/ ) // TODO: temp fix (bug 17121) + { + maskSanity = false; + MITK_INFO << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")"; + } + } + } + } + + // check spacing + PointType imageSpacing = m_Image->GetSpacing(); + PointType maskSpacing = m_Mask->GetSpacing(); + for (unsigned int i = 0; i < VImageDimension; i++) + { + if ( fabs( maskSpacing[i] - imageSpacing[i] ) > mitk::eps ) + { + maskSanity = false; + MITK_INFO << "Spacing of mask and image is not equal. Mask: " << maskSpacing << " image: " << imageSpacing; + } + } + + // check alignment + // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images + PointType imageOrigin = m_Image->GetOrigin(); + PointType maskOrigin = m_Mask->GetOrigin(); + + typedef itk::ContinuousIndex ContinousIndexType; + ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; + + m_Image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); + m_Image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); + + for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) + { + double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); + // misalignment must be a multiple (int) of spacing in that direction + if ( fmod(misalignment,imageSpacing[i]) > mitk::eps ) + { + maskSanity = false; + MITK_INFO << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << fmod(misalignment,imageSpacing[i]) << ")"; + } + } + + // mask must be completely inside image region + // Make sure that mask region is contained within image region + if ( m_Mask!=nullptr && + !m_Image->GetLargestPossibleRegion().IsInside( m_Mask->GetLargestPossibleRegion() ) ) + { + maskSanity = false; + MITK_INFO << "Mask region needs to be inside of image region! (Image region: " + << m_Image->GetLargestPossibleRegion() << "; Mask region: " << m_Mask->GetLargestPossibleRegion() << ")"; + } + return maskSanity; + } + + template + typename itk::Image::Pointer MaskUtilities::ExtractMaskImageRegion() + { + if (m_Mask==nullptr || m_Image==nullptr) + { + MITK_ERROR << "Set an image and a mask first"; + } + + bool maskSanity = CheckMaskSanity(); + + if (!maskSanity) + { + MITK_ERROR << "Mask and image are not compatible"; + } + + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< unsigned short, VImageDimension > MaskType; + typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; + + typename ImageType::SizeType imageSize = m_Image->GetBufferedRegion().GetSize(); + typename ImageType::SizeType maskSize = m_Mask->GetBufferedRegion().GetSize(); + + typename itk::Image::Pointer extractedImg = itk::Image::New(); + + bool maskSmallerImage = false; + for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) + { + if ( maskSize[i] < imageSize[i] ) + { + maskSmallerImage = true; + } + } + + if ( maskSmallerImage ) + { + typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); + typename MaskType::PointType maskOrigin = m_Mask->GetOrigin(); + typename ImageType::PointType imageOrigin = m_Image->GetOrigin(); + typename MaskType::SpacingType maskSpacing = m_Mask->GetSpacing(); + typename ImageType::RegionType extractionRegion; + typename ImageType::IndexType extractionRegionIndex; + + + for (unsigned int i=0; i < maskOrigin.GetPointDimension(); i++) + { + extractionRegionIndex[i] = (maskOrigin[i] - imageOrigin[i]) / maskSpacing[i]; + } + + extractionRegion.SetIndex(extractionRegionIndex); + extractionRegion.SetSize(m_Mask->GetLargestPossibleRegion().GetSize()); + + extractImageFilter->SetInput( m_Image ); + extractImageFilter->SetExtractionRegion( extractionRegion ); + extractImageFilter->SetCoordinateTolerance( 0.001 ); + extractImageFilter->SetDirectionTolerance( 0.001 ); + extractImageFilter->Update(); + extractedImg = extractImageFilter->GetOutput(); + extractedImg->SetOrigin(m_Mask->GetOrigin()); + extractedImg->SetLargestPossibleRegion(m_Mask->GetLargestPossibleRegion()); + extractedImg->SetBufferedRegion(m_Mask->GetBufferedRegion()); + + } + else + { + extractedImg = m_Image; + } + + return extractedImg; + } + +} + +#endif diff --git a/Modules/ImageStatistics/mitkMaskUtilities.h b/Modules/ImageStatistics/mitkMaskUtilities.h new file mode 100644 index 0000000000..23d2b6e9fc --- /dev/null +++ b/Modules/ImageStatistics/mitkMaskUtilities.h @@ -0,0 +1,69 @@ +#ifndef MITKMASKUTIL +#define MITKMASKUTIL + +#include +#include +#include + +namespace mitk +{ +/** + * @brief Utility class for mask operations. It checks whether an image and a mask are compatible (spacing, orientation, etc...) + * and it can also crop an image to the LargestPossibleRegion of the Mask + */ +template +class MITKIMAGESTATISTICS_EXPORT MaskUtilities: public itk::Object + { + public: + /** Standard Self typedef */ + typedef MaskUtilities Self; + typedef itk::Object Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(MaskUtilities, itk::Object) + + typedef itk::Image ImageType; + typedef itk::Image MaskType; + + /** + * @brief Set image + */ + void SetImage(ImageType* image); + + /** + * @brief Set mask + */ + void SetMask(MaskType* mask); + + /** + * @brief Checks whether mask and image are compatible for joint access (as via iterators). + * Spacing and direction must be the same between the two and they must be aligned. Also, the mask must be completely inside the image + */ + bool CheckMaskSanity(); + + /** + * @brief Crops the image to the LargestPossibleRegion of the mask + */ + typename itk::Image::Pointer ExtractMaskImageRegion(); + + protected: + MaskUtilities(){} + + ~MaskUtilities(){} + + private: + itk::Image* m_Image; + itk::Image* m_Mask; + }; +} + +#ifndef ITK_MANUAL_INSTANTIATION +#include +#endif + +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.h b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.h new file mode 100644 index 0000000000..b5768f4cd4 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.h @@ -0,0 +1,84 @@ +#ifndef MITK_MINMAXIMAGEFILTERWITHINDEX_H +#define MITK_MINMAXIMAGEFILTERWITHINDEX_H + +#include + +#include +#include +#include + + + +namespace itk +{ +template +class MinMaxImageFilterWithIndex: public itk::ImageToImageFilter +{ +public: + /** Standard Self typedef */ + typedef MinMaxImageFilterWithIndex Self; + typedef ImageToImageFilter< TInputImage, TInputImage > Superclass; + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(MinMaxImageFilterWithIndex, ImageToImageFilter); + + typedef typename TInputImage::RegionType RegionType; + typedef typename TInputImage::SizeType SizeType; + typedef typename TInputImage::IndexType IndexType; + typedef typename TInputImage::PixelType PixelType; + typedef typename NumericTraits< PixelType >::RealType RealType; + + + RealType GetMin() const + { + return m_Min; + } + + RealType GetMax() const + { + return m_Max; + } + + IndexType GetMinIndex() const + { + return m_MinIndex; + } + + IndexType GetMaxIndex() const + { + return m_MaxIndex; + } + +protected: + void AllocateOutputs(); + + void ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId); + + void BeforeThreadedGenerateData(); + + void AfterThreadedGenerateData(); + +private: + std::vector m_ThreadMin; + std::vector m_ThreadMax; + std::vector m_ThreadMinIndex; + std::vector m_ThreadMaxIndex; + + PixelType m_Min; + PixelType m_Max; + IndexType m_MinIndex; + IndexType m_MaxIndex; +}; +} + +#include "mitkMinMaxImageFilterWithIndex.hxx" + + +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.hxx b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.hxx new file mode 100644 index 0000000000..c925cb5aa4 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxImageFilterWithIndex.hxx @@ -0,0 +1,106 @@ +#ifndef MITK_MinMaxImageFilterWithIndex_HXX +#define MITK_MinMaxImageFilterWithIndex_HXX + +#include +#include + +namespace itk +{ + + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::AllocateOutputs() +{ + // Pass the input through as the output + typename TInputImage::Pointer image = + const_cast< TInputImage * >( this->GetInput() ); + + this->GraftOutput(image); + + // Nothing that needs to be allocated for the remaining outputs +} + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId) +{ + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) + { + return; + } + PixelType value; + + PixelType threadMin, threadMax; + IndexType threadMinIndex, threadMaxIndex; + + threadMin = std::numeric_limits::max(); + threadMax = std::numeric_limits::min(); + + ImageRegionConstIteratorWithIndex< TInputImage > it (this->GetInput(), outputRegionForThread); + + // do the work + while ( !it.IsAtEnd() ) + { + value = it.Get(); + if (value < threadMin) + { + threadMin = value; + threadMinIndex = it.GetIndex(); + } + if (value > threadMax) + { + threadMax = value; + threadMaxIndex = it.GetIndex(); + } + ++it; + } + + m_ThreadMax[threadId] = threadMax; + m_ThreadMin[threadId] = threadMin; + m_ThreadMaxIndex[threadId] = threadMaxIndex; + m_ThreadMinIndex[threadId] = threadMinIndex; +} + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::BeforeThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + m_ThreadMin.resize(numberOfThreads); + m_ThreadMax.resize(numberOfThreads); + m_ThreadMinIndex.resize(numberOfThreads); + m_ThreadMaxIndex.resize(numberOfThreads); + + for (unsigned int i =0; i < numberOfThreads; i++) + { + m_ThreadMin[i] = std::numeric_limits::max(); + m_ThreadMax[i] = std::numeric_limits::min(); + } + + m_Min = std::numeric_limits::max(); + m_Max = std::numeric_limits::min(); + +} + +template< typename TInputImage > +void MinMaxImageFilterWithIndex< TInputImage >::AfterThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + for (ThreadIdType i = 0; i < numberOfThreads; i++) + { + if (m_ThreadMin[i] < m_Min) + { + m_Min = m_ThreadMin[i]; + m_MinIndex = m_ThreadMinIndex[i]; + } + if (m_ThreadMax[i] > m_Max) + { + m_Max = m_ThreadMax[i]; + m_MaxIndex = m_ThreadMaxIndex[i]; + } + } +} +} +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.h b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.h new file mode 100644 index 0000000000..e0c4158777 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.h @@ -0,0 +1,177 @@ +#ifndef MITK_MINMAXLABELIMAGEFILTERWITHINDEX_H +#define MITK_MINMAXLABELIMAGEFILTERWITHINDEX_H + +#include + +#include +#include +#include +#include "itksys/hash_map.hxx" + + +namespace itk +{ +template +class MinMaxLabelImageFilterWithIndex: public itk::ImageToImageFilter +{ +public: + /** Standard Self typedef */ + typedef MinMaxLabelImageFilterWithIndex Self; + typedef ImageToImageFilter< TInputImage, TInputImage > Superclass; + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(MinMaxLabelImageFilterWithIndex, ImageToImageFilter); + + typedef typename TInputImage::RegionType RegionType; + typedef typename TInputImage::SizeType SizeType; + typedef typename TInputImage::IndexType IndexType; + typedef typename TInputImage::PixelType PixelType; + typedef typename NumericTraits< PixelType >::RealType RealType; + + typedef typename TLabelImage::RegionType LabelRegionType; + typedef typename TLabelImage::SizeType LabelSizeType; + typedef typename TLabelImage::IndexType LabelIndexType; + typedef typename TLabelImage::PixelType LabelPixelType; + + /** + * @brief The LabelExtrema class is just a container for global min/max values and their indices as well as all min and max values (+indices) of the mask labels + */ + class LabelExtrema + { + public: + PixelType m_Min, m_Max; + IndexType m_MinIndex, m_MaxIndex; + + LabelExtrema(): + m_Min(std::numeric_limits::max()), + m_Max(std::numeric_limits::min()) + {} + }; + + typedef typename itksys::hash_map ExtremaMapType; + typedef typename ExtremaMapType::iterator ExtremaMapTypeIterator; + typedef typename ExtremaMapType::const_iterator ExtremaMapTypeConstIterator; + typedef typename ExtremaMapType::value_type MapValueType; + + PixelType GetMin(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_Min; + } + + PixelType GetMax(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_Max; + } + + /** + * @brief Returns a std::vector containing all labels for which min and max values (and indices) have been computed + */ + std::vector GetRelevantLabels() const + { + std::vector labels; + for (auto&& it:m_LabelExtrema) + { + labels.push_back(it.first); + } + return labels; + } + + IndexType GetMinIndex(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_MinIndex; + + } + + IndexType GetMaxIndex(LabelPixelType label) const + { + ExtremaMapTypeConstIterator it = m_LabelExtrema.find(label); + if (it == m_LabelExtrema.end()) + { + MITK_ERROR << "invalid label"; + } + + return (*it).second.m_MaxIndex; + + } + + PixelType GetGlobalMin() const + { + return m_GlobalMin; + } + + PixelType GetGlobalMax() const + { + return m_GlobalMax; + } + + IndexType GetGlobalMinIndex() const + { + return m_GlobalMinIndex; + } + + IndexType GetGlobalMaxIndex() const + { + return m_GlobalMaxIndex; + } + + /** Set the label image */ + void SetLabelInput(const TLabelImage *input) + { + // Process object is not const-correct so the const casting is required. + this->SetNthInput( 1, const_cast< TLabelImage * >( input ) ); + } + + /** Get the label image */ + const TLabelImage * GetLabelInput() const + { + return itkDynamicCastInDebugMode< TLabelImage * >( const_cast< DataObject * >( this->ProcessObject::GetInput(1) ) ); + } + +protected: + void AllocateOutputs(); + + void ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId); + + void BeforeThreadedGenerateData(); + + void AfterThreadedGenerateData(); + +private: + std::vector m_ThreadExtrema; + + ExtremaMapType m_LabelExtrema; + PixelType m_GlobalMin; + PixelType m_GlobalMax; + IndexType m_GlobalMinIndex, m_GlobalMaxIndex; +}; +} + +#include "mitkMinMaxLabelmageFilterWithIndex.hxx" + + +#endif diff --git a/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.hxx b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.hxx new file mode 100644 index 0000000000..c1a0af7ae2 --- /dev/null +++ b/Modules/ImageStatistics/mitkMinMaxLabelmageFilterWithIndex.hxx @@ -0,0 +1,130 @@ +#ifndef MITK_MinMaxLabelImageFilterWithIndex_HXX +#define MITK_MinMaxLabelImageFilterWithIndex_HXX + +#include +#include + +namespace itk +{ + + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::AllocateOutputs() +{ + // Pass the input through as the output + typename TInputImage::Pointer image = + const_cast< TInputImage * >( this->GetInput() ); + + this->GraftOutput(image); + + // Nothing that needs to be allocated for the remaining outputs +} + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::ThreadedGenerateData(const RegionType & + outputRegionForThread, + ThreadIdType threadId) +{ + const SizeValueType size0 = outputRegionForThread.GetSize(0); + if( size0 == 0) + { + return; + } + PixelType value; + LabelPixelType label; + + ExtremaMapType threadExtrema; + ExtremaMapTypeIterator threadExtremaIt; + + ImageRegionConstIteratorWithIndex< TInputImage > it (this->GetInput(), outputRegionForThread); + ImageRegionConstIteratorWithIndex< TLabelImage > labelit (this->GetLabelInput(), outputRegionForThread); + + // do the work + while ( !it.IsAtEnd() ) + { + value = it.Get(); + label = labelit.Get(); + + threadExtremaIt = threadExtrema.find(label); + + // if label does not exist yet, create a new entry in the map. + if (threadExtremaIt == threadExtrema.end()) + { + threadExtremaIt = threadExtrema.insert( MapValueType(label, LabelExtrema()) ).first; + } + + if (value < (*threadExtremaIt).second.m_Min) + { + (*threadExtremaIt).second.m_Min = value; + (*threadExtremaIt).second.m_MinIndex = it.GetIndex(); + } + if (value > (*threadExtremaIt).second.m_Max) + { + (*threadExtremaIt).second.m_Max = value; + (*threadExtremaIt).second.m_MaxIndex = it.GetIndex(); + } + ++it; + ++labelit; + } + + m_ThreadExtrema[threadId] = threadExtrema; +} + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::BeforeThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + m_ThreadExtrema.resize(numberOfThreads); + + for (unsigned int i =0; i < numberOfThreads; i++) + { + m_ThreadExtrema[i] = ExtremaMapType(); + } +} + +template< typename TInputImage, typename TLabelImage > +void MinMaxLabelImageFilterWithIndex< TInputImage, TLabelImage >::AfterThreadedGenerateData() +{ + ThreadIdType numberOfThreads = this->GetNumberOfThreads(); + + m_GlobalMin = std::numeric_limits::max(); + m_GlobalMax = std::numeric_limits::min(); + + ExtremaMapTypeIterator it; + + for (ThreadIdType i = 0; i < numberOfThreads; i++) + { + for (auto&& it2 : m_ThreadExtrema[i]) + { + it = m_LabelExtrema.find(it2.first); + if (it == m_LabelExtrema.end()) + { + it = m_LabelExtrema.insert( MapValueType(it2.first, LabelExtrema()) ).first; + } + + if (it2.second.m_Min < (*it).second.m_Min) + { + (*it).second.m_Min = it2.second.m_Min; + (*it).second.m_MinIndex = it2.second.m_MinIndex; + if (it2.second.m_Min < m_GlobalMin) + { + m_GlobalMin = it2.second.m_Min; + m_GlobalMinIndex = it2.second.m_MinIndex; + } + } + + if (it2.second.m_Max > (*it).second.m_Max) + { + (*it).second.m_Max = it2.second.m_Max; + (*it).second.m_MaxIndex = it2.second.m_MaxIndex; + if (it2.second.m_Max > m_GlobalMax) + { + m_GlobalMax = it2.second.m_Max; + m_GlobalMaxIndex = it2.second.m_MaxIndex; + } + } + } + } +} +} +#endif diff --git a/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.cpp b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.cpp new file mode 100644 index 0000000000..e3d467c857 --- /dev/null +++ b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.cpp @@ -0,0 +1 @@ +#include diff --git a/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.h b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.h new file mode 100644 index 0000000000..0adef4b488 --- /dev/null +++ b/Modules/ImageStatistics/mitkMultiLabelMaskGenerator.h @@ -0,0 +1,43 @@ +#ifndef MITKMULTILABELMASKGENERATOR +#define MITKMULTILABELMASKGENERATOR + +#include +#include +#include +#include + + +namespace mitk +{ +/** + * @brief The MultiLabelMaskGenerator class NOT IMPLEMENTED YET! + */ +class MITKIMAGESTATISTICS_EXPORT MultiLabelMaskGenerator: public MaskGenerator +{ +public: + void setLabelSetImage(mitk::LabelSetImage::Pointer labelSetImage); + + void addLabel(LabelSetImage::PixelType, std::vector::size_type layer=0); + void removeLabel(LabelSetImage::PixelType, std::vector::size_type layer=0); + + void addLabels(std::pair::size_type, std::vector> labelsToAdd); + void removeLabels(std::pair::size_type, std::vector> labelsToAdd); + + void addLabels(std::vector labels, std::vector::size_type layer=0); + void removeLabels(std::vector labels, std::vector::size_type layer=0); + + void removeLayer(std::vector::size_type layer); + + mitk::Image::Pointer GetMask(); + +protected: + +private: + mitk::LabelSetImage::Pointer m_LabelSetImage; + std::vector> m_selectedLabels; + +}; + +} + +#endif diff --git a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp new file mode 100644 index 0000000000..487e463cf9 --- /dev/null +++ b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp @@ -0,0 +1,418 @@ +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + + + +namespace mitk +{ + +void PlanarFigureMaskGenerator::SetPlanarFigure(mitk::PlanarFigure::Pointer planarFigure) +{ + if ( planarFigure.IsNull() ) + { + throw std::runtime_error( "Error: planar figure empty!" ); + } + if ( !planarFigure->IsClosed() ) + { + throw std::runtime_error( "Masking not possible for non-closed figures" ); + } + + const PlaneGeometry *planarFigurePlaneGeometry = planarFigure->GetPlaneGeometry(); + if ( planarFigurePlaneGeometry == nullptr ) + { + throw std::runtime_error( "Planar-Figure not yet initialized!" ); + } + + const PlaneGeometry *planarFigureGeometry = + dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); + if ( planarFigureGeometry == nullptr ) + { + throw std::runtime_error( "Non-planar planar figures not supported!" ); + } + + if (planarFigure != m_PlanarFigure) + { + this->Modified(); + m_PlanarFigure = planarFigure; + } + +} + +mitk::Image::Pointer PlanarFigureMaskGenerator::GetReferenceImage() +{ + if (IsUpdateRequired()) + { + this->CalculateMask(); + } + return m_ReferenceImage; +} + +template < typename TPixel, unsigned int VImageDimension > +void PlanarFigureMaskGenerator::InternalCalculateMaskFromPlanarFigure( + const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) +{ + typedef itk::Image< TPixel, VImageDimension > ImageType; + typedef itk::Image< unsigned short, 2 > MaskImage2DType; + + typename MaskImage2DType::Pointer maskImage = MaskImage2DType::New(); + maskImage->SetOrigin(image->GetOrigin()); + maskImage->SetSpacing(image->GetSpacing()); + maskImage->SetLargestPossibleRegion(image->GetLargestPossibleRegion()); + maskImage->SetBufferedRegion(image->GetBufferedRegion()); + maskImage->SetDirection(image->GetDirection()); + maskImage->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); + maskImage->Allocate(); + maskImage->FillBuffer(1); + + // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. + // These points are used by the vtkLassoStencilSource to create + // a vtkImageStencil. + const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); + const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); + const mitk::BaseGeometry *imageGeometry3D = m_inputImage->GetGeometry( 0 ); + // If there is a second poly line in a closed planar figure, treat it as a hole. + PlanarFigure::PolyLineType planarFigureHolePolyline; + + if (m_PlanarFigure->GetPolyLinesSize() == 2) + planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); + + + // Determine x- and y-dimensions depending on principal axis + // TODO use plane geometry normal to determine that automatically, then check whether the PF is aligned with one of the three principal axis + int i0, i1; + switch ( axis ) + { + case 0: + i0 = 1; + i1 = 2; + break; + + case 1: + i0 = 0; + i1 = 2; + break; + + case 2: + default: + i0 = 0; + i1 = 1; + break; + } + + // store the polyline contour as vtkPoints object + bool outOfBounds = false; + vtkSmartPointer points = vtkSmartPointer::New(); + typename PlanarFigure::PolyLineType::const_iterator it; + for ( it = planarFigurePolyline.begin(); + it != planarFigurePolyline.end(); + ++it ) + { + Point3D point3D; + + // Convert 2D point back to the local index coordinates of the selected + // image + // Fabian: From PlaneGeometry documentation: + // Converts a 2D point given in mm (pt2d_mm) relative to the upper-left corner of the geometry into the corresponding world-coordinate (a 3D point in mm, pt3d_mm). + // To convert a 2D point given in units (e.g., pixels in case of an image) into a 2D point given in mm (as required by this method), use IndexToWorld. + planarFigurePlaneGeometry->Map( *it, point3D ); + + // Polygons (partially) outside of the image bounds can not be processed + // further due to a bug in vtkPolyDataToImageStencil + if ( !imageGeometry3D->IsInside( point3D ) ) + { + outOfBounds = true; + } + + imageGeometry3D->WorldToIndex( point3D, point3D ); + + points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); + } + + vtkSmartPointer holePoints = nullptr; + + if (!planarFigureHolePolyline.empty()) + { + holePoints = vtkSmartPointer::New(); + + Point3D point3D; + PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); + + for (it = planarFigureHolePolyline.begin(); it != end; ++it) + { + // Fabian: same as above + planarFigurePlaneGeometry->Map(*it, point3D); + imageGeometry3D->WorldToIndex(point3D, point3D); + holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); + } + } + + // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds + // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero + double bounds[6] = {0, 0, 0, 0, 0, 0}; + points->GetBounds( bounds ); + bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; + bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; + bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; + + // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent + if ( m_PlanarFigure->IsClosed() && + ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) + { + mitkThrow() << "Figure has a zero area and cannot be used for masking."; + } + + if ( outOfBounds ) + { + throw std::runtime_error( "Figure at least partially outside of image bounds!" ); + } + + // create a vtkLassoStencilSource and set the points of the Polygon + vtkSmartPointer lassoStencil = vtkSmartPointer::New(); + lassoStencil->SetShapeToPolygon(); + lassoStencil->SetPoints( points ); + + vtkSmartPointer holeLassoStencil = nullptr; + + if (holePoints.GetPointer() != nullptr) + { + holeLassoStencil = vtkSmartPointer::New(); + holeLassoStencil->SetShapeToPolygon(); + holeLassoStencil->SetPoints(holePoints); + } + + // Export from ITK to VTK (to use a VTK filter) + typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; + typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; + + typename ImageExportType::Pointer itkExporter = ImageExportType::New(); + itkExporter->SetInput( maskImage ); +// itkExporter->SetInput( castFilter->GetOutput() ); + + vtkSmartPointer vtkImporter = vtkSmartPointer::New(); + this->ConnectPipelines( itkExporter, vtkImporter ); + + // Apply the generated image stencil to the input image + vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); + imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); + imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); + imageStencilFilter->ReverseStencilOff(); + imageStencilFilter->SetBackgroundValue( 0 ); + imageStencilFilter->Update(); + + vtkSmartPointer holeStencilFilter = nullptr; + + if (holeLassoStencil.GetPointer() != nullptr) + { + holeStencilFilter = vtkSmartPointer::New(); + holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); + holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); + holeStencilFilter->ReverseStencilOn(); + holeStencilFilter->SetBackgroundValue(0); + holeStencilFilter->Update(); + } + + // Export from VTK back to ITK + vtkSmartPointer vtkExporter = vtkSmartPointer::New(); + vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr + ? imageStencilFilter->GetOutputPort() + : holeStencilFilter->GetOutputPort()); + vtkExporter->Update(); + + typename ImageImportType::Pointer itkImporter = ImageImportType::New(); + this->ConnectPipelines( vtkExporter, itkImporter ); + itkImporter->Update(); + + typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; + DuplicatorType::Pointer duplicator = DuplicatorType::New(); + duplicator->SetInputImage( itkImporter->GetOutput() ); + duplicator->Update(); + + // Store mask + m_InternalITKImageMask2D = duplicator->GetOutput(); +} + +bool PlanarFigureMaskGenerator::GetPrincipalAxis( + const BaseGeometry *geometry, Vector3D vector, + unsigned int &axis ) +{ + vector.Normalize(); + for ( unsigned int i = 0; i < 3; ++i ) + { + Vector3D axisVector = geometry->GetAxisVector( i ); + axisVector.Normalize(); + + if ( fabs( fabs( axisVector * vector ) - 1.0) < mitk::eps ) + { + axis = i; + return true; + } + } + + return false; +} + +void PlanarFigureMaskGenerator::CalculateMask() +{ + if (m_inputImage.IsNull()) + { + MITK_ERROR << "Image is not set."; + } + + if (m_PlanarFigure.IsNull()) + { + MITK_ERROR << "PlanarFigure is not set."; + } + + if (m_TimeStep != 0) + { + MITK_WARN << "Multiple TimeSteps are not supported in PlanarFigureMaskGenerator (yet)."; + } + + const BaseGeometry *imageGeometry = m_inputImage->GetGeometry(); + if ( imageGeometry == nullptr ) + { + throw std::runtime_error( "Image geometry invalid!" ); + } + + if (m_inputImage->GetTimeSteps() > 0) + { + mitk::ImageTimeSelector::Pointer imgTimeSel = mitk::ImageTimeSelector::New(); + imgTimeSel->SetInput(m_inputImage); + imgTimeSel->SetTimeNr(m_TimeStep); + imgTimeSel->UpdateLargestPossibleRegion(); + m_InternalTimeSliceImage = imgTimeSel->GetOutput(); + } + else + { + m_InternalTimeSliceImage = m_inputImage; + } + + m_InternalITKImageMask2D = nullptr; + const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); + const PlaneGeometry *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); + //const BaseGeometry *imageGeometry = m_inputImage->GetGeometry(); + + // Find principal direction of PlanarFigure in input image + unsigned int axis; + if ( !this->GetPrincipalAxis( imageGeometry, + planarFigureGeometry->GetNormal(), axis ) ) + { + throw std::runtime_error( "Non-aligned planar figures not supported!" ); + } + m_PlanarFigureAxis = axis; + + // Find slice number corresponding to PlanarFigure in input image + typename itk::Image< unsigned short, 3 >::IndexType index; + imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); + + unsigned int slice = index[axis]; + + // extract image slice which corresponds to the planarFigure and store it in m_InternalImageSlice + mitk::Image::Pointer inputImageSlice = extract2DImageSlice(axis, slice); + //mitk::IOUtil::SaveImage(inputImageSlice, "/home/fabian/inputSliceImage.nrrd"); + // Compute mask from PlanarFigure + AccessFixedDimensionByItk_1(inputImageSlice, + InternalCalculateMaskFromPlanarFigure, + 2, axis) + + //convert itk mask to mitk::Image::Pointer and return it + mitk::Image::Pointer planarFigureMaskImage; + planarFigureMaskImage = mitk::GrabItkImageMemory(m_InternalITKImageMask2D); + //mitk::IOUtil::SaveImage(planarFigureMaskImage, "/home/fabian/planarFigureMaskImage.nrrd"); + + //Convert2Dto3DImageFilter::Pointer sliceTo3DImageConverter = Convert2Dto3DImageFilter::New(); + //sliceTo3DImageConverter->SetInput(planarFigureMaskImage); + //sliceTo3DImageConverter->Update(); + //mitk::IOUtil::SaveImage(sliceTo3DImageConverter->GetOutput(), "/home/fabian/3DsliceImage.nrrd"); + + m_ReferenceImage = inputImageSlice; + //mitk::IOUtil::SaveImage(m_ReferenceImage, "/home/fabian/referenceImage.nrrd"); + m_InternalMask = planarFigureMaskImage; +} + +mitk::Image::Pointer PlanarFigureMaskGenerator::GetMask() +{ + if (IsUpdateRequired()) + { + this->CalculateMask(); + this->Modified(); + } + + m_InternalMaskUpdateTime = m_InternalMask->GetMTime(); + return m_InternalMask; +} + +mitk::Image::Pointer PlanarFigureMaskGenerator::extract2DImageSlice(unsigned int axis, unsigned int slice) +{ + // Extract slice with given position and direction from image + unsigned int dimension = m_InternalTimeSliceImage->GetDimension(); + mitk::Image::Pointer imageSlice = mitk::Image::New(); + + if (dimension == 3) + { + ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); + imageExtractor->SetInput( m_InternalTimeSliceImage ); + imageExtractor->SetSliceDimension( axis ); + imageExtractor->SetSliceIndex( slice ); + imageExtractor->Update(); + imageSlice = imageExtractor->GetOutput(); + } + else if(dimension == 2) + { + imageSlice = m_InternalTimeSliceImage; + } + else + { + MITK_ERROR << "Unsupported image dimension. Dimension is: " << dimension << ". Only 2D and 3D images are supported."; + } + + return imageSlice; +} + +bool PlanarFigureMaskGenerator::IsUpdateRequired() const +{ + unsigned long thisClassTimeStamp = this->GetMTime(); + unsigned long internalMaskTimeStamp = m_InternalMask->GetMTime(); + unsigned long planarFigureTimeStamp = m_PlanarFigure->GetMTime(); + unsigned long inputImageTimeStamp = m_inputImage->GetMTime(); + + if (thisClassTimeStamp > m_InternalMaskUpdateTime) // inputs have changed + { + return true; + } + + if (m_InternalMaskUpdateTime < planarFigureTimeStamp || m_InternalMaskUpdateTime < inputImageTimeStamp) // mask image has changed outside of this class + { + return true; + } + + if (internalMaskTimeStamp > m_InternalMaskUpdateTime) // internal mask has been changed outside of this class + { + return true; + } + + return false; +} + +} + diff --git a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.h b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.h new file mode 100644 index 0000000000..15169c0267 --- /dev/null +++ b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.h @@ -0,0 +1,119 @@ +#ifndef MITKPLANARFIGUREMASKGENERATOR +#define MITKPLANARFIGUREMASKGENERATOR + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mitk +{ +/** +* \class PlanarFigureMaskGenerator +* \brief Derived from MaskGenerator. This class is used to convert a mitk::PlanarFigure into a binary image mask +*/ +class MITKIMAGESTATISTICS_EXPORT PlanarFigureMaskGenerator: public MaskGenerator + { + public: + /** Standard Self typedef */ + typedef PlanarFigureMaskGenerator Self; + typedef MaskGenerator Superclass; + typedef itk::SmartPointer< Self > Pointer; + typedef itk::SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Runtime information support. */ + itkTypeMacro(PlanarFigureMaskGenerator, MaskGenerator) + + /** + * @brief GetMask Computes and returns the mask + * @return mitk::Image::Pointer of the generated mask + */ + mitk::Image::Pointer GetMask(); + + void SetPlanarFigure(mitk::PlanarFigure::Pointer planarFigure); + + mitk::Image::Pointer GetReferenceImage(); + + protected: + PlanarFigureMaskGenerator():Superclass(){ + m_InternalMaskUpdateTime = 0; + m_InternalMask = mitk::Image::New(); + m_ReferenceImage = nullptr; + } + + private: + void CalculateMask(); + + template < typename TPixel, unsigned int VImageDimension > + void InternalCalculateMaskFromPlanarFigure( + const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ); + + mitk::Image::Pointer extract2DImageSlice(unsigned int axis, unsigned int slice); + + bool GetPrincipalAxis(const BaseGeometry *geometry, Vector3D vector, + unsigned int &axis ); + + /** Connection from ITK to VTK */ + template + void ConnectPipelines(ITK_Exporter exporter, vtkSmartPointer importer) + { + importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); + + importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); + importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); + importer->SetSpacingCallback(exporter->GetSpacingCallback()); + importer->SetOriginCallback(exporter->GetOriginCallback()); + importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); + + importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); + + importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); + importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); + importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); + importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); + importer->SetCallbackUserData(exporter->GetCallbackUserData()); + } + + /** Connection from VTK to ITK */ + template + void ConnectPipelines(vtkSmartPointer exporter, ITK_Importer importer) + { + importer->SetUpdateInformationCallback(exporter->GetUpdateInformationCallback()); + + importer->SetPipelineModifiedCallback(exporter->GetPipelineModifiedCallback()); + importer->SetWholeExtentCallback(exporter->GetWholeExtentCallback()); + importer->SetSpacingCallback(exporter->GetSpacingCallback()); + importer->SetOriginCallback(exporter->GetOriginCallback()); + importer->SetScalarTypeCallback(exporter->GetScalarTypeCallback()); + + importer->SetNumberOfComponentsCallback(exporter->GetNumberOfComponentsCallback()); + + importer->SetPropagateUpdateExtentCallback(exporter->GetPropagateUpdateExtentCallback()); + importer->SetUpdateDataCallback(exporter->GetUpdateDataCallback()); + importer->SetDataExtentCallback(exporter->GetDataExtentCallback()); + importer->SetBufferPointerCallback(exporter->GetBufferPointerCallback()); + importer->SetCallbackUserData(exporter->GetCallbackUserData()); + } + + bool IsUpdateRequired() const; + + mitk::PlanarFigure::Pointer m_PlanarFigure; + typename itk::Image::Pointer m_InternalITKImageMask2D; + mitk::Image::Pointer m_InternalTimeSliceImage; + mitk::Image::Pointer m_ReferenceImage; + unsigned int m_PlanarFigureAxis; + unsigned long m_InternalMaskUpdateTime; + }; +} + +#endif // MITKPLANARFIGUREMASKGENERATOR + diff --git a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp index 8e5d173598..221b97c69e 100644 --- a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.cpp @@ -1,214 +1,216 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPointSetDifferenceStatisticsCalculator.h" mitk::PointSetDifferenceStatisticsCalculator::PointSetDifferenceStatisticsCalculator() : m_StatisticsCalculated(false) { + m_Statistics = ImageStatisticsCalculator::StatisticsContainer::New(); m_PointSet1 = mitk::PointSet::New(); m_PointSet2 = mitk::PointSet::New(); //m_Statistics.Reset(); } mitk::PointSetDifferenceStatisticsCalculator::PointSetDifferenceStatisticsCalculator(mitk::PointSet::Pointer pSet1, mitk::PointSet::Pointer pSet2) { + m_Statistics = ImageStatisticsCalculator::StatisticsContainer::New(); m_PointSet1 = pSet1; m_PointSet2 = pSet2; m_StatisticsCalculated = false; //m_Statistics.Reset(); } mitk::PointSetDifferenceStatisticsCalculator::~PointSetDifferenceStatisticsCalculator() { } void mitk::PointSetDifferenceStatisticsCalculator::SetPointSets(mitk::PointSet::Pointer pSet1, mitk::PointSet::Pointer pSet2) { if (pSet1.IsNotNull()) { m_PointSet1 = pSet1; } if (pSet2.IsNotNull()) { m_PointSet2 = pSet2; } m_StatisticsCalculated = false; //m_Statistics.Reset(); } std::vector mitk::PointSetDifferenceStatisticsCalculator::GetDifferences() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } return m_DifferencesVector; } std::vector mitk::PointSetDifferenceStatisticsCalculator::GetSquaredDifferences() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } return m_SquaredDifferencesVector; } double mitk::PointSetDifferenceStatisticsCalculator::GetMean() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMean(); + return m_Statistics->GetMean(); } double mitk::PointSetDifferenceStatisticsCalculator::GetSD() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetSigma(); + return m_Statistics->GetStd(); } double mitk::PointSetDifferenceStatisticsCalculator::GetVariance() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetVariance(); + return m_Statistics->GetVariance(); } double mitk::PointSetDifferenceStatisticsCalculator::GetRMS() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetRMS(); + return m_Statistics->GetRMS(); } double mitk::PointSetDifferenceStatisticsCalculator::GetMedian() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMedian(); + return m_Statistics->GetMedian(); } double mitk::PointSetDifferenceStatisticsCalculator::GetMax() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMax(); + return m_Statistics->GetMax(); } double mitk::PointSetDifferenceStatisticsCalculator::GetMin() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetMin(); + return m_Statistics->GetMin(); } double mitk::PointSetDifferenceStatisticsCalculator::GetNumberOfPoints() { if (!m_StatisticsCalculated) { this->ComputeStatistics(); } - return m_Statistics.GetN(); + return m_Statistics->GetN(); } void mitk::PointSetDifferenceStatisticsCalculator::ComputeStatistics() { if ((m_PointSet1.IsNull())||(m_PointSet2.IsNull())) { itkExceptionMacro("Point sets specified are not valid. Please specify correct Point sets"); } else if (m_PointSet1->GetSize()!=m_PointSet2->GetSize()) { itkExceptionMacro("PointSets are not equal. Please make sure that your PointSets have the same size and hold corresponding points."); } else if (m_PointSet1->GetSize()==0) { itkExceptionMacro("There are no points in the PointSets. Please make sure that the PointSets contain points"); } else { double mean = 0.0; double sd = 0.0; double rms= 0.0; std::vector differencesVector; mitk::Point3D point1; mitk::Point3D point2; int numberOfPoints = m_PointSet1->GetSize(); //Iterate over both pointsets in order to compare all points pair-wise mitk::PointSet::PointsIterator end = m_PointSet1->End(); for( mitk::PointSet::PointsIterator pointSetIterator = m_PointSet1->Begin(), pointSetIterator2 = m_PointSet2->Begin(); pointSetIterator != end; ++pointSetIterator, ++pointSetIterator2) //iterate simultaneously over both sets { point1 = pointSetIterator.Value(); point2 = pointSetIterator2.Value(); double squaredDistance = point1.SquaredEuclideanDistanceTo(point2); mean+=sqrt(squaredDistance); rms+=squaredDistance; this->m_SquaredDifferencesVector.push_back(squaredDistance); differencesVector.push_back(sqrt(squaredDistance)); } m_DifferencesVector = differencesVector; mean = mean/numberOfPoints; rms = sqrt(rms/numberOfPoints); for (std::vector::size_type i=0; iSetMean(mean); + m_Statistics->SetStd(sd); + m_Statistics->SetVariance(variance); + m_Statistics->SetRMS(rms); + m_Statistics->SetMin(differencesVector.at(0)); + m_Statistics->SetMax(differencesVector.at(numberOfPoints-1)); + m_Statistics->SetMedian(median); + m_Statistics->SetN(numberOfPoints); m_StatisticsCalculated = true; } } diff --git a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h index 1dbffb566b..0ab47a40e6 100644 --- a/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h +++ b/Modules/ImageStatistics/mitkPointSetDifferenceStatisticsCalculator.h @@ -1,110 +1,110 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _MITK_PointSetDifferenceStatisticsCalculator_H #define _MITK_PointSetDifferenceStatisticsCalculator_H #include #include #include "mitkImageStatisticsCalculator.h" #include namespace mitk { /** * \brief Class for calculating the difference between two corresponding point sets. * The user can access the single distances between corresponding points as well as a complete statistic (mean, sd, rms, median, max, min) * The point sets must be of equal size! */ class MITKIMAGESTATISTICS_EXPORT PointSetDifferenceStatisticsCalculator : public itk::Object { public: mitkClassMacroItkParent( PointSetDifferenceStatisticsCalculator, itk::Object ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) mitkNewMacro2Param(PointSetDifferenceStatisticsCalculator,mitk::PointSet::Pointer,mitk::PointSet::Pointer); /*! \brief set point sets to be compared */ void SetPointSets(mitk::PointSet::Pointer pSet1, mitk::PointSet::Pointer pSet2); /*! \brief returns a vector holding the differences between the corresponding points of the point sets */ std::vector GetDifferences(); /*! \brief returns a vector holding the squared differences between the corresponding points of the point sets */ std::vector GetSquaredDifferences(); /*! \brief returns the mean distance of all corresponding points of the point sets */ double GetMean(); /*! \brief returns the standard deviation of the distances between all corresponding points of the point sets */ double GetSD(); /*! \brief returns the variance of the distances between all corresponding points of the point sets */ double GetVariance(); /*! \brief returns the root mean squared distance of all corresponding points of the point sets */ double GetRMS(); /*! \brief returns the median distance of all corresponding points of the point sets */ double GetMedian(); /*! \brief returns the maximal distance of all corresponding points of the point sets */ double GetMax(); /*! \brief returns the minimal distance of all corresponding points of the point sets */ double GetMin(); /*! \brief returns the total number of corresponding points of the point sets */ double GetNumberOfPoints(); protected: PointSetDifferenceStatisticsCalculator(); PointSetDifferenceStatisticsCalculator(mitk::PointSet::Pointer,mitk::PointSet::Pointer); virtual ~PointSetDifferenceStatisticsCalculator(); /*! \brief Method for computing the complete statistics of the differences between the given point sets. */ void ComputeStatistics(); - mitk::ImageStatisticsCalculator::Statistics m_Statistics; ///< struct holding the statistics + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer m_Statistics; ///< struct holding the statistics std::vector m_DifferencesVector; ///< vector holding the differences between the corresponding points std::vector m_SquaredDifferencesVector; ///< vector holding the squared differences between the corresponding points mitk::PointSet::Pointer m_PointSet1; ///< first point set used for comparison mitk::PointSet::Pointer m_PointSet2; ///< second point set used for comparison bool m_StatisticsCalculated; ///< flag indicating whether statistics are already calculated or not. }; } #endif // #define _MITK_PointSetDifferenceStatisticsCalculator_H diff --git a/Modules/ImageStatistics/mitkitkMaskImageFilter.h b/Modules/ImageStatistics/mitkitkMaskImageFilter.h new file mode 100644 index 0000000000..24fcfd8ecf --- /dev/null +++ b/Modules/ImageStatistics/mitkitkMaskImageFilter.h @@ -0,0 +1,289 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +#ifndef __itkMaskImageFilter2_h +#define __itkMaskImageFilter2_h + +#include "itkBinaryFunctorImageFilter.h" +#include "itkNumericTraits.h" +#include "itkVariableLengthVector.h" +#include + +namespace itk +{ +namespace Functor +{ +/** + * \class MaskInput2 + * \brief + * \ingroup ITKImageIntensity + */ +template< typename TInput, typename TMask, typename TOutput = TInput > +class MaskInput2 +{ +public: + typedef typename NumericTraits< TInput >::AccumulateType AccumulatorType; + + MaskInput2() + { + m_MaskingValue = NumericTraits< TMask >::ZeroValue(); + InitializeOutsideValue( static_cast( ITK_NULLPTR ) ); + } + ~MaskInput2() {} + bool operator!=(const MaskInput2 &) const + { + return false; + } + + bool operator==(const MaskInput2 & other) const + { + return !( *this != other ); + } + + inline TOutput operator()(const TInput & A, const TMask & B) const + { + if ( B == m_MaskingValue ) + { + return static_cast< TOutput >( A ); + } + else + { + return m_OutsideValue; + } + } + + /** Method to explicitly set the outside value of the mask */ + void SetOutsideValue(const TOutput & outsideValue) + { + m_OutsideValue = outsideValue; + } + + /** Method to get the outside value of the mask */ + const TOutput & GetOutsideValue() const + { + return m_OutsideValue; + } + + /** Method to explicitly set the masking value */ + void SetMaskingValue(const TMask & maskingValue) + { + m_MaskingValue = maskingValue; + } + /** Method to get the masking value */ + const TMask & GetMaskingValue() const + { + return m_MaskingValue; + } + +private: + + template < typename TPixelType > + void InitializeOutsideValue( TPixelType * ) + { + this->m_OutsideValue = NumericTraits< TPixelType >::ZeroValue(); + } + + template < typename TValue > + void InitializeOutsideValue( VariableLengthVector * ) + { + // set the outside value to be of zero length + this->m_OutsideValue = VariableLengthVector< TValue >(0); + } + + TOutput m_OutsideValue; + TMask m_MaskingValue; +}; +} +/** \class MaskImageFilter + * \brief Mask an image with a mask. + * + * This class is templated over the types of the + * input image type, the mask image type and the type of the output image. + * Numeric conversions (castings) are done by the C++ defaults. + * + * The pixel type of the input 2 image must have a valid definition of the + * operator != with zero. This condition is required because internally this + * filter will perform the operation + * + * \code + * if pixel_from_mask_image == masking_value + * pixel_output_image = pixel_input_image + * else + * pixel_output_image = outside_value + * \endcode + * + * The pixel from the input 1 is cast to the pixel type of the output image. + * + * Note that the input and the mask images must be of the same size. + * + * + * \sa MaskNegatedImageFilter + * \ingroup IntensityImageFilters + * \ingroup MultiThreaded + * \ingroup ITKImageIntensity + * + * \wiki + * \wikiexample{ImageProcessing/MaskImageFilter,Apply a mask to an image} + * \endwiki + */ +template< typename TInputImage, typename TMaskImage, typename TOutputImage = TInputImage > +class MITKIMAGESTATISTICS_EXPORT MaskImageFilter2: + public + BinaryFunctorImageFilter< TInputImage, TMaskImage, TOutputImage, + Functor::MaskInput2< + typename TInputImage::PixelType, + typename TMaskImage::PixelType, + typename TOutputImage::PixelType > > + +{ +public: + /** Standard class typedefs. */ + typedef MaskImageFilter2 Self; + typedef BinaryFunctorImageFilter< TInputImage, TMaskImage, TOutputImage, + Functor::MaskInput2< + typename TInputImage::PixelType, + typename TMaskImage::PixelType, + typename TOutputImage::PixelType > + > Superclass; + + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(MaskImageFilter2, + BinaryFunctorImageFilter); + + /** Typedefs **/ + typedef TMaskImage MaskImageType; + + /** Set/Get the mask image. Pixels set in the mask image will retain + * the original value of the input image while pixels not set in + * the mask will be set to the "OutsideValue". + */ + void SetMaskImage(const MaskImageType *maskImage) + { + // Process object is not const-correct so the const casting is required. + this->SetNthInput( 1, const_cast< MaskImageType * >( maskImage ) ); + } + const MaskImageType * GetMaskImage() + { + return static_cast(this->ProcessObject::GetInput(1)); + } + + /** Method to explicitly set the outside value of the mask. Defaults to 0 */ + void SetOutsideValue(const typename TOutputImage::PixelType & outsideValue) + { + if ( this->GetOutsideValue() != outsideValue ) + { + this->Modified(); + this->GetFunctor().SetOutsideValue(outsideValue); + } + } + + const typename TOutputImage::PixelType & GetOutsideValue() const + { + return this->GetFunctor().GetOutsideValue(); + } + + /** Method to explicitly set the masking value of the mask. Defaults to 0 */ + void SetMaskingValue(const typename TMaskImage::PixelType & maskingValue) + { + if ( this->GetMaskingValue() != maskingValue ) + { + this->Modified(); + this->GetFunctor().SetMaskingValue(maskingValue); + } + } + + /** Method to get the masking value of the mask. */ + const typename TMaskImage::PixelType & GetMaskingValue() const + { + return this->GetFunctor().GetMaskingValue(); + } + + void BeforeThreadedGenerateData() + { + typedef typename TOutputImage::PixelType PixelType; + this->CheckOutsideValue( static_cast(ITK_NULLPTR) ); + } + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro( MaskEqualityComparableCheck, + ( Concept::EqualityComparable< typename TMaskImage::PixelType > ) ); + itkConceptMacro( InputConvertibleToOutputCheck, + ( Concept::Convertible< typename TInputImage::PixelType, + typename TOutputImage::PixelType > ) ); + // End concept checking +#endif + +protected: + MaskImageFilter2() {} + virtual ~MaskImageFilter2() {} + + void PrintSelf(std::ostream & os, Indent indent) const + { + Superclass::PrintSelf(os, indent); + os << indent << "OutsideValue: " << this->GetOutsideValue() << std::endl; + } + +private: + MaskImageFilter2(const Self &); //purposely not implemented + void operator=(const Self &); //purposely not implemented + + template < typename TPixelType > + void CheckOutsideValue( const TPixelType * ) {} + + template < typename TValue > + void CheckOutsideValue( const VariableLengthVector< TValue > * ) + { + // Check to see if the outside value contains only zeros. If so, + // resize it to have the same number of zeros as the output + // image. Otherwise, check that the number of components in the + // outside value is the same as the number of components in the + // output image. If not, throw an exception. + VariableLengthVector< TValue > currentValue = + this->GetFunctor().GetOutsideValue(); + VariableLengthVector< TValue > zeroVector( currentValue.GetSize() ); + zeroVector.Fill( NumericTraits< TValue >::ZeroValue() ); + + if ( currentValue == zeroVector ) + { + zeroVector.SetSize( this->GetOutput()->GetVectorLength() ); + zeroVector.Fill( NumericTraits< TValue >::ZeroValue() ); + this->GetFunctor().SetOutsideValue( zeroVector ); + } + else if ( this->GetFunctor().GetOutsideValue().GetSize() != + this->GetOutput()->GetVectorLength() ) + { + itkExceptionMacro( + << "Number of components in OutsideValue: " + << this->GetFunctor().GetOutsideValue().GetSize() + << " is not the same as the " + << "number of components in the image: " + << this->GetOutput()->GetVectorLength()); + } + } + +}; +} // end namespace itk + +#endif + diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h b/Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.h similarity index 100% copy from Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.h copy to Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.h diff --git a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx b/Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.hxx similarity index 99% copy from Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx copy to Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.hxx index 42865e425c..b7e26546b7 100644 --- a/Modules/ImageStatistics/mitkExtendedLabelStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/old/mitkExtendedLabelStatisticsImageFilter.hxx @@ -1,324 +1,323 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _mitkExtendedLabelStatisticsImageFilter_hxx #define _mitkExtendedLabelStatisticsImageFilter_hxx #include "mitkExtendedLabelStatisticsImageFilter.h" #include "itkImageRegionConstIteratorWithIndex.h" #include "itkImageRegionConstIterator.h" #include #include #include "mitkNumericConstants.h" #include "mitkLogMacros.h" namespace itk { template< class TInputImage , class TLabelImage> ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::ExtendedLabelStatisticsImageFilter() : LabelStatisticsImageFilter< TInputImage, TLabelImage >() { CalculateSettingsForLabels(); } template< class TInputImage, class TLabelImage > std::list ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetRelevantLabels() const { return m_RelevantLabels; } template< class TInputImage, class TLabelImage > bool ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetMaskingNonEmpty() const { return m_MaskNonEmpty; } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetUniformity(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Uniformity; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetEntropy(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Entropy; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetUPP(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_UPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetMPP(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_MPP; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetKurtosis(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Kurtosis; } } template< class TInputImage, class TLabelImage > typename ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >::RealType ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage > ::GetSkewness(LabelPixelType label) const { CoefficientsMapConstIterator mapIt; mapIt = m_LabelStatisticsCoefficients.find(label); if ( mapIt == m_LabelStatisticsCoefficients.end() ) { // label does not exist, return a default value return NumericTraits< PixelType >::Zero; } else { return ( *mapIt ).second.m_Skewness; } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: CalculateSettingsForLabels() { LabelPixelType i; m_MaskNonEmpty = false; for ( i = 1; i < 4096; ++i ) { if ( this->HasLabel( i ) ) { m_RelevantLabels.push_back( i ); m_MaskNonEmpty = true; m_LabelStatisticsCoefficients.insert( std::make_pair(i, CoefficientsClass()) ); } } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: ComputeEntropyUniformityAndUPP() { - double baseChange = std::log10(2); RealType partialProbability( 0.0 ); RealType uniformity( 0.0 ); RealType entropy( 0.0 ); RealType upp( 0.0 ); LabelPixelType i; if ( m_MaskNonEmpty ) { typename std::list< int >::const_iterator it; for ( it = m_RelevantLabels.cbegin(), i = 0; it != m_RelevantLabels.cend(); ++it, ++i ) { HistogramType::Pointer histogramForEntropy = this->GetHistogram(*it); for (int i = 0; i < histogramForEntropy->Size(); i++) { partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; if( partialProbability != 0) { entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; uniformity += std::pow(partialProbability,2); if(histogramForEntropy->GetMeasurement(i,0) > 0) { upp += std::pow(partialProbability,2); } } } m_LabelStatisticsCoefficients[*it].m_Entropy = entropy; m_LabelStatisticsCoefficients[*it].m_Uniformity = uniformity; m_LabelStatisticsCoefficients[*it].m_UPP = upp; } } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: ComputeSkewnessKurtosisAndMPP() { typename TLabelImage::RegionType Subregion; RealType baseOfSkewnessAndCurtosis( 0.0 ); RealType kurtosis( 0.0 ); RealType skewness( 0.0 ); RealType mpp( 0.0 ); RealType currentPixel( 0.0 ); std::list< LabelPixelType> relevantLabels; LabelPixelType i; if ( m_MaskNonEmpty ) { typename std::list< int >::const_iterator it; for ( it = m_RelevantLabels.cbegin(), i = 0; it != m_RelevantLabels.cend(); ++it ) { RealType sigma = this->GetSigma( *it ); RealType mean = this->GetMean( *it ); Subregion = Superclass::GetRegion(*it); int count( this->GetCount(*it) ); if ( count == 0 || sigma < mitk::eps) { throw std::logic_error( "Empty segmentation" ); } if ( fabs( sigma ) < mitk::sqrteps ) { throw std::logic_error( "Sigma == 0" ); } ImageRegionConstIteratorWithIndex< TInputImage > it1 (this->GetInput(), Subregion); ImageRegionConstIterator< TLabelImage > labelIt (this->GetLabelInput(), Subregion); for (it1.GoToBegin(); !it1.IsAtEnd(); ++it1, ++labelIt) { if (labelIt.Get() == *it) { currentPixel = it1.Get(); baseOfSkewnessAndCurtosis = (currentPixel -mean) / sigma; kurtosis += std::pow( baseOfSkewnessAndCurtosis, 4.0 ); skewness += std::pow( baseOfSkewnessAndCurtosis, 3.0 ); if(currentPixel > 0) { mpp+= currentPixel; } } } m_LabelStatisticsCoefficients[*it].m_Skewness = RealType(skewness/count); m_LabelStatisticsCoefficients[*it].m_Kurtosis = RealType(kurtosis/count); m_LabelStatisticsCoefficients[*it].m_MPP = RealType(mpp/count); } } } template< class TInputImage, class TLabelImage > void ExtendedLabelStatisticsImageFilter< TInputImage, TLabelImage >:: AfterThreadedGenerateData() { Superclass::AfterThreadedGenerateData(); CalculateSettingsForLabels(); ComputeSkewnessKurtosisAndMPP(); if(this->GetUseHistograms()) { ComputeEntropyUniformityAndUPP(); } else { MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; } } } // end namespace itk #endif diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h b/Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.h similarity index 100% copy from Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.h copy to Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.h diff --git a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx b/Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.hxx similarity index 99% copy from Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx copy to Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.hxx index 004cf1a90b..3f3d810edb 100644 --- a/Modules/ImageStatistics/mitkExtendedStatisticsImageFilter.hxx +++ b/Modules/ImageStatistics/old/mitkExtendedStatisticsImageFilter.hxx @@ -1,344 +1,343 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef __mitkExtendedStatisticsImageFilter_hxx #define __mitkExtendedStatisticsImageFilter_hxx #include "mitkExtendedStatisticsImageFilter.h" namespace itk { template< class TInputImage > ExtendedStatisticsImageFilter< TInputImage >::ExtendedStatisticsImageFilter() : StatisticsImageFilter< TInputImage >() { /* * add the Skewness,Kurtosis,Entropy,Uniformity,MPP, UPP, Median to the other statistical calculated Values * of the mitkStatisticsImageFilter as the 7th to the 13th Output */ for ( int i = 7; i < 14; ++i ) { typename RealObjectType::Pointer output = static_cast< RealObjectType * >( this->MakeOutput(i).GetPointer() ); this->ProcessObject::SetNthOutput( i, output.GetPointer() ); } this->GetSkewnessOutput()->Set( 0.0 ); this->GetKurtosisOutput()->Set( 0.0 ); this->GetEntropyOutput()->Set( -1.0 ); this->GetUniformityOutput()->Set( 0.0 ); this->GetUPPOutput()->Set( 0.0 ); this->GetMPPOutput()->Set( 0.0 ); this->GetMedianOutput()->Set( 0.0 ); this->m_HistogramCalculated = false; this->m_HistogramGenerator = HistogramGeneratorType::New(); } template< class TInputImage > DataObject::Pointer ExtendedStatisticsImageFilter< TInputImage >::MakeOutput( ProcessObject::DataObjectPointerArraySizeType output) { switch ( output ) { case 7: case 8: case 9: case 10: case 11: case 12: { return RealObjectType::New().GetPointer(); break; } case 13: { return RealObjectType::New().GetPointer(); break; } default: { // might as well make an image return Superclass::MakeOutput( output ); break; } } } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::SetBinSize( int size ) { m_BinSize = size; } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetSkewnessOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(7) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetKurtosisOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(8) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUniformityOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(9) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetEntropyOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(10) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetUPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(11) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMPPOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(12) ); } template< class TInputImage > typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() { return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(13) ); } template< class TInputImage > const typename ExtendedStatisticsImageFilter< TInputImage >::RealObjectType * ExtendedStatisticsImageFilter< TInputImage > ::GetMedianOutput() const { return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(13) ); } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::AfterThreadedGenerateData() { Superclass::AfterThreadedGenerateData(); ComputeSkewnessKurtosisAndMPP(); CalculateHistogram(); if(m_HistogramCalculated == true) { ComputeEntropyUniformityMedianAndUPP(); } else { MITK_WARN << "Cannot compute coefficients UPP,Entropy,Uniformity because of missing histogram"; } } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::ComputeSkewnessKurtosisAndMPP() { RealType mean = this->GetMean(); RealType sigma = this->GetSigma(); RealType baseOfSkewnessAndKurtosis; RealType kurtosis(0.0); RealType skewness(0.0); RealType mpp(0.0); RealType currentPixel(0.0); if ( sigma < mitk::eps ) { throw std::logic_error( "Empty segmentation" ); } ImageRegionConstIterator< TInputImage > it (this->GetInput(), this->GetInput()->GetLargestPossibleRegion() ); int counter = 0; for (it.GoToBegin(); !it.IsAtEnd(); ++it) { currentPixel = it.Get(); baseOfSkewnessAndKurtosis = (currentPixel - mean) / sigma ; kurtosis += std::pow( baseOfSkewnessAndKurtosis, 4.0 ); skewness += std::pow( baseOfSkewnessAndKurtosis, 3.0 ); if(currentPixel > 0) { mpp+= currentPixel; } counter++; } if ( counter == 0 ) { throw std::logic_error( "Empty segmentation" ); } kurtosis = kurtosis / counter; skewness = skewness / counter; mpp = mpp/counter; this->GetKurtosisOutput()->Set( kurtosis ); this->GetSkewnessOutput()->Set( skewness ); this->GetMPPOutput()->Set( mpp ); } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::CalculateHistogram() { m_HistogramGenerator->SetInput( this->GetInput() ); m_HistogramGenerator->SetMarginalScale( 100 ); m_HistogramGenerator->SetNumberOfBins( m_BinSize ); m_HistogramGenerator->SetHistogramMin( this->GetMinimum() ); m_HistogramGenerator->SetHistogramMax( this->GetMaximum() ); m_HistogramGenerator->Compute(); m_HistogramCalculated = true; } template< class TInputImage > void ExtendedStatisticsImageFilter< TInputImage > ::ComputeEntropyUniformityMedianAndUPP() { - double baseChange = std::log10(2); RealType partialProbability( 0.0 ); RealType uniformity( 0.0 ); RealType entropy( 0.0 ); RealType upp( 0.0 ); RealType median( 0.0 ); const typename HistogramGeneratorType::HistogramType* histogramForEntropy = GetHistogram(); double cumulativeProbability = 0.0; bool medianFound = false; for (int i = 0; i < histogramForEntropy->Size(); i++) { partialProbability = histogramForEntropy->GetFrequency(i,0) / double ( histogramForEntropy->GetTotalFrequency() ) ; cumulativeProbability += double ( partialProbability ); if( partialProbability != 0) { entropy -= partialProbability *( std::log10(partialProbability) / std::log10(2) ) ; uniformity += std::pow(partialProbability,2); if(histogramForEntropy->GetMeasurement(i,0) > 0) { upp += std::pow(partialProbability,2); } } if( cumulativeProbability >= 0.5 && !medianFound ) { RealType binMin = histogramForEntropy->GetBinMin( 0, i ); RealType binMax = histogramForEntropy->GetBinMax( 0, i ); median = ( binMin + binMax ) / 2.0; medianFound = true; } } this->GetEntropyOutput()->Set( entropy ); this->GetUniformityOutput()->Set( uniformity ); this->GetUPPOutput()->Set( upp ); this->GetMedianOutput()->Set( median ); } } #endif diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp b/Modules/ImageStatistics/old/mitkImageStatisticsCalculator.cpp similarity index 98% copy from Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp copy to Modules/ImageStatistics/old/mitkImageStatisticsCalculator.cpp index b88432606f..612aeb71f6 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/old/mitkImageStatisticsCalculator.cpp @@ -1,2249 +1,2246 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkImageStatisticsCalculator.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkExtractImageFilter.h" #include "mitkImageTimeSelector.h" #include "mitkITKImageImport.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "itkImage.h" + //#define DEBUG_HOTSPOTSEARCH #define _USE_MATH_DEFINES #include #include "vtkLassoStencilSource.h" namespace mitk { ImageStatisticsCalculator::ImageStatisticsCalculator() : m_MaskingMode( MASKING_MODE_NONE ), m_MaskingModeChanged( false ), m_IgnorePixelValue(0.0), m_DoIgnorePixelValue(false), m_IgnorePixelValueChanged(false), m_PlanarFigureAxis (0), m_PlanarFigureSlice (0), m_PlanarFigureCoordinate0 (0), m_PlanarFigureCoordinate1 (0), m_HistogramBinSize(1.0), m_UseDefaultBinSize(true), m_UseBinSizeBasedOnVOIRegion(false), m_HotspotRadiusInMM(6.2035049089940), // radius of a 1cm3 sphere in mm m_CalculateHotspot(false), m_HotspotRadiusInMMChanged(false), m_HotspotMustBeCompletelyInsideImage(true) { m_EmptyHistogram = HistogramType::New(); m_EmptyHistogram->SetMeasurementVectorSize(1); HistogramType::SizeType histogramSize(1); histogramSize.Fill( 256 ); m_EmptyHistogram->Initialize( histogramSize ); m_EmptyStatistics.Reset(); } ImageStatisticsCalculator::~ImageStatisticsCalculator() { } void ImageStatisticsCalculator::SetUseDefaultBinSize(bool useDefault) { m_UseDefaultBinSize = useDefault; } ImageStatisticsCalculator::Statistics::Statistics(bool withHotspotStatistics) :m_HotspotStatistics(withHotspotStatistics ? new Statistics(false) : nullptr) { Reset(); } ImageStatisticsCalculator::Statistics::Statistics(const Statistics& other) :m_HotspotStatistics( nullptr) { this->SetLabel( other.GetLabel() ); this->SetN( other.GetN() ); this->SetMin( other.GetMin() ); this->SetMax( other.GetMax() ); this->SetMedian( other.GetMedian() ); this->SetMean( other.GetMean() ); this->SetVariance( other.GetVariance() ); this->SetKurtosis( other.GetKurtosis() ); this->SetSkewness( other.GetSkewness() ); this->SetUniformity( other.GetUniformity() ); this->SetEntropy( other.GetEntropy() ); this->SetUPP( other.GetUPP() ); this->SetMPP( other.GetMPP() ); this->SetSigma( other.GetSigma() ); this->SetRMS( other.GetRMS() ); this->SetMaxIndex( other.GetMaxIndex() ); this->SetMinIndex( other.GetMinIndex() ); this->SetHotspotIndex( other.GetHotspotIndex() ); if (other.m_HotspotStatistics) { this->m_HotspotStatistics = new Statistics(false); *this->m_HotspotStatistics = *other.m_HotspotStatistics; } } bool ImageStatisticsCalculator::Statistics::HasHotspotStatistics() const { return m_HotspotStatistics != nullptr; } void ImageStatisticsCalculator::Statistics::SetHasHotspotStatistics(bool hasHotspotStatistics) { m_HasHotspotStatistics = hasHotspotStatistics; } ImageStatisticsCalculator::Statistics::~Statistics() { delete m_HotspotStatistics; } double ImageStatisticsCalculator::Statistics::GetVariance() const { return this->Variance; } void ImageStatisticsCalculator::Statistics::SetVariance( const double value ) { if( this->Variance != value ) { if( value < 0.0 ) { this->Variance = 0.0; // if given value is negative set variance to 0.0 } else { this->Variance = value; } } } double ImageStatisticsCalculator::Statistics::GetSigma() const { return this->Sigma; } void ImageStatisticsCalculator::Statistics::SetSigma( const double value ) { if( this->Sigma != value ) { // for some compiler the value != value works to check for NaN but not for all // but we can always be sure that the standard deviation is a positive value if( value != value || value < 0.0 ) { // if standard deviation is NaN we just assume 0.0 this->Sigma = 0.0; } else { this->Sigma = value; } } } void ImageStatisticsCalculator::Statistics::Reset(unsigned int dimension) { SetLabel(0); SetN( 0 ); SetMin( 0.0 ); SetMax( 0.0 ); SetMedian( 0.0 ); SetVariance( 0.0 ); SetMean( 0.0 ); SetSigma( 0.0 ); SetRMS( 0.0 ); SetSkewness( 0.0 ); SetKurtosis( 0.0 ); SetUniformity( 0.0 ); SetEntropy( 0.0 ); SetMPP( 0.0 ); SetUPP( 0.0 ); vnl_vector zero; zero.set_size(dimension); for(unsigned int i = 0; i < dimension; ++i) { zero[i] = 0; } SetMaxIndex(zero); SetMinIndex(zero); SetHotspotIndex(zero); if (m_HotspotStatistics != nullptr) { m_HotspotStatistics->Reset(dimension); } } const ImageStatisticsCalculator::Statistics& ImageStatisticsCalculator::Statistics::GetHotspotStatistics() const { if (m_HotspotStatistics) { return *m_HotspotStatistics; } else { throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); } } ImageStatisticsCalculator::Statistics& ImageStatisticsCalculator::Statistics::GetHotspotStatistics() { if (m_HotspotStatistics) { return *m_HotspotStatistics; } else { throw std::logic_error("Object has no hostspot statistics, see HasHotspotStatistics()"); } } ImageStatisticsCalculator::Statistics& ImageStatisticsCalculator::Statistics::operator=(ImageStatisticsCalculator::Statistics const& other) { if (this == &other) return *this; this->SetLabel( other.GetLabel() ); this->SetN( other.GetN() ); this->SetMin( other.GetMin() ); this->SetMax( other.GetMax() ); this->SetMean( other.GetMean() ); this->SetMedian( other.GetMedian() ); this->SetVariance( other.GetVariance() ); this->SetSigma( other.GetSigma() ); this->SetRMS( other.GetRMS() ); this->SetMinIndex( other.GetMinIndex() ); this->SetMaxIndex( other.GetMaxIndex() ); this->SetHotspotIndex( other.GetHotspotIndex() ); this->SetSkewness( other.GetSkewness() ); this->SetKurtosis( other.GetKurtosis() ); this->SetUniformity( other.GetUniformity() ); this->SetEntropy( other.GetEntropy() ); this->SetUPP( other.GetUPP() ); this->SetMPP( other.GetMPP() ); delete this->m_HotspotStatistics; this->m_HotspotStatistics = nullptr; if (other.m_HotspotStatistics) { this->m_HotspotStatistics = new Statistics(false); *this->m_HotspotStatistics = *other.m_HotspotStatistics; } return *this; } void ImageStatisticsCalculator::SetImage( const mitk::Image *image ) { if ( m_Image != image ) { m_Image = image; this->Modified(); unsigned int numberOfTimeSteps = image->GetTimeSteps(); // Initialize vectors to time-size of this image m_ImageHistogramVector.resize( numberOfTimeSteps ); m_MaskedImageHistogramVector.resize( numberOfTimeSteps ); m_PlanarFigureHistogramVector.resize( numberOfTimeSteps ); m_ImageStatisticsVector.resize( numberOfTimeSteps ); m_MaskedImageStatisticsVector.resize( numberOfTimeSteps ); m_PlanarFigureStatisticsVector.resize( numberOfTimeSteps ); m_ImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); m_MaskedImageStatisticsTimeStampVector.resize( numberOfTimeSteps ); m_PlanarFigureStatisticsTimeStampVector.resize( numberOfTimeSteps ); m_ImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); m_MaskedImageStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); m_PlanarFigureStatisticsCalculationTriggerVector.resize( numberOfTimeSteps ); for ( unsigned int t = 0; t < image->GetTimeSteps(); ++t ) { m_ImageStatisticsTimeStampVector[t].Modified(); m_ImageStatisticsCalculationTriggerVector[t] = true; } } } void ImageStatisticsCalculator::SetImageMask( const mitk::Image *imageMask ) { if ( m_Image.IsNull() ) { itkExceptionMacro( << "Image needs to be set first!" ); } if ( m_ImageMask != imageMask ) { m_ImageMask = imageMask; this->Modified(); for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) { m_MaskedImageStatisticsTimeStampVector[t].Modified(); m_MaskedImageStatisticsCalculationTriggerVector[t] = true; } } } void ImageStatisticsCalculator::SetPlanarFigure( mitk::PlanarFigure *planarFigure ) { if ( m_Image.IsNull() ) { itkExceptionMacro( << "Image needs to be set first!" ); } if ( m_PlanarFigure != planarFigure ) { m_PlanarFigure = planarFigure; this->Modified(); for ( unsigned int t = 0; t < m_Image->GetTimeSteps(); ++t ) { m_PlanarFigureStatisticsTimeStampVector[t].Modified(); m_PlanarFigureStatisticsCalculationTriggerVector[t] = true; } } } void ImageStatisticsCalculator::SetMaskingMode( unsigned int mode ) { if ( m_MaskingMode != mode ) { m_MaskingMode = mode; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetMaskingModeToNone() { if ( m_MaskingMode != MASKING_MODE_NONE ) { m_MaskingMode = MASKING_MODE_NONE; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetMaskingModeToImage() { if ( m_MaskingMode != MASKING_MODE_IMAGE ) { m_MaskingMode = MASKING_MODE_IMAGE; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetMaskingModeToPlanarFigure() { if ( m_MaskingMode != MASKING_MODE_PLANARFIGURE ) { m_MaskingMode = MASKING_MODE_PLANARFIGURE; m_MaskingModeChanged = true; this->Modified(); } } void ImageStatisticsCalculator::SetIgnorePixelValue(double value) { if ( m_IgnorePixelValue != value ) { m_IgnorePixelValue = value; if(m_DoIgnorePixelValue) { m_IgnorePixelValueChanged = true; } this->Modified(); } } double ImageStatisticsCalculator::GetIgnorePixelValue() { return m_IgnorePixelValue; } void ImageStatisticsCalculator::SetDoIgnorePixelValue(bool value) { if ( m_DoIgnorePixelValue != value ) { m_DoIgnorePixelValue = value; m_IgnorePixelValueChanged = true; this->Modified(); } } bool ImageStatisticsCalculator::GetDoIgnorePixelValue() { return m_DoIgnorePixelValue; } void ImageStatisticsCalculator::SetHistogramBinSize(double size) { this->m_HistogramBinSize = size; } double ImageStatisticsCalculator::GetHistogramBinSize() { return this->m_HistogramBinSize; } void ImageStatisticsCalculator::SetHotspotRadiusInMM(double value) { if ( m_HotspotRadiusInMM != value ) { m_HotspotRadiusInMM = value; if(m_CalculateHotspot) { m_HotspotRadiusInMMChanged = true; //MITK_INFO <<"Hotspot radius changed, new convolution required"; } this->Modified(); } } double ImageStatisticsCalculator::GetHotspotRadiusInMM() { return m_HotspotRadiusInMM; } void ImageStatisticsCalculator::SetCalculateHotspot(bool on) { if ( m_CalculateHotspot != on ) { m_CalculateHotspot = on; m_HotspotRadiusInMMChanged = true; //MITK_INFO <<"Hotspot calculation changed, new convolution required"; this->Modified(); } } bool ImageStatisticsCalculator::IsHotspotCalculated() { return m_CalculateHotspot; } void ImageStatisticsCalculator::SetHotspotMustBeCompletlyInsideImage(bool hotspotMustBeCompletelyInsideImage, bool warn) { m_HotspotMustBeCompletelyInsideImage = hotspotMustBeCompletelyInsideImage; if (!m_HotspotMustBeCompletelyInsideImage && warn) { MITK_WARN << "Hotspot calculation will extrapolate pixels at image borders. Be aware of the consequences for the hotspot location."; } } bool ImageStatisticsCalculator::GetHotspotMustBeCompletlyInsideImage() const { return m_HotspotMustBeCompletelyInsideImage; } /* Implementation of the min max values for setting the range of the histogram */ template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::GetMinAndMaxValue( double &min, double &max, int &counter, double &sigma, const itk::Image< TPixel, VImageDimension > *InputImage, itk::Image< unsigned short, VImageDimension > *MaskedImage ) { typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::ImageRegionConstIteratorWithIndex Imageie; typedef itk::ImageRegionConstIteratorWithIndex Imageie2; Imageie2 labelIterator2( MaskedImage, MaskedImage->GetRequestedRegion() ); Imageie labelIterator3( InputImage, InputImage->GetRequestedRegion() ); max = 0; min = 0; counter = 0; sigma = 0; double SumOfSquares = 0; double sumSquared = 0; double actualPielValue = 0; int counterOfPixelsInROI = 0; for( labelIterator2.GoToBegin(); !labelIterator2.IsAtEnd(); ++labelIterator2, ++labelIterator3) { if( labelIterator2.Value()== 1.0) { counter++; counterOfPixelsInROI++; actualPielValue = labelIterator3.Value(); sumSquared = sumSquared + actualPielValue; SumOfSquares = SumOfSquares + std::pow(actualPielValue,2); if(counterOfPixelsInROI == 1) { max = actualPielValue; min = actualPielValue; } if(actualPielValue >= max) { max = actualPielValue; } else if(actualPielValue <= min) { min = actualPielValue; } } } if (counter > 1) { sigma = ( SumOfSquares - std::pow( sumSquared, 2) / counter ) / ( counter-1 ); } else { sigma = 0; } } bool ImageStatisticsCalculator::ComputeStatistics( unsigned int timeStep ) { if (m_Image.IsNull() ) { mitkThrow() << "Image not set!"; } if (!m_Image->IsInitialized()) { mitkThrow() << "Image not initialized!"; } if ( m_Image->GetReferenceCount() == 1 ) { // Image no longer valid; we are the only ones to still hold a reference on it return false; } if ( timeStep >= m_Image->GetTimeSteps() ) { throw std::runtime_error( "Error: invalid time step!" ); } // If a mask was set but we are the only ones to still hold a reference on // it, delete it. if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() == 1) ) { m_ImageMask = nullptr; } // Check if statistics is already up-to-date unsigned long imageMTime = m_ImageStatisticsTimeStampVector[timeStep].GetMTime(); unsigned long maskedImageMTime = m_MaskedImageStatisticsTimeStampVector[timeStep].GetMTime(); unsigned long planarFigureMTime = m_PlanarFigureStatisticsTimeStampVector[timeStep].GetMTime(); bool imageStatisticsCalculationTrigger = m_ImageStatisticsCalculationTriggerVector[timeStep]; bool maskedImageStatisticsCalculationTrigger = m_MaskedImageStatisticsCalculationTriggerVector[timeStep]; bool planarFigureStatisticsCalculationTrigger = m_PlanarFigureStatisticsCalculationTriggerVector[timeStep]; if ( !m_IgnorePixelValueChanged && !m_HotspotRadiusInMMChanged && ((m_MaskingMode != MASKING_MODE_NONE) || (imageMTime > m_Image->GetMTime() && !imageStatisticsCalculationTrigger)) && ((m_MaskingMode != MASKING_MODE_IMAGE) || (maskedImageMTime > m_ImageMask->GetMTime() && !maskedImageStatisticsCalculationTrigger)) && ((m_MaskingMode != MASKING_MODE_PLANARFIGURE) || (planarFigureMTime > m_PlanarFigure->GetMTime() && !planarFigureStatisticsCalculationTrigger)) ) { // Statistics is up to date! if ( m_MaskingModeChanged ) { m_MaskingModeChanged = false; } else { return false; } } // Reset state changed flag m_MaskingModeChanged = false; m_IgnorePixelValueChanged = false; // Depending on masking mode, extract and/or generate the required image // and mask data from the user input this->ExtractImageAndMask( timeStep ); StatisticsContainer *statisticsContainer; HistogramContainer *histogramContainer; switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: if(!m_DoIgnorePixelValue) { statisticsContainer = &m_ImageStatisticsVector[timeStep]; histogramContainer = &m_ImageHistogramVector[timeStep]; m_ImageStatisticsTimeStampVector[timeStep].Modified(); m_ImageStatisticsCalculationTriggerVector[timeStep] = false; } else { statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; histogramContainer = &m_MaskedImageHistogramVector[timeStep]; m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; } break; case MASKING_MODE_IMAGE: statisticsContainer = &m_MaskedImageStatisticsVector[timeStep]; histogramContainer = &m_MaskedImageHistogramVector[timeStep]; m_MaskedImageStatisticsTimeStampVector[timeStep].Modified(); m_MaskedImageStatisticsCalculationTriggerVector[timeStep] = false; break; case MASKING_MODE_PLANARFIGURE: statisticsContainer = &m_PlanarFigureStatisticsVector[timeStep]; histogramContainer = &m_PlanarFigureHistogramVector[timeStep]; m_PlanarFigureStatisticsTimeStampVector[timeStep].Modified(); m_PlanarFigureStatisticsCalculationTriggerVector[timeStep] = false; break; } // Calculate statistics and histogram(s) if ( m_InternalImage->GetDimension() == 3 ) { if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) { AccessFixedDimensionByItk_2( m_InternalImage, InternalCalculateStatisticsUnmasked, 3, statisticsContainer, histogramContainer ); } else { AccessFixedDimensionByItk_3( m_InternalImage, InternalCalculateStatisticsMasked, 3, m_InternalImageMask3D.GetPointer(), statisticsContainer, histogramContainer ); } } else if ( m_InternalImage->GetDimension() == 2 ) { if ( m_MaskingMode == MASKING_MODE_NONE && !m_DoIgnorePixelValue ) { AccessFixedDimensionByItk_2( m_InternalImage, InternalCalculateStatisticsUnmasked, 2, statisticsContainer, histogramContainer ); } else { AccessFixedDimensionByItk_3( m_InternalImage, InternalCalculateStatisticsMasked, 2, m_InternalImageMask2D.GetPointer(), statisticsContainer, histogramContainer ); } } else { MITK_ERROR << "ImageStatistics: Image dimension not supported!"; } // Release unused image smart pointers to free memory m_InternalImage = mitk::Image::ConstPointer(); m_InternalImageMask3D = MaskImage3DType::Pointer(); m_InternalImageMask2D = MaskImage2DType::Pointer(); return true; } ImageStatisticsCalculator::BinFrequencyType ImageStatisticsCalculator::GetBinsAndFreuqencyForHistograms( unsigned int timeStep , unsigned int label ) const { const HistogramType *binsAndFrequencyToCalculate = this->GetHistogram(0); // ToDo: map should be created on stack not on heap std::map returnedHistogramMap; unsigned int size = binsAndFrequencyToCalculate->Size(); for( unsigned int bin=0; bin < size; ++bin ) { double frequency = binsAndFrequencyToCalculate->GetFrequency( bin, 0 ); //if( frequency > mitk::eps ) { returnedHistogramMap.insert( std::pair(binsAndFrequencyToCalculate->GetMeasurement( bin, 0 ), binsAndFrequencyToCalculate->GetFrequency( bin, 0 ) ) ); } } return returnedHistogramMap; } const ImageStatisticsCalculator::HistogramType * ImageStatisticsCalculator::GetHistogram( unsigned int timeStep, unsigned int label ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return nullptr; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageHistogramVector[timeStep][label]; return m_ImageHistogramVector[timeStep][label]; } case MASKING_MODE_IMAGE: return m_MaskedImageHistogramVector[timeStep][label]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureHistogramVector[timeStep][label]; } } const ImageStatisticsCalculator::HistogramContainer & ImageStatisticsCalculator::GetHistogramVector( unsigned int timeStep ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return m_EmptyHistogramContainer; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageHistogramVector[timeStep]; return m_ImageHistogramVector[timeStep]; } case MASKING_MODE_IMAGE: return m_MaskedImageHistogramVector[timeStep]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureHistogramVector[timeStep]; } } const ImageStatisticsCalculator::Statistics & ImageStatisticsCalculator::GetStatistics( unsigned int timeStep, unsigned int label ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return m_EmptyStatistics; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageStatisticsVector[timeStep][label]; return m_ImageStatisticsVector[timeStep][label]; } case MASKING_MODE_IMAGE: return m_MaskedImageStatisticsVector[timeStep][label]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureStatisticsVector[timeStep][label]; } } const ImageStatisticsCalculator::StatisticsContainer & ImageStatisticsCalculator::GetStatisticsVector( unsigned int timeStep ) const { if ( m_Image.IsNull() || (timeStep >= m_Image->GetTimeSteps()) ) { return m_EmptyStatisticsContainer; } switch ( m_MaskingMode ) { case MASKING_MODE_NONE: default: { if(m_DoIgnorePixelValue) return m_MaskedImageStatisticsVector[timeStep]; return m_ImageStatisticsVector[timeStep]; } case MASKING_MODE_IMAGE: return m_MaskedImageStatisticsVector[timeStep]; case MASKING_MODE_PLANARFIGURE: return m_PlanarFigureStatisticsVector[timeStep]; } } void ImageStatisticsCalculator::ExtractImageAndMask( unsigned int timeStep ) { if ( m_Image.IsNull() ) { throw std::runtime_error( "Error: image empty!" ); } if ( timeStep >= m_Image->GetTimeSteps() ) { throw std::runtime_error( "Error: invalid time step!" ); } ImageTimeSelector::Pointer imageTimeSelector = ImageTimeSelector::New(); imageTimeSelector->SetInput( m_Image ); imageTimeSelector->SetTimeNr( timeStep ); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image *timeSliceImage = imageTimeSelector->GetOutput(); switch ( m_MaskingMode ) { case MASKING_MODE_NONE: { m_InternalImage = timeSliceImage; m_InternalImageMask2D = nullptr; m_InternalImageMask3D = nullptr; if(m_DoIgnorePixelValue) { if( m_InternalImage->GetDimension() == 3 ) { if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) CastToItkImage( timeSliceImage, m_InternalImageMask3D ); else CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask3D ); m_InternalImageMask3D->FillBuffer(1); } if( m_InternalImage->GetDimension() == 2 ) { if(itk::ImageIOBase::USHORT != timeSliceImage->GetPixelType().GetComponentType()) CastToItkImage( timeSliceImage, m_InternalImageMask2D ); else CastToItkImage( timeSliceImage->Clone(), m_InternalImageMask2D ); m_InternalImageMask2D->FillBuffer(1); } } break; } case MASKING_MODE_IMAGE: { if ( m_ImageMask.IsNotNull() && (m_ImageMask->GetReferenceCount() > 1) ) { if ( timeStep >= m_ImageMask->GetTimeSteps() ) { // Use the last mask time step in case the current time step is bigger than the total // number of mask time steps. // It makes more sense setting this to the last mask time step than to 0. // For instance if you have a mask with 2 time steps and an image with 5: // If time step 0 is selected, the mask will use time step 0. // If time step 1 is selected, the mask will use time step 1. // If time step 2+ is selected, the mask will use time step 1. // If you have a mask with only one time step instead, this will always default to 0. timeStep = m_ImageMask->GetTimeSteps() - 1; } ImageTimeSelector::Pointer maskedImageTimeSelector = ImageTimeSelector::New(); maskedImageTimeSelector->SetInput( m_ImageMask ); maskedImageTimeSelector->SetTimeNr( timeStep ); maskedImageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image *timeSliceMaskedImage = maskedImageTimeSelector->GetOutput(); m_InternalImage = timeSliceImage; CastToItkImage( timeSliceMaskedImage, m_InternalImageMask3D ); } else { throw std::runtime_error( "Error: image mask empty!" ); } break; } case MASKING_MODE_PLANARFIGURE: { m_InternalImageMask2D = nullptr; if ( m_PlanarFigure.IsNull() ) { throw std::runtime_error( "Error: planar figure empty!" ); } if ( !m_PlanarFigure->IsClosed() ) { throw std::runtime_error( "Masking not possible for non-closed figures" ); } const BaseGeometry *imageGeometry = timeSliceImage->GetGeometry(); if ( imageGeometry == nullptr ) { throw std::runtime_error( "Image geometry invalid!" ); } const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); if ( planarFigurePlaneGeometry == nullptr ) { throw std::runtime_error( "Planar-Figure not yet initialized!" ); } const PlaneGeometry *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); if ( planarFigureGeometry == nullptr ) { throw std::runtime_error( "Non-planar planar figures not supported!" ); } // Find principal direction of PlanarFigure in input image unsigned int axis; if ( !this->GetPrincipalAxis( imageGeometry, planarFigureGeometry->GetNormal(), axis ) ) { throw std::runtime_error( "Non-aligned planar figures not supported!" ); } m_PlanarFigureAxis = axis; // Find slice number corresponding to PlanarFigure in input image MaskImage3DType::IndexType index; imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); unsigned int slice = index[axis]; m_PlanarFigureSlice = slice; // Extract slice with given position and direction from image unsigned int dimension = timeSliceImage->GetDimension(); if (dimension != 2) { ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); imageExtractor->SetInput( timeSliceImage ); imageExtractor->SetSliceDimension( axis ); imageExtractor->SetSliceIndex( slice ); imageExtractor->Update(); m_InternalImage = imageExtractor->GetOutput(); } else { m_InternalImage = timeSliceImage; } // Compute mask from PlanarFigure AccessFixedDimensionByItk_1( m_InternalImage, InternalCalculateMaskFromPlanarFigure, 2, axis ); } } if(m_DoIgnorePixelValue) { if ( m_InternalImage->GetDimension() == 3 ) { AccessFixedDimensionByItk_1( m_InternalImage, InternalMaskIgnoredPixels, 3, m_InternalImageMask3D.GetPointer() ); } else if ( m_InternalImage->GetDimension() == 2 ) { AccessFixedDimensionByItk_1( m_InternalImage, InternalMaskIgnoredPixels, 2, m_InternalImageMask2D.GetPointer() ); } } } bool ImageStatisticsCalculator::GetPrincipalAxis( const BaseGeometry *geometry, Vector3D vector, unsigned int &axis ) { vector.Normalize(); for ( unsigned int i = 0; i < 3; ++i ) { Vector3D axisVector = geometry->GetAxisVector( i ); axisVector.Normalize(); if ( fabs( fabs( axisVector * vector ) - 1.0) < mitk::eps ) { axis = i; return true; } } return false; } unsigned int ImageStatisticsCalculator::calcNumberOfBins(mitk::ScalarType min, mitk::ScalarType max) { return std::ceil( ( (max - min ) / m_HistogramBinSize) ); } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( const itk::Image< TPixel, VImageDimension > *image, StatisticsContainer *statisticsContainer, HistogramContainer* histogramContainer ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef typename ImageType::IndexType IndexType; typedef itk::Statistics::ScalarImageToHistogramGenerator< ImageType > HistogramGeneratorType; statisticsContainer->clear(); histogramContainer->clear(); // Progress listening... typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; ITKCommandType::Pointer progressListener; progressListener = ITKCommandType::New(); progressListener->SetCallbackFunction( this, &ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate ); // Issue 100 artificial progress events since ScalarIMageToHistogramGenerator // does not (yet?) support progress reporting this->InvokeEvent( itk::StartEvent() ); for ( unsigned int i = 0; i < 100; ++i ) { this->UnmaskedStatisticsProgressUpdate(); } // Calculate statistics (separate filter) typedef itk::ExtendedStatisticsImageFilter< ImageType > StatisticsFilterType; typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); statisticsFilter->SetInput( image ); statisticsFilter->SetBinSize( 100 ); statisticsFilter->SetCoordinateTolerance( 0.001 ); statisticsFilter->SetDirectionTolerance( 0.001 ); unsigned long observerTag = statisticsFilter->AddObserver( itk::ProgressEvent(), progressListener ); try { statisticsFilter->Update(); } catch (const itk::ExceptionObject& e) { mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } statisticsFilter->RemoveObserver( observerTag ); this->InvokeEvent( itk::EndEvent() ); // Calculate minimum and maximum typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); minMaxFilter->SetImage( image ); unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); minMaxFilter->Compute(); minMaxFilter->RemoveObserver( observerTag2 ); this->InvokeEvent( itk::EndEvent() ); Statistics statistics; statistics.Reset(); statistics.SetLabel(1); statistics.SetN(image->GetBufferedRegion().GetNumberOfPixels()); statistics.SetMin(statisticsFilter->GetMinimum()); statistics.SetMax(statisticsFilter->GetMaximum()); statistics.SetMean(statisticsFilter->GetMean()); statistics.SetMedian(statisticsFilter->GetMedian()); statistics.SetVariance(statisticsFilter->GetVariance()); statistics.SetSkewness(statisticsFilter->GetSkewness()); statistics.SetKurtosis(statisticsFilter->GetKurtosis()); statistics.SetUniformity( statisticsFilter->GetUniformity()); statistics.SetEntropy( statisticsFilter->GetEntropy()); statistics.SetUPP( statisticsFilter->GetUPP()); statistics.SetMPP( statisticsFilter->GetMPP()); statistics.SetSigma(statisticsFilter->GetSigma()); statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() + statistics.GetSigma() * statistics.GetSigma() )); statistics.GetMinIndex().set_size(image->GetImageDimension()); statistics.GetMaxIndex().set_size(image->GetImageDimension()); - vnl_vector maxIndex; - vnl_vector minIndex; - - maxIndex.set_size( VImageDimension ); - minIndex.set_size( VImageDimension ); + vnl_vector tmpMaxIndex; + vnl_vector tmpMinIndex; - typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); - typename MinMaxFilterType::IndexType tempMinIndex = minMaxFilter->GetIndexOfMinimum(); + tmpMaxIndex.set_size(image->GetImageDimension() ); + tmpMinIndex.set_size(image->GetImageDimension() ); - for (unsigned int i=0; i GetIndexOfMaximum()[i]; + tmpMinIndex[i] = minMaxFilter->GetIndexOfMinimum()[i]; } - statistics.SetMaxIndex(maxIndex); - statistics.SetMinIndex(minIndex); + statistics.SetMinIndex(tmpMaxIndex); + statistics.SetMinIndex(tmpMinIndex); if( IsHotspotCalculated() && VImageDimension == 3 ) { typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typename MaskImageType::Pointer nullMask; bool isHotspotDefined(false); Statistics hotspotStatistics = this->CalculateHotspotStatistics(image, nullMask.GetPointer(), m_HotspotRadiusInMM, isHotspotDefined, 0 ); if (isHotspotDefined) { statistics.SetHasHotspotStatistics(true); statistics.GetHotspotStatistics() = hotspotStatistics; } else { statistics.SetHasHotspotStatistics(false); } if(statistics.GetHotspotStatistics().HasHotspotStatistics() ) { MITK_DEBUG << "Hotspot statistics available"; statistics.SetHotspotIndex(hotspotStatistics.GetHotspotIndex()); } else { MITK_ERROR << "No hotspot statistics available!"; } } statisticsContainer->push_back( statistics ); // Calculate histogram // calculate bin size or number of bins unsigned int numberOfBins = 200; // default number of bins if (m_UseDefaultBinSize) { m_HistogramBinSize = std::ceil( (statistics.GetMax() - statistics.GetMin() + 1)/numberOfBins ); } else { numberOfBins = calcNumberOfBins(statistics.GetMin(), statistics.GetMax()); } typename HistogramGeneratorType::Pointer histogramGenerator = HistogramGeneratorType::New(); histogramGenerator->SetInput( image ); histogramGenerator->SetMarginalScale( 100 ); histogramGenerator->SetNumberOfBins( numberOfBins ); histogramGenerator->SetHistogramMin( statistics.GetMin() ); histogramGenerator->SetHistogramMax( statistics.GetMax() ); histogramGenerator->Compute(); histogramContainer->push_back( histogramGenerator->GetOutput() ); } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalMaskIgnoredPixels( const itk::Image< TPixel, VImageDimension > *image, itk::Image< unsigned short, VImageDimension > *maskImage ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; itk::ImageRegionIterator itmask(maskImage, maskImage->GetLargestPossibleRegion()); itk::ImageRegionConstIterator itimage(image, image->GetLargestPossibleRegion()); itmask.GoToBegin(); itimage.GoToBegin(); while( !itmask.IsAtEnd() ) { if(m_IgnorePixelValue == itimage.Get()) { itmask.Set(0); } ++itmask; ++itimage; } } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsMasked( const itk::Image< TPixel, VImageDimension > *image, itk::Image< unsigned short, VImageDimension > *maskImage, StatisticsContainer* statisticsContainer, HistogramContainer* histogramContainer ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typedef typename ImageType::IndexType IndexType; typedef typename ImageType::PointType PointType; typedef typename ImageType::SpacingType SpacingType; typedef typename ImageType::Pointer ImagePointer; typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, MaskImageType > LabelStatisticsFilterType; typedef itk::ChangeInformationImageFilter< MaskImageType > ChangeInformationFilterType; typedef itk::ExtractImageFilter< ImageType, ImageType > ExtractImageFilterType; statisticsContainer->clear(); histogramContainer->clear(); // Make sure that mask is set if ( maskImage == nullptr ) { itkExceptionMacro( << "Mask image needs to be set!" ); } // Make sure that spacing of mask and image are the same //SpacingType imageSpacing = image->GetSpacing(); //SpacingType maskSpacing = maskImage->GetSpacing(); //PointType zeroPoint; zeroPoint.Fill( 0.0 ); //if ( (zeroPoint + imageSpacing).SquaredEuclideanDistanceTo( (zeroPoint + maskSpacing) ) > mitk::eps ) //{ // itkExceptionMacro( << "Mask needs to have same spacing as image! (Image spacing: " << imageSpacing << "; Mask spacing: " << maskSpacing << ")" ); //} // Make sure that orientation of mask and image are the same typedef typename ImageType::DirectionType DirectionType; DirectionType imageDirection = image->GetDirection(); DirectionType maskDirection = maskImage->GetDirection(); for( int i = 0; i < imageDirection.ColumnDimensions; ++i ) { for( int j = 0; j < imageDirection.ColumnDimensions; ++j ) { double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; if ( fabs( differenceDirection ) > mitk::eps ) { double differenceDirection = imageDirection[i][j] - maskDirection[i][j]; if ( fabs( differenceDirection ) > 0.001 /*mitk::eps*/ ) // TODO: temp fix (bug 17121) { itkExceptionMacro( << "Mask needs to have same direction as image! (Image direction: " << imageDirection << "; Mask direction: " << maskDirection << ")" ); } } } } // Make sure that the voxels of mask and image are correctly "aligned", i.e., voxel boundaries are the same in both images PointType imageOrigin = image->GetOrigin(); PointType maskOrigin = maskImage->GetOrigin(); long offset[ImageType::ImageDimension]; typedef itk::ContinuousIndex ContinousIndexType; ContinousIndexType maskOriginContinousIndex, imageOriginContinousIndex; image->TransformPhysicalPointToContinuousIndex(maskOrigin, maskOriginContinousIndex); image->TransformPhysicalPointToContinuousIndex(imageOrigin, imageOriginContinousIndex); for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { double misalignment = maskOriginContinousIndex[i] - floor( maskOriginContinousIndex[i] + 0.5 ); if ( fabs( misalignment ) > mitk::eps ) { itkWarningMacro( << "Pixels/voxels of mask and image are not sufficiently aligned! (Misalignment: " << misalignment << ")" ); } double indexCoordDistance = maskOriginContinousIndex[i] - imageOriginContinousIndex[i]; offset[i] = int( indexCoordDistance + image->GetBufferedRegion().GetIndex()[i] + 0.5 ); } // Adapt the origin and region (index/size) of the mask so that the origin of both are the same typename ChangeInformationFilterType::Pointer adaptMaskFilter; adaptMaskFilter = ChangeInformationFilterType::New(); adaptMaskFilter->ChangeOriginOn(); adaptMaskFilter->ChangeRegionOn(); adaptMaskFilter->SetInput( maskImage ); adaptMaskFilter->SetOutputOrigin( image->GetOrigin() ); adaptMaskFilter->SetOutputOffset( offset ); adaptMaskFilter->SetCoordinateTolerance( 0.001 ); adaptMaskFilter->SetDirectionTolerance( 0.001 ); typename MaskImageType::Pointer adaptedMaskImage; try { adaptMaskFilter->Update(); adaptedMaskImage = adaptMaskFilter->GetOutput(); } catch( const itk::ExceptionObject &e) { mitkThrow() << "Attempt to adapt shifted origin of the mask image failed due to ITK Exception: \n" << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } // Make sure that mask region is contained within image region if ( adaptedMaskImage.IsNotNull() && !image->GetLargestPossibleRegion().IsInside( adaptedMaskImage->GetLargestPossibleRegion() ) ) { itkWarningMacro( << "Mask region needs to be inside of image region! (Image region: " << image->GetLargestPossibleRegion() << "; Mask region: " << adaptedMaskImage->GetLargestPossibleRegion() << ")" ); } // If mask region is smaller than image region, extract the sub-sampled region from the original image typename ImageType::SizeType imageSize = image->GetBufferedRegion().GetSize(); typename ImageType::SizeType maskSize = maskImage->GetBufferedRegion().GetSize(); bool maskSmallerImage = false; for ( unsigned int i = 0; i < ImageType::ImageDimension; ++i ) { if ( maskSize[i] < imageSize[i] ) { maskSmallerImage = true; } } typename ImageType::ConstPointer adaptedImage; if ( maskSmallerImage ) { typename ExtractImageFilterType::Pointer extractImageFilter = ExtractImageFilterType::New(); extractImageFilter->SetInput( image ); extractImageFilter->SetExtractionRegion( adaptedMaskImage->GetBufferedRegion() ); extractImageFilter->SetCoordinateTolerance( 0.001 ); extractImageFilter->SetDirectionTolerance( 0.001 ); extractImageFilter->Update(); adaptedImage = extractImageFilter->GetOutput(); } else { adaptedImage = image; } // Initialize Filter typedef itk::StatisticsImageFilter< ImageType > StatisticsFilterType; typename StatisticsFilterType::Pointer statisticsFilter = StatisticsFilterType::New(); statisticsFilter->SetInput( adaptedImage ); try { statisticsFilter->Update(); } catch( const itk::ExceptionObject& e) { mitkThrow() << "Image statistics initialization computation failed with ITK Exception: \n " << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } // Calculate bin size or number of bins - unsigned int numberOfBins = 200; // default number of bins + unsigned int numberOfBins = m_HistogramBinSize; // default number of bins double maximum = 0.0; double minimum = 0.0; if (m_UseBinSizeBasedOnVOIRegion) { maximum = statisticsFilter->GetMaximum(); minimum = statisticsFilter->GetMinimum(); if (m_UseDefaultBinSize) { m_HistogramBinSize = std::ceil( static_cast((statisticsFilter->GetMaximum() - statisticsFilter->GetMinimum() + 1)/numberOfBins) ); } else { numberOfBins = calcNumberOfBins(statisticsFilter->GetMinimum(), statisticsFilter->GetMaximum()); } } else { double sig = 0.0; int counter = 0; //Find the min and max values for the Roi to set the range for the histogram GetMinAndMaxValue( minimum, maximum, counter, sig, image, maskImage); - numberOfBins = maximum - minimum; - if(maximum - minimum <= 10) - { - numberOfBins = 100; - } +// numberOfBins = maximum - minimum; +// if(maximum - minimum <= 10) +// { +// numberOfBins = 100; +// } } typename LabelStatisticsFilterType::Pointer labelStatisticsFilter = LabelStatisticsFilterType::New(); labelStatisticsFilter->SetInput( adaptedImage ); labelStatisticsFilter->SetLabelInput( adaptedMaskImage ); labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); labelStatisticsFilter->SetDirectionTolerance( 0.001 ); labelStatisticsFilter->UseHistogramsOn(); labelStatisticsFilter->SetHistogramParameters( numberOfBins, floor(minimum), ceil(maximum) ); //statisticsFilter->GetMinimum() statisticsFilter->GetMaximum() // Add progress listening typedef itk::SimpleMemberCommand< ImageStatisticsCalculator > ITKCommandType; ITKCommandType::Pointer progressListener; progressListener = ITKCommandType::New(); progressListener->SetCallbackFunction( this, &ImageStatisticsCalculator::MaskedStatisticsProgressUpdate ); unsigned long observerTag = labelStatisticsFilter->AddObserver( itk::ProgressEvent(), progressListener ); // Execute filter this->InvokeEvent( itk::StartEvent() ); // Make sure that only the mask region is considered (otherwise, if the mask region is smaller // than the image region, the Update() would result in an exception). labelStatisticsFilter->GetOutput()->SetRequestedRegion( adaptedMaskImage->GetLargestPossibleRegion() ); // Execute the filter try { labelStatisticsFilter->Update(); } catch( const itk::ExceptionObject& e) { mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } catch( const std::exception& e ) { //mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } this->InvokeEvent( itk::EndEvent() ); if( observerTag ) labelStatisticsFilter->RemoveObserver( observerTag ); // Find all relevant labels of mask (other than 0) std::list< int > relevantLabels = labelStatisticsFilter->GetRelevantLabels(); unsigned int i; if ( labelStatisticsFilter->GetMaskingNonEmpty() ) { std::list< int >::iterator it; for ( it = relevantLabels.begin(), i = 0; it != relevantLabels.end(); ++it, ++i ) { Statistics statistics; // restore previous code labelStatisticsFilter->GetHistogram(*it) ; histogramContainer->push_back( HistogramType::ConstPointer( labelStatisticsFilter->GetHistogram( (*it) ) ) ); statistics.SetLabel (*it); statistics.SetN(labelStatisticsFilter->GetCount( *it )); statistics.SetMin(labelStatisticsFilter->GetMinimum( *it )); statistics.SetMax(labelStatisticsFilter->GetMaximum( *it )); statistics.SetMean(labelStatisticsFilter->GetMean( *it )); statistics.SetMedian(labelStatisticsFilter->GetMedian( *it)); statistics.SetMedian(labelStatisticsFilter->GetMedian( *it )); statistics.SetVariance(labelStatisticsFilter->GetVariance( *it )); statistics.SetSigma(labelStatisticsFilter->GetSigma( *it )); statistics.SetSkewness(labelStatisticsFilter->GetSkewness( *it )); statistics.SetKurtosis(labelStatisticsFilter->GetKurtosis( *it )); statistics.SetUniformity( labelStatisticsFilter->GetUniformity( *it )); statistics.SetEntropy( labelStatisticsFilter->GetEntropy( *it )); statistics.SetUPP( labelStatisticsFilter->GetUPP( *it)); statistics.SetMPP( labelStatisticsFilter->GetMPP( *it)); statistics.SetRMS(sqrt( statistics.GetMean() * statistics.GetMean() + statistics.GetSigma() * statistics.GetSigma() )); // restrict image to mask area for min/max index calculation typedef itk::MaskImageFilter< ImageType, MaskImageType, ImageType > MaskImageFilterType; typename MaskImageFilterType::Pointer masker = MaskImageFilterType::New(); bool isMinAndMaxSameValue = (statistics.GetMin() == statistics.GetMax()); // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here double outsideValue = (isMinAndMaxSameValue ? (statistics.GetMax()/2) : (statistics.GetMin()+statistics.GetMax())/2); masker->SetOutsideValue( outsideValue ); masker->SetInput1(adaptedImage); masker->SetInput2(adaptedMaskImage); masker->SetCoordinateTolerance( 0.001 ); masker->SetDirectionTolerance( 0.001 ); masker->Update(); // get index of minimum and maximum typedef itk::MinimumMaximumImageCalculator< ImageType > MinMaxFilterType; typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); minMaxFilter->SetImage( masker->GetOutput() ); unsigned long observerTag2 = minMaxFilter->AddObserver( itk::ProgressEvent(), progressListener ); minMaxFilter->Compute(); minMaxFilter->RemoveObserver( observerTag2 ); this->InvokeEvent( itk::EndEvent() ); typename MinMaxFilterType::IndexType tempMaxIndex = minMaxFilter->GetIndexOfMaximum(); // bug 17962: following is a workaround for the case when min and max are the same, we can probably find a nicer way here typename MinMaxFilterType::IndexType tempMinIndex = (isMinAndMaxSameValue ? minMaxFilter->GetIndexOfMaximum() : minMaxFilter->GetIndexOfMinimum()); // FIX BUG 14644 //If a PlanarFigure is used for segmentation the //adaptedImage is a single slice (2D). Adding the // 3. dimension. - // FIX Bug 19625 pt. 1 - // m_Image will yield 4 coordinates if it has 4 Dimensions, the min max value is however only searched for in one timeSliceImage (3D) - // the 3D min/max coordinates are then set and the 4th coordinate is blank, causing random numbers to appear in the GUI - // (Fix = replace m_Image with VImageDimension (Dimension of image)) - vnl_vector maxIndex; vnl_vector minIndex; - unsigned int imageDimension = m_Image->GetDimension(); - unsigned int maxDimensionsToDisplay = 3; // we do not want to display the time step as part of the coordinates - maxIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); - minIndex.set_size(std::min(imageDimension, maxDimensionsToDisplay)); + maxIndex.set_size(m_Image->GetDimension()); + minIndex.set_size(m_Image->GetDimension()); - if (m_MaskingMode == MASKING_MODE_PLANARFIGURE && imageDimension == 3) + if (m_MaskingMode == MASKING_MODE_PLANARFIGURE && m_Image->GetDimension()==3) { maxIndex[m_PlanarFigureCoordinate0] = tempMaxIndex[0]; maxIndex[m_PlanarFigureCoordinate1] = tempMaxIndex[1]; maxIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; minIndex[m_PlanarFigureCoordinate0] = tempMinIndex[0] ; minIndex[m_PlanarFigureCoordinate1] = tempMinIndex[1]; minIndex[m_PlanarFigureAxis] = m_PlanarFigureSlice; } else { for (unsigned int i = 0; ipush_back( statistics ); } } else { histogramContainer->push_back( HistogramType::ConstPointer( m_EmptyHistogram ) ); statisticsContainer->push_back( Statistics() ); } } template ImageStatisticsCalculator::ImageExtrema ImageStatisticsCalculator::CalculateExtremaWorld( const itk::Image *inputImage, itk::Image *maskImage, double neccessaryDistanceToImageBorderInMM, unsigned int label) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; typedef itk::ImageRegionConstIteratorWithIndex MaskImageIteratorType; typedef itk::ImageRegionConstIteratorWithIndex InputImageIndexIteratorType; typename ImageType::SpacingType spacing = inputImage->GetSpacing(); ImageExtrema minMax; minMax.Defined = false; minMax.MaxIndex.set_size(VImageDimension); minMax.MaxIndex.set_size(VImageDimension); typename ImageType::RegionType allowedExtremaRegion = inputImage->GetLargestPossibleRegion(); bool keepDistanceToImageBorders( neccessaryDistanceToImageBorderInMM > 0 ); if (keepDistanceToImageBorders) { long distanceInPixels[VImageDimension]; for(unsigned short dimension = 0; dimension < VImageDimension; ++dimension) { // To confirm that the whole hotspot is inside the image we have to keep a specific distance to the image-borders, which is as long as // the radius. To get the amount of indices we divide the radius by spacing and add 0.5 because voxels are center based: // For example with a radius of 2.2 and a spacing of 1 two indices are enough because 2.2 / 1 + 0.5 = 2.7 => 2. // But with a radius of 2.7 we need 3 indices because 2.7 / 1 + 0.5 = 3.2 => 3 distanceInPixels[dimension] = int( neccessaryDistanceToImageBorderInMM / spacing[dimension] + 0.5); } allowedExtremaRegion.ShrinkByRadius(distanceInPixels); } InputImageIndexIteratorType imageIndexIt(inputImage, allowedExtremaRegion); float maxValue = itk::NumericTraits::min(); float minValue = itk::NumericTraits::max(); typename ImageType::IndexType maxIndex; typename ImageType::IndexType minIndex; for(unsigned short i = 0; i < VImageDimension; ++i) { maxIndex[i] = 0; minIndex[i] = 0; } if (maskImage != nullptr) { MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); typename ImageType::IndexType imageIndex; typename ImageType::PointType worldPosition; typename ImageType::IndexType maskIndex; for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { imageIndex = maskIndex = maskIt.GetIndex(); if(maskIt.Get() == label) { if( allowedExtremaRegion.IsInside(imageIndex) ) { imageIndexIt.SetIndex( imageIndex ); double value = imageIndexIt.Get(); minMax.Defined = true; //Calculate minimum, maximum and corresponding index-values if( value > maxValue ) { maxIndex = imageIndexIt.GetIndex(); maxValue = value; } if(value < minValue ) { minIndex = imageIndexIt.GetIndex(); minValue = value; } } } } } else { for(imageIndexIt.GoToBegin(); !imageIndexIt.IsAtEnd(); ++imageIndexIt) { double value = imageIndexIt.Get(); minMax.Defined = true; //Calculate minimum, maximum and corresponding index-values if( value > maxValue ) { maxIndex = imageIndexIt.GetIndex(); maxValue = value; } if(value < minValue ) { minIndex = imageIndexIt.GetIndex(); minValue = value; } } } minMax.MaxIndex.set_size(VImageDimension); minMax.MinIndex.set_size(VImageDimension); for(unsigned int i = 0; i < minMax.MaxIndex.size(); ++i) { minMax.MaxIndex[i] = maxIndex[i]; } for(unsigned int i = 0; i < minMax.MinIndex.size(); ++i) { minMax.MinIndex[i] = minIndex[i]; } minMax.Max = maxValue; minMax.Min = minValue; return minMax; } template itk::Size ImageStatisticsCalculator ::CalculateConvolutionKernelSize(double spacing[VImageDimension], double radiusInMM) { typedef itk::Image< float, VImageDimension > KernelImageType; typedef typename KernelImageType::SizeType SizeType; SizeType maskSize; for(unsigned int i = 0; i < VImageDimension; ++i) { maskSize[i] = static_cast( 2 * radiusInMM / spacing[i]); // We always want an uneven size to have a clear center point in the convolution mask if(maskSize[i] % 2 == 0 ) { ++maskSize[i]; } } return maskSize; } template itk::SmartPointer< itk::Image > ImageStatisticsCalculator ::GenerateHotspotSearchConvolutionKernel(double mmPerPixel[VImageDimension], double radiusInMM) { std::stringstream ss; for (unsigned int i = 0; i < VImageDimension; ++i) { ss << mmPerPixel[i]; if (i < VImageDimension -1) ss << ","; } MITK_DEBUG << "Update convolution kernel for spacing (" << ss.str() << ") and radius " << radiusInMM << "mm"; double radiusInMMSquared = radiusInMM * radiusInMM; typedef itk::Image< float, VImageDimension > KernelImageType; typename KernelImageType::Pointer convolutionKernel = KernelImageType::New(); // Calculate size and allocate mask image typedef typename KernelImageType::SizeType SizeType; SizeType maskSize = this->CalculateConvolutionKernelSize(mmPerPixel, radiusInMM); Point3D convolutionMaskCenterIndex; convolutionMaskCenterIndex.Fill(0.0); for(unsigned int i = 0; i < VImageDimension; ++i) { convolutionMaskCenterIndex[i] = 0.5 * (double)(maskSize[i]-1); } typedef typename KernelImageType::IndexType IndexType; IndexType maskIndex; maskIndex.Fill(0); typedef typename KernelImageType::RegionType RegionType; RegionType maskRegion; maskRegion.SetSize(maskSize); maskRegion.SetIndex(maskIndex); convolutionKernel->SetRegions(maskRegion); convolutionKernel->SetSpacing(mmPerPixel); convolutionKernel->Allocate(); // Fill mask image values by subsampling the image grid typedef itk::ImageRegionIteratorWithIndex MaskIteratorType; MaskIteratorType maskIt(convolutionKernel,maskRegion); int numberOfSubVoxelsPerDimension = 2; // per dimension! int numberOfSubVoxels = ::pow( static_cast(numberOfSubVoxelsPerDimension), static_cast(VImageDimension) ); double subVoxelSizeInPixels = 1.0 / (double)numberOfSubVoxelsPerDimension; double valueOfOneSubVoxel = 1.0 / (double)numberOfSubVoxels; double maskValue = 0.0; Point3D subVoxelIndexPosition; double distanceSquared = 0.0; typedef itk::ContinuousIndex ContinuousIndexType; for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { ContinuousIndexType indexPoint(maskIt.GetIndex()); Point3D voxelPosition; for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) { voxelPosition[dimension] = indexPoint[dimension]; } maskValue = 0.0; Vector3D subVoxelOffset; subVoxelOffset.Fill(0.0); // iterate sub-voxels by iterating all possible offsets for (subVoxelOffset[0] = -0.5 + subVoxelSizeInPixels / 2.0; subVoxelOffset[0] < +0.5; subVoxelOffset[0] += subVoxelSizeInPixels) { for (subVoxelOffset[1] = -0.5 + subVoxelSizeInPixels / 2.0; subVoxelOffset[1] < +0.5; subVoxelOffset[1] += subVoxelSizeInPixels) { for (subVoxelOffset[2] = -0.5 + subVoxelSizeInPixels / 2.0; subVoxelOffset[2] < +0.5; subVoxelOffset[2] += subVoxelSizeInPixels) { subVoxelIndexPosition = voxelPosition + subVoxelOffset; // this COULD be integrated into the for-loops if neccessary (add voxelPosition to initializer and end condition) distanceSquared = (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] * (subVoxelIndexPosition[0]-convolutionMaskCenterIndex[0]) * mmPerPixel[0] + (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] * (subVoxelIndexPosition[1]-convolutionMaskCenterIndex[1]) * mmPerPixel[1] + (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2] * (subVoxelIndexPosition[2]-convolutionMaskCenterIndex[2]) * mmPerPixel[2]; if (distanceSquared <= radiusInMMSquared) { maskValue += valueOfOneSubVoxel; } } } } maskIt.Set( maskValue ); } return convolutionKernel; } template itk::SmartPointer > ImageStatisticsCalculator::GenerateConvolutionImage( const itk::Image* inputImage ) { double mmPerPixel[VImageDimension]; for (unsigned int dimension = 0; dimension < VImageDimension; ++dimension) { mmPerPixel[dimension] = inputImage->GetSpacing()[dimension]; } // update convolution kernel typedef itk::Image< float, VImageDimension > KernelImageType; typename KernelImageType::Pointer convolutionKernel = this->GenerateHotspotSearchConvolutionKernel(mmPerPixel, m_HotspotRadiusInMM); // update convolution image typedef itk::Image< TPixel, VImageDimension > InputImageType; typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; typedef itk::FFTConvolutionImageFilter ConvolutionFilterType; typename ConvolutionFilterType::Pointer convolutionFilter = ConvolutionFilterType::New(); typedef itk::ConstantBoundaryCondition BoundaryConditionType; BoundaryConditionType boundaryCondition; boundaryCondition.SetConstant(0.0); if (GetHotspotMustBeCompletlyInsideImage()) { // overwrite default boundary condition convolutionFilter->SetBoundaryCondition(&boundaryCondition); } convolutionFilter->SetInput(inputImage); convolutionFilter->SetKernelImage(convolutionKernel); convolutionFilter->SetNormalize(true); MITK_DEBUG << "Update Convolution image for hotspot search"; convolutionFilter->UpdateLargestPossibleRegion(); typename ConvolutionImageType::Pointer convolutionImage = convolutionFilter->GetOutput(); convolutionImage->SetSpacing( inputImage->GetSpacing() ); // only workaround because convolution filter seems to ignore spacing of input image m_HotspotRadiusInMMChanged = false; return convolutionImage; } template < typename TPixel, unsigned int VImageDimension> void ImageStatisticsCalculator ::FillHotspotMaskPixels( itk::Image* maskImage, itk::Point sphereCenter, double sphereRadiusInMM) { typedef itk::Image< TPixel, VImageDimension > MaskImageType; typedef itk::ImageRegionIteratorWithIndex MaskImageIteratorType; MaskImageIteratorType maskIt(maskImage, maskImage->GetLargestPossibleRegion()); typename MaskImageType::IndexType maskIndex; typename MaskImageType::PointType worldPosition; for(maskIt.GoToBegin(); !maskIt.IsAtEnd(); ++maskIt) { maskIndex = maskIt.GetIndex(); maskImage->TransformIndexToPhysicalPoint(maskIndex, worldPosition); maskIt.Set( worldPosition.EuclideanDistanceTo(sphereCenter) <= sphereRadiusInMM ? 1 : 0 ); } } template < typename TPixel, unsigned int VImageDimension> ImageStatisticsCalculator::Statistics ImageStatisticsCalculator::CalculateHotspotStatistics( const itk::Image* inputImage, itk::Image* maskImage, double radiusInMM, bool& isHotspotDefined, unsigned int label) { // get convolution image (updated in GenerateConvolutionImage()) typedef itk::Image< TPixel, VImageDimension > InputImageType; typedef itk::Image< TPixel, VImageDimension > ConvolutionImageType; typedef itk::Image< float, VImageDimension > KernelImageType; typedef itk::Image< unsigned short, VImageDimension > MaskImageType; //typename ConvolutionImageType::Pointer convolutionImage = dynamic_cast(this->GenerateConvolutionImage(inputImage)); typename ConvolutionImageType::Pointer convolutionImage = this->GenerateConvolutionImage(inputImage); if (convolutionImage.IsNull()) { MITK_ERROR << "Empty convolution image in CalculateHotspotStatistics(). We should never reach this state (logic error)."; throw std::logic_error("Empty convolution image in CalculateHotspotStatistics()"); } // find maximum in convolution image, given the current mask double requiredDistanceToBorder = m_HotspotMustBeCompletelyInsideImage ? m_HotspotRadiusInMM : -1.0; ImageExtrema convolutionImageInformation = CalculateExtremaWorld(convolutionImage.GetPointer(), maskImage, requiredDistanceToBorder, label); isHotspotDefined = convolutionImageInformation.Defined; if (!isHotspotDefined) { m_EmptyStatistics.Reset(VImageDimension); MITK_ERROR << "No origin of hotspot-sphere was calculated! Returning empty statistics"; return m_EmptyStatistics; } else { // create a binary mask around the "hotspot" region, fill the shape of a sphere around our hotspot center typedef itk::ImageDuplicator< InputImageType > DuplicatorType; typename DuplicatorType::Pointer copyMachine = DuplicatorType::New(); copyMachine->SetInputImage(inputImage); copyMachine->Update(); typedef itk::CastImageFilter< InputImageType, MaskImageType > CastFilterType; typename CastFilterType::Pointer caster = CastFilterType::New(); caster->SetInput( copyMachine->GetOutput() ); caster->Update(); typename MaskImageType::Pointer hotspotMaskITK = caster->GetOutput(); typedef typename InputImageType::IndexType IndexType; IndexType maskCenterIndex; for (unsigned int d =0; d< VImageDimension;++d) maskCenterIndex[d]=convolutionImageInformation.MaxIndex[d]; typename ConvolutionImageType::PointType maskCenter; inputImage->TransformIndexToPhysicalPoint(maskCenterIndex,maskCenter); this->FillHotspotMaskPixels(hotspotMaskITK.GetPointer(), maskCenter, radiusInMM); // calculate statistics within the binary mask typedef itk::ExtendedLabelStatisticsImageFilter< InputImageType, MaskImageType> LabelStatisticsFilterType; typename LabelStatisticsFilterType::Pointer labelStatisticsFilter; labelStatisticsFilter = LabelStatisticsFilterType::New(); labelStatisticsFilter->SetInput( inputImage ); labelStatisticsFilter->SetLabelInput( hotspotMaskITK ); labelStatisticsFilter->SetCoordinateTolerance( 0.001 ); labelStatisticsFilter->SetDirectionTolerance( 0.001 ); labelStatisticsFilter->Update(); Statistics hotspotStatistics; hotspotStatistics.SetHotspotIndex(convolutionImageInformation.MaxIndex); hotspotStatistics.SetMean(convolutionImageInformation.Max); if ( labelStatisticsFilter->HasLabel( 1 ) ) { hotspotStatistics.SetLabel (1); hotspotStatistics.SetN(labelStatisticsFilter->GetCount(1)); hotspotStatistics.SetMin(labelStatisticsFilter->GetMinimum(1)); hotspotStatistics.SetMax(labelStatisticsFilter->GetMaximum(1)); hotspotStatistics.SetMedian(labelStatisticsFilter->GetMedian(1)); hotspotStatistics.SetVariance(labelStatisticsFilter->GetVariance(1)); hotspotStatistics.SetSigma(labelStatisticsFilter->GetSigma(1)); hotspotStatistics.SetRMS(sqrt( hotspotStatistics.GetMean() * hotspotStatistics.GetMean() + hotspotStatistics.GetSigma() * hotspotStatistics.GetSigma() )); MITK_DEBUG << "Statistics for inside hotspot: Mean " << hotspotStatistics.GetMean() << ", SD " << hotspotStatistics.GetSigma() << ", Max " << hotspotStatistics.GetMax() << ", Min " << hotspotStatistics.GetMin(); } else { MITK_ERROR << "Uh oh! Unable to calculate statistics for hotspot region..."; return m_EmptyStatistics; } return hotspotStatistics; } } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateMaskFromPlanarFigure( const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::CastImageFilter< ImageType, MaskImage2DType > CastFilterType; // Generate mask image as new image with same header as input image and // initialize with 1. typename CastFilterType::Pointer castFilter = CastFilterType::New(); castFilter->SetInput( image ); castFilter->Update(); castFilter->GetOutput()->FillBuffer( 1 ); // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. // These points are used by the vtkLassoStencilSource to create // a vtkImageStencil. const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); const mitk::BaseGeometry *imageGeometry3D = m_Image->GetGeometry( 0 ); // If there is a second poly line in a closed planar figure, treat it as a hole. PlanarFigure::PolyLineType planarFigureHolePolyline; if (m_PlanarFigure->GetPolyLinesSize() == 2) planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); // Determine x- and y-dimensions depending on principal axis + // TODO use plane geometry normal to determine that automatically, then check whether the PF is aligned with one of the three principal axis int i0, i1; switch ( axis ) { case 0: i0 = 1; i1 = 2; break; case 1: i0 = 0; i1 = 2; break; case 2: default: i0 = 0; i1 = 1; break; } m_PlanarFigureCoordinate0= i0; m_PlanarFigureCoordinate1= i1; // store the polyline contour as vtkPoints object bool outOfBounds = false; vtkSmartPointer points = vtkSmartPointer::New(); typename PlanarFigure::PolyLineType::const_iterator it; for ( it = planarFigurePolyline.begin(); it != planarFigurePolyline.end(); ++it ) { Point3D point3D; // Convert 2D point back to the local index coordinates of the selected // image + // Fabian: From PlaneGeometry documentation: + // Converts a 2D point given in mm (pt2d_mm) relative to the upper-left corner of the geometry into the corresponding world-coordinate (a 3D point in mm, pt3d_mm). + // To convert a 2D point given in units (e.g., pixels in case of an image) into a 2D point given in mm (as required by this method), use IndexToWorld. planarFigurePlaneGeometry->Map( *it, point3D ); // Polygons (partially) outside of the image bounds can not be processed // further due to a bug in vtkPolyDataToImageStencil if ( !imageGeometry3D->IsInside( point3D ) ) { outOfBounds = true; } + // Fabian: Why convert to index coordinates? imageGeometry3D->WorldToIndex( point3D, point3D ); points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); } vtkSmartPointer holePoints = nullptr; if (!planarFigureHolePolyline.empty()) { holePoints = vtkSmartPointer::New(); Point3D point3D; PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); for (it = planarFigureHolePolyline.begin(); it != end; ++it) { + // Fabian: same as above planarFigurePlaneGeometry->Map(*it, point3D); imageGeometry3D->WorldToIndex(point3D, point3D); holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); } } // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero double bounds[6] = {0, 0, 0, 0, 0, 0}; points->GetBounds( bounds ); bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent if ( m_PlanarFigure->IsClosed() && ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) { mitkThrow() << "Figure has a zero area and cannot be used for masking."; } if ( outOfBounds ) { throw std::runtime_error( "Figure at least partially outside of image bounds!" ); } // create a vtkLassoStencilSource and set the points of the Polygon vtkSmartPointer lassoStencil = vtkSmartPointer::New(); lassoStencil->SetShapeToPolygon(); lassoStencil->SetPoints( points ); vtkSmartPointer holeLassoStencil = nullptr; if (holePoints.GetPointer() != nullptr) { holeLassoStencil = vtkSmartPointer::New(); holeLassoStencil->SetShapeToPolygon(); holeLassoStencil->SetPoints(holePoints); } // Export from ITK to VTK (to use a VTK filter) typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; typename ImageExportType::Pointer itkExporter = ImageExportType::New(); itkExporter->SetInput( castFilter->GetOutput() ); vtkSmartPointer vtkImporter = vtkSmartPointer::New(); this->ConnectPipelines( itkExporter, vtkImporter ); // Apply the generated image stencil to the input image vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); imageStencilFilter->ReverseStencilOff(); imageStencilFilter->SetBackgroundValue( 0 ); imageStencilFilter->Update(); vtkSmartPointer holeStencilFilter = nullptr; if (holeLassoStencil.GetPointer() != nullptr) { holeStencilFilter = vtkSmartPointer::New(); holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); holeStencilFilter->ReverseStencilOn(); holeStencilFilter->SetBackgroundValue(0); holeStencilFilter->Update(); } // Export from VTK back to ITK vtkSmartPointer vtkExporter = vtkSmartPointer::New(); vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr ? imageStencilFilter->GetOutputPort() : holeStencilFilter->GetOutputPort()); vtkExporter->Update(); typename ImageImportType::Pointer itkImporter = ImageImportType::New(); this->ConnectPipelines( vtkExporter, itkImporter ); itkImporter->Update(); typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage( itkImporter->GetOutput() ); duplicator->Update(); // Store mask m_InternalImageMask2D = duplicator->GetOutput(); } void ImageStatisticsCalculator::UnmaskedStatisticsProgressUpdate() { // Need to throw away every second progress event to reach a final count of // 100 since two consecutive filters are used in this case static int updateCounter = 0; if ( updateCounter++ % 2 == 0 ) { this->InvokeEvent( itk::ProgressEvent() ); } } void ImageStatisticsCalculator::MaskedStatisticsProgressUpdate() { this->InvokeEvent( itk::ProgressEvent() ); } } diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.h b/Modules/ImageStatistics/old/mitkImageStatisticsCalculator.h similarity index 100% copy from Modules/ImageStatistics/mitkImageStatisticsCalculator.h copy to Modules/ImageStatistics/old/mitkImageStatisticsCalculator.h diff --git a/Modules/MaskGenerator/CMakeLists.txt b/Modules/MaskGenerator/CMakeLists.txt new file mode 100644 index 0000000000..d073a4481d --- /dev/null +++ b/Modules/MaskGenerator/CMakeLists.txt @@ -0,0 +1,13 @@ +MITK_CREATE_MODULE( + DEPENDS MitkImageStatistics + #PACKAGE_DEPENDS + # PUBLIC ITK|ITKIOXML + # PRIVATE ITK|ITKVTK+ITKConvolution + # WARNINGS_AS_ERRORS +) + +if(BUILD_TESTING) + + # add_subdirectory(Testing) + +endif(BUILD_TESTING) diff --git a/Modules/MaskGenerator/files.cmake b/Modules/MaskGenerator/files.cmake new file mode 100644 index 0000000000..36e7a3e616 --- /dev/null +++ b/Modules/MaskGenerator/files.cmake @@ -0,0 +1,7 @@ +set(CPP_FILES + +) + +set(H_FILES + +) diff --git a/Modules/MaskGenerator/mitkDeleteMe.cpp b/Modules/MaskGenerator/mitkDeleteMe.cpp new file mode 100644 index 0000000000..8f5253af72 --- /dev/null +++ b/Modules/MaskGenerator/mitkDeleteMe.cpp @@ -0,0 +1,6 @@ +#include + +void deleteMe::poebelMalHerum() +{ + std::cout << "was fuer ein dummes Modul" << std::endl; +} \ No newline at end of file diff --git a/Modules/MaskGenerator/mitkDeleteMe.h b/Modules/MaskGenerator/mitkDeleteMe.h new file mode 100644 index 0000000000..e2b9473478 --- /dev/null +++ b/Modules/MaskGenerator/mitkDeleteMe.h @@ -0,0 +1,7 @@ +#include + +class deleteMe +{ +public: +void poebelMalHerum(); +}; diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 282fbb5391..254c0061fb 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,81 +1,81 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(mitk_modules Core CommandLine AppUtil DCMTesting RDF LegacyIO DataTypesExt Overlays LegacyGL AlgorithmsExt MapperExt DICOMReader DICOMReaderServices DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction - ImageStatistics LegacyAdaptors SceneSerialization Gizmo GraphAlgorithms Multilabel + ImageStatistics ContourModel SurfaceInterpolation Segmentation PlanarFigureSegmentation OpenViewCore QtWidgets QtWidgetsExt QtWidgetsWeb QmlItems SegmentationUI DiffusionImaging GPGPU OpenIGTLink IGTBase IGT CameraCalibration RigidRegistration RigidRegistrationUI DeformableRegistration DeformableRegistrationUI OpenCL OpenCVVideoSupport QtOverlays ToFHardware ToFProcessing ToFUI US USUI DicomUI Simulation Remeshing Python QtPython Persistence OpenIGTLinkUI IGTUI VtkShaders DicomRT RTUI IOExt XNAT TubeGraph BiophotonicsHardware Classification TumorInvasionAnalysis MatchPointRegistration MatchPointRegistrationUI BoundingShape ) if(MITK_ENABLE_PIC_READER) list(APPEND mitk_modules IpPicSupportIO) endif() diff --git a/Modules/QtWidgetsWeb/include/QmitkHistogramJSWidget.h b/Modules/QtWidgetsWeb/include/QmitkHistogramJSWidget.h index 042a53cfbe..64d57cefd5 100644 --- a/Modules/QtWidgetsWeb/include/QmitkHistogramJSWidget.h +++ b/Modules/QtWidgetsWeb/include/QmitkHistogramJSWidget.h @@ -1,273 +1,277 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QMITKHISTOGRAMJSWIDGET_H #define QMITKHISTOGRAMJSWIDGET_H #include "MitkQtWidgetsWebExports.h" #include "mitkImage.h" #include "mitkPlanarFigure.h" #include #include #include #include #include #include /** * \brief Widget which shows a histogram using JavaScript. * * This class is a QWebView. It shows the histogram for a selected image * or segmentation. It also can display an intensity profile for * path elements, which lais over an image. */ class MITKQTWIDGETSWEB_EXPORT QmitkHistogramJSWidget : public QWebEngineView { Q_OBJECT /** * \brief Measurement property. * * This property is used in JavaScript as member of the current object. * It holds a QList, containing the measurements of the current histogram. * @see GetMeasurement() */ Q_PROPERTY(QList measurement READ GetMeasurement) /** * \brief Frequency property. * * This property is used in JavaScript as member of the current object. * It holds a QList, containing the frequencies of the current histogram. * @see GetFrequency() */ Q_PROPERTY(QList frequency READ GetFrequency) /** * \brief Line graph property. * * This property is used in JavaScript as member of the current object. * It holds a boolean, which sais wether to use a line or not. * @see GetUseLineGraph() */ Q_PROPERTY(bool useLineGraph READ GetUseLineGraph) /** * @brief intensity profile property. * * This property is used in JavaScript as member of the current object. * It holds a boolean, which says whether to use an intensity profile or not. * @see GetIntensityProfile() */ Q_PROPERTY(bool intensityProfile READ GetIntensityProfile) public: typedef mitk::Image::HistogramType HistogramType; typedef mitk::Image::HistogramType::ConstIterator HistogramConstIteratorType; typedef itk::PolyLineParametricPath<3> ParametricPathType; typedef itk::ParametricPath<3>::Superclass PathType; typedef mitk::PlanarFigure::PolyLineType VertexContainerType; explicit QmitkHistogramJSWidget(QWidget *parent = nullptr); ~QmitkHistogramJSWidget(); /** * \brief Event which notifies a change of the widget size. * * Reimplemented from QWebView::resizeEvent(), * reloads the webframe */ void resizeEvent(QResizeEvent *resizeEvent) override; /** * \brief Calculates the histogram. * * This function removes all frequencies of 0 until the first bin and behind the last bin. * It writes the measurement and frequency, which are given from the HistogramType, into * m_Measurement and m_Frequency. * The SignalDataChanged is called, to update the information, which is displayed in the webframe. */ void ComputeHistogram(HistogramType *histogram); /** * \brief Calculates the intensityprofile. * * If an image and a pathelement are set, this function * calculates an intensity profile for a pathelement which lies over an image. * Sets m_IntensityProfile and m_UseLineGraph to true. * The SignalDataChanged is called, to update the information, which is displayed in the webframe. */ void ComputeIntensityProfile(unsigned int timeStep = 0, bool computeStatistics = false); /** * \brief Clears the Histogram. * * This function clears the data and calls SignalDataChanged to update * the displayed information in the webframe. */ void ClearHistogram(); /** * \brief Getter for measurement. * * @return List of measurements. */ QList GetMeasurement(); /** * \brief Getter for frequency. * * @return List of frequencies. */ QList GetFrequency(); /** * \brief Getter for uselineGraph. * * @return True if a linegraph should be used. */ bool GetUseLineGraph(); /** * \brief Getter for intensity profile. * * @return True if current histogram is an intensityprofile */ bool GetIntensityProfile(); - mitk::ImageStatisticsCalculator::Statistics &GetStatistics() { return m_Statistics; }; + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer GetStatistics() + { + return m_Statistics; + }; + /** * \brief Setter for reference image. * * @param image The corresponding image for an intensity profile. */ void SetImage(mitk::Image *image); /** * \brief Setter for planarFigure. * * @param planarFigure The pathelement for an intensity profile. */ void SetPlanarFigure(const mitk::PlanarFigure *planarFigure); private: /** * \brief List of frequencies. * * A QList which holds the frequencies of the current histogram * or holds the intensities of current intensity profile. */ QList m_Frequency; /** * \brief List of measurements. * * A QList which holds the measurements of the current histogram * or holds the distances of current intensity profile. */ QList m_Measurement; - mitk::ImageStatisticsCalculator::Statistics m_Statistics; + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer m_Statistics; /** * \brief Reference image. * * Holds the image to calculate an intensity profile. */ mitk::Image::Pointer m_Image; /** * \brief Pathelement. * * Holds a not closed planar figure to calculate an intensity profile. */ mitk::PlanarFigure::ConstPointer m_PlanarFigure; bool m_UseLineGraph; bool m_IntensityProfile; /** * Holds the current histogram */ HistogramType::ConstPointer m_Histogram; /** * Path derived either form user-specified path or from PlanarFigure-generated * path */ PathType::ConstPointer m_DerivedPath; /** * Parametric path as generated from PlanarFigure */ ParametricPathType::Pointer m_ParametricPath; /** * \brief Clears data. * * Clears the QLists m_Measurement and m_Frequency */ void ClearData(); QWebEnginePage *m_Page; private slots: /** * \brief Adds an object to JavaScript. * * Adds an object of the widget to JavaScript. * By using this object JavaScript can react to the signals of the widget * and can access the QProperties as members of the object. */ void AddJSObject(); public slots: /** * \brief Slot for radiobutton m_barRadioButton. * * Sets m_UseLineGraph to false. * Calls signal GraphChanged to update the graph in the webframe. */ void OnBarRadioButtonSelected(); /** * \brief Slot for radiobutton m_lineRadioButton. * * Sets m_UseLineGraph to true. * Calls signal GraphChanged to update the graph in the webframe. */ void OnLineRadioButtonSelected(); signals: /** * \brief Signal data has changed. * * It has to be called when the data of the histogram or intensity profile has changed. */ void SignalDataChanged(); /** * \brief Signal graph has changed. * * It has to be called when the graph changed from barchart to linegraph. Vice versa. */ void SignalGraphChanged(); }; #endif diff --git a/Modules/QtWidgetsWeb/src/QmitkHistogramJSWidget.cpp b/Modules/QtWidgetsWeb/src/QmitkHistogramJSWidget.cpp index e24898155c..484555996e 100644 --- a/Modules/QtWidgetsWeb/src/QmitkHistogramJSWidget.cpp +++ b/Modules/QtWidgetsWeb/src/QmitkHistogramJSWidget.cpp @@ -1,241 +1,242 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkHistogramJSWidget.h" #include "mitkBaseRenderer.h" #include "mitkExtractSliceFilter.h" #include "mitkImageTimeSelector.h" #include "mitkPixelTypeMultiplex.h" #include "mitkRenderingManager.h" #include #include QmitkHistogramJSWidget::QmitkHistogramJSWidget(QWidget *parent) : QWebEngineView(parent) { // set histogram type to barchart in first instance m_UseLineGraph = false; m_Page = new QWebEnginePage(this); setPage(m_Page); // set html from source // connect(page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(AddJSObject())); QUrl myUrl = QUrl("qrc:///QtWidgetsWeb/Histogram.html"); setUrl(myUrl); // set Scrollbars to be always disabled // page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); // page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); m_ParametricPath = ParametricPathType::New(); + m_Statistics = mitk::ImageStatisticsCalculator::StatisticsContainer::New(); } QmitkHistogramJSWidget::~QmitkHistogramJSWidget() { } // adds an Object of Type QmitkHistogramJSWidget to the JavaScript, using QtWebkitBridge void QmitkHistogramJSWidget::AddJSObject() { // page()->mainFrame()->addToJavaScriptWindowObject(QString("histogramData"), this); } // reloads WebView, everytime its size has been changed, so the size of the Histogram fits to the size of the widget void QmitkHistogramJSWidget::resizeEvent(QResizeEvent *resizeEvent) { QWebEngineView::resizeEvent(resizeEvent); // workaround for Qt Bug: https://bugs.webkit.org/show_bug.cgi?id=75984 // page()->mainFrame()->evaluateJavaScript("disconnectSignals()"); this->reload(); } // method to expose data to JavaScript by using properties void QmitkHistogramJSWidget::ComputeHistogram(HistogramType *histogram) { m_Histogram = histogram; HistogramConstIteratorType startIt = m_Histogram->End(); HistogramConstIteratorType endIt = m_Histogram->End(); HistogramConstIteratorType it = m_Histogram->Begin(); ClearData(); unsigned int i = 0; bool firstValue = false; // removes frequencies of 0, which are outside the first and last bin for (; it != m_Histogram->End(); ++it) { if (it.GetFrequency() > 0.0) { endIt = it; if (!firstValue) { firstValue = true; startIt = it; } } } ++endIt; // generating Lists of measurement and frequencies for (it = startIt; it != endIt; ++it, ++i) { QVariant frequency = QVariant::fromValue(it.GetFrequency()); QVariant measurement = it.GetMeasurementVector()[0]; m_Frequency.insert(i, frequency); m_Measurement.insert(i, measurement); } m_IntensityProfile = false; this->SignalDataChanged(); } void QmitkHistogramJSWidget::ClearData() { m_Frequency.clear(); m_Measurement.clear(); } void QmitkHistogramJSWidget::ClearHistogram() { this->ClearData(); this->SignalDataChanged(); } QList QmitkHistogramJSWidget::GetFrequency() { return m_Frequency; } QList QmitkHistogramJSWidget::GetMeasurement() { return m_Measurement; } bool QmitkHistogramJSWidget::GetUseLineGraph() { return m_UseLineGraph; } void QmitkHistogramJSWidget::OnBarRadioButtonSelected() { if (m_UseLineGraph) { m_UseLineGraph = false; this->SignalGraphChanged(); } } void QmitkHistogramJSWidget::OnLineRadioButtonSelected() { if (!m_UseLineGraph) { m_UseLineGraph = true; this->SignalGraphChanged(); } } void QmitkHistogramJSWidget::SetImage(mitk::Image *image) { m_Image = image; } void QmitkHistogramJSWidget::SetPlanarFigure(const mitk::PlanarFigure *planarFigure) { m_PlanarFigure = planarFigure; } template void ReadPixel(mitk::PixelType, mitk::Image::Pointer image, itk::Index<3> indexPoint, double &value) { if (image->GetDimension() == 2) { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData(0)); itk::Index<2> idx; idx[0] = indexPoint[0]; idx[1] = indexPoint[1]; value = readAccess.GetPixelByIndex(idx); } else if (image->GetDimension() == 3) { mitk::ImagePixelReadAccessor readAccess(image, image->GetVolumeData(0)); itk::Index<3> idx; idx[0] = indexPoint[0]; idx[1] = indexPoint[1]; idx[2] = indexPoint[2]; value = readAccess.GetPixelByIndex(idx); } else { // unhandled } } void QmitkHistogramJSWidget::ComputeIntensityProfile(unsigned int timeStep, bool computeStatistics) { this->ClearData(); m_ParametricPath->Initialize(); if (m_PlanarFigure.IsNull()) { mitkThrow() << "PlanarFigure not set!"; } if (m_Image.IsNull()) { mitkThrow() << "Image not set!"; } mitk::Image::Pointer image; if (m_Image->GetDimension() == 4) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { image = m_Image; } mitk::IntensityProfile::Pointer intensityProfile = mitk::ComputeIntensityProfile(image, const_cast(m_PlanarFigure.GetPointer())); m_Frequency.clear(); m_Measurement.clear(); int i = -1; mitk::IntensityProfile::ConstIterator end = intensityProfile->End(); for (mitk::IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { m_Frequency.push_back(it.GetMeasurementVector()[0]); m_Measurement.push_back(++i); } if (computeStatistics) { mitk::ComputeIntensityProfileStatistics(intensityProfile, m_Statistics); } m_IntensityProfile = true; m_UseLineGraph = true; this->SignalDataChanged(); } bool QmitkHistogramJSWidget::GetIntensityProfile() { return m_IntensityProfile; } diff --git a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp b/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp index 9c16220fe7..0189188fce 100644 --- a/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp +++ b/Modules/Segmentation/Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp @@ -1,198 +1,200 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkFeatureBasedEdgeDetectionFilter.h" #include #include #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include mitk::FeatureBasedEdgeDetectionFilter::FeatureBasedEdgeDetectionFilter() { this->SetNumberOfRequiredInputs(1); this->SetNumberOfIndexedOutputs(1); } mitk::FeatureBasedEdgeDetectionFilter::~FeatureBasedEdgeDetectionFilter() { } void mitk::FeatureBasedEdgeDetectionFilter::GenerateData() { mitk::Image::Pointer image = ImageToUnstructuredGridFilter::GetInput(); if (m_SegmentationMask.IsNull()) { MITK_WARN << "Please set a segmentation mask first" << std::endl; return; } // First create a threshold segmentation of the image. The threshold is determined // by the mean +/- stddev of the pixel values that are covered by the segmentation mask // Compute mean and stdDev based on the current segmentation mitk::ImageStatisticsCalculator::Pointer statCalc = mitk::ImageStatisticsCalculator::New(); - statCalc->SetImage(image); - statCalc->SetMaskingModeToImage(); - statCalc->SetImageMask(m_SegmentationMask); - statCalc->ComputeStatistics(); - - mitk::ImageStatisticsCalculator::Statistics stats = statCalc->GetStatistics(); - double mean = stats.GetMean(); - double stdDev = stats.GetSigma(); + statCalc->SetInputImage(image); + + mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); + imgMask->SetImageMask(m_SegmentationMask); + + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = statCalc->GetStatistics(); + double mean = stats->GetMean(); + double stdDev = stats->GetStd(); double upperThreshold = mean + stdDev; double lowerThreshold = mean - stdDev; // Perform thresholding mitk::Image::Pointer thresholdImage = mitk::Image::New(); AccessByItk_3(image.GetPointer(), ITKThresholding, lowerThreshold, upperThreshold, thresholdImage) mitk::ProgressBar::GetInstance() ->Progress(2); // Postprocess threshold segmentation // First a closing will be executed mitk::Image::Pointer closedImage = mitk::Image::New(); AccessByItk_1(thresholdImage, ThreadedClosing, closedImage); // Then we will holes that might exist mitk::MorphologicalOperations::FillHoles(closedImage); mitk::ProgressBar::GetInstance()->Progress(); // Extract the binary edges of the resulting segmentation mitk::Image::Pointer edgeImage = mitk::Image::New(); AccessByItk_1(closedImage, ContourSearch, edgeImage); // Convert the edge image into an unstructured grid mitk::ImageToUnstructuredGridFilter::Pointer i2UFilter = mitk::ImageToUnstructuredGridFilter::New(); i2UFilter->SetInput(edgeImage); i2UFilter->SetThreshold(1.0); i2UFilter->Update(); m_PointGrid = this->GetOutput(); if (m_PointGrid.IsNull()) m_PointGrid = mitk::UnstructuredGrid::New(); m_PointGrid->SetVtkUnstructuredGrid(i2UFilter->GetOutput()->GetVtkUnstructuredGrid()); mitk::ProgressBar::GetInstance()->Progress(); } template void mitk::FeatureBasedEdgeDetectionFilter::ThreadedClosing(itk::Image *originalImage, mitk::Image::Pointer &result) { typedef itk::BinaryBallStructuringElement myKernelType; myKernelType ball; ball.SetRadius(1); ball.CreateStructuringElement(); typedef typename itk::Image ImageType; typename itk::DilateObjectMorphologyImageFilter::Pointer dilationFilter = itk::DilateObjectMorphologyImageFilter::New(); dilationFilter->SetInput(originalImage); dilationFilter->SetKernel(ball); dilationFilter->Update(); typename itk::Image::Pointer dilatedImage = dilationFilter->GetOutput(); typename itk::ErodeObjectMorphologyImageFilter::Pointer erodeFilter = itk::ErodeObjectMorphologyImageFilter::New(); erodeFilter->SetInput(dilatedImage); erodeFilter->SetKernel(ball); erodeFilter->Update(); mitk::GrabItkImageMemory(erodeFilter->GetOutput(), result); } template void mitk::FeatureBasedEdgeDetectionFilter::ContourSearch(itk::Image *originalImage, mitk::Image::Pointer &result) { typedef itk::Image ImageType; typedef itk::BinaryContourImageFilter binaryContourImageFilterType; typedef unsigned short OutputPixelType; typedef itk::Image OutputImageType; typename binaryContourImageFilterType::Pointer binaryContourFilter = binaryContourImageFilterType::New(); binaryContourFilter->SetInput(originalImage); binaryContourFilter->SetForegroundValue(1); binaryContourFilter->SetBackgroundValue(0); binaryContourFilter->Update(); typename itk::Image::Pointer itkImage = itk::Image::New(); itkImage->Graft(binaryContourFilter->GetOutput()); mitk::GrabItkImageMemory(itkImage, result); } template void mitk::FeatureBasedEdgeDetectionFilter::ITKThresholding(itk::Image *originalImage, double lower, double upper, mitk::Image::Pointer &result) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; if (typeid(TPixel) != typeid(float) && typeid(TPixel) != typeid(double)) { // round the thresholds if we have nor a float or double image lower = std::floor(lower + 0.5); upper = std::floor(upper - 0.5); } if (lower >= upper) { upper = lower; } typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(originalImage); filter->SetLowerThreshold(lower); filter->SetUpperThreshold(upper); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); mitk::GrabItkImageMemory(filter->GetOutput(), result); } void mitk::FeatureBasedEdgeDetectionFilter::SetSegmentationMask(mitk::Image::Pointer segmentation) { this->m_SegmentationMask = segmentation; } void mitk::FeatureBasedEdgeDetectionFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } diff --git a/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp b/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp index 508148291f..235f5a9972 100644 --- a/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp +++ b/Modules/SurfaceInterpolation/mitkImageToPointCloudFilter.cpp @@ -1,165 +1,166 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkImageToPointCloudFilter.h" #include #include #include #include +#include #include #include #include #include mitk::ImageToPointCloudFilter::ImageToPointCloudFilter() : m_NumberOfExtractedPoints(0) { m_Method = DetectionMethod(0); this->SetNumberOfRequiredInputs(1); this->SetNumberOfIndexedOutputs(1); } mitk::ImageToPointCloudFilter::~ImageToPointCloudFilter() { } void mitk::ImageToPointCloudFilter::GenerateData() { mitk::Image::ConstPointer image = ImageToUnstructuredGridFilter::GetInput(); m_Geometry = image->GetGeometry(); if (image.IsNull()) { MITK_ERROR << "mitk::ImageToContourFilter: No input available. " "Please set the input!" << std::endl; return; } mitk::Image::Pointer notConstImage = const_cast(image.GetPointer()); switch (m_Method) { case 0: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 2) break; case 1: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 3) break; case 2: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 4) break; default: AccessByItk_1(notConstImage.GetPointer(), StdDeviations, 2) break; } } template void mitk::ImageToPointCloudFilter::StdDeviations(itk::Image *image, int amount) { typedef itk::Image InputImageType; typedef itk::CastImageFilter ImagePTypeToFloatPTypeCasterType; typedef itk::LaplacianImageFilter LaplacianFilterType; typename LaplacianFilterType::Pointer lapFilter = LaplacianFilterType::New(); typename ImagePTypeToFloatPTypeCasterType::Pointer caster = ImagePTypeToFloatPTypeCasterType::New(); caster->SetInput(image); caster->Update(); FloatImageType::Pointer fImage = caster->GetOutput(); lapFilter->SetInput(fImage); lapFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer edgeImage = mitk::ImportItkImage(lapFilter->GetOutput()); - mitk::ImageStatisticsCalculator::Pointer statCalc = mitk::ImageStatisticsCalculator::New(); - statCalc->SetImage(edgeImage); - statCalc->ComputeStatistics(); - mitk::ImageStatisticsCalculator::Statistics stats = statCalc->GetStatistics(); - double mean = stats.GetMean(); - double stdDev = stats.GetSigma(); + mitk::ImageStatisticsCalculator::Pointer statCalc = + mitk::ImageStatisticsCalculator::New(); + statCalc->SetInputImage(edgeImage); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = statCalc->GetStatistics(); + double mean = stats->GetMean(); + double stdDev = stats->GetStd(); double upperThreshold = mean + stdDev * amount; double lowerThreshold = mean - stdDev * amount; typename itk::ImageRegionIterator it(lapFilter->GetOutput(), lapFilter->GetOutput()->GetRequestedRegion()); vtkSmartPointer points = vtkSmartPointer::New(); double greatX = 0, greatY = 0, greatZ = 0; it.GoToBegin(); while (!it.IsAtEnd()) { if (it.Get() > lowerThreshold && it.Get() < upperThreshold) { it.Set(0); } else { it.Set(1); mitk::Point3D imagePoint; mitk::Point3D worldPoint; imagePoint[0] = it.GetIndex()[0]; imagePoint[1] = it.GetIndex()[1]; imagePoint[2] = it.GetIndex()[2]; m_Geometry->IndexToWorld(imagePoint, worldPoint); if (worldPoint[0] > greatX) greatX = worldPoint[0]; if (worldPoint[1] > greatY) greatY = worldPoint[1]; if (worldPoint[2] > greatZ) greatZ = worldPoint[2]; points->InsertNextPoint(worldPoint[0], worldPoint[1], worldPoint[2]); m_NumberOfExtractedPoints++; } ++it; } /*need to build the UnstructuredGrid with at least one vertex otherwise its not visible*/ vtkSmartPointer verts = vtkSmartPointer::New(); verts->GetPointIds()->SetNumberOfIds(m_NumberOfExtractedPoints); for (int i = 0; i < m_NumberOfExtractedPoints; i++) { verts->GetPointIds()->SetId(i, i); } vtkSmartPointer uGrid = vtkSmartPointer::New(); uGrid->Allocate(1); uGrid->InsertNextCell(verts->GetCellType(), verts->GetPointIds()); uGrid->SetPoints(points); mitk::UnstructuredGrid::Pointer outputGrid = mitk::UnstructuredGrid::New(); outputGrid->SetVtkUnstructuredGrid(uGrid); this->SetNthOutput(0, outputGrid); } void mitk::ImageToPointCloudFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } diff --git a/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp b/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp index 7c20ea6e22..82f5d2a6de 100644 --- a/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp +++ b/Modules/SurfaceInterpolation/mitkReduceContourSetFilter.cpp @@ -1,522 +1,523 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkReduceContourSetFilter.h" mitk::ReduceContourSetFilter::ReduceContourSetFilter() { m_MaxSegmentLenght = 0; m_StepSize = 10; m_Tolerance = -1; m_ReductionType = DOUGLAS_PEUCKER; m_MaxSpacing = -1; m_MinSpacing = -1; this->m_UseProgressBar = false; this->m_ProgressStepSize = 1; m_NumberOfPointsAfterReduction = 0; mitk::Surface::Pointer output = mitk::Surface::New(); this->SetNthOutput(0, output.GetPointer()); } mitk::ReduceContourSetFilter::~ReduceContourSetFilter() { } void mitk::ReduceContourSetFilter::SetInput(unsigned int idx, const mitk::Surface *surface) { this->SetNthInput(idx, const_cast(surface)); this->Modified(); } void mitk::ReduceContourSetFilter::SetInput(const mitk::Surface *surface) { this->SetInput(0, const_cast(surface)); } void mitk::ReduceContourSetFilter::GenerateData() { unsigned int numberOfInputs = this->GetNumberOfIndexedInputs(); unsigned int numberOfOutputs(0); vtkSmartPointer newPolyData; vtkSmartPointer newPolygons; vtkSmartPointer newPoints; // For the purpose of evaluation // unsigned int numberOfPointsBefore (0); m_NumberOfPointsAfterReduction = 0; for (unsigned int i = 0; i < numberOfInputs; i++) { mitk::Surface *currentSurface = const_cast(this->GetInput(i)); vtkSmartPointer polyData = currentSurface->GetVtkPolyData(); newPolyData = vtkSmartPointer::New(); newPolygons = vtkSmartPointer::New(); newPoints = vtkSmartPointer::New(); vtkSmartPointer existingPolys = polyData->GetPolys(); vtkSmartPointer existingPoints = polyData->GetPoints(); existingPolys->InitTraversal(); vtkIdType *cell(nullptr); vtkIdType cellSize(0); for (existingPolys->InitTraversal(); existingPolys->GetNextCell(cellSize, cell);) { bool incorporatePolygon = this->CheckForIntersection(cell, cellSize, existingPoints, /*numberOfIntersections, intersectionPoints, */ i); if (!incorporatePolygon) continue; vtkSmartPointer newPolygon = vtkSmartPointer::New(); if (m_ReductionType == NTH_POINT) { this->ReduceNumberOfPointsByNthPoint(cellSize, cell, existingPoints, newPolygon, newPoints); if (newPolygon->GetPointIds()->GetNumberOfIds() != 0) { newPolygons->InsertNextCell(newPolygon); } } else if (m_ReductionType == DOUGLAS_PEUCKER) { this->ReduceNumberOfPointsByDouglasPeucker(cellSize, cell, existingPoints, newPolygon, newPoints); if (newPolygon->GetPointIds()->GetNumberOfIds() > 3) { newPolygons->InsertNextCell(newPolygon); } } // Again for evaluation // numberOfPointsBefore += cellSize; m_NumberOfPointsAfterReduction += newPolygon->GetPointIds()->GetNumberOfIds(); } if (newPolygons->GetNumberOfCells() != 0) { newPolyData->SetPolys(newPolygons); newPolyData->SetPoints(newPoints); newPolyData->BuildLinks(); this->SetNumberOfIndexedOutputs(numberOfOutputs + 1); mitk::Surface::Pointer surface = mitk::Surface::New(); this->SetNthOutput(numberOfOutputs, surface.GetPointer()); surface->SetVtkPolyData(newPolyData); numberOfOutputs++; } } // MITK_INFO<<"Points before: "<SetNumberOfIndexedOutputs(numberOfOutputs); if (numberOfOutputs == 0) { mitk::Surface::Pointer tmp_output = mitk::Surface::New(); tmp_output->SetVtkPolyData(vtkPolyData::New()); this->SetNthOutput(0, tmp_output.GetPointer()); } // Setting progressbar if (this->m_UseProgressBar) mitk::ProgressBar::GetInstance()->Progress(this->m_ProgressStepSize); } void mitk::ReduceContourSetFilter::ReduceNumberOfPointsByNthPoint( vtkIdType cellSize, vtkIdType *cell, vtkPoints *points, vtkPolygon *reducedPolygon, vtkPoints *reducedPoints) { unsigned int newNumberOfPoints(0); unsigned int mod = cellSize % m_StepSize; if (mod == 0) { newNumberOfPoints = cellSize / m_StepSize; } else { newNumberOfPoints = ((cellSize - mod) / m_StepSize) + 1; } if (newNumberOfPoints <= 3) { return; } reducedPolygon->GetPointIds()->SetNumberOfIds(newNumberOfPoints); reducedPolygon->GetPoints()->SetNumberOfPoints(newNumberOfPoints); for (vtkIdType i = 0; i < cellSize; i++) { if (i % m_StepSize == 0) { double point[3]; points->GetPoint(cell[i], point); vtkIdType id = reducedPoints->InsertNextPoint(point); reducedPolygon->GetPointIds()->SetId(i / m_StepSize, id); } } vtkIdType id = cell[0]; double point[3]; points->GetPoint(id, point); id = reducedPoints->InsertNextPoint(point); reducedPolygon->GetPointIds()->SetId(newNumberOfPoints - 1, id); } void mitk::ReduceContourSetFilter::ReduceNumberOfPointsByDouglasPeucker( vtkIdType cellSize, vtkIdType *cell, vtkPoints *points, vtkPolygon *reducedPolygon, vtkPoints *reducedPoints) { // If the cell is too small to obtain a reduced polygon with the given stepsize return if (cellSize <= static_cast(m_StepSize * 3)) return; /* What we do now is (see the Douglas Peucker Algorithm): 1. Divide the current contour in two line segments (start - middle; middle - end), put them into the stack 2. Fetch first line segment and create the following vectors: - v1 = (start;end) - v2 = (start;currentPoint) -> for each point of the current line segment! 3. Calculate the distance from the currentPoint to v1: a. Determine the length of the orthogonal projection of v2 to v1 by: l = v2 * (normalized v1) b. There a three possibilities for the distance then: d = sqrt(lenght(v2)^2 - l^2) if l > 0 and l < length(v1) d = lenght(v2-v1) if l > 0 and l > lenght(v1) d = length(v2) if l < 0 because v2 is then pointing in a different direction than v1 4. Memorize the point with the biggest distance and create two new line segments with it at the end of the iteration and put it into the stack 5. If the distance value D <= m_Tolerance, then add the start and end index and the corresponding points to the reduced ones */ // First of all set tolerance if none is specified if (m_Tolerance < 0) { if (m_MaxSpacing > 0) { m_Tolerance = m_MinSpacing; } else { m_Tolerance = 1.5; } } std::stack lineSegments; // 1. Divide in line segments LineSegment ls2; ls2.StartIndex = cell[cellSize / 2]; ls2.EndIndex = cell[cellSize - 1]; lineSegments.push(ls2); LineSegment ls1; ls1.StartIndex = cell[0]; ls1.EndIndex = cell[cellSize / 2]; lineSegments.push(ls1); LineSegment currentSegment; double v1[3]; double v2[3]; double tempV[3]; double lenghtV1; double currentMaxDistance(0); vtkIdType currentMaxDistanceIndex(0); double l; double d; vtkIdType pointId(0); // Add the start index to the reduced points. From now on just the end indices will be added pointId = reducedPoints->InsertNextPoint(points->GetPoint(cell[0])); reducedPolygon->GetPointIds()->InsertNextId(pointId); while (!lineSegments.empty()) { currentSegment = lineSegments.top(); lineSegments.pop(); // 2. Create vectors points->GetPoint(currentSegment.EndIndex, tempV); points->GetPoint(currentSegment.StartIndex, v1); v1[0] = tempV[0] - v1[0]; v1[1] = tempV[1] - v1[1]; v1[2] = tempV[2] - v1[2]; lenghtV1 = vtkMath::Norm(v1); vtkMath::Normalize(v1); int range = currentSegment.EndIndex - currentSegment.StartIndex; for (int i = 1; i < abs(range); ++i) { points->GetPoint(currentSegment.StartIndex + i, tempV); points->GetPoint(currentSegment.StartIndex, v2); v2[0] = tempV[0] - v2[0]; v2[1] = tempV[1] - v2[1]; v2[2] = tempV[2] - v2[2]; // 3. Calculate the distance l = vtkMath::Dot(v2, v1); d = vtkMath::Norm(v2); if (l > 0 && l < lenghtV1) { d = sqrt((d * d - l * l)); } else if (l > 0 && l > lenghtV1) { tempV[0] = lenghtV1 * v1[0] - v2[0]; tempV[1] = lenghtV1 * v1[1] - v2[1]; tempV[2] = lenghtV1 * v1[2] - v2[2]; d = vtkMath::Norm(tempV); } // 4. Memorize maximum distance if (d > currentMaxDistance) { currentMaxDistance = d; currentMaxDistanceIndex = currentSegment.StartIndex + i; } } // 4. & 5. if (currentMaxDistance <= m_Tolerance) { // double temp[3]; int segmentLenght = currentSegment.EndIndex - currentSegment.StartIndex; if (segmentLenght > (int)m_MaxSegmentLenght) { m_MaxSegmentLenght = (unsigned int)segmentLenght; } // MITK_INFO<<"Lenght: "< 25) { unsigned int newLenght(segmentLenght); while (newLenght > 25) { newLenght = newLenght * 0.5; } unsigned int divisions = abs(segmentLenght) / newLenght; // MITK_INFO<<"Divisions: "<InsertNextPoint(points->GetPoint(currentSegment.StartIndex + newLenght * i)); reducedPolygon->GetPointIds()->InsertNextId(pointId); } } // MITK_INFO<<"Inserting END: "<InsertNextPoint(points->GetPoint(currentSegment.EndIndex)); reducedPolygon->GetPointIds()->InsertNextId(pointId); } else { ls2.StartIndex = currentMaxDistanceIndex; ls2.EndIndex = currentSegment.EndIndex; lineSegments.push(ls2); ls1.StartIndex = currentSegment.StartIndex; ls1.EndIndex = currentMaxDistanceIndex; lineSegments.push(ls1); } currentMaxDistance = 0; } } bool mitk::ReduceContourSetFilter::CheckForIntersection( vtkIdType *currentCell, vtkIdType currentCellSize, vtkPoints *currentPoints, /* vtkIdType numberOfIntersections, vtkIdType* intersectionPoints,*/ unsigned int currentInputIndex) { /* If we check the current cell for intersections then we have to consider three possibilies: 1. There is another cell among all the other input surfaces which intersects the current polygon: - That means we have to save the intersection points because these points should not be eliminated 2. There current polygon exists just because of an intersection of another polygon with the current plane defined by the current polygon - That means the current polygon should not be incorporated and all of its points should be eliminated 3. There is no intersection - That mean we can just reduce the current polygons points without considering any intersections */ for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); i++) { // Don't check for intersection with the polygon itself if (i == currentInputIndex) continue; // Get the next polydata to check for intersection vtkSmartPointer poly = const_cast(this->GetInput(i))->GetVtkPolyData(); vtkSmartPointer polygonArray = poly->GetPolys(); polygonArray->InitTraversal(); vtkIdType anotherInputPolygonSize(0); vtkIdType *anotherInputPolygonIDs(nullptr); /* The procedure is: - Create the equation of the plane, defined by the points of next input - Calculate the distance of each point of the current polygon to the plane - If the maximum distance is not bigger than 1.5 of the maximum spacing AND the minimal distance is not bigger than 0.5 of the minimum spacing then the current contour is an intersection contour */ for (polygonArray->InitTraversal(); polygonArray->GetNextCell(anotherInputPolygonSize, anotherInputPolygonIDs);) { // Choosing three plane points to calculate the plane vectors double p1[3]; double p2[3]; double p3[3]; // The plane vectors double v1[3]; double v2[3] = {0}; // The plane normal double normal[3]; // Create first Vector poly->GetPoint(anotherInputPolygonIDs[0], p1); poly->GetPoint(anotherInputPolygonIDs[1], p2); v1[0] = p2[0] - p1[0]; v1[1] = p2[1] - p1[1]; v1[2] = p2[2] - p1[2]; // Find 3rd point for 2nd vector (The angle between the two plane vectors should be bigger than 30 degrees) double maxDistance(0); double minDistance(10000); for (vtkIdType j = 2; j < anotherInputPolygonSize; j++) { poly->GetPoint(anotherInputPolygonIDs[j], p3); v2[0] = p3[0] - p1[0]; v2[1] = p3[1] - p1[1]; v2[2] = p3[2] - p1[2]; // Calculate the angle between the two vector for the current point double dotV1V2 = vtkMath::Dot(v1, v2); double absV1 = sqrt(vtkMath::Dot(v1, v1)); double absV2 = sqrt(vtkMath::Dot(v2, v2)); double cosV1V2 = dotV1V2 / (absV1 * absV2); double arccos = acos(cosV1V2); double degree = vtkMath::DegreesFromRadians(arccos); // If angle is bigger than 30 degrees break if (degree > 30) break; } // for (to find 3rd point) // Calculate normal of the plane by taking the cross product of the two vectors vtkMath::Cross(v1, v2, normal); vtkMath::Normalize(normal); // Determine position of the plane double lambda = vtkMath::Dot(normal, p1); /* Calculate the distance to the plane for each point of the current polygon If the distance is zero then save the currentPoint as intersection point */ for (vtkIdType k = 0; k < currentCellSize; k++) { double currentPoint[3]; currentPoints->GetPoint(currentCell[k], currentPoint); double tempPoint[3]; tempPoint[0] = normal[0] * currentPoint[0]; tempPoint[1] = normal[1] * currentPoint[1]; tempPoint[2] = normal[2] * currentPoint[2]; double temp = tempPoint[0] + tempPoint[1] + tempPoint[2] - lambda; double distance = fabs(temp); if (distance > maxDistance) { maxDistance = distance; } if (distance < minDistance) { minDistance = distance; } } // for (to calculate distance and intersections with currentPolygon) if (maxDistance < 1.5 * m_MaxSpacing && minDistance < 0.5 * m_MinSpacing) { return false; } // Because we are considering the plane defined by the acual input polygon only one iteration is sufficient // We do not need to consider each cell of the plane break; } // for (to traverse through all cells of actualInputPolyData) } // for (to iterate through all inputs) return true; } void mitk::ReduceContourSetFilter::GenerateOutputInformation() { Superclass::GenerateOutputInformation(); } void mitk::ReduceContourSetFilter::Reset() { for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); i++) { this->PopBackInput(); } this->SetNumberOfIndexedInputs(0); this->SetNumberOfIndexedOutputs(0); + // BUG XXXXX Fix mitk::Surface::Pointer output = mitk::Surface::New(); this->SetNthOutput(0, output.GetPointer()); m_NumberOfPointsAfterReduction = 0; } void mitk::ReduceContourSetFilter::SetUseProgressBar(bool status) { this->m_UseProgressBar = status; } void mitk::ReduceContourSetFilter::SetProgressStepSize(unsigned int stepSize) { this->m_ProgressStepSize = stepSize; } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp index aa91be8c6f..12d321fe1e 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp @@ -1,209 +1,266 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkImageStatisticsCalculationThread.h" //QT headers #include #include +#include +#include +#include QmitkImageStatisticsCalculationThread::QmitkImageStatisticsCalculationThread():QThread(), m_StatisticsImage(nullptr), m_BinaryMask(nullptr), m_PlanarFigureMask(nullptr), m_TimeStep(0), - m_IgnoreZeros(false), m_CalculationSuccessful(false), m_StatisticChanged(false), m_HistogramBinSize(1.0), m_UseDefaultBinSize(true) + m_IgnoreZeros(false), m_CalculationSuccessful(false), m_StatisticChanged(false), m_HistogramBinSize(10.0), m_UseDefaultNBins(true), m_nBinsForHistogramStatistics(100), m_prioritizeNBinsOverBinSize(true) { } QmitkImageStatisticsCalculationThread::~QmitkImageStatisticsCalculationThread() { } void QmitkImageStatisticsCalculationThread::Initialize( mitk::Image::Pointer image, mitk::Image::Pointer binaryImage, mitk::PlanarFigure::Pointer planarFig ) { // reset old values if( this->m_StatisticsImage.IsNotNull() ) this->m_StatisticsImage = nullptr; if( this->m_BinaryMask.IsNotNull() ) this->m_BinaryMask = nullptr; if( this->m_PlanarFigureMask.IsNotNull()) this->m_PlanarFigureMask = nullptr; // set new values if passed in if(image.IsNotNull()) this->m_StatisticsImage = image->Clone(); if(binaryImage.IsNotNull()) this->m_BinaryMask = binaryImage->Clone(); if(planarFig.IsNotNull()) this->m_PlanarFigureMask = planarFig->Clone(); } -void QmitkImageStatisticsCalculationThread::SetUseDefaultBinSize(bool useDefault) +void QmitkImageStatisticsCalculationThread::SetUseDefaultNBins(bool useDefault) { - m_UseDefaultBinSize = useDefault; + m_UseDefaultNBins = useDefault; } void QmitkImageStatisticsCalculationThread::SetTimeStep( int times ) { this->m_TimeStep = times; } int QmitkImageStatisticsCalculationThread::GetTimeStep() { return this->m_TimeStep; } -std::vector QmitkImageStatisticsCalculationThread::GetStatisticsData() +std::vector QmitkImageStatisticsCalculationThread::GetStatisticsData() { return this->m_StatisticsVector; } mitk::Image::Pointer QmitkImageStatisticsCalculationThread::GetStatisticsImage() { return this->m_StatisticsImage; } void QmitkImageStatisticsCalculationThread::SetIgnoreZeroValueVoxel(bool _arg) { this->m_IgnoreZeros = _arg; } bool QmitkImageStatisticsCalculationThread::GetIgnoreZeroValueVoxel() { return this->m_IgnoreZeros; } void QmitkImageStatisticsCalculationThread::SetHistogramBinSize(double size) { this->m_HistogramBinSize = size; + this->m_prioritizeNBinsOverBinSize = false; } -double QmitkImageStatisticsCalculationThread::GetHistogramBinSize() +double QmitkImageStatisticsCalculationThread::GetHistogramBinSize() const { return this->m_HistogramBinSize; } +void QmitkImageStatisticsCalculationThread::SetHistogramNBins(double size) +{ + this->m_nBinsForHistogramStatistics = size; + this->m_prioritizeNBinsOverBinSize = true; +} + +double QmitkImageStatisticsCalculationThread::GetHistogramNBins() const +{ + return this->m_nBinsForHistogramStatistics; +} + std::string QmitkImageStatisticsCalculationThread::GetLastErrorMessage() { return m_message; } QmitkImageStatisticsCalculationThread::HistogramType::Pointer QmitkImageStatisticsCalculationThread::GetTimeStepHistogram(unsigned int t) { if (t >= this->m_HistogramVector.size()) return nullptr; return this->m_HistogramVector[t]; } bool QmitkImageStatisticsCalculationThread::GetStatisticsChangedFlag() { return m_StatisticChanged; } bool QmitkImageStatisticsCalculationThread::GetStatisticsUpdateSuccessFlag() { return m_CalculationSuccessful; } void QmitkImageStatisticsCalculationThread::run() { bool statisticCalculationSuccessful = true; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); if(this->m_StatisticsImage.IsNotNull()) { - calculator->SetImage(m_StatisticsImage); - calculator->SetMaskingModeToNone(); + calculator->SetInputImage(m_StatisticsImage); } else { statisticCalculationSuccessful = false; } // Bug 13416 : The ImageStatistics::SetImageMask() method can throw exceptions, i.e. when the dimensionality // of the masked and input image differ, we need to catch them and mark the calculation as failed // the same holds for the ::SetPlanarFigure() try { if(this->m_BinaryMask.IsNotNull()) { - - calculator->SetImageMask(m_BinaryMask); - calculator->SetMaskingModeToImage(); + mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); + imgMask->SetImageMask(m_BinaryMask); + calculator->SetMask(imgMask.GetPointer()); } if(this->m_PlanarFigureMask.IsNotNull()) { - calculator->SetPlanarFigure(m_PlanarFigureMask); - calculator->SetMaskingModeToPlanarFigure(); + mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); + pfMaskGen->SetInputImage(m_StatisticsImage); + pfMaskGen->SetPlanarFigure(m_PlanarFigureMask); + calculator->SetMask(pfMaskGen.GetPointer()); } } catch( const itk::ExceptionObject& e) { MITK_ERROR << "ITK Exception:" << e.what(); statisticCalculationSuccessful = false; } + catch( const mitk::Exception& e ) + { + MITK_ERROR<< "MITK Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + catch ( const std::runtime_error &e ) + { + MITK_ERROR<< "Runtime Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + catch ( const std::exception &e ) + { + //m_message = "Failure: " + std::string(e.what()); + MITK_ERROR<< "Standard Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + bool statisticChanged = false; - calculator->SetDoIgnorePixelValue(this->m_IgnoreZeros); - calculator->SetIgnorePixelValue(0); - calculator->SetHistogramBinSize( m_HistogramBinSize ); - calculator->SetUseDefaultBinSize( m_UseDefaultBinSize ); + if (this->m_IgnoreZeros) + { + mitk::IgnorePixelMaskGenerator::Pointer ignorePixelValueMaskGen = mitk::IgnorePixelMaskGenerator::New(); + ignorePixelValueMaskGen->SetIgnoredPixelValue(0); + ignorePixelValueMaskGen->SetInputImage(m_StatisticsImage); + calculator->SetSecondaryMask(ignorePixelValueMaskGen.GetPointer()); + } + else + { + calculator->SetSecondaryMask(nullptr); + } + + if (m_UseDefaultNBins) + { + calculator->SetNBinsForHistogramStatistics(100); + } + else + { + if (!m_prioritizeNBinsOverBinSize) + { + calculator->SetBinSizeForHistogramStatistics(m_HistogramBinSize); + } + else + { + calculator->SetNBinsForHistogramStatistics(100); + } + } + + //calculator->SetHistogramBinSize( m_HistogramBinSize ); + //calculator->SetUseDefaultBinSize( m_UseDefaultBinSize ); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { try { - statisticChanged = calculator->ComputeStatistics(i); + calculator->GetStatistics(i); } catch ( mitk::Exception& e) { //m_message = e.GetDescription(); MITK_ERROR<< "MITK Exception: " << e.what(); statisticCalculationSuccessful = false; } catch ( const std::runtime_error &e ) { //m_message = "Failure: " + std::string(e.what()); MITK_ERROR<< "Runtime Exception: " << e.what(); statisticCalculationSuccessful = false; } catch ( const std::exception &e ) { //m_message = "Failure: " + std::string(e.what()); MITK_ERROR<< "Standard Exception: " << e.what(); statisticCalculationSuccessful = false; } } this->m_StatisticChanged = statisticChanged; this->m_CalculationSuccessful = statisticCalculationSuccessful; if(statisticCalculationSuccessful) { this->m_StatisticsVector.clear(); this->m_HistogramVector.clear(); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { this->m_StatisticsVector.push_back(calculator->GetStatistics(i)); - this->m_HistogramVector.push_back((HistogramType*)calculator->GetHistogram(i)); + this->m_HistogramVector.push_back((HistogramType*)this->m_StatisticsVector[i]->GetHistogram()); } } - - m_HistogramBinSize = calculator->GetHistogramBinSize(); } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h index e30ef93ed2..a18d6fd287 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.h @@ -1,115 +1,123 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED #define QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED //QT headers #include #include //mitk headers #include "mitkImage.h" #include "mitkPlanarFigure.h" #include "mitkImageStatisticsCalculator.h" // itk headers #ifndef __itkHistogram_h #include #endif /** /brief This class is executed as background thread for image statistics calculation. * Documentation: This class is derived from QThread and is intended to be used by QmitkImageStatisticsView to run the image statistics calculation in a background thread keepung the gui usable. * \ingroup Plugins/MeasurementToolbox */ class QmitkImageStatisticsCalculationThread : public QThread { Q_OBJECT public: typedef itk::Statistics::Histogram HistogramType; /*! /brief standard constructor. */ QmitkImageStatisticsCalculationThread(); /*! /brief standard destructor. */ ~QmitkImageStatisticsCalculationThread(); /*! *\brief Automatically calculate bin size to obtain 200 bins. */ - void SetUseDefaultBinSize(bool useDefault); + void SetUseDefaultNBins(bool useDefault); /*! /brief Initializes the object with necessary data. */ void Initialize( mitk::Image::Pointer image, mitk::Image::Pointer binaryImage, mitk::PlanarFigure::Pointer planarFig ); /*! /brief returns the calculated image statistics. */ - std::vector GetStatisticsData(); + std::vector GetStatisticsData(); /*! /brief */ mitk::Image::Pointer GetStatisticsImage(); /*! /brief Set the time step of the image you want to process. */ void SetTimeStep( int times ); /*! /brief Get the time step of the image you want to process. */ int GetTimeStep(); /*! /brief Set flag to ignore zero valued voxels */ void SetIgnoreZeroValueVoxel( bool _arg ); /*! /brief Get status of zero value voxel ignoring. */ bool GetIgnoreZeroValueVoxel(); /*! /brief Set bin size for histogram resolution.*/ void SetHistogramBinSize( double size); /*! /brief Get bin size for histogram resolution.*/ - double GetHistogramBinSize(); + double GetHistogramBinSize() const; + /*! + /brief Set bin size for histogram resolution.*/ + void SetHistogramNBins( double size); + /*! + /brief Get bin size for histogram resolution.*/ + double GetHistogramNBins() const; /*! /brief Returns the histogram of the currently selected time step. */ HistogramType::Pointer GetTimeStepHistogram(unsigned int t = 0); /*! /brief Returns a flag indicating if the statistics have changed during calculation */ bool GetStatisticsChangedFlag(); /*! /brief Returns a flag the indicates if the statistics are updated successfully */ bool GetStatisticsUpdateSuccessFlag(); /*! /brief Method called once the thread is executed. */ void run() override; std::string GetLastErrorMessage(); private: //member declaration mitk::Image::Pointer m_StatisticsImage; ///< member variable holds the input image for which the statistics need to be calculated. mitk::Image::Pointer m_BinaryMask; ///< member variable holds the binary mask image for segmentation image statistics calculation. mitk::PlanarFigure::Pointer m_PlanarFigureMask; ///< member variable holds the planar figure for segmentation image statistics calculation. - std::vector m_StatisticsVector; ///< member variable holds the result structs. + std::vector m_StatisticsVector; ///< member variable holds the result structs. int m_TimeStep; ///< member variable holds the time step for statistics calculation bool m_IgnoreZeros; ///< member variable holds flag to indicate if zero valued voxel should be suppressed double m_HistogramBinSize; ///< member variable holds the bin size for histogram resolution. bool m_StatisticChanged; ///< flag set if statistics have changed bool m_CalculationSuccessful; ///< flag set if statistics calculation was successful std::vector m_HistogramVector; ///< member holds the histograms of all time steps. std::string m_message; - bool m_UseDefaultBinSize; + bool m_UseDefaultNBins; + unsigned int m_nBinsForHistogramStatistics; + bool m_prioritizeNBinsOverBinSize; }; #endif // QMITKIMAGESTATISTICSCALCULATIONTHREAD_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index 52fe5e6aa8..276c45d13b 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,1211 +1,1325 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkImageStatisticsView.h" // Qt includes #include #include #include // berry includes #include // mitk includes #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateOr.h" #include "mitkPlanarFigureInteractor.h" // itk includes #include "itksys/SystemTools.hxx" #include #include +#include + const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; const int QmitkImageStatisticsView::STAT_TABLE_BASE_HEIGHT = 180; QmitkImageStatisticsView::QmitkImageStatisticsView(QObject* /*parent*/, const char* /*name*/) : m_Controls( NULL ), m_TimeStepperAdapter( NULL ), m_SelectedImage( NULL ), m_SelectedImageMask( NULL ), m_SelectedPlanarFigure( NULL ), m_ImageObserverTag( -1 ), m_ImageMaskObserverTag( -1 ), m_PlanarFigureObserverTag( -1 ), m_TimeObserverTag( -1 ), m_CurrentStatisticsValid( false ), m_StatisticsUpdatePending( false ), m_DataNodeSelectionChanged ( false ), m_Visible(false) { this->m_CalculationThread = new QmitkImageStatisticsCalculationThread; } QmitkImageStatisticsView::~QmitkImageStatisticsView() { if ( m_SelectedImage != NULL ) m_SelectedImage->RemoveObserver( m_ImageObserverTag ); if ( m_SelectedImageMask != NULL ) m_SelectedImageMask->RemoveObserver( m_ImageMaskObserverTag ); if ( m_SelectedPlanarFigure != NULL ) m_SelectedPlanarFigure->RemoveObserver( m_PlanarFigureObserverTag ); while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } delete this->m_CalculationThread; } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { if (m_Controls == NULL) { m_Controls = new Ui::QmitkImageStatisticsViewControls; m_Controls->setupUi(parent); CreateConnections(); m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_BinSizeFrame->setVisible(false); } } void QmitkImageStatisticsView::CreateConnections() { if ( m_Controls ) { connect( (QObject*)(this->m_Controls->m_ButtonCopyHistogramToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardHistogramButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_ButtonCopyStatisticsToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardStatisticsButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_IgnoreZerosCheckbox), SIGNAL(clicked()),(QObject*) this, SLOT(OnIgnoreZerosCheckboxClicked()) ); connect( (QObject*) this->m_CalculationThread, SIGNAL(finished()),this, SLOT( OnThreadedStatisticsCalculationEnds()),Qt::QueuedConnection); connect( (QObject*) this, SIGNAL(StatisticsUpdate()),this, SLOT( RequestStatisticsUpdate()), Qt::QueuedConnection); connect( (QObject*) this->m_Controls->m_StatisticsTable, SIGNAL(cellDoubleClicked(int,int)),this, SLOT( JumpToCoordinates(int,int)) ); connect( (QObject*) (this->m_Controls->m_barRadioButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(OnBarRadioButtonSelected())); connect( (QObject*) (this->m_Controls->m_lineRadioButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(OnLineRadioButtonSelected())); connect( (QObject*) (this->m_Controls->m_HistogramBinSizeSpinbox), SIGNAL(editingFinished()), this, SLOT(OnHistogramBinSizeBoxValueChanged())); connect( (QObject*)(this->m_Controls->m_UseDefaultBinSizeBox), SIGNAL(clicked()),(QObject*) this, SLOT(OnDefaultBinSizeBoxChanged()) ); } } void QmitkImageStatisticsView::OnDefaultBinSizeBoxChanged() { - if (m_CalculationThread!=NULL) - m_Controls->m_HistogramBinSizeSpinbox->setValue(m_CalculationThread->GetHistogramBinSize()); - if (m_Controls->m_UseDefaultBinSizeBox->isChecked()) - m_Controls->m_BinSizeFrame->setVisible(false); - else - m_Controls->m_BinSizeFrame->setVisible(true); +// if (m_CalculationThread!=NULL) +// { +// m_Controls->m_HistogramBinSizeSpinbox->setValue(m_CalculationThread->GetHistogramBinSize()); +// } + + m_Controls->m_BinSizeFrame->setVisible(!m_Controls->m_UseDefaultBinSizeBox->isChecked()); + m_CalculationThread->SetUseDefaultNBins(m_Controls->m_UseDefaultBinSizeBox->isChecked()); + + this->UpdateStatistics(); } void QmitkImageStatisticsView::PartClosed(const berry::IWorkbenchPartReference::Pointer& ) { } void QmitkImageStatisticsView::OnTimeChanged(const itk::EventObject& e) { if (this->m_SelectedDataNodes.isEmpty() || this->m_SelectedImage == NULL) return; const mitk::SliceNavigationController::GeometryTimeEvent* timeEvent = dynamic_cast(&e); assert(timeEvent != NULL); unsigned int timestep = timeEvent->GetPos(); if (this->m_SelectedImage->GetTimeSteps() > 1) { for (int x = 0; x < this->m_Controls->m_StatisticsTable->columnCount(); x++) { for (int y = 0; y < this->m_Controls->m_StatisticsTable->rowCount(); y++) { QTableWidgetItem* item = this->m_Controls->m_StatisticsTable->item(y, x); if (item == NULL) break; if (x == timestep) { item->setBackgroundColor(Qt::yellow); } else { if (y % 2 == 0) item->setBackground(this->m_Controls->m_StatisticsTable->palette().base()); else item->setBackground(this->m_Controls->m_StatisticsTable->palette().alternateBase()); } } } this->m_Controls->m_StatisticsTable->viewport()->update(); } if ((this->m_SelectedImage->GetTimeSteps() == 1 && timestep == 0) || this->m_SelectedImage->GetTimeSteps() > 1) { // display histogram for selected timestep this->m_Controls->m_JSHistogram->ClearHistogram(); QmitkImageStatisticsCalculationThread::HistogramType::Pointer histogram = this->m_CalculationThread->GetTimeStepHistogram(timestep); if (histogram.IsNotNull()) { bool closedFigure = this->m_CalculationThread->GetStatisticsUpdateSuccessFlag(); if ( closedFigure ) { this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); } //this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); /*else { m_Controls->m_JSHistogram->ComputeIntensityProfile(timestep, true); }*/ // this->m_Controls->m_JSHistogram->SignalGraphChanged(); // hacky way to make sure the protected SignalGraphChanged() is called if (this->m_Controls->m_JSHistogram->GetUseLineGraph()) { this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); } else { this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); } } } } void QmitkImageStatisticsView::JumpToCoordinates(int row ,int col) { if(m_SelectedDataNodes.isEmpty()) { MITK_WARN("QmitkImageStatisticsView") << "No data node selected for statistics calculation." ; return; } mitk::Point3D world; if (row==4 && !m_WorldMinList.empty()) world = m_WorldMinList[col]; else if (row==3 && !m_WorldMaxList.empty()) world = m_WorldMaxList[col]; else return; mitk::IRenderWindowPart* part = this->GetRenderWindowPart(); if (part) { part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()->SelectSliceByPoint(world); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), col); part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SetGeometryTime(timeEvent); } } void QmitkImageStatisticsView::OnIgnoreZerosCheckboxClicked() { emit StatisticsUpdate(); } void QmitkImageStatisticsView::OnClipboardHistogramButtonClicked() { if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != NULL)) { const unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); typedef mitk::ImageStatisticsCalculator::HistogramType HistogramType; const HistogramType *histogram = this->m_CalculationThread->GetTimeStepHistogram(t).GetPointer(); QString clipboard( "Measurement \t Frequency\n" ); for ( HistogramType::ConstIterator it = histogram->Begin(); it != histogram->End(); ++it ) { if( m_Controls->m_HistogramBinSizeSpinbox->value() == 1.0) { clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( it.GetMeasurementVector()[0], 0, 'f', 0 ) .arg( it.GetFrequency() ); } else { clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( it.GetMeasurementVector()[0], 0, 'f', 2 ) .arg( it.GetFrequency() ); } } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); } // If a (non-closed) PlanarFigure is selected, display a line profile widget else if ( m_CurrentStatisticsValid && (m_SelectedPlanarFigure != NULL )) { auto intensity = m_Controls->m_JSHistogram->GetFrequency(); auto pixel = m_Controls->m_JSHistogram->GetMeasurement(); QString clipboard( "Pixel \t Intensity\n" ); auto j = pixel.begin(); for (auto i = intensity.begin(); i < intensity.end(); i++) { assert(j != pixel.end()); clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( (*j).toString()) .arg( (*i).toString()); j++; } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); } else { QApplication::clipboard()->clear(); } } void QmitkImageStatisticsView::OnClipboardStatisticsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != NULL)) { - const std::vector &statistics = + const std::vector &statistics = this->m_CalculationThread->GetStatisticsData(); // Set time borders for for loop ;) unsigned int startT, endT; if(this->m_Controls->m_CheckBox4dCompleteTable->checkState()==Qt::CheckState::Unchecked) { startT = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); endT = startT+1; } else { startT = 0; endT = statistics.size(); } QVector< QVector > statisticsTable; QStringList headline; // Create Headline headline << " " << "Mean" << "Median" << "StdDev" << "RMS" << "Max" << "Min" << "NumberOfVoxels" << "Skewness" << "Kurtosis" << "Uniformity" << "Entropy" << "MPP" << "UPP" << "V [mm³]"; for(int i=0;i row; row.append(headline.at(i)); statisticsTable.append(row); } // Fill Table for(unsigned int t=startT;tGetMean()) + << QString::number(statistics[t]->GetMedian()) + << QString::number(statistics[t]->GetStd()) + << QString::number(statistics[t]->GetRMS()) + << QString::number(statistics[t]->GetMax()) + << QString::number(statistics[t]->GetMin()) + << QString::number(statistics[t]->GetN()) + << QString::number(statistics[t]->GetSkewness()) + << QString::number(statistics[t]->GetKurtosis()) + << QString::number(statistics[t]->GetUniformity()) + << QString::number(statistics[t]->GetEntropy()) + << QString::number(statistics[t]->GetMPP()) + << QString::number(statistics[t]->GetUPP()) << QString::number(m_Controls->m_StatisticsTable->item(7, 0)->data(Qt::DisplayRole).toDouble()); for(int z=0;zsetText(clipboard, QClipboard::Clipboard); } else { QApplication::clipboard()->clear(); } QLocale::setDefault(tempLocal); } void QmitkImageStatisticsView::OnSelectionChanged( berry::IWorkbenchPart::Pointer /*part*/, const QList &selectedNodes ) { if (this->m_Visible) { this->SelectionChanged( selectedNodes ); } else { this->m_DataNodeSelectionChanged = true; } } void QmitkImageStatisticsView::SelectionChanged(const QList &selectedNodes) { if( this->m_StatisticsUpdatePending ) { this->m_DataNodeSelectionChanged = true; return; // not ready for new data now! } if (selectedNodes.size() == this->m_SelectedDataNodes.size()) { int i = 0; for (; i < selectedNodes.size(); ++i) { if (selectedNodes.at(i) != this->m_SelectedDataNodes.at(i)) { break; } } // node selection did not change if (i == selectedNodes.size()) return; } //reset the feature image and image mask field m_Controls->m_SelectedFeatureImageLabel->setText("None"); m_Controls->m_SelectedMaskLabel->setText("None"); this->ReinitData(); if (selectedNodes.isEmpty()) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); // m_Controls->horizontalLayout_3->setEnabled(false); m_Controls->groupBox->setEnabled(false); m_Controls->groupBox_3->setEnabled(false); } else { // m_Controls->horizontalLayout_3->setEnabled(true); m_Controls->groupBox->setEnabled(true); m_Controls->groupBox_3->setEnabled(true); } if(selectedNodes.size() == 1 || selectedNodes.size() == 2) { bool isBinary = false; selectedNodes.value(0)->GetBoolProperty("binary",isBinary); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); isBinary |= isLabelSet->CheckNode(selectedNodes.value(0)); if(isBinary) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); } for (int i= 0; i< selectedNodes.size(); ++i) { this->m_SelectedDataNodes.push_back(selectedNodes.at(i)); } this->m_DataNodeSelectionChanged = false; this->m_Controls->m_ErrorMessageLabel->setText( "" ); this->m_Controls->m_ErrorMessageLabel->hide(); emit StatisticsUpdate(); } else { this->m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::ReinitData() { while( this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if(this->m_SelectedImage != NULL) { this->m_SelectedImage->RemoveObserver( this->m_ImageObserverTag); this->m_SelectedImage = NULL; } if(this->m_SelectedImageMask != NULL) { this->m_SelectedImageMask->RemoveObserver( this->m_ImageMaskObserverTag); this->m_SelectedImageMask = NULL; } if(this->m_SelectedPlanarFigure != NULL) { this->m_SelectedPlanarFigure->RemoveObserver( this->m_PlanarFigureObserverTag); this->m_SelectedPlanarFigure = NULL; } this->m_SelectedDataNodes.clear(); this->m_StatisticsUpdatePending = false; m_Controls->m_ErrorMessageLabel->setText( "" ); m_Controls->m_ErrorMessageLabel->hide(); this->InvalidateStatisticsTableView(); m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); } void QmitkImageStatisticsView::OnThreadedStatisticsCalculationEnds() { std::stringstream message; message << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->hide(); this->WriteStatisticsToGUI(); } void QmitkImageStatisticsView::UpdateStatistics() { mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); if ( renderPart == NULL ) { this->m_StatisticsUpdatePending = false; return; } m_WorldMinList.clear(); m_WorldMaxList.clear(); // classify selected nodes mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateOr::Pointer imagePredicate = mitk::NodePredicateOr::New(isImage, isLabelSet); std::string maskName = std::string(); std::string maskType = std::string(); std::string featureImageName = std::string(); unsigned int maskDimension = 0; // reset data from last run ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction( this, &QmitkImageStatisticsView::SelectedDataModified ); mitk::DataNode::Pointer planarFigureNode; for( int i= 0 ; i < this->m_SelectedDataNodes.size(); ++i) { mitk::PlanarFigure::Pointer planarFig = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); if( imagePredicate->CheckNode(this->m_SelectedDataNodes.at(i)) ) { bool isMask = false; this->m_SelectedDataNodes.at(i)->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(this->m_SelectedDataNodes.at(i)); if( this->m_SelectedImageMask == NULL && isMask) { this->m_SelectedImageMask = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageMaskObserverTag = this->m_SelectedImageMask->AddObserver(itk::ModifiedEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = m_SelectedImageMask->GetNameOfClass(); maskDimension = 3; } else if( !isMask ) { if(this->m_SelectedImage == NULL) { this->m_SelectedImage = static_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } featureImageName = this->m_SelectedDataNodes.at(i)->GetName(); } } else if (planarFig.IsNotNull()) { if(this->m_SelectedPlanarFigure == NULL) { this->m_SelectedPlanarFigure = planarFig; this->m_PlanarFigureObserverTag = this->m_SelectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = this->m_SelectedPlanarFigure->GetNameOfClass(); maskDimension = 2; planarFigureNode = m_SelectedDataNodes.at(i); } } else { std::stringstream message; message << "" << "Invalid data node type!" << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); } } if(maskName == "") { maskName = "None"; maskType = ""; maskDimension = 0; } if(featureImageName == "") { featureImageName = "None"; } if (m_SelectedPlanarFigure != NULL && m_SelectedImage == NULL) { mitk::DataStorage::SetOfObjects::ConstPointer parentSet = this->GetDataStorage()->GetSources(planarFigureNode); for (int i=0; iSize(); i++) { mitk::DataNode::Pointer node = parentSet->ElementAt(i); if( imagePredicate->CheckNode(node) ) { bool isMask = false; node->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(node); if( !isMask ) { if(this->m_SelectedImage == NULL) { this->m_SelectedImage = static_cast(node->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } } } } } unsigned int timeStep = renderPart->GetTimeNavigationController()->GetTime()->GetPos(); if ( m_SelectedImage != NULL && m_SelectedImage->IsInitialized()) { // Check if a the selected image is a multi-channel image. If yes, statistics // cannot be calculated currently. if ( m_SelectedImage->GetPixelType().GetNumberOfComponents() > 1 ) { std::stringstream message; message << "Multi-component images not supported."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); return; } std::stringstream maskLabel; maskLabel << maskName; if ( maskDimension > 0 ) { maskLabel << " [" << maskDimension << "D " << maskType << "]"; } m_Controls->m_SelectedMaskLabel->setText( maskLabel.str().c_str() ); m_Controls->m_SelectedFeatureImageLabel->setText(featureImageName.c_str()); // check time step validity if(m_SelectedImage->GetDimension() <= 3 && timeStep > m_SelectedImage->GetDimension(3)-1) { timeStep = m_SelectedImage->GetDimension(3)-1; } // Add the used mask time step to the mask label so the user knows which mask time step was used // if the image time step is bigger than the total number of mask time steps (see // ImageStatisticsCalculator::ExtractImageAndMask) if (m_SelectedImageMask != NULL) { unsigned int maskTimeStep = timeStep; if (maskTimeStep >= m_SelectedImageMask->GetTimeSteps()) { maskTimeStep = m_SelectedImageMask->GetTimeSteps() - 1; } m_Controls->m_SelectedMaskLabel->setText(m_Controls->m_SelectedMaskLabel->text() + QString(" (t=") + QString::number(maskTimeStep) + QString(")")); } //// initialize thread and trigger it this->m_CalculationThread->SetIgnoreZeroValueVoxel( m_Controls->m_IgnoreZerosCheckbox->isChecked() ); this->m_CalculationThread->Initialize( m_SelectedImage, m_SelectedImageMask, m_SelectedPlanarFigure ); this->m_CalculationThread->SetTimeStep( timeStep ); - this->m_CalculationThread->SetHistogramBinSize(m_Controls->m_HistogramBinSizeSpinbox->value()); + std::stringstream message; message << "Calculating statistics..."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); try { // Compute statistics - this->m_CalculationThread->SetUseDefaultBinSize(m_Controls->m_UseDefaultBinSizeBox->isChecked()); + // this->m_CalculationThread->SetUseDefaultBinSize(m_Controls->m_UseDefaultBinSizeBox->isChecked()); this->m_CalculationThread->start(); } catch ( const mitk::Exception& e) { std::stringstream message; message << "" << e.GetDescription() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::runtime_error &e ) { // In case of exception, print error message on GUI std::stringstream message; message << "" << e.what() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::exception &e ) { MITK_ERROR << "Caught exception: " << e.what(); // In case of exception, print error message on GUI std::stringstream message; message << "Error! Unequal Dimensions of Image and Segmentation. No recompute possible "; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } } else { this->m_StatisticsUpdatePending = false; } } void QmitkImageStatisticsView::SelectedDataModified() { if( !m_StatisticsUpdatePending ) { emit StatisticsUpdate(); } } void QmitkImageStatisticsView::NodeRemoved(const mitk::DataNode *node) { while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if (node->GetData() == m_SelectedImage) { m_SelectedImage = NULL; } } void QmitkImageStatisticsView::RequestStatisticsUpdate() { if ( !m_StatisticsUpdatePending ) { if(this->m_DataNodeSelectionChanged) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->m_StatisticsUpdatePending = true; this->UpdateStatistics(); } } if (this->GetRenderWindowPart()) this->GetRenderWindowPart()->RequestUpdate(); } void QmitkImageStatisticsView::OnHistogramBinSizeBoxValueChanged() { - this->UpdateStatistics(); + if (m_Controls->m_HistogramBinSizeSpinbox->value() != m_HistogramBinSize) + { + m_HistogramBinSize = m_Controls->m_HistogramBinSizeSpinbox->value(); + this->m_CalculationThread->SetHistogramBinSize(m_Controls->m_HistogramBinSizeSpinbox->value()); + this->UpdateStatistics(); + } } void QmitkImageStatisticsView::WriteStatisticsToGUI() { m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); if(m_DataNodeSelectionChanged) { this->m_StatisticsUpdatePending = false; this->RequestStatisticsUpdate(); return; // stop visualization of results and calculate statistics of new selection } if ( this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { if ( this->m_CalculationThread->GetStatisticsChangedFlag() ) { // Do not show any error messages m_Controls->m_ErrorMessageLabel->hide(); m_CurrentStatisticsValid = true; } if (m_Controls->m_barRadioButton->isChecked()) { m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); } m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_HistogramBinSizeSpinbox->setValue( this->m_CalculationThread->GetHistogramBinSize() ); //m_Controls->m_JSHistogram->ComputeHistogram( this->m_CalculationThread->GetTimeStepHistogram(this->m_CalculationThread->GetTimeStep()).GetPointer() ); this->FillStatisticsTableView( this->m_CalculationThread->GetStatisticsData(), this->m_CalculationThread->GetStatisticsImage()); } else { m_Controls->m_SelectedMaskLabel->setText( "None" ); m_Controls->m_ErrorMessageLabel->setText( m_CalculationThread->GetLastErrorMessage().c_str() ); m_Controls->m_ErrorMessageLabel->show(); // Clear statistics and histogram this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); //m_Controls->m_JSHistogram->clearHistogram(); m_CurrentStatisticsValid = false; // If a (non-closed) PlanarFigure is selected, display a line profile widget if ( m_SelectedPlanarFigure != NULL ) { // Check if the (closed) planar figure is out of bounds and so no image mask could be calculated--> Intensity Profile can not be calculated bool outOfBounds = false; if ( m_SelectedPlanarFigure->IsClosed() && m_SelectedImageMask == NULL) { outOfBounds = true; std::stringstream message; message << "Planar figure is on a rotated image plane or outside the image bounds."; m_Controls->m_InfoLabel->setText(message.str().c_str()); } // check whether PlanarFigure is initialized const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_SelectedPlanarFigure->GetPlaneGeometry(); if ( !(planarFigurePlaneGeometry == NULL || outOfBounds)) { unsigned int timeStep = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); m_Controls->m_JSHistogram->SetImage(this->m_CalculationThread->GetStatisticsImage()); m_Controls->m_JSHistogram->SetPlanarFigure(m_SelectedPlanarFigure); m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep, true); //m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep); m_Controls->m_lineRadioButton->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(false); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(false); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(false); // m_Controls->m_HistogramBinSizeLabel->setEnabled(false); this->FillLinearProfileStatisticsTableView( this->m_CalculationThread->GetStatisticsImage() ); std::stringstream message; message << "Only linegraph available for an intensity profile!"; m_Controls->m_InfoLabel->setText(message.str().c_str()); m_CurrentStatisticsValid = true; } else { // Clear statistics, histogram, and GUI this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_SelectedMaskLabel->setText( "None" ); this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); if (!outOfBounds) m_Controls->m_InfoLabel->setText(QString("")); return; // Sebastian Wirkert: would suggest to remove this return, since it is an artifact of previous // code architecture. However, removing it will cause m_StatisticsUpdatePending to be set to false // in case of invalid statistics which it previously was not. } } } this->m_StatisticsUpdatePending = false; } void QmitkImageStatisticsView::FillStatisticsTableView( - const std::vector &s, + const std::vector &s, const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(image->GetTimeSteps()); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(image->GetTimeSteps() > 1); // Set Checkbox for complete copy of statistic table if(image->GetTimeSteps()>1) { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(true); } else { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(false); this->m_Controls->m_CheckBox4dCompleteTable->setChecked(false); } int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } for (unsigned int t = 0; t < image->GetTimeSteps(); t++) { this->m_Controls->m_StatisticsTable->setHorizontalHeaderItem(t, new QTableWidgetItem(QString::number(t))); - if (s[t].GetMaxIndex().size()==3) + if (s[t]->GetMaxIndex().size()==3) { mitk::Point3D index, max, min; - index[0] = s[t].GetMaxIndex()[0]; - index[1] = s[t].GetMaxIndex()[1]; - index[2] = s[t].GetMaxIndex()[2]; + index[0] = s[t]->GetMaxIndex()[0]; + index[1] = s[t]->GetMaxIndex()[1]; + index[2] = s[t]->GetMaxIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, max); this->m_WorldMaxList.push_back(max); - index[0] = s[t].GetMinIndex()[0]; - index[1] = s[t].GetMinIndex()[1]; - index[2] = s[t].GetMinIndex()[2]; + index[0] = s[t]->GetMinIndex()[0]; + index[1] = s[t]->GetMinIndex()[1]; + index[2] = s[t]->GetMinIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, min); this->m_WorldMinList.push_back(min); } + typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; + RealType maxVal = std::numeric_limits::max(); + this->m_Controls->m_StatisticsTable->setItem( 0, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetMean(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetMean(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 1, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetMedian(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetMedian(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 2, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetSigma(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetStd(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 3, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetRMS(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetRMS(), 0, 'f', decimals) ) ); - QString max; max.append(QString("%1").arg(s[t].GetMax(), 0, 'f', decimals)); + QString max; max.append(QString("%1").arg(s[t]->GetMax(), 0, 'f', decimals)); max += " ("; - for (int i=0; iGetMaxIndex().size(); i++) { - max += QString::number(s[t].GetMaxIndex()[i]); - if (iGetMaxIndex()[i]); + if (iGetMaxIndex().size()-1) max += ","; } max += ")"; this->m_Controls->m_StatisticsTable->setItem( 4, t, new QTableWidgetItem( max ) ); - QString min; min.append(QString("%1").arg(s[t].GetMin(), 0, 'f', decimals)); + QString min; min.append(QString("%1").arg(s[t]->GetMin(), 0, 'f', decimals)); min += " ("; - for (int i=0; iGetMinIndex().size(); i++) { - min += QString::number(s[t].GetMinIndex()[i]); - if (iGetMinIndex()[i]); + if (iGetMinIndex().size()-1) min += ","; } min += ")"; this->m_Controls->m_StatisticsTable->setItem( 5, t, new QTableWidgetItem( min ) ); this->m_Controls->m_StatisticsTable->setItem( 6, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetN()) ) ); + QString("%1").arg(s[t]->GetN()) ) ); const mitk::BaseGeometry *geometry = image->GetGeometry(); if ( geometry != NULL ) { const mitk::Vector3D &spacing = image->GetGeometry()->GetSpacing(); - double volume = spacing[0] * spacing[1] * spacing[2] * (double) s[t].GetN(); + double volume = spacing[0] * spacing[1] * spacing[2] * (double) s[t]->GetN(); this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( QString("%1").arg(volume, 0, 'f', decimals) ) ); } else { this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( "NA" ) ); } //statistics of higher order should have 5 decimal places because they used to be very small this->m_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetSkewness(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetSkewness(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetKurtosis(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetKurtosis(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 10, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetUniformity(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetUniformity(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 11, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetEntropy(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetEntropy(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 12, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetMPP(), 0, 'f', decimals) ) ); + QString("%1").arg(s[t]->GetMPP(), 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 13, t, new QTableWidgetItem( - QString("%1").arg(s[t].GetUPP(), 0, 'f', 5) ) ); + QString("%1").arg(s[t]->GetUPP(), 0, 'f', 5) ) ); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); // make sure the current timestep's column is highlighted (and the correct histogram is displayed) unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), t); this->OnTimeChanged(timeEvent); t = std::min(image->GetTimeSteps() - 1, t); // See bug 18340 /*QString hotspotMean; hotspotMean.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMean(), 0, 'f', decimals)); hotspotMean += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( hotspotMean ) ); QString hotspotMax; hotspotMax.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMax(), 0, 'f', decimals)); hotspotMax += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( hotspotMax ) ); QString hotspotMin; hotspotMin.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMin(), 0, 'f', decimals)); hotspotMin += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( hotspotMin ) );*/ } std::vector QmitkImageStatisticsView::CalculateStatisticsForPlanarFigure( const mitk::Image *image) { std::vector result; int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } - mitk::ImageStatisticsCalculator::Statistics &stats = m_Controls->m_JSHistogram->GetStatistics(); + mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = m_Controls->m_JSHistogram->GetStatistics(); + + typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; + RealType maxVal = std::numeric_limits::max(); + + if (stats->GetMean() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMean(), 0, 'f', decimals)); + } + + if (stats->GetMedian() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMedian(), 0, 'f', decimals)); + } - result.push_back(QString("%1").arg(stats.GetMean(), 0, 'f', decimals)); - result.push_back(QString("%1").arg(stats.GetMedian(), 0, 'f', decimals)); + if (stats->GetStd() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back( QString("%1").arg( stats->GetStd(), 0, 'f', decimals)); + } + + if (stats->GetRMS() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg( stats->GetRMS(), 0, 'f', decimals)); + } + + if (stats->GetMax() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + QString max; + max.append(QString("%1").arg(stats->GetMax(), 0, 'f', decimals)); + result.push_back(max); + } - double stdDev = sqrt( stats.GetVariance() ); - result.push_back( QString("%1").arg( stdDev, 0, 'f', decimals)); + if (stats->GetMin() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + QString min; + min.append(QString("%1").arg(stats->GetMin(), 0, 'f', decimals)); + result.push_back(min); - double rms = stats.GetRMS(); - result.push_back(QString("%1").arg( rms, 0, 'f', decimals)); + } - QString max; max.append(QString("%1").arg(stats.GetMax(), 0, 'f', decimals)); - result.push_back(max); - QString min; min.append(QString("%1").arg(stats.GetMin(), 0, 'f', decimals)); - result.push_back(min); - result.push_back(QString("%1").arg(stats.GetN())); + if (stats->GetN() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetN())); + } result.push_back(QString("NA")); //statistics of higher order should have 5 decimal places because they used to be very small - result.push_back(QString("%1").arg(stats.GetSkewness(), 0, 'f', 5 )); + if (stats->GetSkewness() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetSkewness(), 0, 'f', 5 )); + } - result.push_back(QString("%1").arg(stats.GetKurtosis(), 0, 'f', 5) ); + if (stats->GetKurtosis() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetKurtosis(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats.GetUniformity(), 0, 'f', 5) ); + if (stats->GetUniformity() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetUniformity(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats.GetEntropy(), 0, 'f', 5) ); + if (stats->GetEntropy() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetEntropy(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats.GetMPP(), 0, 'f', decimals) ); + if (stats->GetMPP() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMPP(), 0, 'f', decimals) ); + } - result.push_back(QString("%1").arg(stats.GetUPP(), 0, 'f', 5) ); + if (stats->GetUPP() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetUPP(), 0, 'f', 5) ); + } return result; } void QmitkImageStatisticsView::FillLinearProfileStatisticsTableView( const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(1); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); m_PlanarFigureStatistics = this->CalculateStatisticsForPlanarFigure(image); for (int i = 0; i< m_PlanarFigureStatistics.size(); i++) { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem(m_PlanarFigureStatistics[i] )); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); } void QmitkImageStatisticsView::InvalidateStatisticsTableView() { this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); this->m_Controls->m_StatisticsTable->setColumnCount(1); for ( unsigned int i = 0; i < this->m_Controls->m_StatisticsTable->rowCount(); ++i ) { { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem( "NA" ) ); } } this->m_Controls->m_StatisticsTable->setMinimumHeight(STAT_TABLE_BASE_HEIGHT); } void QmitkImageStatisticsView::Activated() { } void QmitkImageStatisticsView::Deactivated() { } void QmitkImageStatisticsView::Visible() { m_Visible = true; mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkImageStatisticsView::OnTimeChanged); // It is sufficient to add the observer to the axial render window since the GeometryTimeEvent // is always triggered by all views. m_TimeObserverTag = renderWindow->GetQmitkRenderWindow("axial")-> GetSliceNavigationController()-> AddObserver(mitk::SliceNavigationController::GeometryTimeEvent(NULL, 0), cmdTimeEvent); } if (m_DataNodeSelectionChanged) { if (this->IsCurrentSelectionValid()) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->SelectionChanged(this->GetDataManagerSelection()); } m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::Hidden() { m_Visible = false; // The slice navigation controller observer is removed here instead of in the destructor. // If it was called in the destructor, the application would freeze because the view's // destructor gets called after the render windows have been destructed. if ( m_TimeObserverTag != NULL ) { mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { renderWindow->GetQmitkRenderWindow("axial")->GetSliceNavigationController()-> RemoveObserver( m_TimeObserverTag ); } m_TimeObserverTag = NULL; } } void QmitkImageStatisticsView::SetFocus() { } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h index 6a96944481..0fb88d3013 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.h @@ -1,190 +1,191 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkImageStatisticsView_H__INCLUDED #define QmitkImageStatisticsView_H__INCLUDED #include "ui_QmitkImageStatisticsViewControls.h" // Qmitk includes #include #include "QmitkStepperAdapter.h" #include "QmitkImageStatisticsCalculationThread.h" #include // mitk includes #include "mitkImageStatisticsCalculator.h" #include "mitkILifecycleAwarePart.h" #include "mitkPlanarLine.h" /*! \brief QmitkImageStatisticsView is a bundle that allows statistics calculation from images. Three modes are supported: 1. Statistics of one image, 2. Statistics of an image and a segmentation, 3. Statistics of an image and a Planar Figure. The statistics calculation is realized in a seperate thread to keep the gui accessable during calculation. \ingroup Plugins/org.mitk.gui.qt.measurementtoolbox */ class QmitkImageStatisticsView : public QmitkAbstractView, public mitk::ILifecycleAwarePart, public berry::IPartListener { Q_OBJECT private: /*! \ Convenient typedefs */ typedef mitk::DataStorage::SetOfObjects ConstVector; typedef ConstVector::ConstPointer ConstVectorPointer; typedef ConstVector::ConstIterator ConstVectorIterator; typedef std::map< mitk::Image *, mitk::ImageStatisticsCalculator::Pointer > ImageStatisticsMapType; typedef QList SelectedDataNodeVectorType; typedef itk::SimpleMemberCommand< QmitkImageStatisticsView > ITKCommandType; public: /*! \brief default constructor */ QmitkImageStatisticsView(QObject *parent=nullptr, const char *name=nullptr); /*! \brief default destructor */ virtual ~QmitkImageStatisticsView(); /*! \brief method for creating the widget containing the application controls, like sliders, buttons etc. */ virtual void CreateQtPartControl(QWidget *parent) override; /*! \brief method for creating the connections of main and control widget */ virtual void CreateConnections(); /*! \brief not implemented*/ //bool IsExclusiveFunctionality() const; /*! \brief Is called from the selection mechanism once the data manager selection has changed*/ void OnSelectionChanged( berry::IWorkbenchPart::Pointer part, const QList &nodes ) override; static const std::string VIEW_ID; static const int STAT_TABLE_BASE_HEIGHT; public slots: /** \brief Called when the statistics update is finished, sets the results to GUI.*/ void OnThreadedStatisticsCalculationEnds(); /** \brief Update bin size for histogram resolution. */ void OnHistogramBinSizeBoxValueChanged(); protected slots: /** \brief Saves the histogram to the clipboard */ void OnClipboardHistogramButtonClicked(); /** \brief Saves the statistics to the clipboard */ void OnClipboardStatisticsButtonClicked(); /** \brief Indicates if zeros should be excluded from statistics calculation */ void OnIgnoreZerosCheckboxClicked( ); /** \brief Checks if update is possible and calls StatisticsUpdate() possible */ void RequestStatisticsUpdate(); /** \brief Jump to coordinates stored in the double clicked cell */ void JumpToCoordinates(int row, int col); /** \brief Toogle GUI elements if histogram default bin size checkbox value changed. */ void OnDefaultBinSizeBoxChanged(); signals: /** \brief Method to set the data to the member and start the threaded statistics update */ void StatisticsUpdate(); protected: /** \brief Writes the calculated statistics to the GUI */ - void FillStatisticsTableView( const std::vector &s, - const mitk::Image *image ); - + void FillStatisticsTableView(const std::vector &s, + const mitk::Image *image ); std::vector CalculateStatisticsForPlanarFigure( const mitk::Image *image); void FillLinearProfileStatisticsTableView( const mitk::Image *image ); /** \brief Removes statistics from the GUI */ void InvalidateStatisticsTableView(); /** \brief Recalculate statistics for currently selected image and mask and * update the GUI. */ void UpdateStatistics(); /** \brief Listener for progress events to update progress bar. */ void UpdateProgressBar(); /** \brief Removes any cached images which are no longer referenced elsewhere. */ void RemoveOrphanImages(); /** \brief Computes an Intensity Profile along line and updates the histogram widget with it. */ void ComputeIntensityProfile( mitk::PlanarLine* line ); /** \brief Removes all Observers to images, masks and planar figures and sets corresponding members to zero */ void ClearObservers(); void Activated() override; void Deactivated() override; void Visible() override; void Hidden() override; void SetFocus() override; /** \brief Method called when itkModifiedEvent is called by selected data. */ void SelectedDataModified(); /** \brief Method called when the data manager selection changes */ void SelectionChanged(const QList &selectedNodes); /** \brief Method called to remove old selection when a new selection is present */ void ReinitData(); /** \brief writes the statistics to the gui*/ void WriteStatisticsToGUI(); void NodeRemoved(const mitk::DataNode *node) override; /** \brief Is called right before the view closes (before the destructor) */ virtual void PartClosed(const berry::IWorkbenchPartReference::Pointer& ) override; /** \brief Is called from the image navigator once the time step has changed */ void OnTimeChanged( const itk::EventObject& ); /** \brief Required for berry::IPartListener */ virtual Events::Types GetPartEventTypes() const override { return Events::CLOSED; } // member variables Ui::QmitkImageStatisticsViewControls *m_Controls; // if you have a planar figure selected, the statistics values will be saved in this one. std::vector m_PlanarFigureStatistics; QmitkImageStatisticsCalculationThread* m_CalculationThread; QmitkStepperAdapter* m_TimeStepperAdapter; unsigned int m_CurrentTime; QString m_Clipboard; // Image and mask data mitk::Image* m_SelectedImage; mitk::Image* m_SelectedImageMask; mitk::PlanarFigure* m_SelectedPlanarFigure; // observer tags long m_ImageObserverTag; long m_ImageMaskObserverTag; long m_PlanarFigureObserverTag; long m_TimeObserverTag; SelectedDataNodeVectorType m_SelectedDataNodes; bool m_CurrentStatisticsValid; bool m_StatisticsUpdatePending; bool m_StatisticsIntegrationPending; bool m_DataNodeSelectionChanged; bool m_Visible; + double m_HistogramBinSize; + std::vector m_WorldMinList; std::vector m_WorldMaxList; }; #endif // QmitkImageStatisticsView_H__INCLUDED diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui index 9d496cf7ea..d53335a4d7 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui @@ -1,625 +1,625 @@ QmitkImageStatisticsViewControls true 0 0 548 800 Form 0 0 Qt::LeftToRight Feature Image: 0 0 None 0 0 Mask: 0 0 None -1 0 0 color: rgb(255, 0, 0); Error Message Qt::AutoText Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Ignore zero-valued voxels false Statistics 9 9 9 100 180 16777215 16777215 Qt::ScrollBarAsNeeded Qt::ScrollBarAsNeeded true QAbstractItemView::NoEditTriggers true true Qt::DotLine false 14 false false 80 true 80 false true true false 25 25 false false Mean Median StdDev RMS Max Min N V (mm³) Skewness Kurtosis Uniformity Entropy MPP UPP 0 0 0 0 0 0 Copy to Clipboard Qt::Horizontal QSizePolicy::Fixed 20 20 false copy complete table Qt::Horizontal 40 20 false 150 160 Histogram false 0 0 0 0 16777215 16777215 Plot 0 0 Barchart true 0 0 0 0 Linegraph Qt::Horizontal 40 20 Use default bin size true QFrame::NoFrame QFrame::Raised 0 0 0 0 0 60 0 100 16777215 Bin size: Press enter to recalculate statistics with new bin size. true 5 0.000010000000000 1000000.000000000000000 - 10.000000000000000 + 100.000000000000000 0 0 0 0 0 0 0 0 Copy to Clipboard Qt::Horizontal 40 20 Qt::Vertical 20 40 QmitkHistogramJSWidget QWidget
QmitkHistogramJSWidget.h
1