diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.cpp b/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.cpp index 783e680228..cdd84c95b4 100644 --- a/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.cpp +++ b/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.cpp @@ -1,860 +1,835 @@ /*=================================================================== 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. ===================================================================*/ //MITK #include #include #include #include #include #include #include #include #include #include #include #include "mitkImageStatisticsHolder.h" #include "mitkPlaneClipping.h" #include "mitkRegVisPropertyTags.h" #include "mitkRegVisHelper.h" #include "mitkRegEvalStyleProperty.h" #include "mitkRegEvalWipeStyleProperty.h" //MITK Rendering #include "mitkRegEvaluationMapper2D.h" #include "vtkMitkThickSlicesFilter.h" #include "vtkMitkLevelWindowFilter.h" #include "vtkNeverTranslucentTexture.h" //VTK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //ITK #include #include //MatchPoint #include #include mitk::RegEvaluationMapper2D::RegEvaluationMapper2D() { } mitk::RegEvaluationMapper2D::~RegEvaluationMapper2D() { } //set the two points defining the textured plane according to the dimension and spacing void mitk::RegEvaluationMapper2D::GeneratePlane(mitk::BaseRenderer* renderer, double planeBounds[6]) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float depth = this->CalculateLayerDepth(renderer); //Set the origin to (xMin; yMin; depth) of the plane. This is necessary for obtaining the correct //plane size in crosshair rotation and swivel mode. localStorage->m_Plane->SetOrigin(planeBounds[0], planeBounds[2], depth); //These two points define the axes of the plane in combination with the origin. //Point 1 is the x-axis and point 2 the y-axis. //Each plane is transformed according to the view (axial, coronal and saggital) afterwards. localStorage->m_Plane->SetPoint1(planeBounds[1] , planeBounds[2], depth); //P1: (xMax, yMin, depth) localStorage->m_Plane->SetPoint2(planeBounds[0], planeBounds[3], depth); //P2: (xMin, yMax, depth) } float mitk::RegEvaluationMapper2D::CalculateLayerDepth(mitk::BaseRenderer* renderer) { //get the clipping range to check how deep into z direction we can render images double maxRange = renderer->GetVtkRenderer()->GetActiveCamera()->GetClippingRange()[1]; //Due to a VTK bug, we cannot use the whole clipping range. /100 is empirically determined float depth = -maxRange*0.01; // divide by 100 int layer = 0; GetDataNode()->GetIntProperty( "layer", layer, renderer); //add the layer property for each image to render images with a higher layer on top of the others depth += layer*10; //*10: keep some room for each image (e.g. for QBalls in between) if(depth > 0.0f) { depth = 0.0f; MITK_WARN << "Layer value exceeds clipping range. Set to minimum instead."; } return depth; } const mitk::Image* mitk::RegEvaluationMapper2D::GetTargetImage( void ) { const mitk::RegEvaluationObject* evalObj = dynamic_cast< const mitk::RegEvaluationObject* >( GetDataNode()->GetData() ); if (evalObj) { return evalObj->GetTargetImage(); } return NULL; } const mitk::Image* mitk::RegEvaluationMapper2D::GetMovingImage( void ) { const mitk::RegEvaluationObject* evalObj = dynamic_cast< const mitk::RegEvaluationObject* >( GetDataNode()->GetData() ); if (evalObj) { return evalObj->GetMovingImage(); } return NULL; } const mitk::DataNode* mitk::RegEvaluationMapper2D::GetTargetNode(void) { const mitk::RegEvaluationObject* evalObj = dynamic_cast< const mitk::RegEvaluationObject* >(GetDataNode()->GetData()); if (evalObj) { return evalObj->GetTargetNode(); } return NULL; } const mitk::DataNode* mitk::RegEvaluationMapper2D::GetMovingNode(void) { const mitk::RegEvaluationObject* evalObj = dynamic_cast< const mitk::RegEvaluationObject* >(GetDataNode()->GetData()); if (evalObj) { return evalObj->GetMovingNode(); } return NULL; } const mitk::MAPRegistrationWrapper* mitk::RegEvaluationMapper2D::GetRegistration( void ) { const mitk::RegEvaluationObject* evalObj = dynamic_cast< const mitk::RegEvaluationObject* >( GetDataNode()->GetData() ); if (evalObj) { return evalObj->GetRegistration(); } return NULL; } vtkProp* mitk::RegEvaluationMapper2D::GetVtkProp(mitk::BaseRenderer* renderer) { //return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } void mitk::RegEvaluationMapper2D::GenerateDataForRenderer( mitk::BaseRenderer *renderer ) { bool updated = false; LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::Image::Pointer targetInput = const_cast< mitk::Image * >( this->GetTargetImage() ); mitk::DataNode* datanode = this->GetDataNode(); if ( targetInput.IsNull() || targetInput->IsInitialized() == false ) { return; } mitk::Image::ConstPointer movingInput = this->GetMovingImage(); if ( movingInput.IsNull() || movingInput->IsInitialized() == false ) { return; } mitk::MAPRegistrationWrapper::ConstPointer reg = this->GetRegistration(); //check if there is a valid worldGeometry const Geometry2D *worldGeometry = renderer->GetCurrentWorldGeometry2D(); if( ( worldGeometry == NULL ) || ( !worldGeometry->IsValid() ) || ( !worldGeometry->HasReferenceGeometry() )) { return; } if(targetInput->GetMTime()>localStorage->m_LastUpdateTime || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2DUpdateTime()) //was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2D()->GetMTime())) { //target input has been modified -> reslice target input targetInput->Update(); // early out if there is no intersection of the current rendering geometry // and the geometry of the image that is to be rendered. if ( !RenderingGeometryIntersectsImage( worldGeometry, targetInput->GetSlicedGeometry() ) ) { // set image to NULL, to clear the texture in 3D, because // the latest image is used there if the plane is out of the geometry // see bug-13275 localStorage->m_EvaluationImage = NULL; localStorage->m_Mapper->SetInputData( localStorage->m_EmptyPolyData ); return; } //set main input for ExtractSliceFilter localStorage->m_Reslicer->SetInput(targetInput); localStorage->m_Reslicer->SetWorldGeometry(worldGeometry); localStorage->m_Reslicer->SetTimeStep( this->GetTimestep() ); //set the transformation of the image to adapt reslice axis localStorage->m_Reslicer->SetResliceTransformByGeometry( targetInput->GetTimeGeometry()->GetGeometryForTimeStep( this->GetTimestep() ) ); //is the geometry of the slice based on the input image or the worldgeometry? bool inPlaneResampleExtentByGeometry = false; datanode->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); localStorage->m_Reslicer->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); // Initialize the interpolation mode for resampling; switch to nearest // neighbor if the input image is too small. if ( (targetInput->GetDimension() >= 3) && (targetInput->GetDimension(2) > 1) ) { VtkResliceInterpolationProperty *resliceInterpolationProperty; datanode->GetProperty( resliceInterpolationProperty, "reslice interpolation" ); int interpolationMode = VTK_RESLICE_NEAREST; if ( resliceInterpolationProperty != NULL ) { interpolationMode = resliceInterpolationProperty->GetInterpolation(); } switch ( interpolationMode ) { case VTK_RESLICE_NEAREST: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); break; case VTK_RESLICE_LINEAR: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_LINEAR); break; case VTK_RESLICE_CUBIC: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_CUBIC); break; } } else { localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); } //this is needed when thick mode was enable bevore. These variable have to be reset to default values localStorage->m_Reslicer->SetOutputDimensionality( 2 ); localStorage->m_Reslicer->SetOutputSpacingZDirection(1.0); localStorage->m_Reslicer->SetOutputExtentZDirection( 0, 0 ); localStorage->m_Reslicer->Modified(); //start the pipeline with updating the largest possible, needed if the geometry of the input has changed localStorage->m_Reslicer->UpdateLargestPossibleRegion(); localStorage->m_slicedTargetImage = localStorage->m_Reslicer->GetOutput(); updated = true; } if(updated || movingInput->GetMTime() > localStorage->m_LastUpdateTime || reg->GetMTime() > localStorage->m_LastUpdateTime) { //Map moving image localStorage->m_slicedMappedImage = mitk::ImageMappingHelper::map(movingInput,reg,false,0,localStorage->m_slicedTargetImage->GetGeometry(),false,0); updated = true; } // Bounds information for reslicing (only required if reference geometry // is present) //this used for generating a vtkPLaneSource with the right size double sliceBounds[6] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; if (updated || (localStorage->m_LastUpdateTime < datanode->GetPropertyList()->GetMTime()) //was a property modified? || (localStorage->m_LastUpdateTime < datanode->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastUpdateTime < this->GetTargetNode()->GetMTime()) || (localStorage->m_LastUpdateTime < this->GetMovingNode()->GetMTime())) { localStorage->m_Reslicer->GetClippedPlaneBounds(sliceBounds); //get the spacing of the slice localStorage->m_mmPerPixel = localStorage->m_Reslicer->GetOutputSpacing(); // calculate minimum bounding rect of IMAGE in texture { double textureClippingBounds[6] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; // Calculate the actual bounds of the transformed plane clipped by the // dataset bounding box; this is required for drawing the texture at the // correct position during 3D mapping. const PlaneGeometry *planeGeometry = dynamic_cast(worldGeometry); mitk::PlaneClipping::CalculateClippedPlaneBounds(targetInput->GetGeometry(), planeGeometry, textureClippingBounds); textureClippingBounds[0] = static_cast(textureClippingBounds[0] / localStorage->m_mmPerPixel[0] + 0.5); textureClippingBounds[1] = static_cast(textureClippingBounds[1] / localStorage->m_mmPerPixel[0] + 0.5); textureClippingBounds[2] = static_cast(textureClippingBounds[2] / localStorage->m_mmPerPixel[1] + 0.5); textureClippingBounds[3] = static_cast(textureClippingBounds[3] / localStorage->m_mmPerPixel[1] + 0.5); //clipping bounds for cutting the image localStorage->m_TargetLevelWindowFilter->SetClippingBounds(textureClippingBounds); localStorage->m_MappedLevelWindowFilter->SetClippingBounds(textureClippingBounds); } this->ApplyLookuptable(renderer, this->GetTargetNode(), localStorage->m_TargetLevelWindowFilter); this->ApplyLookuptable(renderer, this->GetMovingNode(), localStorage->m_MappedLevelWindowFilter); this->ApplyLevelWindow(renderer, this->GetTargetNode(), localStorage->m_TargetLevelWindowFilter); this->ApplyLevelWindow(renderer, this->GetMovingNode(), localStorage->m_MappedLevelWindowFilter); //connect the input with the levelwindow filter localStorage->m_TargetLevelWindowFilter->SetInputData(localStorage->m_slicedTargetImage->GetVtkImageData()); localStorage->m_MappedLevelWindowFilter->SetInputData(localStorage->m_slicedMappedImage->GetVtkImageData()); - localStorage->m_TargetLevelWindowFilter->Update(); - localStorage->m_MappedLevelWindowFilter->Update(); + + localStorage->m_TargetExtractFilter->SetInputConnection(localStorage->m_TargetLevelWindowFilter->GetOutputPort()); + localStorage->m_MappedExtractFilter->SetInputConnection(localStorage->m_MappedLevelWindowFilter->GetOutputPort()); + localStorage->m_TargetExtractFilter->SetComponents(0); + localStorage->m_MappedExtractFilter->SetComponents(0); updated = true; } - //Generate evaulation image + //Generate evaluation image bool isStyleOutdated = mitk::PropertyIsOutdated(datanode,mitk::nodeProp_RegEvalStyle,localStorage->m_LastUpdateTime); bool isBlendOutdated = mitk::PropertyIsOutdated(datanode,mitk::nodeProp_RegEvalBlendFactor,localStorage->m_LastUpdateTime); bool isCheckerOutdated = mitk::PropertyIsOutdated(datanode,mitk::nodeProp_RegEvalCheckerCount,localStorage->m_LastUpdateTime); bool isWipeStyleOutdated = mitk::PropertyIsOutdated(datanode,mitk::nodeProp_RegEvalWipeStyle,localStorage->m_LastUpdateTime); bool isContourOutdated = mitk::PropertyIsOutdated(datanode,mitk::nodeProp_RegEvalTargetContour,localStorage->m_LastUpdateTime); + bool isPositionOutdated = mitk::PropertyIsOutdated(datanode, mitk::nodeProp_RegEvalCurrentPosition, localStorage->m_LastUpdateTime); if (updated || isStyleOutdated || isBlendOutdated || isCheckerOutdated || isWipeStyleOutdated || - isContourOutdated) + isContourOutdated || + isPositionOutdated) { mitk::RegEvalStyleProperty::Pointer evalStyleProp = mitk::RegEvalStyleProperty::New(); datanode->GetProperty(evalStyleProp, mitk::nodeProp_RegEvalStyle); switch (evalStyleProp->GetValueAsId()) { case 0 : { PrepareBlend(datanode, localStorage); break; } case 1 : { PrepareColorBlend(localStorage); break; } case 2 : { PrepareCheckerBoard(datanode, localStorage); break; } case 3 : { - PrepareWipe(datanode, localStorage); + const PlaneGeometry *worldGeometry = renderer->GetCurrentWorldGeometry2D(); + + Point3D currentPos3D; + datanode->GetPropertyValue(mitk::nodeProp_RegEvalCurrentPosition, currentPos3D); + + Point2D currentPos2D; + worldGeometry->Map(currentPos3D, currentPos2D); + Point2D currentIndex2D; + worldGeometry->WorldToIndex(currentPos2D, currentIndex2D); + + PrepareWipe(datanode, localStorage, currentIndex2D); break; } case 4 : { PrepareDifference(localStorage); break; } case 5 : { PrepareContour(datanode, localStorage); break; } } updated = true; } if(updated || (localStorage->m_LastUpdateTime < datanode->GetPropertyList()->GetMTime()) //was a property modified? || (localStorage->m_LastUpdateTime < datanode->GetPropertyList(renderer)->GetMTime()) ) { this->ApplyOpacity( renderer ); // do not use a VTK lookup table (we do that ourselves in m_LevelWindowFilter) localStorage->m_Texture->MapColorScalarsThroughLookupTableOff(); - ////connect the input with the levelwindow filter - //localStorage->m_LevelWindowFilter->SetInputData(localStorage->m_EvaluationImage); - // check for texture interpolation property bool textureInterpolation = false; GetDataNode()->GetBoolProperty( "texture interpolation", textureInterpolation, renderer ); //set the interpolation modus according to the property localStorage->m_Texture->SetInterpolate(textureInterpolation); // connect the texture with the output of the levelwindow filter localStorage->m_Texture->SetInputData(localStorage->m_EvaluationImage); this->TransformActor( renderer ); vtkActor* contourShadowActor = dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0)); //Connect the mapper with the input texture. This is the standard case. //setup the textured plane this->GeneratePlane( renderer, sliceBounds ); //set the plane as input for the mapper localStorage->m_Mapper->SetInputConnection(localStorage->m_Plane->GetOutputPort()); //set the texture for the actor localStorage->m_Actor->SetTexture(localStorage->m_Texture); contourShadowActor->SetVisibility( false ); // We have been modified => save this for next Update() localStorage->m_LastUpdateTime.Modified(); } } void mitk::RegEvaluationMapper2D::PrepareContour( mitk::DataNode* datanode, LocalStorage * localStorage ) { bool targetContour = true; datanode->GetBoolProperty(mitk::nodeProp_RegEvalTargetContour,targetContour); - vtkSmartPointer extractFilter1 = - vtkSmartPointer::New(); - vtkSmartPointer extractFilter2 = - vtkSmartPointer::New(); - extractFilter1->SetInputConnection(localStorage->m_TargetLevelWindowFilter->GetOutputPort()); - extractFilter1->SetComponents(0); - extractFilter2->SetInputConnection(localStorage->m_MappedLevelWindowFilter->GetOutputPort()); - extractFilter2->SetComponents(0); - vtkSmartPointer magFilter = vtkSmartPointer::New(); + if(targetContour) { - magFilter->SetInputConnection(extractFilter1->GetOutputPort()); + magFilter->SetInputConnection(localStorage->m_TargetExtractFilter->GetOutputPort()); } else { - magFilter->SetInputConnection(extractFilter2->GetOutputPort()); + magFilter->SetInputConnection(localStorage->m_MappedExtractFilter->GetOutputPort()); } vtkSmartPointer appendFilter = vtkSmartPointer::New(); appendFilter->AddInputConnection(magFilter->GetOutputPort()); appendFilter->AddInputConnection(magFilter->GetOutputPort()); if(targetContour) { - appendFilter->AddInputConnection(extractFilter2->GetOutputPort()); + appendFilter->AddInputConnection(localStorage->m_MappedExtractFilter->GetOutputPort()); } else { - appendFilter->AddInputConnection(extractFilter1->GetOutputPort()); + appendFilter->AddInputConnection(localStorage->m_TargetExtractFilter->GetOutputPort()); } appendFilter->Update(); localStorage->m_EvaluationImage = appendFilter->GetOutput(); } void mitk::RegEvaluationMapper2D::PrepareDifference( LocalStorage * localStorage ) { vtkSmartPointer diffFilter = vtkSmartPointer::New(); vtkSmartPointer absFilter = vtkSmartPointer::New(); - vtkSmartPointer extractFilter1 = - vtkSmartPointer::New(); - vtkSmartPointer extractFilter2 = - vtkSmartPointer::New(); - - extractFilter1->SetInputConnection(localStorage->m_TargetLevelWindowFilter->GetOutputPort()); - extractFilter1->SetComponents(0); - extractFilter2->SetInputConnection(localStorage->m_MappedLevelWindowFilter->GetOutputPort()); - extractFilter2->SetComponents(0); - - diffFilter->SetInputConnection(0, extractFilter1->GetOutputPort()); - diffFilter->SetInputConnection(1, extractFilter1->GetOutputPort()); + + diffFilter->SetInputConnection(0, localStorage->m_TargetExtractFilter->GetOutputPort()); + diffFilter->SetInputConnection(1, localStorage->m_MappedExtractFilter->GetOutputPort()); diffFilter->SetOperationToSubtract(); absFilter->SetInputConnection(diffFilter->GetOutputPort()); absFilter->SetOperationToAbsoluteValue(); - diffFilter->Update(); - localStorage->m_EvaluationImage = diffFilter->GetOutput(); + absFilter->Update(); + localStorage->m_EvaluationImage = absFilter->GetOutput(); } -void mitk::RegEvaluationMapper2D::PrepareWipe( mitk::DataNode* datanode, LocalStorage * localStorage ) +void mitk::RegEvaluationMapper2D::PrepareWipe(mitk::DataNode* datanode, LocalStorage * localStorage, const Point2D& currentIndex2D) { mitk::RegEvalWipeStyleProperty::Pointer evalWipeStyleProp = mitk::RegEvalWipeStyleProperty::New(); datanode->GetProperty(evalWipeStyleProp, mitk::nodeProp_RegEvalWipeStyle); vtkSmartPointer wipedFilter = vtkSmartPointer::New(); wipedFilter->SetInputConnection(0, localStorage->m_TargetLevelWindowFilter->GetOutputPort()); wipedFilter->SetInputConnection(1, localStorage->m_MappedLevelWindowFilter->GetOutputPort()); - wipedFilter->SetPosition(30, 30); + wipedFilter->SetPosition(currentIndex2D[0], currentIndex2D[1]); if (evalWipeStyleProp->GetValueAsId() == 0) { wipedFilter->SetWipeToQuad(); } else if (evalWipeStyleProp->GetValueAsId() == 1) { wipedFilter->SetWipeToHorizontal(); } else if (evalWipeStyleProp->GetValueAsId() == 2) { wipedFilter->SetWipeToVertical(); } wipedFilter->Update(); localStorage->m_EvaluationImage = wipedFilter->GetOutput(); } void mitk::RegEvaluationMapper2D::PrepareCheckerBoard( mitk::DataNode* datanode, LocalStorage * localStorage ) { int checkerCount = 5; datanode->GetIntProperty(mitk::nodeProp_RegEvalCheckerCount,checkerCount); vtkSmartPointer checkerboardFilter = vtkSmartPointer::New(); checkerboardFilter->SetInputConnection(0, localStorage->m_TargetLevelWindowFilter->GetOutputPort()); checkerboardFilter->SetInputConnection(1, localStorage->m_MappedLevelWindowFilter->GetOutputPort()); checkerboardFilter->SetNumberOfDivisions(checkerCount, checkerCount, 1); checkerboardFilter->Update(); localStorage->m_EvaluationImage = checkerboardFilter->GetOutput(); } void mitk::RegEvaluationMapper2D::PrepareColorBlend( LocalStorage * localStorage ) { vtkSmartPointer appendFilter = vtkSmartPointer::New(); - vtkSmartPointer appendFilter2 = - vtkSmartPointer::New(); - - vtkSmartPointer extractFilter1 = - vtkSmartPointer::New(); - vtkSmartPointer extractFilter2 = - vtkSmartPointer::New(); - vtkSmartPointer extractFilter3 = - vtkSmartPointer::New(); //red channel - extractFilter1->SetInputConnection(localStorage->m_MappedLevelWindowFilter->GetOutputPort()); - extractFilter1->SetComponents(0); - appendFilter->AddInputConnection(extractFilter1->GetOutputPort()); + appendFilter->AddInputConnection(localStorage->m_MappedExtractFilter->GetOutputPort()); //green channel - extractFilter2->SetInputConnection(localStorage->m_MappedLevelWindowFilter->GetOutputPort()); - extractFilter2->SetComponents(0); - appendFilter->AddInputConnection(extractFilter2->GetOutputPort()); + appendFilter->AddInputConnection(localStorage->m_MappedExtractFilter->GetOutputPort()); //blue channel - extractFilter3->SetInputConnection(localStorage->m_TargetLevelWindowFilter->GetOutputPort()); - extractFilter3->SetComponents(0); - appendFilter2->AddInputConnection(appendFilter->GetOutputPort()); - appendFilter->AddInputConnection(extractFilter3->GetOutputPort()); - appendFilter2->Update(); + appendFilter->AddInputConnection(localStorage->m_TargetExtractFilter->GetOutputPort()); + appendFilter->Update(); localStorage->m_EvaluationImage = appendFilter->GetOutput(); } void mitk::RegEvaluationMapper2D::PrepareBlend( mitk::DataNode* datanode, LocalStorage * localStorage ) { int blendfactor = 50; datanode->GetIntProperty(mitk::nodeProp_RegEvalBlendFactor,blendfactor); vtkSmartPointer blendFilter = vtkSmartPointer::New(); - vtkSmartPointer extractFilter1 = - vtkSmartPointer::New(); - vtkSmartPointer extractFilter2 = - vtkSmartPointer::New(); - - extractFilter1->SetInputConnection(localStorage->m_TargetLevelWindowFilter->GetOutputPort()); - extractFilter1->SetComponents(0); - extractFilter2->SetInputConnection(localStorage->m_MappedLevelWindowFilter->GetOutputPort()); - extractFilter2->SetComponents(0); - - blendFilter->AddInputConnection(extractFilter1->GetOutputPort()); - blendFilter->AddInputConnection(extractFilter2->GetOutputPort()); + blendFilter->AddInputConnection(localStorage->m_TargetExtractFilter->GetOutputPort()); + blendFilter->AddInputConnection(localStorage->m_MappedExtractFilter->GetOutputPort()); blendFilter->SetWeight(0, (100 - blendfactor) / 100.); blendFilter->SetWeight(1,blendfactor/100.); blendFilter->Update(); localStorage->m_EvaluationImage = blendFilter->GetOutput(); } void mitk::RegEvaluationMapper2D::ApplyLevelWindow(mitk::BaseRenderer *renderer, const mitk::DataNode* dataNode, vtkMitkLevelWindowFilter* levelFilter) { LocalStorage *localStorage = this->GetLocalStorage( renderer ); LevelWindow levelWindow; dataNode->GetLevelWindow(levelWindow, renderer, "levelwindow"); levelFilter->GetLookupTable()->SetRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); mitk::LevelWindow opacLevelWindow; if (dataNode->GetLevelWindow(opacLevelWindow, renderer, "opaclevelwindow")) { //pass the opaque level window to the filter levelFilter->SetMinOpacity(opacLevelWindow.GetLowerWindowBound()); levelFilter->SetMaxOpacity(opacLevelWindow.GetUpperWindowBound()); } else { //no opaque level window levelFilter->SetMinOpacity(0.0); levelFilter->SetMaxOpacity(255.0); } } void mitk::RegEvaluationMapper2D::ApplyLookuptable(mitk::BaseRenderer* renderer, const mitk::DataNode* dataNode, vtkMitkLevelWindowFilter* levelFilter) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); vtkLookupTable* usedLookupTable = localStorage->m_ColorLookupTable; // If lookup table or transferfunction use is requested... mitk::LookupTableProperty::Pointer lookupTableProp = dynamic_cast(dataNode->GetProperty("LookupTable")); if (lookupTableProp.IsNotNull()) // is a lookuptable set? { usedLookupTable = lookupTableProp->GetLookupTable()->GetVtkLookupTable(); } else { //"Image Rendering.Mode was set to use a lookup table but there is no property 'LookupTable'. //A default (rainbow) lookup table will be used. //Here have to do nothing. Warning for the user has been removed, due to unwanted console output //in every interation of the rendering. } levelFilter->SetLookupTable(usedLookupTable); } void mitk::RegEvaluationMapper2D::ApplyOpacity( mitk::BaseRenderer* renderer ) { LocalStorage* localStorage = this->GetLocalStorage( renderer ); float opacity = 1.0f; // check for opacity prop and use it for rendering if it exists GetDataNode()->GetOpacity( opacity, renderer, "opacity" ); //set the opacity according to the properties localStorage->m_Actor->GetProperty()->SetOpacity(opacity); if ( localStorage->m_Actors->GetParts()->GetNumberOfItems() > 1 ) { dynamic_cast( localStorage->m_Actors->GetParts()->GetItemAsObject(0) )->GetProperty()->SetOpacity(opacity); } } void mitk::RegEvaluationMapper2D::Update(mitk::BaseRenderer* renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if ( !visible ) { return; } mitk::Image* data = const_cast( this->GetTargetImage() ); if ( data == NULL ) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep( renderer ); // Check if time step is valid const TimeGeometry *dataTimeGeometry = data->GetTimeGeometry(); if ( ( dataTimeGeometry == NULL ) || ( dataTimeGeometry->CountTimeSteps() == 0 ) || ( !dataTimeGeometry->IsValidTimeStep( this->GetTimestep() ) ) ) { return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); //check if something important has changed and we need to rerender if ( (localStorage->m_LastUpdateTime < node->GetMTime()) //was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) //Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2DUpdateTime()) //was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2D()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) //was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastUpdateTime < this->GetTargetNode()->GetMTime()) //was the target node modified? || (localStorage->m_LastUpdateTime < this->GetMovingNode()->GetMTime()) //was the moving node modified? || (localStorage->m_LastUpdateTime < this->GetTargetNode()->GetPropertyList()->GetMTime()) //was a target node property modified? || (localStorage->m_LastUpdateTime < this->GetTargetNode()->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastUpdateTime < this->GetMovingNode()->GetPropertyList()->GetMTime()) //was a moving node property modified? || (localStorage->m_LastUpdateTime < this->GetMovingNode()->GetPropertyList(renderer)->GetMTime())) { this->GenerateDataForRenderer( renderer ); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } void mitk::RegEvaluationMapper2D::SetDefaultProperties(mitk::DataNode* node, mitk::BaseRenderer* renderer, bool overwrite) { mitk::RegEvaluationObject* regEval = dynamic_cast(node->GetData()); if(!regEval) { return; } // Properties common for both images and segmentations node->AddProperty( "depthOffset", mitk::FloatProperty::New( 0.0 ), renderer, overwrite ); if(regEval->GetTargetImage() && regEval->GetTargetImage()->IsRotated()) node->AddProperty( "reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_CUBIC) ); else node->AddProperty( "reslice interpolation", mitk::VtkResliceInterpolationProperty::New() ); node->AddProperty( "texture interpolation", mitk::BoolProperty::New( false ) ); // set to user configurable default value (see global options) node->AddProperty( "in plane resample extent by geometry", mitk::BoolProperty::New( false ) ); node->AddProperty( "bounding box", mitk::BoolProperty::New( false ) ); mitk::RenderingModeProperty::Pointer renderingModeProperty = mitk::RenderingModeProperty::New(); node->AddProperty( "Image Rendering.Mode", renderingModeProperty); // Set default grayscale look-up table mitk::LookupTable::Pointer mitkLut = mitk::LookupTable::New(); mitk::LookupTableProperty::Pointer mitkLutProp = mitk::LookupTableProperty::New(); mitkLutProp->SetLookupTable(mitkLut); node->SetProperty("LookupTable", mitkLutProp); node->AddProperty( "opacity", mitk::FloatProperty::New(1.0f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,1.0,1.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(0), renderer, overwrite); node->AddProperty(mitk::nodeProp_RegEvalStyle, mitk::RegEvalStyleProperty::New(0), renderer, overwrite); node->AddProperty(mitk::nodeProp_RegEvalBlendFactor, mitk::IntProperty::New(50), renderer, overwrite); node->AddProperty(mitk::nodeProp_RegEvalCheckerCount, mitk::IntProperty::New(3), renderer, overwrite); node->AddProperty(mitk::nodeProp_RegEvalTargetContour, mitk::BoolProperty::New(true), renderer, overwrite); node->AddProperty(mitk::nodeProp_RegEvalWipeStyle, mitk::RegEvalWipeStyleProperty::New(0), renderer, overwrite); - + node->AddProperty(mitk::nodeProp_RegEvalCurrentPosition, mitk::GenericProperty::New(mitk::Point3D()), renderer, overwrite); + Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::RegEvaluationMapper2D::LocalStorage* mitk::RegEvaluationMapper2D::GetLocalStorage(mitk::BaseRenderer* renderer) { return m_LSH.GetLocalStorage(renderer); } void mitk::RegEvaluationMapper2D::TransformActor(mitk::BaseRenderer* renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); //get the transformation matrix of the reslicer in order to render the slice as axial, coronal or saggital vtkSmartPointer trans = vtkSmartPointer::New(); vtkSmartPointer matrix = localStorage->m_Reslicer->GetResliceAxes(); trans->SetMatrix(matrix); //transform the plane/contour (the actual actor) to the corresponding view (axial, coronal or saggital) localStorage->m_Actor->SetUserTransform(trans); //transform the origin to center based coordinates, because MITK is center based. localStorage->m_Actor->SetPosition( -0.5*localStorage->m_mmPerPixel[0], -0.5*localStorage->m_mmPerPixel[1], 0.0); if ( localStorage->m_Actors->GetNumberOfPaths() > 1 ) { vtkActor* secondaryActor = dynamic_cast( localStorage->m_Actors->GetParts()->GetItemAsObject(0) ); secondaryActor->SetUserTransform(trans); secondaryActor->SetPosition( -0.5*localStorage->m_mmPerPixel[0], -0.5*localStorage->m_mmPerPixel[1], 0.0); } } bool mitk::RegEvaluationMapper2D::RenderingGeometryIntersectsImage( const Geometry2D* renderingGeometry, SlicedGeometry3D* imageGeometry ) { // if either one of the two geometries is NULL we return true // for safety reasons if ( renderingGeometry == NULL || imageGeometry == NULL ) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance( imageGeometry->GetCornerPoint( 0 ) ); for( int i=1; i<8; i++ ) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint( i ); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance( cornerPoint ); // if it has not the same signing as the distance of the first point if ( initialDistance * distance < 0 ) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } mitk::RegEvaluationMapper2D::LocalStorage::~LocalStorage() { } mitk::RegEvaluationMapper2D::LocalStorage::LocalStorage() { m_TargetLevelWindowFilter = vtkSmartPointer::New(); m_MappedLevelWindowFilter = vtkSmartPointer::New(); + m_TargetExtractFilter = vtkSmartPointer::New(); + m_MappedExtractFilter = vtkSmartPointer::New(); + + + //Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); //m_Texture = vtkSmartPointer::New().GetPointer(); m_Texture = vtkSmartPointer::New().GetPointer(); m_DefaultLookupTable = vtkSmartPointer::New(); m_ColorLookupTable = vtkSmartPointer::New(); m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_Actors = vtkSmartPointer::New(); m_Reslicer = mitk::ExtractSliceFilter::New(); m_EvaluationImage = vtkSmartPointer::New(); m_EmptyPolyData = vtkSmartPointer::New(); mitk::LookupTable::Pointer mitkLUT = mitk::LookupTable::New(); //built a default lookuptable mitkLUT->SetType(mitk::LookupTable::GRAYSCALE); m_DefaultLookupTable = mitkLUT->GetVtkLookupTable(); mitkLUT->SetType(mitk::LookupTable::JET); m_ColorLookupTable = mitkLUT->GetVtkLookupTable(); //do not repeat the texture (the image) m_Texture->RepeatOff(); //set the mapper for the actor m_Actor->SetMapper( m_Mapper ); vtkSmartPointer outlineShadowActor = vtkSmartPointer::New(); outlineShadowActor->SetMapper( m_Mapper ); m_Actors->AddPart( outlineShadowActor ); m_Actors->AddPart( m_Actor ); } diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.h b/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.h index 0dbfb1cd8b..05b2e628fe 100644 --- a/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.h +++ b/Modules/MatchPointRegistration/Rendering/mitkRegEvaluationMapper2D.h @@ -1,246 +1,249 @@ /*=================================================================== 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_REG_EVALUATION_MAPPER_2D_H #define MITK_REG_EVALUATION_MAPPER_2D_H //MatchPoint #include #include "mitkRegEvaluationObject.h" //MITK #include //MITK Rendering #include "mitkBaseRenderer.h" #include "mitkVtkMapper.h" #include "mitkExtractSliceFilter.h" //VTK #include #include //MITK #include "MitkMatchPointRegistrationExports.h" class vtkActor; class vtkPolyDataMapper; class vtkPlaneSource; class vtkImageData; class vtkLookupTable; class vtkImageExtractComponents; class vtkImageReslice; class vtkImageChangeInformation; class vtkPoints; class vtkMitkThickSlicesFilter; class vtkPolyData; class vtkMitkApplyLevelWindowToRGBFilter; class vtkMitkLevelWindowFilter; namespace mitk { /** \brief Mapper to resample and display 2D slices of registration evaluation visualization. * \ingroup Mapper */ class MITKMATCHPOINTREGISTRATION_EXPORT RegEvaluationMapper2D : public VtkMapper { public: /** Standard class typedefs. */ mitkClassMacro( RegEvaluationMapper2D,VtkMapper ); /** Method for creation through the object factory. */ itkFactorylessNewMacro(Self) itkCloneMacro(Self) const mitk::DataNode* GetTargetNode(void); const mitk::DataNode* GetMovingNode(void); /** \brief Get the target image to map */ const mitk::Image *GetTargetImage(void); /** \brief Get the moving image to map */ const mitk::Image *GetMovingImage(void); /** \brief Get the target image to map */ const mitk::MAPRegistrationWrapper *GetRegistration(void); /** \brief Checks whether this mapper needs to update itself and generate * data. */ virtual void Update(mitk::BaseRenderer * renderer); //### methods of MITK-VTK rendering pipeline virtual vtkProp* GetVtkProp(mitk::BaseRenderer* renderer); //### end of methods of MITK-VTK rendering pipeline /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ /** * To render transveral, coronal, and sagittal, the mapper is called three times. * For performance reasons, the corresponding data for each view is saved in the * internal helper class LocalStorage. This allows rendering n views with just * 1 mitkMapper using n vtkMapper. * */ class MITKMATCHPOINTREGISTRATION_EXPORT LocalStorage : public mitk::Mapper::BaseLocalStorage { public: /** \brief Actor of a 2D render window. */ vtkSmartPointer m_Actor; vtkSmartPointer m_Actors; /** \brief Mapper of a 2D render window. */ vtkSmartPointer m_Mapper; /** \brief Current slice of a 2D render window.*/ vtkSmartPointer m_EvaluationImage; /** \brief Empty vtkPolyData that is set when rendering geometry does not * intersect the image geometry. * \warning This member variable is set to NULL, * if no image geometry is inside the plane geometry * of the respective render window. Any user of this * slice has to check whether it is set to NULL! */ vtkSmartPointer m_EmptyPolyData; /** \brief Plane on which the slice is rendered as texture. */ vtkSmartPointer m_Plane; /** \brief The texture which is used to render the current slice. */ vtkSmartPointer m_Texture; /** \brief The lookuptables for colors and level window */ vtkSmartPointer m_ColorLookupTable; vtkSmartPointer m_DefaultLookupTable; /** \brief The actual reslicer (one per renderer) */ mitk::ExtractSliceFilter::Pointer m_Reslicer; /** part of the target image that is relevant for the rendering*/ mitk::Image::Pointer m_slicedTargetImage; /** part of the moving image mapped into the slicedTargetImage geometry*/ mitk::Image::Pointer m_slicedMappedImage; /** \brief Timestamp of last update of stored data. */ itk::TimeStamp m_LastUpdateTime; /** \brief mmPerPixel relation between pixel and mm. (World spacing).*/ mitk::ScalarType* m_mmPerPixel; /** \brief This filter is used to apply the level window to target image. */ vtkSmartPointer m_TargetLevelWindowFilter; /** \brief This filter is used to apply the level window to moving image. */ vtkSmartPointer m_MappedLevelWindowFilter; + vtkSmartPointer m_TargetExtractFilter; + vtkSmartPointer m_MappedExtractFilter; + /** \brief Default constructor of the local storage. */ LocalStorage(); /** \brief Default deconstructor of the local storage. */ ~LocalStorage(); }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; /** \brief Get the LocalStorage corresponding to the current renderer. */ LocalStorage* GetLocalStorage(mitk::BaseRenderer* renderer); /** \brief Set the default properties for general image rendering. */ static void SetDefaultProperties(mitk::DataNode* node, mitk::BaseRenderer* renderer = NULL, bool overwrite = false); protected: /** \brief Transforms the actor to the actual position in 3D. * \param renderer The current renderer corresponding to the render window. */ void TransformActor(mitk::BaseRenderer* renderer); /** \brief Generates a plane according to the size of the resliced image in milimeters. * * \image html texturedPlane.png * * In VTK a vtkPlaneSource is defined through three points. The origin and two * points defining the axes of the plane (see VTK documentation). The origin is * set to (xMin; yMin; Z), where xMin and yMin are the minimal bounds of the * resliced image in space. Z is relevant for blending and the layer property. * The center of the plane (C) is also the center of the view plane (cf. the image above). * * \note For the standard MITK view with three 2D render windows showing three * different slices, three such planes are generated. All these planes are generated * in the XY-plane (even if they depict a YZ-slice of the volume). * */ void GeneratePlane(mitk::BaseRenderer* renderer, double planeBounds[6]); /** Default constructor */ RegEvaluationMapper2D(); /** Default deconstructor */ virtual ~RegEvaluationMapper2D(); /** \brief Does the actual resampling, without rendering the image yet. * All the data is generated inside this method. The vtkProp (or Actor) * is filled with content (i.e. the resliced image). * * After generation, a 4x4 transformation matrix(t) of the current slice is obtained * from the vtkResliceImage object via GetReslicesAxis(). This matrix is * applied to each textured plane (actor->SetUserTransform(t)) to transform everything * to the actual 3D position (cf. the following image). * * \image html cameraPositioning3D.png * */ virtual void GenerateDataForRenderer(mitk::BaseRenderer *renderer); void PrepareContour( mitk::DataNode* datanode, LocalStorage * localStorage ); void PrepareDifference( LocalStorage * localStorage ); - void PrepareWipe( mitk::DataNode* datanode, LocalStorage * localStorage ); + void PrepareWipe(mitk::DataNode* datanode, LocalStorage * localStorage, const Point2D& currentIndex2D); void PrepareCheckerBoard( mitk::DataNode* datanode, LocalStorage * localStorage ); void PrepareColorBlend( LocalStorage * localStorage ); void PrepareBlend( mitk::DataNode* datanode, LocalStorage * localStorage ); /** \brief This method uses the vtkCamera clipping range and the layer property * to calcualte the depth of the object (e.g. image or contour). The depth is used * to keep the correct order for the final VTK rendering.*/ float CalculateLayerDepth(mitk::BaseRenderer* renderer); /** \brief This method applies (or modifies) the lookuptable for all types of images. * \warning To use the lookup table, the property 'Lookup Table' must be set and a 'Image Rendering.Mode' * which uses the lookup table must be set. */ void ApplyLookuptable(mitk::BaseRenderer* renderer, const mitk::DataNode* dataNode, vtkMitkLevelWindowFilter* levelFilter); /** * @brief ApplyLevelWindow Apply the level window for the given renderer. * \warning To use the level window, the property 'LevelWindow' must be set and a 'Image Rendering.Mode' which uses the level window must be set. * @param renderer Level window for which renderer? */ void ApplyLevelWindow(mitk::BaseRenderer *renderer, const mitk::DataNode* dataNode, vtkMitkLevelWindowFilter* levelFilter); /** \brief Set the opacity of the actor. */ void ApplyOpacity( mitk::BaseRenderer* renderer ); /** * \brief Calculates whether the given rendering geometry intersects the * given SlicedGeometry3D. * * This method checks if the given Geometry2D intersects the given * SlicedGeometry3D. It calculates the distance of the Geometry2D to all * 8 cornerpoints of the SlicedGeometry3D. If all distances have the same * sign (all positive or all negative) there is no intersection. * If the distances have different sign, there is an intersection. **/ bool RenderingGeometryIntersectsImage( const Geometry2D* renderingGeometry, SlicedGeometry3D* imageGeometry ); }; } // namespace mitk #endif /* MITKRegEvaluationMapper2D_H_HEADER_INCLUDED_C10E906E */ diff --git a/Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.h b/Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.h index 83cd3a4791..1aded79c0a 100644 --- a/Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.h +++ b/Modules/MatchPointRegistration/Rendering/mitkRegVisPropertyTags.h @@ -1,60 +1,61 @@ /*=================================================================== 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_REG_VIS_PROPERTY_TAGS__H_ #define _MITK_REG_VIS_PROPERTY_TAGS__H_ // MITK #include "MitkMatchPointRegistrationExports.h" namespace mitk { const char* const nodeProp_RegVisGrid = "matchpoint.RegVis.Grid"; const char* const nodeProp_RegVisGlyph = "matchpoint.RegVis.Glyph"; const char* const nodeProp_RegVisPoints = "matchpoint.RegVis.Points"; const char* const nodeProp_RegVisDirection = "matchpoint.RegVis.Direction"; const char* const nodeProp_RegVisFOVSize = "matchpoint.RegVis.FOV.size"; const char* const nodeProp_RegVisFOVOrigin = "matchpoint.RegVis.FOV.origin"; const char* const nodeProp_RegVisFOVSpacing = "matchpoint.RegVis.FOV.spacing"; const char* const nodeProp_RegVisFOVOrientation1 = "matchpoint.RegVis.FOV.orientation.row.1"; const char* const nodeProp_RegVisFOVOrientation2 = "matchpoint.RegVis.FOV.orientation.row.2"; const char* const nodeProp_RegVisFOVOrientation3 = "matchpoint.RegVis.FOV.orientation.row.3"; const char* const nodeProp_RegVisGridFrequence = "matchpoint.RegVis.Grid.Frequence"; const char* const nodeProp_RegVisGridShowStart = "matchpoint.RegVis.Grid.ShowStart"; const char* const nodeProp_RegVisColorStyle = "matchpoint.RegVis.ColorStyle"; const char* const nodeProp_RegVisGridStartColor = "matchpoint.RegVis.Grid.StartColor"; const char* const nodeProp_RegVisColorUni = "matchpoint.RegVis.Color.uni"; const char* const nodeProp_RegVisColor1Value = "matchpoint.RegVis.Color.1.value"; const char* const nodeProp_RegVisColor1Magnitude = "matchpoint.RegVis.Color.1.magnitude"; const char* const nodeProp_RegVisColor2Value = "matchpoint.RegVis.Color.2.value"; const char* const nodeProp_RegVisColor2Magnitude = "matchpoint.RegVis.Color.2.magnitude"; const char* const nodeProp_RegVisColor3Value = "matchpoint.RegVis.Color.3.value"; const char* const nodeProp_RegVisColor3Magnitude = "matchpoint.RegVis.Color.3.magnitude"; const char* const nodeProp_RegVisColor4Value = "matchpoint.RegVis.Color.4.value"; const char* const nodeProp_RegVisColor4Magnitude = "matchpoint.RegVis.Color.4.magnitude"; const char* const nodeProp_RegVisColorInterpolate = "matchpoint.RegVis.ColorInterpolate"; const char* const nodeProp_RegEvalStyle = "matchpoint.RegEval.Style"; const char* const nodeProp_RegEvalBlendFactor = "matchpoint.RegEval.BlendFactor"; const char* const nodeProp_RegEvalCheckerCount = "matchpoint.RegEval.CheckerCount"; const char* const nodeProp_RegEvalWipeStyle = "matchpoint.RegEval.WipeStyle"; const char* const nodeProp_RegEvalTargetContour = "matchpoint.RegEval.TargetContour"; +const char* const nodeProp_RegEvalCurrentPosition = "matchpoint.RegEval.CurrentPosition"; } #endif diff --git a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp index 0ff086731d..edf15e5b91 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp +++ b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.cpp @@ -1,250 +1,456 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ // Blueberry #include #include // Mitk #include #include #include #include #include "mitkRegVisPropertyTags.h" #include "mitkRegEvaluationObject.h" #include "mitkRegEvalStyleProperty.h" #include "mitkRegEvalWipeStyleProperty.h" // Qmitk +#include "QmitkRenderWindow.h" #include "QmitkMatchPointRegistrationEvaluator.h" // Qt #include #include +#include + + + +///********************************************** +QmitkSliceChangedListener::QmitkSliceChangedListener(): m_renderWindowPart(NULL), +m_PendingSliceChangedEvent(false), +m_internalUpdateFlag(false) +{ +} + +QmitkSliceChangedListener::~QmitkSliceChangedListener() +{ + this->RemoveAllObservers(); +}; + +void QmitkSliceChangedListener::OnSliceChangedDelayed() +{ + m_PendingSliceChangedEvent = false; + emit SliceChanged(); +}; + +void +QmitkSliceChangedListener::OnSliceChangedInternal(const itk::EventObject& e) +{ + // Taken from QmitkStdMultiWidget::HandleCrosshairPositionEvent(). + // Since there are always 3 events arriving (one for each render window) every time the slice + // or time changes, the slot OnSliceChangedDelayed is triggered - and only if it hasn't been + // triggered yet - so it is only executed once for every slice/time change. + if (!m_PendingSliceChangedEvent) + { + m_PendingSliceChangedEvent = true; + + QTimer::singleShot(0, this, SLOT(OnSliceChangedDelayed())); + } +}; + +void QmitkSliceChangedListener::OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/) +{ + const mitk::SliceNavigationController* sendingSlicer = + dynamic_cast(sender); + + this->RemoveObservers(sendingSlicer); +}; + +void QmitkSliceChangedListener::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) +{ + if (m_renderWindowPart != renderWindowPart) + { + m_renderWindowPart = renderWindowPart; + + if (!InitObservers()) + { + QMessageBox::information(NULL, "Error", "Unable to set up the event observers. The " \ + "plot will not be triggered on changing the crosshair, " \ + "position or time step."); + } + } +}; + +void QmitkSliceChangedListener::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) +{ + m_renderWindowPart = NULL; + this->RemoveAllObservers(renderWindowPart); +}; + +bool QmitkSliceChangedListener::InitObservers() +{ + bool result = true; + + typedef QHash WindowMapType; + WindowMapType windowMap = m_renderWindowPart->GetQmitkRenderWindows(); + + auto i = windowMap.begin(); + + while (i != windowMap.end()) + { + mitk::SliceNavigationController* sliceNavController = + i.value()->GetSliceNavigationController(); + + if (sliceNavController) + { + itk::ReceptorMemberCommand::Pointer cmdSliceEvent = + itk::ReceptorMemberCommand::New(); + cmdSliceEvent->SetCallbackFunction(this, &QmitkSliceChangedListener::OnSliceChangedInternal); + int tag = sliceNavController->AddObserver( + mitk::SliceNavigationController::GeometrySliceEvent(NULL, 0), + cmdSliceEvent); + + m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, + i.key().toStdString(), m_renderWindowPart))); + + itk::ReceptorMemberCommand::Pointer cmdTimeEvent = + itk::ReceptorMemberCommand::New(); + cmdTimeEvent->SetCallbackFunction(this, &QmitkSliceChangedListener::OnSliceChangedInternal); + tag = sliceNavController->AddObserver( + mitk::SliceNavigationController::GeometryTimeEvent(NULL, 0), + cmdTimeEvent); + + m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, + i.key().toStdString(), m_renderWindowPart))); + + itk::MemberCommand::Pointer cmdDelEvent = + itk::MemberCommand::New(); + cmdDelEvent->SetCallbackFunction(this, + &QmitkSliceChangedListener::OnSliceNavigationControllerDeleted); + tag = sliceNavController->AddObserver( + itk::DeleteEvent(), cmdDelEvent); + + m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, + i.key().toStdString(), m_renderWindowPart))); + } + + ++i; + + result = result && sliceNavController; + } + + return result; +}; + +void QmitkSliceChangedListener::RemoveObservers(const mitk::SliceNavigationController* deletedSlicer) +{ + std::pair < ObserverMapType::const_iterator, ObserverMapType::const_iterator> obsRange = + m_ObserverMap.equal_range(deletedSlicer); + + for (ObserverMapType::const_iterator pos = obsRange.first; pos != obsRange.second; ++pos) + { + pos->second.controller->RemoveObserver(pos->second.observerTag); + } + + m_ObserverMap.erase(deletedSlicer); +}; + +void QmitkSliceChangedListener::RemoveAllObservers(mitk::IRenderWindowPart* deletedPart) +{ + for (ObserverMapType::const_iterator pos = m_ObserverMap.begin(); pos != m_ObserverMap.end();) + { + ObserverMapType::const_iterator delPos = pos++; + + if (deletedPart == NULL || deletedPart == delPos->second.renderWindowPart) + { + delPos->second.controller->RemoveObserver(delPos->second.observerTag); + m_ObserverMap.erase(delPos); + } + } +}; + +QmitkSliceChangedListener::ObserverInfo::ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, + const std::string& renderWindowName, mitk::IRenderWindowPart* part) : controller(controller), observerTag(observerTag), + renderWindowName(renderWindowName), renderWindowPart(part) +{ +}; + +///********************************************** + + + + + + + + + + const std::string QmitkMatchPointRegistrationEvaluator::VIEW_ID = "org.mitk.views.matchpoint.registration.evaluator"; QmitkMatchPointRegistrationEvaluator::QmitkMatchPointRegistrationEvaluator() - : m_Parent(NULL), m_internalBlendUpdate(false) + : m_Parent(NULL), m_internalBlendUpdate(false), m_currentSelectedTimeStep(0) { + m_currentSelectedPosition.Fill(0.0); } void QmitkMatchPointRegistrationEvaluator::SetFocus() { } void QmitkMatchPointRegistrationEvaluator::Error(QString msg) { mitk::StatusBar::GetInstance()->DisplayErrorText(msg.toLatin1()); MITK_ERROR << msg.toStdString().c_str(); } void QmitkMatchPointRegistrationEvaluator::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Parent = parent; mitk::RegEvalStyleProperty::Pointer sampleProp = mitk::RegEvalStyleProperty::New(); for (unsigned int pos = 0; pos < sampleProp->Size(); ++pos) { this->m_Controls.comboStyle->insertItem(pos, QString::fromStdString(sampleProp->GetEnumString(pos))); } connect(m_Controls.comboStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(OnComboStyleChanged(int))); connect(m_Controls.pbBlend50, SIGNAL(clicked()), this, SLOT(OnBlend50Pushed())); connect(m_Controls.pbBlendTarget, SIGNAL(clicked()), this, SLOT(OnBlendTargetPushed())); connect(m_Controls.pbBlendMoving, SIGNAL(clicked()), this, SLOT(OnBlendMovingPushed())); connect(m_Controls.pbBlendToggle, SIGNAL(clicked()), this, SLOT(OnBlendTogglePushed())); connect(m_Controls.slideBlend, SIGNAL(valueChanged(int)), this, SLOT(OnSlideBlendChanged(int))); connect(m_Controls.sbBlend, SIGNAL(valueChanged(int)), this, SLOT(OnSpinBlendChanged(int))); connect(m_Controls.sbChecker, SIGNAL(valueChanged(int)), this, SLOT(OnSpinCheckerChanged(int))); connect(m_Controls.radioWipeCross, SIGNAL(toggled(bool)), this, SLOT(OnWipeStyleChanged())); connect(m_Controls.radioWipeH, SIGNAL(toggled(bool)), this, SLOT(OnWipeStyleChanged())); connect(m_Controls.radioWipeV, SIGNAL(toggled(bool)), this, SLOT(OnWipeStyleChanged())); connect(m_Controls.radioTargetContour, SIGNAL(toggled(bool)), this, SLOT(OnContourStyleChanged())); + this->m_SliceChangeListener.RenderWindowPartActivated(this->GetRenderWindowPart()); + connect(&m_SliceChangeListener, SIGNAL(SliceChanged()), this, SLOT(OnSliceChanged())); + this->ConfigureControls(); } +void QmitkMatchPointRegistrationEvaluator::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) +{ + this->m_SliceChangeListener.RenderWindowPartActivated(renderWindowPart); +} + +void QmitkMatchPointRegistrationEvaluator::RenderWindowPartDeactivated( + mitk::IRenderWindowPart* renderWindowPart) +{ + this->m_SliceChangeListener.RenderWindowPartDeactivated(renderWindowPart); +} + + void QmitkMatchPointRegistrationEvaluator::OnSelectionChanged(berry::IWorkbenchPart::Pointer source, const QList& nodes) { m_selectedEvalNode = NULL; if (nodes.size() > 0) { mitk::RegEvaluationObject* evalObj = dynamic_cast(nodes[0]->GetData()); if (evalObj) { this->m_selectedEvalNode = nodes[0]; + m_selectedNodeTime.Modified(); + OnSliceChanged(); } } ConfigureControls(); }; void QmitkMatchPointRegistrationEvaluator::ConfigureControls() { this->m_Controls.comboStyle->setEnabled(this->m_selectedEvalNode.IsNotNull()); this->m_Controls.labelNoSelect->setVisible(this->m_selectedEvalNode.IsNull()); if (this->m_selectedEvalNode.IsNotNull()) { mitk::RegEvalStyleProperty* evalProp = NULL; if (this->m_selectedEvalNode->GetProperty(evalProp, mitk::nodeProp_RegEvalStyle)) { OnComboStyleChanged(evalProp->GetValueAsId()); this->m_Controls.comboStyle->setCurrentIndex(evalProp->GetValueAsId()); } else { this->Error(QString("Cannot configure plugin controlls correctly. Node property ") + QString( mitk::nodeProp_RegEvalStyle) + QString(" has not the assumed type.")); } int factor = 50; this->m_selectedEvalNode->GetIntProperty(mitk::nodeProp_RegEvalBlendFactor, factor); this->m_Controls.sbBlend->setValue(factor); int count = 3; this->m_selectedEvalNode->GetIntProperty(mitk::nodeProp_RegEvalCheckerCount, count); this->m_Controls.sbChecker->setValue(count); bool targetContour = true; this->m_selectedEvalNode->GetBoolProperty(mitk::nodeProp_RegEvalTargetContour, targetContour); this->m_Controls.radioTargetContour->setChecked(targetContour); } else { this->m_Controls.groupBlend->setVisible(false); this->m_Controls.groupCheck->setVisible(false); this->m_Controls.groupWipe->setVisible(false); this->m_Controls.groupContour->setVisible(false); } } + +void QmitkMatchPointRegistrationEvaluator::OnSliceChanged() +{ + mitk::Point3D currentSelectedPosition = GetRenderWindowPart()->GetSelectedPosition(NULL); + unsigned int currentSelectedTimeStep = GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); + + if (m_currentSelectedPosition != currentSelectedPosition + || m_currentSelectedTimeStep != currentSelectedTimeStep + || m_selectedNodeTime > m_currentPositionTime) + { + //the current position has been changed or the selected node has been changed since the last position validation -> check position + m_currentSelectedPosition = currentSelectedPosition; + m_currentSelectedTimeStep = currentSelectedTimeStep; + m_currentPositionTime.Modified(); + + if (this->m_selectedEvalNode.IsNotNull()) + { + this->m_selectedEvalNode->SetProperty(mitk::nodeProp_RegEvalCurrentPosition, mitk::GenericProperty::New(currentSelectedPosition)); + } + } +} + void QmitkMatchPointRegistrationEvaluator::OnComboStyleChanged(int index) { m_Controls.groupBlend->setVisible(index == 0); m_Controls.groupCheck->setVisible(index == 2); m_Controls.groupWipe->setVisible(index == 3); m_Controls.groupContour->setVisible(index == 5); if (m_selectedEvalNode.IsNotNull()) { m_selectedEvalNode->SetProperty(mitk::nodeProp_RegEvalStyle, mitk::RegEvalStyleProperty::New(index)); this->GetRenderWindowPart()->RequestUpdate(); } }; void QmitkMatchPointRegistrationEvaluator::OnBlend50Pushed() { m_Controls.sbBlend->setValue(50); }; void QmitkMatchPointRegistrationEvaluator::OnBlendTargetPushed() { m_Controls.sbBlend->setValue(0); }; void QmitkMatchPointRegistrationEvaluator::OnBlendMovingPushed() { m_Controls.sbBlend->setValue(100); }; void QmitkMatchPointRegistrationEvaluator::OnBlendTogglePushed() { m_Controls.sbBlend->setValue(100 - m_Controls.sbBlend->value()); }; void QmitkMatchPointRegistrationEvaluator::OnSlideBlendChanged(int factor) { m_internalBlendUpdate = true; m_Controls.sbBlend->setValue(factor); m_internalBlendUpdate = false; }; void QmitkMatchPointRegistrationEvaluator::OnSpinBlendChanged(int factor) { if (m_selectedEvalNode.IsNotNull()) { m_selectedEvalNode->SetIntProperty(mitk::nodeProp_RegEvalBlendFactor, factor); this->GetRenderWindowPart()->RequestUpdate(); if (!m_internalBlendUpdate) { this->m_Controls.slideBlend->setValue(factor); } } }; void QmitkMatchPointRegistrationEvaluator::OnSpinCheckerChanged(int count) { if (m_selectedEvalNode.IsNotNull()) { m_selectedEvalNode->SetIntProperty(mitk::nodeProp_RegEvalCheckerCount, count); this->GetRenderWindowPart()->RequestUpdate(); } }; void QmitkMatchPointRegistrationEvaluator::OnWipeStyleChanged() { if (m_selectedEvalNode.IsNotNull()) { if (this->m_Controls.radioWipeCross->isChecked()) { m_selectedEvalNode->SetProperty(mitk::nodeProp_RegEvalWipeStyle, mitk::RegEvalWipeStyleProperty::New(0)); } else if (this->m_Controls.radioWipeH->isChecked()) { m_selectedEvalNode->SetProperty(mitk::nodeProp_RegEvalWipeStyle, mitk::RegEvalWipeStyleProperty::New(1)); } else { m_selectedEvalNode->SetProperty(mitk::nodeProp_RegEvalWipeStyle, mitk::RegEvalWipeStyleProperty::New(2)); } this->GetRenderWindowPart()->RequestUpdate(); } }; void QmitkMatchPointRegistrationEvaluator::OnContourStyleChanged() { if (m_selectedEvalNode.IsNotNull()) { m_selectedEvalNode->SetBoolProperty(mitk::nodeProp_RegEvalTargetContour, m_Controls.radioTargetContour->isChecked()); this->GetRenderWindowPart()->RequestUpdate(); } }; \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h index b901de54c9..5049a51cb8 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h +++ b/Plugins/org.mitk.gui.qt.matchpoint.evaluator/src/internal/QmitkMatchPointRegistrationEvaluator.h @@ -1,95 +1,172 @@ /*=================================================================== 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 __Q_MITK_MATCHPOINT_REGISTRATION_EVALUATOR_H #define __Q_MITK_MATCHPOINT_REGISTRATION_EVALUATOR_H #include +#include #include "ui_QmitkMatchPointRegistrationEvaluator.h" + +class QmitkSliceChangedListener : public QObject + { + // this is needed for all Qt objects that should have a Qt meta-object + // (everything that derives from QObject and wants to have signal/slots) + Q_OBJECT + + public: + QmitkSliceChangedListener(); + virtual ~QmitkSliceChangedListener(); + + signals: + void SliceChanged(); + + public slots: + void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart); + void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart); + + protected slots: + /** Overwrite function to implement the behavior on slice/time changes. */ + void OnSliceChangedDelayed(); + + protected: + + /** @brief Calls OnSliceChangedDelayed so the event isn't triggered multiple times. */ + void OnSliceChangedInternal(const itk::EventObject& e); + + void OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/); + + /** Initializes and sets the observers that are used to monitor changes in the selected position + or time point in order to actualize the view.h*/ + bool InitObservers(); + void RemoveObservers(const mitk::SliceNavigationController* deletedSlicer); + /** Removes all observers of the deletedPart. If null pointer is passed all observers will be removed.*/ + void RemoveAllObservers(mitk::IRenderWindowPart* deletedPart = NULL); + + mitk::IRenderWindowPart* m_renderWindowPart; + + // Needed for observing the events for when a slice or time step is changed. + bool m_PendingSliceChangedEvent; + + /**Helper structure to manage the registered observer events.*/ + struct ObserverInfo + { + mitk::SliceNavigationController* controller; + int observerTag; + std::string renderWindowName; + mitk::IRenderWindowPart* renderWindowPart; + + ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, + const std::string& renderWindowName, mitk::IRenderWindowPart* part); + }; + + typedef std::multimap ObserverMapType; + ObserverMapType m_ObserverMap; + + /** @brief Is a visualization currently running? */ + bool m_internalUpdateFlag; + }; + /*! \brief QmitkMatchPointRegistrationEvaluator \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. \sa QmitkFunctionality \ingroup ${plugin_target}_internal */ -class QmitkMatchPointRegistrationEvaluator : public QmitkAbstractView +class QmitkMatchPointRegistrationEvaluator : public QmitkAbstractView, public mitk::IRenderWindowPartListener { // this is needed for all Qt objects that should have a Qt meta-object // (everything that derives from QObject and wants to have signal/slots) Q_OBJECT public: static const std::string VIEW_ID; /** * Creates smartpointer typedefs */ berryObjectMacro(QmitkMatchPointRegistrationEvaluator) QmitkMatchPointRegistrationEvaluator(); virtual void CreateQtPartControl(QWidget *parent); protected slots: /// \brief Called when the user clicks the GUI button void OnComboStyleChanged(int); void OnBlend50Pushed(); void OnBlendTargetPushed(); void OnBlendMovingPushed(); void OnBlendTogglePushed(); void OnSlideBlendChanged(int); void OnSpinBlendChanged(int); void OnSpinCheckerChanged(int); void OnWipeStyleChanged(); void OnContourStyleChanged(); + void OnSliceChanged(); + protected: /// \brief called by QmitkFunctionality when DataManager's selection has changed virtual void OnSelectionChanged( berry::IWorkbenchPart::Pointer source, const QList& nodes); virtual void SetFocus(); + virtual void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart); + virtual void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart); + Ui::MatchPointRegistrationEvaluatorControls m_Controls; private: QWidget *m_Parent; void Error(QString msg); /** Methods returns a list of all eval nodes in the data manager.*/ QList GetEvalNodes(); /** * Updates the state of controls regarding to selected eval object.*/ void ConfigureControls(); mitk::DataNode::Pointer m_selectedEvalNode; mitk::DataStorage::SetOfObjects::ConstPointer m_evalNodes; + QmitkSliceChangedListener m_SliceChangeListener; + bool m_internalBlendUpdate; + + itk::TimeStamp m_selectedNodeTime; + itk::TimeStamp m_currentPositionTime; + + /** @brief currently valid selected position in the inspector*/ + mitk::Point3D m_currentSelectedPosition; + /** @brief indicates if the currently selected position is valid for the currently selected fit. + * This it is within the input image */ + unsigned int m_currentSelectedTimeStep; }; #endif // MatchPoint_h