diff --git a/Core/Code/Rendering/mitkGeometry2DDataVtkMapper3D.cpp b/Core/Code/Rendering/mitkGeometry2DDataVtkMapper3D.cpp index 02e1198a73..741635a4f8 100644 --- a/Core/Code/Rendering/mitkGeometry2DDataVtkMapper3D.cpp +++ b/Core/Code/Rendering/mitkGeometry2DDataVtkMapper3D.cpp @@ -1,699 +1,698 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "mitkGeometry2DDataVtkMapper3D.h" #include "mitkImageVtkMapper2D.h" #include "mitkLookupTableProperty.h" #include "mitkSmartPointerProperty.h" #include "mitkSurface.h" #include "mitkVtkRepresentationProperty.h" #include "mitkWeakPointerProperty.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateOr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { Geometry2DDataVtkMapper3D::Geometry2DDataVtkMapper3D() : m_NormalsActorAdded(false), m_DataStorage(NULL) { m_EdgeTuber = vtkTubeFilter::New(); m_EdgeMapper = vtkPolyDataMapper::New(); m_SurfaceCreator = Geometry2DDataToSurfaceFilter::New(); m_SurfaceCreatorBoundingBox = BoundingBox::New(); m_SurfaceCreatorPointsContainer = BoundingBox::PointsContainer::New(); m_Edges = vtkFeatureEdges::New(); m_Edges->BoundaryEdgesOn(); m_Edges->FeatureEdgesOff(); m_Edges->NonManifoldEdgesOff(); m_Edges->ManifoldEdgesOff(); m_EdgeTransformer = vtkTransformPolyDataFilter::New(); m_NormalsTransformer = vtkTransformPolyDataFilter::New(); m_EdgeActor = vtkActor::New(); m_BackgroundMapper = vtkPolyDataMapper::New(); m_BackgroundActor = vtkActor::New(); m_Prop3DAssembly = vtkAssembly::New(); m_ImageAssembly = vtkAssembly::New(); m_SurfaceCreatorBoundingBox->SetPoints( m_SurfaceCreatorPointsContainer ); m_Cleaner = vtkCleanPolyData::New(); m_Cleaner->PieceInvariantOn(); m_Cleaner->ConvertLinesToPointsOn(); m_Cleaner->ConvertPolysToLinesOn(); m_Cleaner->ConvertStripsToPolysOn(); m_Cleaner->PointMergingOn(); // Make sure that the FeatureEdge algorithm is initialized with a "valid" // (though empty) input vtkPolyData *emptyPolyData = vtkPolyData::New(); m_Cleaner->SetInput( emptyPolyData ); emptyPolyData->Delete(); m_Edges->SetInput(m_Cleaner->GetOutput()); m_EdgeTransformer->SetInput( m_Edges->GetOutput() ); m_EdgeTuber->SetInput( m_EdgeTransformer->GetOutput() ); m_EdgeTuber->SetVaryRadiusToVaryRadiusOff(); m_EdgeTuber->SetNumberOfSides( 12 ); m_EdgeTuber->CappingOn(); m_EdgeMapper->SetInput( m_EdgeTuber->GetOutput() ); m_EdgeMapper->ScalarVisibilityOff(); m_BackgroundMapper->SetInput(emptyPolyData); m_EdgeActor->SetMapper( m_EdgeMapper ); m_BackgroundActor->GetProperty()->SetAmbient( 0.5 ); m_BackgroundActor->GetProperty()->SetColor( 0.0, 0.0, 0.0 ); m_BackgroundActor->GetProperty()->SetOpacity( 1.0 ); m_BackgroundActor->SetMapper( m_BackgroundMapper ); vtkProperty * backfaceProperty = m_BackgroundActor->MakeProperty(); backfaceProperty->SetColor( 0.0, 0.0, 0.0 ); m_BackgroundActor->SetBackfaceProperty( backfaceProperty ); backfaceProperty->Delete(); m_FrontHedgeHog = vtkHedgeHog::New(); m_BackHedgeHog = vtkHedgeHog::New(); m_FrontNormalsMapper = vtkPolyDataMapper::New(); m_FrontNormalsMapper->SetInput( m_FrontHedgeHog->GetOutput() ); m_BackNormalsMapper = vtkPolyDataMapper::New(); m_Prop3DAssembly->AddPart( m_EdgeActor ); m_Prop3DAssembly->AddPart( m_ImageAssembly ); m_FrontNormalsActor = vtkActor::New(); m_FrontNormalsActor->SetMapper(m_FrontNormalsMapper); m_BackNormalsActor = vtkActor::New(); m_BackNormalsActor->SetMapper(m_BackNormalsMapper); m_DefaultLookupTable = vtkLookupTable::New(); m_DefaultLookupTable->SetTableRange( -1024.0, 4096.0 ); m_DefaultLookupTable->SetSaturationRange( 0.0, 0.0 ); m_DefaultLookupTable->SetHueRange( 0.0, 0.0 ); m_DefaultLookupTable->SetValueRange( 0.0, 1.0 ); m_DefaultLookupTable->Build(); m_DefaultLookupTable->SetTableValue( 0, 0.0, 0.0, 0.0, 0.0 ); m_ImageMapperDeletedCommand = MemberCommandType::New(); m_ImageMapperDeletedCommand->SetCallbackFunction( this, &Geometry2DDataVtkMapper3D::ImageMapperDeletedCallback ); } Geometry2DDataVtkMapper3D::~Geometry2DDataVtkMapper3D() { m_ImageAssembly->Delete(); m_Prop3DAssembly->Delete(); m_EdgeTuber->Delete(); m_EdgeMapper->Delete(); m_EdgeTransformer->Delete(); m_Cleaner->Delete(); m_Edges->Delete(); m_NormalsTransformer->Delete(); m_EdgeActor->Delete(); m_BackgroundMapper->Delete(); m_BackgroundActor->Delete(); m_DefaultLookupTable->Delete(); m_FrontNormalsMapper->Delete(); m_FrontNormalsActor->Delete(); m_FrontHedgeHog->Delete(); m_BackNormalsMapper->Delete(); m_BackNormalsActor->Delete(); m_BackHedgeHog->Delete(); // Delete entries in m_ImageActors list one by one m_ImageActors.clear(); LookupTablePropertiesList::iterator it; for(it = m_LookupTableProperties.begin(); it != m_LookupTableProperties.end();++it) { if ( it->second.LookupTableSource != NULL ) { it->second.LookupTableSource->Delete(); it->second.LookupTableSource = NULL; } } m_DataStorage = NULL; } vtkProp* Geometry2DDataVtkMapper3D::GetVtkProp(mitk::BaseRenderer * /*renderer*/) { if ( (this->GetDataNode() != NULL ) && (m_ImageAssembly != NULL) ) { // Do not transform the entire Prop3D assembly, but only the image part // here. The colored frame is transformed elsewhere (via m_EdgeTransformer), // since only vertices should be transformed there, not the poly data // itself, to avoid distortion for anisotropic datasets. m_ImageAssembly->SetUserTransform( this->GetDataNode()->GetVtkTransform() ); } return m_Prop3DAssembly; } void Geometry2DDataVtkMapper3D::UpdateVtkTransform(mitk::BaseRenderer * /*renderer*/) { m_ImageAssembly->SetUserTransform( this->GetDataNode()->GetVtkTransform(this->GetTimestep()) ); } const Geometry2DData* Geometry2DDataVtkMapper3D::GetInput() { return static_cast ( GetData() ); } void Geometry2DDataVtkMapper3D::SetDataStorageForTexture(mitk::DataStorage* storage) { if(storage != NULL && m_DataStorage != storage ) { m_DataStorage = storage; this->Modified(); } } void Geometry2DDataVtkMapper3D::ImageMapperDeletedCallback( itk::Object *caller, const itk::EventObject& /*event*/ ) { ImageVtkMapper2D *imageMapper = dynamic_cast< ImageVtkMapper2D * >( caller ); if ( (imageMapper != NULL) ) { if ( m_ImageActors.count( imageMapper ) > 0) { m_ImageActors[imageMapper].m_Sender = NULL; // sender is already destroying itself m_ImageActors.erase( imageMapper ); } if ( m_LookupTableProperties.count( imageMapper ) > 0 ) { m_LookupTableProperties[imageMapper].LookupTableSource->Delete(); m_LookupTableProperties.erase( imageMapper ); } } } void Geometry2DDataVtkMapper3D::GenerateDataForRenderer(BaseRenderer* renderer) { SetVtkMapperImmediateModeRendering(m_EdgeMapper); SetVtkMapperImmediateModeRendering(m_BackgroundMapper); // Remove all actors from the assembly, and re-initialize it with the // edge actor m_ImageAssembly->GetParts()->RemoveAllItems(); if ( !this->IsVisible(renderer) ) { // visibility has explicitly to be set in the single actors // due to problems when using cell picking: // even if the assembly is invisible, the renderer contains // references to the assemblies parts. During picking the // visibility of each part is checked, and not only for the // whole assembly. m_ImageAssembly->VisibilityOff(); m_EdgeActor->VisibilityOff(); return; } // visibility has explicitly to be set in the single actors // due to problems when using cell picking: // even if the assembly is invisible, the renderer contains // references to the assemblies parts. During picking the // visibility of each part is checked, and not only for the // whole assembly. m_ImageAssembly->VisibilityOn(); m_EdgeActor->VisibilityOn(); Geometry2DData::Pointer input = const_cast< Geometry2DData * >(this->GetInput()); if (input.IsNotNull() && (input->GetGeometry2D() != NULL)) { SmartPointerProperty::Pointer surfacecreatorprop; surfacecreatorprop = dynamic_cast< SmartPointerProperty * >(GetDataNode()->GetProperty("surfacegeometry", renderer)); if ( (surfacecreatorprop.IsNull()) || (surfacecreatorprop->GetSmartPointer().IsNull()) || ((m_SurfaceCreator = dynamic_cast (surfacecreatorprop->GetSmartPointer().GetPointer())).IsNull() ) ) { m_SurfaceCreator->PlaceByGeometryOn(); surfacecreatorprop = SmartPointerProperty::New( m_SurfaceCreator ); GetDataNode()->SetProperty("surfacegeometry", surfacecreatorprop); } m_SurfaceCreator->SetInput(input); int res; if (GetDataNode()->GetIntProperty("xresolution", res, renderer)) { m_SurfaceCreator->SetXResolution(res); } if (GetDataNode()->GetIntProperty("yresolution", res, renderer)) { m_SurfaceCreator->SetYResolution(res); } double tubeRadius = 1.0; // Radius of tubular edge surrounding plane // Clip the Geometry2D with the reference geometry bounds (if available) if ( input->GetGeometry2D()->HasReferenceGeometry() ) { Geometry3D *referenceGeometry = input->GetGeometry2D()->GetReferenceGeometry(); BoundingBox::PointType boundingBoxMin, boundingBoxMax; boundingBoxMin = referenceGeometry->GetBoundingBox()->GetMinimum(); boundingBoxMax = referenceGeometry->GetBoundingBox()->GetMaximum(); if ( referenceGeometry->GetImageGeometry() ) { for ( unsigned int i = 0; i < 3; ++i ) { boundingBoxMin[i] -= 0.5; boundingBoxMax[i] -= 0.5; } } m_SurfaceCreatorPointsContainer->CreateElementAt( 0 ) = boundingBoxMin; m_SurfaceCreatorPointsContainer->CreateElementAt( 1 ) = boundingBoxMax; m_SurfaceCreatorBoundingBox->ComputeBoundingBox(); m_SurfaceCreator->SetBoundingBox( m_SurfaceCreatorBoundingBox ); tubeRadius = referenceGeometry->GetDiagonalLength() / 450.0; } // If no reference geometry is available, clip with the current global // bounds else if (m_DataStorage.IsNotNull()) { m_SurfaceCreator->SetBoundingBox(m_DataStorage->ComputeVisibleBoundingBox(NULL, "includeInBoundingBox")); tubeRadius = sqrt( m_SurfaceCreator->GetBoundingBox()->GetDiagonalLength2() ) / 450.0; } // Calculate the surface of the Geometry2D m_SurfaceCreator->Update(); Surface *surface = m_SurfaceCreator->GetOutput(); // Check if there's something to display, otherwise return if ( (surface->GetVtkPolyData() == 0 ) || (surface->GetVtkPolyData()->GetNumberOfCells() == 0) ) { m_ImageAssembly->VisibilityOff(); return; } // add a graphical representation of the surface normals if requested DataNode* node = this->GetDataNode(); bool displayNormals = false; bool colorTwoSides = false; bool invertNormals = false; node->GetBoolProperty("draw normals 3D", displayNormals, renderer); node->GetBoolProperty("color two sides", colorTwoSides, renderer); node->GetBoolProperty("invert normals", invertNormals, renderer); //if we want to draw the display normals or render two sides we have to get the colors if( displayNormals || colorTwoSides ) { //get colors float frontColor[3] = { 0.0, 0.0, 1.0 }; node->GetColor( frontColor, renderer, "front color" ); float backColor[3] = { 1.0, 0.0, 0.0 }; node->GetColor( backColor, renderer, "back color" ); if ( displayNormals ) { m_NormalsTransformer->SetInput( surface->GetVtkPolyData() ); m_NormalsTransformer->SetTransform(node->GetVtkTransform(this->GetTimestep()) ); m_FrontHedgeHog->SetInput( m_NormalsTransformer->GetOutput() ); m_FrontHedgeHog->SetVectorModeToUseNormal(); m_FrontHedgeHog->SetScaleFactor( invertNormals ? 1.0 : -1.0 ); m_FrontNormalsActor->GetProperty()->SetColor( frontColor[0], frontColor[1], frontColor[2] ); m_BackHedgeHog->SetInput( m_NormalsTransformer->GetOutput() ); m_BackHedgeHog->SetVectorModeToUseNormal(); m_BackHedgeHog->SetScaleFactor( invertNormals ? -1.0 : 1.0 ); m_BackNormalsActor->GetProperty()->SetColor( backColor[0], backColor[1], backColor[2] ); //if there is no actor added yet, add one if ( !m_NormalsActorAdded ) { m_Prop3DAssembly->AddPart( m_FrontNormalsActor ); m_Prop3DAssembly->AddPart( m_BackNormalsActor ); m_NormalsActorAdded = true; } } //if we don't want to display normals AND there is an actor added remove the actor else if ( m_NormalsActorAdded ) { m_Prop3DAssembly->RemovePart( m_FrontNormalsActor ); m_Prop3DAssembly->RemovePart( m_BackNormalsActor ); m_NormalsActorAdded = false; } if ( colorTwoSides ) { if ( !invertNormals ) { m_BackgroundActor->GetProperty()->SetColor( backColor[0], backColor[1], backColor[2] ); m_BackgroundActor->GetBackfaceProperty()->SetColor( frontColor[0], frontColor[1], frontColor[2] ); } else { m_BackgroundActor->GetProperty()->SetColor( frontColor[0], frontColor[1], frontColor[2] ); m_BackgroundActor->GetBackfaceProperty()->SetColor( backColor[0], backColor[1], backColor[2] ); } } } // Add black background for all images (which may be transparent) m_BackgroundMapper->SetInput( surface->GetVtkPolyData() ); m_ImageAssembly->AddPart( m_BackgroundActor ); LayerSortedActorList layerSortedActors; // Traverse the data tree to find nodes resliced by ImageMapperGL2D mitk::NodePredicateOr::Pointer p = mitk::NodePredicateOr::New(); //use a predicate to get all data nodes which are "images" or inherit from mitk::Image mitk::TNodePredicateDataType< mitk::Image >::Pointer predicateAllImages = mitk::TNodePredicateDataType< mitk::Image >::New(); mitk::DataStorage::SetOfObjects::ConstPointer all = m_DataStorage->GetSubset(predicateAllImages); //process all found images for (mitk::DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode *node = it->Value(); if (node != NULL) this->ProcessNode(node, renderer, surface, layerSortedActors); } // Add all image actors to the assembly, sorted according to // layer property LayerSortedActorList::iterator actorIt; for ( actorIt = layerSortedActors.begin(); actorIt != layerSortedActors.end(); ++actorIt ) { m_ImageAssembly->AddPart( actorIt->second ); } // Configurate the tube-shaped frame: size according to the surface // bounds, color as specified in the plane's properties vtkPolyData *surfacePolyData = surface->GetVtkPolyData(); m_Cleaner->SetInput(surfacePolyData); m_EdgeTransformer->SetTransform(this->GetDataNode()->GetVtkTransform(this->GetTimestep()) ); // Adjust the radius according to extent m_EdgeTuber->SetRadius( tubeRadius ); // Get the plane's color and set the tube properties accordingly ColorProperty::Pointer colorProperty; colorProperty = dynamic_cast(this->GetDataNode()->GetProperty( "color" )); if ( colorProperty.IsNotNull() ) { const Color& color = colorProperty->GetColor(); m_EdgeActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); } else { m_EdgeActor->GetProperty()->SetColor( 1.0, 1.0, 1.0 ); } m_ImageAssembly->SetUserTransform(this->GetDataNode()->GetVtkTransform(this->GetTimestep()) ); } VtkRepresentationProperty* representationProperty; this->GetDataNode()->GetProperty(representationProperty, "material.representation", renderer); if ( representationProperty != NULL ) m_BackgroundActor->GetProperty()->SetRepresentation( representationProperty->GetVtkRepresentation() ); } void Geometry2DDataVtkMapper3D::ProcessNode( DataNode * node, BaseRenderer* renderer, Surface * surface, LayerSortedActorList &layerSortedActors ) { if ( node != NULL ) { //we need to get the information from the 2D mapper to render the texture on the 3D plane ImageVtkMapper2D *imageMapper = dynamic_cast< ImageVtkMapper2D * >( node->GetMapper(1) ); //GetMapper(1) provides the 2D mapper for the data node //if there is a 2D mapper, which is not the standard image mapper... if(!imageMapper && node->GetMapper(1)) { //... check if it is the composite mapper std::string cname(node->GetMapper(1)->GetNameOfClass()); if(!cname.compare("CompositeMapper")) //string.compare returns 0 if the two strings are equal. { //get the standard image mapper. //This is a special case in MITK and does only work for the CompositeMapper. imageMapper = dynamic_cast( node->GetMapper(3) ); } } if ( (node->IsVisible(renderer)) && imageMapper ) { WeakPointerProperty::Pointer rendererProp = dynamic_cast< WeakPointerProperty * >(GetDataNode()->GetPropertyList()->GetProperty("renderer")); if ( rendererProp.IsNotNull() ) { BaseRenderer::Pointer planeRenderer = dynamic_cast< BaseRenderer * >(rendererProp->GetWeakPointer().GetPointer()); if ( planeRenderer.IsNotNull() ) { // If it has not been initialized already in a previous pass, // generate an actor, a lookup table and a texture object to // render the image associated with the ImageMapperGL2D. vtkActor *imageActor; vtkDataSetMapper *dataSetMapper = NULL; vtkLookupTable *lookupTable; vtkTexture *texture; if ( m_ImageActors.count( imageMapper ) == 0 ) { dataSetMapper = vtkDataSetMapper::New(); //Enable rendering without copying the image. dataSetMapper->ImmediateModeRenderingOn(); lookupTable = vtkLookupTable::New(); lookupTable->DeepCopy( m_DefaultLookupTable ); texture = vtkTexture::New(); texture->SetLookupTable( lookupTable ); texture->RepeatOff(); imageActor = vtkActor::New(); imageActor->GetProperty()->SetAmbient( 0.5 ); imageActor->SetMapper( dataSetMapper ); imageActor->SetTexture( texture ); // Make imageActor the sole owner of the mapper and texture // objects lookupTable->UnRegister( NULL ); dataSetMapper->UnRegister( NULL ); texture->UnRegister( NULL ); // Store the actor so that it may be accessed in following // passes. m_ImageActors[imageMapper].Initialize(imageActor, imageMapper, m_ImageMapperDeletedCommand); } else { // Else, retrieve the actor and associated objects from the // previous pass. imageActor = m_ImageActors[imageMapper].m_Actor; dataSetMapper = (vtkDataSetMapper *)imageActor->GetMapper(); texture = imageActor->GetTexture(); lookupTable = dynamic_cast(texture->GetLookupTable()); } // Set poly data new each time its object changes (e.g. when // switching between planar and curved geometries) if ( (dataSetMapper != NULL) && (dataSetMapper->GetInput() != surface->GetVtkPolyData()) ) { dataSetMapper->SetInput( surface->GetVtkPolyData() ); } imageActor->GetMapper()->GetInput()->Update(); imageActor->GetMapper()->Update(); // ensure the right openGL context, as 3D widgets may render and take their plane texture from 2D image mappers renderer->GetRenderWindow()->MakeCurrent(); // Retrieve and update image to be mapped - const ImageVtkMapper2D::LocalStorage* localStorage = imageMapper->m_LSH.GetLocalStorage(planeRenderer); + const ImageVtkMapper2D::LocalStorage* localStorage = imageMapper->GetLocalStorage(planeRenderer); if(localStorage->m_ReslicedImage != NULL) { - localStorage->m_ReslicedImage->Update(); - texture->SetInput( localStorage->m_ReslicedImage ); + texture->SetInput( localStorage->m_Texture->GetInput() ); //default level window ScalarType windowMin = 0.0; ScalarType windowMax = 255.0; LevelWindow levelWindow; bool binary = false; node->GetBoolProperty( "binary", binary, renderer ); // check for "use color" bool useColor = false; node->GetBoolProperty( "use color", useColor, planeRenderer ); // VTK (mis-)interprets unsigned char (binary) images as color images; // So, we must manually turn on their mapping through a (gray scale) lookup table; texture->SetMapColorScalarsThroughLookupTable(binary); //if we have a binary image, the range is just 0 to 1 if( binary ) { windowMin = 0; windowMax = 1; useColor = true; } // check for level-window-prop and use it if it exists if( !binary && ( node->GetLevelWindow( levelWindow, planeRenderer, "levelWindow" ) || node->GetLevelWindow( levelWindow, planeRenderer ) ) ) { windowMin = levelWindow.GetLowerWindowBound(); windowMax = levelWindow.GetUpperWindowBound(); } vtkLookupTable *lookupTableSource; // check for LookupTable LookupTableProperty::Pointer lookupTableProp; lookupTableProp = dynamic_cast< LookupTableProperty * >(node->GetPropertyList()->GetProperty( "LookupTable" )); // If there is a lookup table supplied and we don't // want to use the color property, use it; //otherwise, use the default grayscale table if ( lookupTableProp.IsNotNull() && !useColor ) { lookupTableSource = lookupTableProp->GetLookupTable()->GetVtkLookupTable(); } else { lookupTableSource = m_DefaultLookupTable; } LookupTableProperties &lutProperties = m_LookupTableProperties[imageMapper]; // If there has been some change since the last pass which // makes it necessary to re-build the lookup table, do it. if ( (lutProperties.LookupTableSource != lookupTableSource) || (lutProperties.windowMin != windowMin) || (lutProperties.windowMax != windowMax) ) { // Note the values for the next pass (lutProperties is a // reference to the list entry!) if ( lutProperties.LookupTableSource != NULL ) { lutProperties.LookupTableSource->Delete(); } lutProperties.LookupTableSource = lookupTableSource; lutProperties.LookupTableSource->Register( NULL ); lutProperties.windowMin = windowMin; lutProperties.windowMax = windowMax; lookupTable->DeepCopy( lookupTableSource ); lookupTable->SetRange( windowMin, windowMax ); } //get the color float rgb[3] = { 1.0, 1.0, 1.0 }; node->GetColor( rgb, renderer ); // Apply color property (of the node, not of the plane) // if we want to use the color if(useColor) { imageActor->GetProperty()->SetColor( rgb[0], rgb[1], rgb[2] ); } else //else default color = white to avoid site effects from the lookuptable { imageActor->GetProperty()->SetColor( 1, 1, 1 ); } // Apply opacity property (of the node, not of the plane) float opacity = 0.999; node->GetOpacity( opacity, renderer ); imageActor->GetProperty()->SetOpacity( opacity ); // Set texture interpolation on/off bool textureInterpolation = node->IsOn( "texture interpolation", renderer ); texture->SetInterpolate( textureInterpolation ); // Store this actor to be added to the actor assembly, sort // by layer int layer = 1; node->GetIntProperty( "layer", layer ); layerSortedActors.insert(std::pair< int, vtkActor * >( layer, imageActor ) ); } } } } } } void Geometry2DDataVtkMapper3D::ActorInfo::Initialize(vtkActor* actor, itk::Object* sender, itk::Command* command) { m_Actor = actor; m_Sender = sender; // Get informed when ImageMapper object is deleted, so that // the data structures built here can be deleted as well m_ObserverID = sender->AddObserver( itk::DeleteEvent(), command ); } Geometry2DDataVtkMapper3D::ActorInfo::ActorInfo() : m_Actor(NULL), m_Sender(NULL), m_ObserverID(0) { } Geometry2DDataVtkMapper3D::ActorInfo::~ActorInfo() { if(m_Sender != NULL) { m_Sender->RemoveObserver(m_ObserverID); } if(m_Actor != NULL) { m_Actor->Delete(); } } } // namespace mitk diff --git a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp index d07619bf02..442bffccb4 100644 --- a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp +++ b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp @@ -1,1137 +1,1167 @@ /*========================================================================= Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ //MITK #include #include #include #include #include #include #include #include #include #include #include #include //#include #include //MITK Rendering #include "mitkImageVtkMapper2D.h" #include "vtkMitkThickSlicesFilter.h" #include "vtkMitkApplyLevelWindowToRGBFilter.h" //VTK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //ITK #include mitk::ImageVtkMapper2D::ImageVtkMapper2D() { } mitk::ImageVtkMapper2D::~ImageVtkMapper2D() { //The 3D RW Mapper (Geometry2DDataVtkMapper3D) is listening to this event, //in order to delete the images from the 3D RW. this->InvokeEvent( itk::DeleteEvent() ); } //set the two points defining the textured plane according to the dimension and spacing void mitk::ImageVtkMapper2D::GeneratePlane(mitk::BaseRenderer* renderer, vtkFloatingPointType 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. - - float depth = this->CalculateLayerDepth(renderer); - 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 (transversal, 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::ImageVtkMapper2D::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::ImageVtkMapper2D::GetInput( void ) { return static_cast< const mitk::Image * >( this->GetData() ); } vtkProp* mitk::ImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer* renderer) { - // this->Update(renderer); //return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actor; } void mitk::ImageVtkMapper2D::MitkRenderOverlay(BaseRenderer* renderer) { if ( this->IsVisible(renderer)==false ) return; if ( this->GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderOverlay(renderer->GetVtkRenderer()); } } void mitk::ImageVtkMapper2D::MitkRenderOpaqueGeometry(BaseRenderer* renderer) { if ( this->IsVisible( renderer )==false ) return; if ( this->GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderOpaqueGeometry( renderer->GetVtkRenderer() ); } } void mitk::ImageVtkMapper2D::MitkRenderTranslucentGeometry(BaseRenderer* renderer) { if ( this->IsVisible(renderer)==false ) return; if ( this->GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderTranslucentPolygonalGeometry(renderer->GetVtkRenderer()); } } void mitk::ImageVtkMapper2D::MitkRenderVolumetricGeometry(BaseRenderer* renderer) { if(IsVisible(renderer)==false) return; if ( GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderVolumetricGeometry(renderer->GetVtkRenderer()); } } void mitk::ImageVtkMapper2D::GenerateDataForRenderer( mitk::BaseRenderer *renderer ) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::Image *input = const_cast< mitk::Image * >( this->GetInput() ); if ( input == NULL ) { return; } //check if there is a valid worldGeometry const Geometry2D *worldGeometry = renderer->GetCurrentWorldGeometry2D(); if( ( worldGeometry == NULL ) || ( !worldGeometry->IsValid() ) || ( !worldGeometry->HasReferenceGeometry() )) { return; } // check if there is something to display if ( !input->IsVolumeSet( this->GetTimestep() ) ) return; input->Update(); vtkImageData* inputData = input->GetVtkImageData( this->GetTimestep() ); if ( inputData == NULL ) { return; } // how big the area is in physical coordinates: widthInMM x heightInMM pixels mitk::ScalarType widthInMM, heightInMM; - // where we want to sample - Point3D origin; - Vector3D right, bottom, normal; - // take transform of input image into account const TimeSlicedGeometry *inputTimeGeometry = input->GetTimeSlicedGeometry(); const Geometry3D* inputGeometry = inputTimeGeometry->GetGeometry3D( this->GetTimestep() ); - //World spacing - ScalarType mmPerPixel[2]; - // Bounds information for reslicing (only reuqired if reference geometry // is present) vtkFloatingPointType sliceBounds[6]; bool boundsInitialized = false; for ( int i = 0; i < 6; ++i ) { sliceBounds[i] = 0.0; } //Extent (in pixels) of the image Vector2D extent; // Do we have a simple PlaneGeometry? // This is the "regular" case (e.g. slicing through an image axis-parallel or even oblique) const PlaneGeometry *planeGeometry = dynamic_cast< const PlaneGeometry * >( worldGeometry ); if ( planeGeometry != NULL ) { - origin = planeGeometry->GetOrigin(); - right = planeGeometry->GetAxisVector( 0 ); // right = Extent of Image in mm (worldspace) - bottom = planeGeometry->GetAxisVector( 1 ); - normal = planeGeometry->GetNormal(); + localStorage->m_Origin = planeGeometry->GetOrigin(); + localStorage->m_Right = planeGeometry->GetAxisVector( 0 ); // right = Extent of Image in mm (worldspace) + localStorage->m_Bottom = planeGeometry->GetAxisVector( 1 ); + localStorage->m_Normal = planeGeometry->GetNormal(); bool inPlaneResampleExtentByGeometry = false; GetDataNode()->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); if ( inPlaneResampleExtentByGeometry ) { // Resampling grid corresponds to the current world geometry. This // means that the spacing of the output 2D image depends on the // currently selected world geometry, and *not* on the image itself. extent[0] = worldGeometry->GetExtent( 0 ); extent[1] = worldGeometry->GetExtent( 1 ); } else { // Resampling grid corresponds to the input geometry. This means that // the spacing of the output 2D image is directly derived from the // associated input image, regardless of the currently selected world // geometry. Vector3D rightInIndex, bottomInIndex; - inputGeometry->WorldToIndex( right, rightInIndex ); - inputGeometry->WorldToIndex( bottom, bottomInIndex ); + inputGeometry->WorldToIndex( localStorage->m_Right, rightInIndex ); + inputGeometry->WorldToIndex( localStorage->m_Bottom, bottomInIndex ); extent[0] = rightInIndex.GetNorm(); extent[1] = bottomInIndex.GetNorm(); } // Get the extent of the current world geometry and calculate resampling // spacing therefrom. widthInMM = worldGeometry->GetExtentInMM( 0 ); heightInMM = worldGeometry->GetExtentInMM( 1 ); - mmPerPixel[0] = widthInMM / extent[0]; - mmPerPixel[1] = heightInMM / extent[1]; + localStorage->m_mmPerPixel[0] = widthInMM / extent[0]; + localStorage->m_mmPerPixel[1] = heightInMM / extent[1]; - right.Normalize(); - bottom.Normalize(); - normal.Normalize(); + localStorage->m_Right.Normalize(); + localStorage->m_Bottom.Normalize(); + localStorage->m_Normal.Normalize(); //transform the origin to corner based coordinates, because VTK is corner based. - origin += right * ( mmPerPixel[0] * 0.5 ); - origin += bottom * ( mmPerPixel[1] * 0.5 ); + localStorage->m_Origin += localStorage->m_Right * ( localStorage->m_mmPerPixel[0] * 0.5 ); + localStorage->m_Origin += localStorage->m_Bottom * ( localStorage->m_mmPerPixel[1] * 0.5 ); // Use inverse transform of the input geometry for reslicing the 3D image localStorage->m_Reslicer->SetResliceTransform( inputGeometry->GetVtkTransform()->GetLinearInverse() ); // Set background level to TRANSLUCENT (see Geometry2DDataVtkMapper3D) localStorage->m_Reslicer->SetBackgroundLevel( -32768 ); // 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. boundsInitialized = this->CalculateClippedPlaneBounds( worldGeometry->GetReferenceGeometry(), planeGeometry, sliceBounds ); } else { // Do we have an AbstractTransformGeometry? // This is the case for AbstractTransformGeometry's (e.g. a thin-plate-spline transform) const mitk::AbstractTransformGeometry* abstractGeometry = dynamic_cast< const AbstractTransformGeometry * >(worldGeometry); if(abstractGeometry != NULL) { extent[0] = abstractGeometry->GetParametricExtent(0); extent[1] = abstractGeometry->GetParametricExtent(1); widthInMM = abstractGeometry->GetParametricExtentInMM(0); heightInMM = abstractGeometry->GetParametricExtentInMM(1); - mmPerPixel[0] = widthInMM / extent[0]; - mmPerPixel[1] = heightInMM / extent[1]; + localStorage->m_mmPerPixel[0] = widthInMM / extent[0]; + localStorage->m_mmPerPixel[1] = heightInMM / extent[1]; - origin = abstractGeometry->GetPlane()->GetOrigin(); + localStorage->m_Origin = abstractGeometry->GetPlane()->GetOrigin(); - right = abstractGeometry->GetPlane()->GetAxisVector(0); - right.Normalize(); + localStorage->m_Right = abstractGeometry->GetPlane()->GetAxisVector(0); + localStorage->m_Right.Normalize(); - bottom = abstractGeometry->GetPlane()->GetAxisVector(1); - bottom.Normalize(); + localStorage->m_Bottom = abstractGeometry->GetPlane()->GetAxisVector(1); + localStorage->m_Bottom.Normalize(); - normal = abstractGeometry->GetPlane()->GetNormal(); - normal.Normalize(); + localStorage->m_Normal = abstractGeometry->GetPlane()->GetNormal(); + localStorage->m_Normal.Normalize(); // Use a combination of the InputGeometry *and* the possible non-rigid // AbstractTransformGeometry for reslicing the 3D Image vtkGeneralTransform *composedResliceTransform = vtkGeneralTransform::New(); composedResliceTransform->Identity(); composedResliceTransform->Concatenate( inputGeometry->GetVtkTransform()->GetLinearInverse() ); composedResliceTransform->Concatenate( abstractGeometry->GetVtkAbstractTransform() ); localStorage->m_Reslicer->SetResliceTransform( composedResliceTransform ); composedResliceTransform->UnRegister( NULL ); // decrease RC // Set background level to BLACK instead of translucent, to avoid // boundary artifacts (see Geometry2DDataVtkMapper3D) localStorage->m_Reslicer->SetBackgroundLevel( -1023 ); } else { //no geometry => we can't reslice return; } } // Make sure that the image to display has a certain minimum size. if ( (extent[0] <= 2) && (extent[1] <= 2) ) { return; } //### begin set reslice interpolation // Initialize the interpolation mode for resampling; switch to nearest // neighbor if the input image is too small. if ( (input->GetDimension() >= 3) && (input->GetDimension(2) > 1) ) { VtkResliceInterpolationProperty *resliceInterpolationProperty; this->GetDataNode()->GetProperty( resliceInterpolationProperty, "reslice interpolation" ); int interpolationMode = VTK_RESLICE_NEAREST; if ( resliceInterpolationProperty != NULL ) { interpolationMode = resliceInterpolationProperty->GetInterpolation(); } switch ( interpolationMode ) { case VTK_RESLICE_NEAREST: localStorage->m_Reslicer->SetInterpolationModeToNearestNeighbor(); break; case VTK_RESLICE_LINEAR: localStorage->m_Reslicer->SetInterpolationModeToLinear(); break; case VTK_RESLICE_CUBIC: localStorage->m_Reslicer->SetInterpolationModeToCubic(); break; } } else { localStorage->m_Reslicer->SetInterpolationModeToNearestNeighbor(); } //### end set reslice interpolation //Thickslicing int thickSlicesMode = 0; int thickSlicesNum = 1; // Thick slices parameters if( inputData->GetNumberOfScalarComponents() == 1 ) // for now only single component are allowed { DataNode *dn=renderer->GetCurrentWorldGeometry2DNode(); if(dn) { ResliceMethodProperty *resliceMethodEnumProperty=0; if( dn->GetProperty( resliceMethodEnumProperty, "reslice.thickslices" ) && resliceMethodEnumProperty ) thickSlicesMode = resliceMethodEnumProperty->GetValueAsId(); IntProperty *intProperty=0; if( dn->GetProperty( intProperty, "reslice.thickslices.num" ) && intProperty ) { thickSlicesNum = intProperty->GetValue(); if(thickSlicesNum < 1) thickSlicesNum=1; if(thickSlicesNum > 10) thickSlicesNum=10; } } else { MITK_WARN << "no associated widget plane data tree node found"; } } localStorage->m_UnitSpacingImageFilter->SetInput( inputData ); localStorage->m_Reslicer->SetInput( localStorage->m_UnitSpacingImageFilter->GetOutput() ); //number of pixels per mm in x- and y-direction of the resampled Vector2D pixelsPerMM; - pixelsPerMM[0] = 1.0 / mmPerPixel[0]; - pixelsPerMM[1] = 1.0 / mmPerPixel[1]; + pixelsPerMM[0] = 1.0 / localStorage->m_mmPerPixel[0]; + pixelsPerMM[1] = 1.0 / localStorage->m_mmPerPixel[1]; //calulate the originArray and the orientations for the reslice-filter double originArray[3]; - itk2vtk( origin, originArray ); + itk2vtk( localStorage->m_Origin, originArray ); localStorage->m_Reslicer->SetResliceAxesOrigin( originArray ); double cosines[9]; - // direction of the X-axis of the sampled result - vnl2vtk( right.Get_vnl_vector(), cosines ); - + vnl2vtk( localStorage->m_Right.Get_vnl_vector(), cosines ); // direction of the Y-axis of the sampled result - vnl2vtk( bottom.Get_vnl_vector(), cosines + 3 );//fill next 3 elements - + vnl2vtk( localStorage->m_Bottom.Get_vnl_vector(), cosines + 3 );//fill next 3 elements // normal of the plane - vnl2vtk( normal.Get_vnl_vector(), cosines + 6 );//fill the last 3 elements + vnl2vtk( localStorage->m_Normal.Get_vnl_vector(), cosines + 6 );//fill the last 3 elements localStorage->m_Reslicer->SetResliceAxesDirectionCosines( cosines ); int xMin, xMax, yMin, yMax; if ( boundsInitialized ) { // Calculate output extent (integer values) - xMin = static_cast< int >( sliceBounds[0] / mmPerPixel[0] + 0.5 ); - xMax = static_cast< int >( sliceBounds[1] / mmPerPixel[0] + 0.5 ); - yMin = static_cast< int >( sliceBounds[2] / mmPerPixel[1] + 0.5 ); - yMax = static_cast< int >( sliceBounds[3] / mmPerPixel[1] + 0.5 ); + xMin = static_cast< int >( sliceBounds[0] / localStorage->m_mmPerPixel[0] + 0.5 ); + xMax = static_cast< int >( sliceBounds[1] / localStorage->m_mmPerPixel[0] + 0.5 ); + yMin = static_cast< int >( sliceBounds[2] / localStorage->m_mmPerPixel[1] + 0.5 ); + yMax = static_cast< int >( sliceBounds[3] / localStorage->m_mmPerPixel[1] + 0.5 ); } else { // If no reference geometry is available, we also don't know about the // maximum plane size; xMin = yMin = 0; xMax = static_cast< int >( extent[0] - pixelsPerMM[0] + 0.5); yMax = static_cast< int >( extent[1] - pixelsPerMM[1] + 0.5); } // Disallow huge dimensions if ( (xMax-xMin) * (yMax-yMin) > 4096*4096 ) { return; } // Calculate dataset spacing in plane z direction (NOT spacing of current // world geometry) double dataZSpacing = 1.0; Vector3D normInIndex; - inputGeometry->WorldToIndex( normal, normInIndex ); + inputGeometry->WorldToIndex( localStorage->m_Normal, normInIndex ); if(thickSlicesMode > 0) { dataZSpacing = 1.0 / normInIndex.GetNorm(); localStorage->m_Reslicer->SetOutputDimensionality( 3 ); localStorage->m_Reslicer->SetOutputExtent( xMin, xMax-1, yMin, yMax-1, -thickSlicesNum, 0+thickSlicesNum ); } else { localStorage->m_Reslicer->SetOutputDimensionality( 2 ); localStorage->m_Reslicer->SetOutputExtent( xMin, xMax-1, yMin, yMax-1, 0, 0 ); } localStorage->m_Reslicer->SetOutputOrigin( 0.0, 0.0, 0.0 ); - localStorage->m_Reslicer->SetOutputSpacing( mmPerPixel[0], mmPerPixel[1], dataZSpacing ); + localStorage->m_Reslicer->SetOutputSpacing( localStorage->m_mmPerPixel[0], localStorage->m_mmPerPixel[1], dataZSpacing ); // xMax and yMax are meant exclusive until now, whereas // SetOutputExtent wants an inclusive bound. Thus, we need // to subtract 1. vtkImageData* reslicedImage = 0; // Do the reslicing. Modified() is called to make sure that the reslicer is // executed even though the input geometry information did not change; this // is necessary when the input /em data, but not the /em geometry changes. if(thickSlicesMode>0) { localStorage->m_TSFilter->SetThickSliceMode( thickSlicesMode-1 ); localStorage->m_TSFilter->SetInput( localStorage->m_Reslicer->GetOutput() ); localStorage->m_TSFilter->Modified(); localStorage->m_TSFilter->Update(); reslicedImage = localStorage->m_TSFilter->GetOutput(); } else { localStorage->m_Reslicer->Modified(); localStorage->m_Reslicer->Update(); reslicedImage = localStorage->m_Reslicer->GetOutput(); } if((reslicedImage == NULL) || (reslicedImage->GetDataDimension() < 1)) { MITK_WARN << "reslicer returned empty image"; return; } //set the current slice for the localStorage // localStorage->m_ReslicedImage = reslicedImage; localStorage->m_ReslicedImage->DeepCopy( reslicedImage ); - //set the current slice as texture for the plane - localStorage->m_Texture->SetInput(localStorage->m_ReslicedImage); - - //setup the textured plane - this->GeneratePlane( renderer, sliceBounds ); + //get the number of scalar components to distinguish between different image types + int numberOfComponents = localStorage->m_ReslicedImage->GetNumberOfScalarComponents(); + //get the binary property + bool binary = false; + bool binaryOutline = false; + this->GetDataNode()->GetBoolProperty( "binary", binary, renderer ); + if(binary) //binary image + { + this->GetDataNode()->GetBoolProperty( "outline binary", binaryOutline, renderer ); + if(binaryOutline) //contour rendering + { + if ( this->GetInput()->GetPixelType().GetBpe() <= 8 ) + { + //generate ontours/outlines + localStorage->m_OutlinePolyData = CreateOutlinePolyData(renderer); - //apply the properties after the slice was set - this->ApplyProperties( renderer, mmPerPixel ); + float binaryOutlineWidth(1.0); + if (this->GetDataNode()->GetFloatProperty( "outline width", binaryOutlineWidth, renderer )) + { + localStorage->m_Actor->GetProperty()->SetLineWidth(binaryOutlineWidth); + } + } + else + { + binaryOutline = false; + this->ApplyLookuptable(renderer); + MITK_WARN << "Type of all binary images should be (un)signed char. Outline does not work on other pixel types!"; + } + } + else //standard binary image + { + if(numberOfComponents != 1) + { + MITK_ERROR << "Rendering Error: Binary Images with more then 1 component are not supported!"; + } + //Interpret the values as binary values + localStorage->m_Texture->MapColorScalarsThroughLookupTableOn(); + this->ApplyLookuptable(renderer); + } + } + else if( numberOfComponents == 1 ) //gray images + { + //Interpret the values as gray values + localStorage->m_Texture->MapColorScalarsThroughLookupTableOn(); - //get the transformation matrix of the reslicer in order to render the slice as transversal, coronal or saggital - vtkSmartPointer trans = vtkSmartPointer::New(); - vtkSmartPointer matrix = localStorage->m_Reslicer->GetResliceAxes(); + this->ApplyLookuptable(renderer); + } + else if ( (numberOfComponents == 3) || (numberOfComponents == 4) ) //RBG(A) images + { + //Interpret the RGB(A) images values correctly + localStorage->m_Texture->MapColorScalarsThroughLookupTableOff(); - //transform the origin to center based coordinates, because MITK is center based. - Point3D originCenterBased = origin; - originCenterBased -= right * ( mmPerPixel[0] * 0.5 ); - originCenterBased -= bottom * ( mmPerPixel[1] * 0.5 ); + this->ApplyLookuptable(renderer); + this->ApplyRBGALevelWindow(renderer); + } + else + { + MITK_ERROR << "2D Reindering Error: Unknown number of components!!! Please report to rendering task force or check your data!"; + } - matrix->SetElement(0, 3, originCenterBased[0]); - matrix->SetElement(1, 3, originCenterBased[1]); - matrix->SetElement(2, 3, originCenterBased[2]); - trans->SetMatrix(matrix); + this->ApplyColor( renderer ); + this->ApplyOpacity( renderer ); + this->TransformActor( renderer ); - //transform the plane/contour (the actual actor) to the corresponding view (transversal, coronal or saggital) - localStorage->m_Actor->SetUserTransform(trans); + //connect mapper with the data + if(binary && binaryOutline) //connect the mapper with the polyData which contains the lines + { + //We need the contour for the binary oultine property as actor + localStorage->m_Mapper->SetInput(localStorage->m_OutlinePolyData); + localStorage->m_Actor->SetTexture(NULL); //no texture for contours + } + else + { //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); + } // We have been modified => save this for next Update() localStorage->m_LastUpdateTime.Modified(); } +void mitk::ImageVtkMapper2D::ApplyColor( mitk::BaseRenderer* renderer ) +{ + LocalStorage *localStorage = this->GetLocalStorage( renderer ); + + // check for interpolation properties + bool textureInterpolation = false; + GetDataNode()->GetBoolProperty( "texture interpolation", textureInterpolation, renderer ); + + //set the interpolation modus according to the property + localStorage->m_Texture->SetInterpolate(textureInterpolation); + + bool useColor = true; + this->GetDataNode()->GetBoolProperty( "use color", useColor, renderer ); + if( useColor ) + { + float rgb[3]= { 1.0f, 1.0f, 1.0f }; + + // check for color prop and use it for rendering if it exists + // binary image hovering & binary image selection + bool hover = false; + bool selected = false; + GetDataNode()->GetBoolProperty("binaryimage.ishovering", hover, renderer); + GetDataNode()->GetBoolProperty("selected", selected, renderer); + if(hover && !selected) + { + mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty + ("binaryimage.hoveringcolor", renderer)); + if(colorprop.IsNotNull()) + { + memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); + } + else + { + GetColor( rgb, renderer ); + } + } + if(selected) + { + mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty + ("binaryimage.selectedcolor", renderer)); + if(colorprop.IsNotNull()) { + memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); + } + else + { + GetColor( rgb, renderer ); + } + } + if(!hover && !selected) + { + GetColor( rgb, renderer ); + } + + double rgbConv[3] = {(double)rgb[0], (double)rgb[1], (double)rgb[2]}; //conversion to double for VTK + localStorage->m_Actor->GetProperty()->SetColor(rgbConv); + } + else + { + //If the user defines a lut, we dont want to use the color and take white instead. + localStorage->m_Actor->GetProperty()->SetColor(1.0, 1.0, 1.0); + } +} + +void mitk::ImageVtkMapper2D::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 + GetOpacity( opacity, renderer ); + //set the opacity according to the properties + localStorage->m_Actor->GetProperty()->SetOpacity(opacity); +} + +void mitk::ImageVtkMapper2D::ApplyLookuptable( mitk::BaseRenderer* renderer ) +{ + bool binary = false; + bool CTFcanBeApplied = false; + this->GetDataNode()->GetBoolProperty( "binary", binary, renderer ); + LocalStorage* localStorage = this->GetLocalStorage(renderer); + + //default lookuptable + localStorage->m_Texture->SetLookupTable( localStorage->m_LookupTable ); + + if(binary) + { + //default lookuptable for binary images + localStorage->m_Texture->GetLookupTable()->SetRange(0.0, 1.0); + } + else + { + bool useColor = true; + this->GetDataNode()->GetBoolProperty( "use color", useColor, renderer ); + if((!useColor)) + { + //BEGIN PROPERTY user-defined lut + //currently we do not allow a lookuptable if it is a binary image + // If lookup table use is requested... + mitk::LookupTableProperty::Pointer LookupTableProp; + LookupTableProp = dynamic_cast + (this->GetDataNode()->GetProperty("LookupTable")); + //...check if there is a lookuptable provided by the user + if ( LookupTableProp.IsNotNull() ) + { + // If lookup table use is requested and supplied by the user: + // only update the lut, when the properties have changed... + if( LookupTableProp->GetLookupTable()->GetMTime() + <= this->GetDataNode()->GetPropertyList()->GetMTime() ) + { + LookupTableProp->GetLookupTable()->ChangeOpacityForAll( localStorage->m_Actor->GetProperty()->GetOpacity() ); + LookupTableProp->GetLookupTable()->ChangeOpacity(0, 0.0); + } + //we use the user-defined lookuptable + localStorage->m_Texture->SetLookupTable( LookupTableProp->GetLookupTable()->GetVtkLookupTable() ); + } + else + { + CTFcanBeApplied = true; + } + }//END PROPERTY user-defined lut + LevelWindow levelWindow; + this->GetLevelWindow( levelWindow, renderer ); + //set up the lookuptable with the level window range + localStorage->m_Texture->GetLookupTable()->SetRange( levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound() ); + } + //the color function can be applied if the user does not want to use color + //and does not provide a lookuptable + if(CTFcanBeApplied) + { + ApplyColorTransferFunction(renderer); + } + localStorage->m_Texture->SetInput( localStorage->m_ReslicedImage ); +} + +void mitk::ImageVtkMapper2D::ApplyColorTransferFunction(mitk::BaseRenderer* renderer) +{ + mitk::TransferFunctionProperty::Pointer transferFunctionProperty = + dynamic_cast(this->GetDataNode()->GetProperty("Image Rendering.Transfer Function",renderer )); + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + if(transferFunctionProperty.IsNotNull()) + { + localStorage->m_Texture->SetLookupTable(transferFunctionProperty->GetValue()->GetColorTransferFunction()); + } + else + { + MITK_WARN << "Neither a lookuptable nor a transfer function is set and use color is off."; + } +} + bool mitk::ImageVtkMapper2D::LineIntersectZero( vtkPoints *points, int p1, int p2, vtkFloatingPointType *bounds ) { vtkFloatingPointType point1[3]; vtkFloatingPointType point2[3]; points->GetPoint( p1, point1 ); points->GetPoint( p2, point2 ); if ( (point1[2] * point2[2] <= 0.0) && (point1[2] != point2[2]) ) { double x, y; x = ( point1[0] * point2[2] - point1[2] * point2[0] ) / ( point2[2] - point1[2] ); y = ( point1[1] * point2[2] - point1[2] * point2[1] ) / ( point2[2] - point1[2] ); if ( x < bounds[0] ) { bounds[0] = x; } if ( x > bounds[1] ) { bounds[1] = x; } if ( y < bounds[2] ) { bounds[2] = y; } if ( y > bounds[3] ) { bounds[3] = y; } bounds[4] = bounds[5] = 0.0; return true; } return false; } bool mitk::ImageVtkMapper2D::CalculateClippedPlaneBounds( const Geometry3D *boundingGeometry, const PlaneGeometry *planeGeometry, vtkFloatingPointType *bounds ) { // Clip the plane with the bounding geometry. To do so, the corner points // of the bounding box are transformed by the inverse transformation // matrix, and the transformed bounding box edges derived therefrom are // clipped with the plane z=0. The resulting min/max values are taken as // bounds for the image reslicer. const mitk::BoundingBox *boundingBox = boundingGeometry->GetBoundingBox(); mitk::BoundingBox::PointType bbMin = boundingBox->GetMinimum(); mitk::BoundingBox::PointType bbMax = boundingBox->GetMaximum(); vtkSmartPointer points = vtkSmartPointer::New(); if(boundingGeometry->GetImageGeometry()) { points->InsertPoint( 0, bbMin[0]-0.5, bbMin[1]-0.5, bbMin[2]-0.5 ); points->InsertPoint( 1, bbMin[0]-0.5, bbMin[1]-0.5, bbMax[2]-0.5 ); points->InsertPoint( 2, bbMin[0]-0.5, bbMax[1]-0.5, bbMax[2]-0.5 ); points->InsertPoint( 3, bbMin[0]-0.5, bbMax[1]-0.5, bbMin[2]-0.5 ); points->InsertPoint( 4, bbMax[0]-0.5, bbMin[1]-0.5, bbMin[2]-0.5 ); points->InsertPoint( 5, bbMax[0]-0.5, bbMin[1]-0.5, bbMax[2]-0.5 ); points->InsertPoint( 6, bbMax[0]-0.5, bbMax[1]-0.5, bbMax[2]-0.5 ); points->InsertPoint( 7, bbMax[0]-0.5, bbMax[1]-0.5, bbMin[2]-0.5 ); } else { points->InsertPoint( 0, bbMin[0], bbMin[1], bbMin[2] ); points->InsertPoint( 1, bbMin[0], bbMin[1], bbMax[2] ); points->InsertPoint( 2, bbMin[0], bbMax[1], bbMax[2] ); points->InsertPoint( 3, bbMin[0], bbMax[1], bbMin[2] ); points->InsertPoint( 4, bbMax[0], bbMin[1], bbMin[2] ); points->InsertPoint( 5, bbMax[0], bbMin[1], bbMax[2] ); points->InsertPoint( 6, bbMax[0], bbMax[1], bbMax[2] ); points->InsertPoint( 7, bbMax[0], bbMax[1], bbMin[2] ); } vtkSmartPointer newPoints = vtkSmartPointer::New(); vtkSmartPointer transform = vtkSmartPointer::New(); transform->Identity(); transform->Concatenate( planeGeometry->GetVtkTransform()->GetLinearInverse() ); transform->Concatenate( boundingGeometry->GetVtkTransform() ); transform->TransformPoints( points, newPoints ); bounds[0] = bounds[2] = 10000000.0; bounds[1] = bounds[3] = -10000000.0; bounds[4] = bounds[5] = 0.0; this->LineIntersectZero( newPoints, 0, 1, bounds ); this->LineIntersectZero( newPoints, 1, 2, bounds ); this->LineIntersectZero( newPoints, 2, 3, bounds ); this->LineIntersectZero( newPoints, 3, 0, bounds ); this->LineIntersectZero( newPoints, 0, 4, bounds ); this->LineIntersectZero( newPoints, 1, 5, bounds ); this->LineIntersectZero( newPoints, 2, 6, bounds ); this->LineIntersectZero( newPoints, 3, 7, bounds ); this->LineIntersectZero( newPoints, 4, 5, bounds ); this->LineIntersectZero( newPoints, 5, 6, bounds ); this->LineIntersectZero( newPoints, 6, 7, bounds ); this->LineIntersectZero( newPoints, 7, 4, bounds ); if ( (bounds[0] > 9999999.0) || (bounds[2] > 9999999.0) || (bounds[1] < -9999999.0) || (bounds[3] < -9999999.0) ) { return false; } else { // The resulting bounds must be adjusted by the plane spacing, since we // we have so far dealt with index coordinates const float *planeSpacing = planeGeometry->GetFloatSpacing(); bounds[0] *= planeSpacing[0]; bounds[1] *= planeSpacing[0]; bounds[2] *= planeSpacing[1]; bounds[3] *= planeSpacing[1]; bounds[4] *= planeSpacing[2]; bounds[5] *= planeSpacing[2]; return true; } } -void mitk::ImageVtkMapper2D::ApplyProperties(mitk::BaseRenderer* renderer, mitk::ScalarType mmPerPixel[2]) +void mitk::ImageVtkMapper2D::ApplyRBGALevelWindow( mitk::BaseRenderer* renderer ) { - //get the current localStorage for the corresponding renderer - LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); - - // check for interpolation properties - bool textureInterpolation = false; - GetDataNode()->GetBoolProperty( "texture interpolation", textureInterpolation, renderer ); - - //set the interpolation modus according to the property - localStorage->m_Texture->SetInterpolate(textureInterpolation); - - //do not repeat the texture (the image) - localStorage->m_Texture->RepeatOff(); - - float rgb[3]= { 1.0f, 1.0f, 1.0f }; - float opacity = 1.0f; - - // check for opacity prop and use it for rendering if it exists - GetOpacity( opacity, renderer ); - //set the opacity according to the properties - localStorage->m_Actor->GetProperty()->SetOpacity(opacity); - - // check for color prop and use it for rendering if it exists - // binary image hovering & binary image selection - bool hover = false; - bool selected = false; - GetDataNode()->GetBoolProperty("binaryimage.ishovering", hover, renderer); - GetDataNode()->GetBoolProperty("selected", selected, renderer); - if(hover && !selected) - { - mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty - ("binaryimage.hoveringcolor", renderer)); - if(colorprop.IsNotNull()) - { - memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); - } - else - { - GetColor( rgb, renderer ); - } - - } - if(selected) - { - mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty - ("binaryimage.selectedcolor", renderer)); - if(colorprop.IsNotNull()) { - memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); - } - else - { - GetColor( rgb, renderer ); - } - - } - if(!hover && !selected) - { - GetColor( rgb, renderer ); - } - - //get the binary property - bool binary = false; - this->GetDataNode()->GetBoolProperty( "binary", binary, renderer ); - localStorage->m_Texture->SetMapColorScalarsThroughLookupTable(binary); - - //use color means that we want to use the color from the property list and not a lookuptable - bool useColor = true; - this->GetDataNode()->GetBoolProperty( "use color", useColor, renderer ); - - //the finalLookuptable will be used for the rendering and can either be a user-defined table or the default lut - vtkSmartPointer finalLookuptable = localStorage->m_LookupTable; - - //BEGIN PROPERTY user-defined lut - //currently we do not allow a lookuptable if it is a binary image - bool useDefaultLut = true; - if((!useColor) && (!binary)) - { - // If lookup table use is requested... - mitk::LookupTableProperty::Pointer LookupTableProp; - LookupTableProp = dynamic_cast - (this->GetDataNode()->GetProperty("LookupTable")); - //...check if there is a lookuptable provided by the user - if ( LookupTableProp.IsNotNull() ) - { - // If lookup table use is requested and supplied by the user: - // only update the lut, when the properties have changed... - if( LookupTableProp->GetLookupTable()->GetMTime() - <= this->GetDataNode()->GetPropertyList()->GetMTime() ) - { - LookupTableProp->GetLookupTable()->ChangeOpacityForAll( opacity ); - LookupTableProp->GetLookupTable()->ChangeOpacity(0, 0.0); - } - //we use the user-defined lookuptable - finalLookuptable = LookupTableProp->GetLookupTable()->GetVtkLookupTable(); - //we obtained a user-defined lut and dont have to use the default table - useDefaultLut = false; - } - }//END PROPERTY user-defined lut - - //use the finalLookuptable for mapping the colors - localStorage->m_Texture->SetLookupTable( finalLookuptable ); - - if((useDefaultLut) && (!useColor)) - { //the color function can be applied if the user does not want to use color - //and does not provide a lookuptable - ApplyColorTransferFunction(renderer); - } - - //check if we need the default table - if( useDefaultLut ) - { - double rgbConv[3] = {(double)rgb[0], (double)rgb[1], (double)rgb[2]}; //conversion to double for VTK - localStorage->m_Actor->GetProperty()->SetColor(rgbConv); - } - else - { - //If the user defines a lut, we dont want to use the color and take white instead. - localStorage->m_Actor->GetProperty()->SetColor(1.0, 1.0, 1.0); - } - - bool binaryOutline = false; - this->GetDataNode()->GetBoolProperty( "outline binary", binaryOutline, renderer ); - if ( binary ) - { - finalLookuptable->SetRange(0.0, 1.0); - //0 is already mapped to transparent. - //1 is now mapped to the current color and alpha - - if ( this->GetInput()->GetPixelType().GetBpe() <= 8 ) - { - if (binaryOutline) - { - //generate ontours/outlines TODO: not always necessary - localStorage->m_OutlinePolyData = CreateOutlinePolyData(renderer ,localStorage->m_ReslicedImage, mmPerPixel); - - float binaryOutlineWidth(1.0); - if (this->GetDataNode()->GetFloatProperty( "outline width", binaryOutlineWidth, renderer )) - { - localStorage->m_Actor->GetProperty()->SetLineWidth(binaryOutlineWidth); - } - } - } - else - { - MITK_WARN << "Type of all binary images should be (un)signed char. Outline does not work on other pixel types!"; - } - } //END binary image handling - else - { - LevelWindow levelWindow; - this->GetLevelWindow( levelWindow, renderer ); - - //set up the lookuptable with the level window range - finalLookuptable->SetRange( levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound() ); - - mitk::PixelType pixelType = this->GetInput()->GetPixelType(); - if( pixelType.GetBitsPerComponent() == pixelType.GetBpe() ) //gray images with just one component - { - localStorage->m_Texture->MapColorScalarsThroughLookupTableOn(); - } - else //RGB, RBGA or other images tpyes with more components - { - // obtain and apply opacity level window if possible - localStorage->m_Texture->MapColorScalarsThroughLookupTableOff(); - localStorage->m_LevelWindowToRGBFilterObject->SetLookupTable(localStorage->m_Texture->GetLookupTable()); - mitk::LevelWindow opacLevelWindow; - if( this->GetLevelWindow( opacLevelWindow, renderer, "opaclevelwindow" ) ) - { - localStorage->m_LevelWindowToRGBFilterObject->SetMinOpacity(opacLevelWindow.GetLowerWindowBound()); - localStorage->m_LevelWindowToRGBFilterObject->SetMaxOpacity(opacLevelWindow.GetUpperWindowBound()); - } - else - { - localStorage->m_LevelWindowToRGBFilterObject->SetMinOpacity(0.0); - localStorage->m_LevelWindowToRGBFilterObject->SetMaxOpacity(255.0); - } - localStorage->m_LevelWindowToRGBFilterObject->SetInput(localStorage->m_ReslicedImage); - localStorage->m_Texture->SetInputConnection(localStorage->m_LevelWindowToRGBFilterObject->GetOutputPort()); - } - } - - if(binaryOutline && binary) - { - //We need the contour for the binary oultine property as actor - localStorage->m_Mapper->SetInput(localStorage->m_OutlinePolyData); - localStorage->m_Actor->SetTexture(NULL); //no texture for contours + LocalStorage* localStorage = this->GetLocalStorage( renderer ); + //pass the LuT to the RBG filter + localStorage->m_LevelWindowToRGBFilterObject->SetLookupTable(localStorage->m_Texture->GetLookupTable()); + mitk::LevelWindow opacLevelWindow; + if( this->GetLevelWindow( opacLevelWindow, renderer, "opaclevelwindow" ) ) + {//pass the opaque level window to the filter + localStorage->m_LevelWindowToRGBFilterObject->SetMinOpacity(opacLevelWindow.GetLowerWindowBound()); + localStorage->m_LevelWindowToRGBFilterObject->SetMaxOpacity(opacLevelWindow.GetUpperWindowBound()); } else - { - //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); + {//no opaque level window + localStorage->m_LevelWindowToRGBFilterObject->SetMinOpacity(0.0); + localStorage->m_LevelWindowToRGBFilterObject->SetMaxOpacity(255.0); } + localStorage->m_LevelWindowToRGBFilterObject->SetInput(localStorage->m_ReslicedImage); + //connect the texture with the output of the RGB filter + localStorage->m_Texture->SetInputConnection(localStorage->m_LevelWindowToRGBFilterObject->GetOutputPort()); } void mitk::ImageVtkMapper2D::Update(mitk::BaseRenderer* renderer) { if ( !this->IsVisible( renderer ) ) { return; } mitk::Image* data = const_cast( this->GetInput() ); 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 TimeSlicedGeometry *dataTimeGeometry = data->GetTimeSlicedGeometry(); if ( ( dataTimeGeometry == NULL ) || ( dataTimeGeometry->GetTimeSteps() == 0 ) || ( !dataTimeGeometry->IsValidTime( 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()) ) { 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::ImageVtkMapper2D::SetDefaultProperties(mitk::DataNode* node, mitk::BaseRenderer* renderer, bool overwrite) { mitk::Image::Pointer image = dynamic_cast(node->GetData()); // Properties common for both images and segmentations node->AddProperty( "use color", mitk::BoolProperty::New( true ), renderer, overwrite ); node->AddProperty( "depthOffset", mitk::FloatProperty::New( 0.0 ), renderer, overwrite ); node->AddProperty( "outline binary", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty( "outline width", mitk::FloatProperty::New( 1.0 ), renderer, overwrite ); if(image->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( mitk::DataNodeFactory::m_TextureInterpolationActive ) ); // 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 ) ); std::string modality; if ( node->GetStringProperty( "dicom.series.Modality", modality ) ) { // modality provided by DICOM or other reader if ( modality == "PT") // NOT a typo, PT is the abbreviation for PET used in DICOM { node->SetProperty( "use color", mitk::BoolProperty::New( false ), renderer ); node->SetProperty( "opacity", mitk::FloatProperty::New( 0.5 ), renderer ); } } bool isBinaryImage(false); if ( ! node->GetBoolProperty("binary", isBinaryImage) ) { // ok, property is not set, use heuristic to determine if this // is a binary image mitk::Image::Pointer centralSliceImage; ScalarType minValue = 0.0; ScalarType maxValue = 0.0; ScalarType min2ndValue = 0.0; ScalarType max2ndValue = 0.0; mitk::ImageSliceSelector::Pointer sliceSelector = mitk::ImageSliceSelector::New(); sliceSelector->SetInput(image); sliceSelector->SetSliceNr(image->GetDimension(2)/2); sliceSelector->SetTimeNr(image->GetDimension(3)/2); sliceSelector->SetChannelNr(image->GetDimension(4)/2); sliceSelector->Update(); centralSliceImage = sliceSelector->GetOutput(); if ( centralSliceImage.IsNotNull() && centralSliceImage->IsInitialized() ) { minValue = centralSliceImage->GetScalarValueMin(); maxValue = centralSliceImage->GetScalarValueMax(); min2ndValue = centralSliceImage->GetScalarValue2ndMin(); max2ndValue = centralSliceImage->GetScalarValue2ndMax(); } if ( minValue == maxValue ) { // centralSlice is strange, lets look at all data minValue = image->GetScalarValueMin(); maxValue = image->GetScalarValueMaxNoRecompute(); min2ndValue = image->GetScalarValue2ndMinNoRecompute(); max2ndValue = image->GetScalarValue2ndMaxNoRecompute(); } isBinaryImage = ( maxValue == min2ndValue && minValue == max2ndValue ); } // some more properties specific for a binary... if (isBinaryImage) { node->AddProperty( "opacity", mitk::FloatProperty::New(0.3f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( true ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(10), renderer, overwrite); } else //...or image type object { 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); } if(image.IsNotNull() && image->IsInitialized()) { if((overwrite) || (node->GetProperty("levelwindow", renderer)==NULL)) { /* initialize level/window from DICOM tags */ std::string sLevel; std::string sWindow; if ( node->GetStringProperty( "dicom.voilut.WindowCenter", sLevel ) - && node->GetStringProperty( "dicom.voilut.WindowWidth", sWindow ) ) - { + && node->GetStringProperty( "dicom.voilut.WindowWidth", sWindow ) ) + { float level = atof( sLevel.c_str() ); float window = atof( sWindow.c_str() ); mitk::LevelWindow contrast; std::string sSmallestPixelValueInSeries; std::string sLargestPixelValueInSeries; if ( node->GetStringProperty( "dicom.series.SmallestPixelValueInSeries", sSmallestPixelValueInSeries ) - && node->GetStringProperty( "dicom.series.LargestPixelValueInSeries", sLargestPixelValueInSeries ) ) - { + && node->GetStringProperty( "dicom.series.LargestPixelValueInSeries", sLargestPixelValueInSeries ) ) + { float smallestPixelValueInSeries = atof( sSmallestPixelValueInSeries.c_str() ); float largestPixelValueInSeries = atof( sLargestPixelValueInSeries.c_str() ); contrast.SetRangeMinMax( smallestPixelValueInSeries-1, largestPixelValueInSeries+1 ); // why not a little buffer? - // might remedy some l/w widget challenges + // might remedy some l/w widget challenges } else { contrast.SetAuto( static_cast(node->GetData()), false, true ); // we need this as a fallback } contrast.SetLevelWindow( level, window); node->SetProperty( "levelwindow", LevelWindowProperty::New( contrast ), renderer ); } } if(((overwrite) || (node->GetProperty("opaclevelwindow", renderer)==NULL)) && (image->GetPixelType().GetItkTypeId() && *(image->GetPixelType().GetItkTypeId()) == typeid(itk::RGBAPixel))) { mitk::LevelWindow opaclevwin; opaclevwin.SetRangeMinMax(0,255); opaclevwin.SetWindowBounds(0,255); mitk::LevelWindowProperty::Pointer prop = mitk::LevelWindowProperty::New(opaclevwin); node->SetProperty( "opaclevelwindow", prop, renderer ); } if((overwrite) || (node->GetProperty("LookupTable", renderer)==NULL)) { // add a default rainbow lookup table for color mapping mitk::LookupTable::Pointer mitkLut = mitk::LookupTable::New(); vtkLookupTable* vtkLut = mitkLut->GetVtkLookupTable(); vtkLut->SetHueRange(0.6667, 0.0); vtkLut->SetTableRange(0.0, 20.0); vtkLut->Build(); mitk::LookupTableProperty::Pointer mitkLutProp = mitk::LookupTableProperty::New(); mitkLutProp->SetLookupTable(mitkLut); node->SetProperty( "LookupTable", mitkLutProp ); } } Superclass::SetDefaultProperties(node, renderer, overwrite); } -vtkSmartPointer mitk::ImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer* renderer, vtkSmartPointer binarySlice, mitk::ScalarType mmPerPixel[2]){ - int* dims = binarySlice->GetDimensions(); //dimensions of the image +mitk::ImageVtkMapper2D::LocalStorage* mitk::ImageVtkMapper2D::GetLocalStorage(mitk::BaseRenderer* renderer) +{ + return m_LSH.GetLocalStorage(renderer); +} + +vtkSmartPointer mitk::ImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer* renderer ){ + LocalStorage* localStorage = this->GetLocalStorage(renderer); + + int* dims = localStorage->m_ReslicedImage->GetDimensions(); //dimensions of the image int line = dims[0]; //how many pixels per line? int x = 0; //pixel index x int y = 0; //pixel index y char* currentPixel; int nn = dims[0]*dims[1]; //max pixel(n,n) //get the depth for each contour float depth = CalculateLayerDepth(renderer); vtkSmartPointer points = vtkSmartPointer::New(); //the points to draw vtkSmartPointer lines = vtkSmartPointer::New(); //the lines to connect the points for (int ii = 0; ii(binarySlice->GetScalarPointer(x, y, 0)); + currentPixel = static_cast(localStorage->m_ReslicedImage->GetScalarPointer(x, y, 0)); //if the current pixel value is set to something if ((currentPixel) && (*currentPixel != 0)) { //check in which direction a line is necessary if (ii >= line && *(currentPixel-line) == 0) { //x direction - bottom edge of the pixel //add the 2 points - vtkIdType p1 = points->InsertNextPoint(x*mmPerPixel[0], y*mmPerPixel[1], depth); - vtkIdType p2 = points->InsertNextPoint((x+1)*mmPerPixel[0], y*mmPerPixel[1], depth); + vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); + vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); //add the line between both points lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } if (ii <= nn-line && *(currentPixel+line) == 0) { //x direction - top edge of the pixel - vtkIdType p1 = points->InsertNextPoint(x*mmPerPixel[0], (y+1)*mmPerPixel[1], depth); - vtkIdType p2 = points->InsertNextPoint((x+1)*mmPerPixel[0], (y+1)*mmPerPixel[1], depth); + vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); + vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } if (ii > 1 && *(currentPixel-1) == 0) { //y direction - left edge of the pixel - vtkIdType p1 = points->InsertNextPoint(x*mmPerPixel[0], y*mmPerPixel[1], depth); - vtkIdType p2 = points->InsertNextPoint(x*mmPerPixel[0], (y+1)*mmPerPixel[1], depth); + vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); + vtkIdType p2 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } if (ii < nn-1 && *(currentPixel+1) == 0) { //y direction - right edge of the pixel - vtkIdType p1 = points->InsertNextPoint((x+1)*mmPerPixel[0], y*mmPerPixel[1], depth); - vtkIdType p2 = points->InsertNextPoint((x+1)*mmPerPixel[0], (y+1)*mmPerPixel[1], depth); + vtkIdType p1 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); + vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } //reached end of line x++; if (x >= line) { x = 0; y++; } } // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); return polyData; } -void mitk::ImageVtkMapper2D::ApplyColorTransferFunction(mitk::BaseRenderer* renderer) +void mitk::ImageVtkMapper2D::TransformActor(mitk::BaseRenderer* renderer) { - mitk::TransferFunctionProperty::Pointer transferFunctionProperty = - dynamic_cast(this->GetDataNode()->GetProperty("Image Rendering.Transfer Function",renderer )); - LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); - if(transferFunctionProperty.IsNotNull()) - { - localStorage->m_Texture->SetLookupTable(transferFunctionProperty->GetValue()->GetColorTransferFunction()); - } - else - { - MITK_WARN << "Neither a lookuptable nor a transfer function is set and use color is off."; - } + LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); + //get the transformation matrix of the reslicer in order to render the slice as transversal, coronal or saggital + vtkSmartPointer trans = vtkSmartPointer::New(); + vtkSmartPointer matrix = localStorage->m_Reslicer->GetResliceAxes(); + + //transform the origin to center based coordinates, because MITK is center based. + Point3D originCenterBased = localStorage->m_Origin; + originCenterBased -= localStorage->m_Right * ( localStorage->m_mmPerPixel[0] * 0.5 ); + originCenterBased -= localStorage->m_Bottom * ( localStorage->m_mmPerPixel[1] * 0.5 ); + + matrix->SetElement(0, 3, originCenterBased[0]); + matrix->SetElement(1, 3, originCenterBased[1]); + matrix->SetElement(2, 3, originCenterBased[2]); + trans->SetMatrix(matrix); + + //transform the plane/contour (the actual actor) to the corresponding view (transversal, coronal or saggital) + localStorage->m_Actor->SetUserTransform(trans); } mitk::ImageVtkMapper2D::LocalStorage::LocalStorage() { //Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); m_Texture = vtkSmartPointer::New(); m_LookupTable = vtkSmartPointer::New(); m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_Reslicer = vtkSmartPointer::New(); m_TSFilter = vtkSmartPointer::New(); m_UnitSpacingImageFilter = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_ReslicedImage = vtkSmartPointer::New(); //the following actions are always the same and thus can be performed //in the constructor for each image (i.e. the image-corresponding local storage) m_TSFilter->ReleaseDataFlagOn(); m_Reslicer->ReleaseDataFlagOn(); m_UnitSpacingImageFilter->SetOutputSpacing( 1.0, 1.0, 1.0 ); //built a default lookuptable m_LookupTable->SetRampToLinear(); m_LookupTable->SetSaturationRange( 0.0, 0.0 ); m_LookupTable->SetHueRange( 0.0, 0.0 ); m_LookupTable->SetValueRange( 0.0, 1.0 ); m_LookupTable->Build(); //map all black values to transparent m_LookupTable->SetTableValue(0, 0.0, 0.0, 0.0, 0.0); + //do not repeat the texture (the image) + m_Texture->RepeatOff(); + //set the mapper for the actor m_Actor->SetMapper(m_Mapper); //filter for RGB(A) images m_LevelWindowToRGBFilterObject = new vtkMitkApplyLevelWindowToRGBFilter(); } diff --git a/Core/Code/Rendering/mitkImageVtkMapper2D.h b/Core/Code/Rendering/mitkImageVtkMapper2D.h index 94fc1e1f52..ded80f35b7 100644 --- a/Core/Code/Rendering/mitkImageVtkMapper2D.h +++ b/Core/Code/Rendering/mitkImageVtkMapper2D.h @@ -1,246 +1,277 @@ /*========================================================================= Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #ifndef MITKIMAGEVTKMAPPER2D_H_HEADER_INCLUDED_C10E906E #define MITKIMAGEVTKMAPPER2D_H_HEADER_INCLUDED_C10E906E //MITK #include //MITK Rendering #include "mitkBaseRenderer.h" #include "mitkVtkMapper2D.h" //VTK #include class vtkActor; class vtkPolyDataMapper; class vtkPlaneSource; class vtkImageData; class vtkLookupTable; class vtkImageReslice; class vtkImageChangeInformation; class vtkPoints; class vtkMitkThickSlicesFilter; class vtkPolyData; class vtkMitkApplyLevelWindowToRGBFilter; namespace mitk { /** \brief Mapper to resample and display 2D slices of a 3D image. * * The following image gives a brief overview of the mapping and the involved parts. * * \image html imageVtkMapper2Darchitecture.png * * First, the image is resliced by means of vtkImageReslice. The volume image * serves as input to the mapper in addition to spatial placement of the slice and a few other * properties such as thick slices. This code was already present in the old version * (mitkImageMapperGL2D). * * Next, the obtained slice (m_ReslicedImage) is used to create a texture * (m_Texture) and a plane onto which the texture is rendered (m_Plane). For * mapping purposes, a vtkPolyDataMapper (m_Mapper) is utilized. Orthographic * projection is applied to create the effect of a 2D image. The mapper and the * texture are assigned to the actor (m_Actor) which is passed to the VTK rendering * pipeline via the method GetVtkProp(). * * In order to transform the textured plane to the correct position in space, the * same transformation as used for reslicing is applied to both the camera and the * vtkActor. All important steps are explained in more detail below. The resulting * 2D image (by reslicing the underlying 3D input image appropriately) can either * be directly rendered in a 2D view or just be calculated to be used later by another * rendering entity, e.g. in texture mapping in a 3D view. * * Properties that can be set for images and influence the imageMapper2D are: * * - \b "opacity": (FloatProperty) Opacity of the image * - \b "color": (ColorProperty) Color of the image * - \b "use color": (BoolProperty) Use the color of the image or not * - \b "binary": (BoolProperty) is the image a binary image or not * - \b "outline binary": (BoolProperty) show outline of the image or not * - \b "texture interpolation": (BoolProperty) texture interpolation of the image * - \b "reslice interpolation": (VtkResliceInterpolationProperty) reslice interpolation of the image * - \b "in plane resample extent by geometry": (BoolProperty) Do it or not * - \b "bounding box": (BoolProperty) Is the Bounding Box of the image shown or not * - \b "layer": (IntProperty) Layer of the image * - \b "volume annotation color": (ColorProperty) color of the volume annotation, TODO has to be reimplemented * - \b "volume annotation unit": (StringProperty) annotation unit as string (does not implicit convert the unit!) unit is ml or cm3, TODO has to be reimplemented * The default properties are: * - \b "opacity", mitk::FloatProperty::New(0.3f), renderer, overwrite ) * - \b "color", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ) * - \b "use color", mitk::BoolProperty::New( true ), renderer, overwrite ) * - \b "binary", mitk::BoolProperty::New( true ), renderer, overwrite ) * - \b "outline binary", mitk::BoolProperty::New( false ), renderer, overwrite ) * - \b "texture interpolation", mitk::BoolProperty::New( mitk::DataNodeFactory::m_TextureInterpolationActive ) ) * - \b "reslice interpolation", mitk::VtkResliceInterpolationProperty::New() ) * - \b "in plane resample extent by geometry", mitk::BoolProperty::New( false ) ) * - \b "bounding box", mitk::BoolProperty::New( false ) ) * - \b "layer", mitk::IntProperty::New(10), renderer, overwrite) * If the modality-property is set for an image, the mapper uses modality-specific default properties, * e.g. color maps, if they are defined. * \ingroup Mapper */ class MITK_CORE_EXPORT ImageVtkMapper2D : public VtkMapper2D { public: - /** Standard class typedefs. */ mitkClassMacro( ImageVtkMapper2D,VtkMapper2D ); /** Method for creation through the object factory. */ itkNewMacro(Self); /** \brief Get the Image to map */ const mitk::Image *GetInput(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); virtual void MitkRenderOverlay(BaseRenderer* renderer); virtual void MitkRenderOpaqueGeometry(BaseRenderer* renderer); virtual void MitkRenderTranslucentGeometry(BaseRenderer* renderer); virtual void MitkRenderVolumetricGeometry(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 MITK_CORE_EXPORT LocalStorage : public mitk::Mapper::BaseLocalStorage { public: /** \brief Actor of a 2D render window. */ vtkSmartPointer m_Actor; /** \brief Mapper of a 2D render window. */ vtkSmartPointer m_Mapper; /** \brief Current slice of a 2D render window. */ vtkSmartPointer m_ReslicedImage; /** \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 lookuptable for colors and level window */ vtkSmartPointer m_LookupTable; /** \brief The actual reslicer (one per renderer) */ vtkSmartPointer m_Reslicer; /** \brief Thickslices post filtering. */ vtkSmartPointer m_TSFilter; /** \brief Using unit spacing for resampling makes life easier TODO improve docu ...*/ vtkSmartPointer m_UnitSpacingImageFilter; /** \brief PolyData object containg all lines/points needed for outlining the contour. This container is used to save a computed contour for the next rendering execution. For instance, if you zoom or pann, there is no need to recompute the contour. */ vtkSmartPointer m_OutlinePolyData; /** \brief Timestamp of last update of stored data. */ itk::TimeStamp m_LastUpdateTime; + /** \brief Origin of the 2D geometry. */ + mitk::Point3D m_Origin; + /** \brief Bottom end point of the y-axis of the 2D geometry. */ + mitk::Vector3D m_Bottom; + /** \brief Right end point of the x-axis of the 2D geometry. */ + mitk::Vector3D m_Right; + /** \brief Normal of the 2D geometry. */ + mitk::Vector3D m_Normal; + /** \brief mmPerPixel relation between pixel and mm. (World spacing).*/ + mitk::ScalarType m_mmPerPixel[2]; + /** \brief This filter is used to apply the level window to RBG(A) images. */ vtkMitkApplyLevelWindowToRGBFilter* m_LevelWindowToRGBFilterObject; /** \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::Mapper::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 Apply all properties to the vtkActor (e.g. color, opacity, binary image handling, etc.).*/ - virtual void ApplyProperties(mitk::BaseRenderer* renderer, ScalarType mmPerPixel[2]); + /** \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, vtkFloatingPointType planeBounds[6]); /** \brief Generates a vtkPolyData object containing the outline of a given binary slice. \param binarySlice - The binary image slice. (Volumes are not supported.) \param mmPerPixel - Spacing of the binary image slice. Hence it's 2D, only in x/y-direction. \note This code has been taken from the deprecated library iil. */ - vtkSmartPointer CreateOutlinePolyData(mitk::BaseRenderer* renderer, vtkSmartPointer binarySlice, ScalarType mmPerPixel[2]); + vtkSmartPointer CreateOutlinePolyData(mitk::BaseRenderer* renderer); /** Default constructor */ ImageVtkMapper2D(); /** Default deconstructor */ virtual ~ImageVtkMapper2D(); /** \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); /** \brief Internal helper method for intersection testing used only in CalculateClippedPlaneBounds() */ bool LineIntersectZero( vtkPoints *points, int p1, int p2, vtkFloatingPointType *bounds ); /** \brief Calculate the bounding box of the resliced image. This is necessary for arbitrarily rotated planes in an image volume. A rotated plane (e.g. in swivel mode) will have a new bounding box, which needs to be calculated. */ bool CalculateClippedPlaneBounds( const Geometry3D *boundingGeometry, const PlaneGeometry *planeGeometry, vtkFloatingPointType *bounds ); /** \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 a level window on RBG(A) images. + * It should only be called for internally for RGB(A) images. */ + void ApplyRBGALevelWindow( mitk::BaseRenderer* renderer ); + + /** \brief This method applies (or modifies) the lookuptable for all types of images. */ + void ApplyLookuptable( mitk::BaseRenderer* renderer ); + + /** \brief This method applies a color transfer function, if no LookuptableProperty is set. + Internally, a vtkColorTransferFunction is used. This is usefull for coloring continous + images (e.g. float) */ void ApplyColorTransferFunction(mitk::BaseRenderer* renderer); + + /** \brief Set the color of the image/polydata */ + void ApplyColor( mitk::BaseRenderer* renderer ); + + /** \brief Set the opacity of the actor. */ + void ApplyOpacity( mitk::BaseRenderer* renderer ); }; } // namespace mitk #endif /* MITKIMAGEVTKMAPPER2D_H_HEADER_INCLUDED_C10E906E */