diff --git a/Core/Code/Rendering/vtkNeverTranslucentTexture.h b/Core/Code/Rendering/vtkNeverTranslucentTexture.h index 2dd2d5188b..9ccd5511dc 100644 --- a/Core/Code/Rendering/vtkNeverTranslucentTexture.h +++ b/Core/Code/Rendering/vtkNeverTranslucentTexture.h @@ -1,107 +1,107 @@ /*=================================================================== 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 mitkNeverTranslucentTexture_h #define mitkNeverTranslucentTexture_h #include #include #include /** \brief VTK Fix to speed up our image rendering. The way we render images while changing the contrast via level/window extremely slows down rendering. The cause of this slowdown is that VTK asks a texture (via a call to IsTranslucent) if it is translucent. When the texture refers to a lookup table this question is answered in a most expensive way: pushing every pixel through the lookup table to see whether it would render translucent. We can speed this up extremely by always answering NO. 2D Image rendering in the context of MITK is still correct. This class is injected into the VTK system by registering it with vtkObjectFactory as a replacement for vtkTexture. We chose vtkOpenGLTexture as super class, because it seems that the other texture super class is deprecated: http://www.cmake.org/Wiki/VTK:How_I_mangled_Mesa \sa ImageVtkMapper2D */ /* NOT exported, this is a 2D image mapper helper */ -class vtkNeverTranslucentTexture : public vtkOpenGLTexture +class MITK_CORE_EXPORT vtkNeverTranslucentTexture : public vtkOpenGLTexture { public: static vtkNeverTranslucentTexture* New(); vtkTypeMacro(vtkNeverTranslucentTexture, vtkTexture); void PrintSelf(ostream& os, vtkIndent indent); /** \brief The FIX (see class description). VTK Description: Is this Texture Translucent? Returns false (0) if the texture is either fully opaque or has only fully transparent pixels and fully opaque pixels and the Interpolate flag is turn off. */ virtual int IsTranslucent(); protected: vtkNeverTranslucentTexture(); private: vtkNeverTranslucentTexture(const vtkNeverTranslucentTexture&); // Not implemented. void operator=(const vtkNeverTranslucentTexture&); // Not implemented. }; /** \brief Factory for vtkNeverTranslucentTexture (see this class!). Registered in CoreActivator to replace all instances of vtkTexture with vtkNeverTranslucentTexture. Required to make rendering of images during level/window operations acceptably fast. */ class vtkNeverTranslucentTextureFactory : public vtkObjectFactory { public: vtkNeverTranslucentTextureFactory(); static vtkNeverTranslucentTextureFactory* New(); virtual const char* GetVTKSourceVersion(); const char* GetDescription(); protected: vtkNeverTranslucentTextureFactory(const vtkNeverTranslucentTextureFactory&); void operator=(const vtkNeverTranslucentTextureFactory&); }; #endif diff --git a/Modules/DicomRT/CMakeLists.txt b/Modules/DicomRT/CMakeLists.txt index 56e50c2ab7..ef345e33b6 100644 --- a/Modules/DicomRT/CMakeLists.txt +++ b/Modules/DicomRT/CMakeLists.txt @@ -1,12 +1,11 @@ # CREATE MODULE HERE IF (NOT DEFINED DCMTK_dcmrt_LIBRARY OR DCMTK_dcmrt_LIBRARY) MITK_CREATE_MODULE( - INCLUDE_DIRS DataStructures + INCLUDE_DIRS DataStructures Rendering DEPENDS MitkSegmentation MitkSceneSerializationBase - PACKAGE_DEPENDS DCMTK - QT_MODULE + PACKAGE_DEPENDS DCMTK Qt4|QtCore ) ADD_SUBDIRECTORY(Testing) ELSE() MESSAGE("MITK DicomRT Support disabled because the DCMTK dcmrt library not found") ENDIF() diff --git a/Modules/DicomRT/Rendering/mitkDoseImageVtkMapper2D.cpp b/Modules/DicomRT/Rendering/mitkDoseImageVtkMapper2D.cpp new file mode 100644 index 0000000000..ba741e1e61 --- /dev/null +++ b/Modules/DicomRT/Rendering/mitkDoseImageVtkMapper2D.cpp @@ -0,0 +1,1079 @@ +/*=================================================================== + +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 +#include +#include +#include +#include "mitkImageStatisticsHolder.h" +#include "mitkPlaneClipping.h" + +//MITK Rendering +#include "mitkDoseImageVtkMapper2D.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 + +//ITK +#include + +mitk::DoseImageVtkMapper2D::DoseImageVtkMapper2D() +{ +} + +mitk::DoseImageVtkMapper2D::~DoseImageVtkMapper2D() +{ + //The 3D RW Mapper (PlaneGeometryDataVtkMapper3D) 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::DoseImageVtkMapper2D::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::DoseImageVtkMapper2D::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::DoseImageVtkMapper2D::GetInput( void ) +{ + return static_cast< const mitk::Image * >( GetDataNode()->GetData() ); +} + +vtkProp* mitk::DoseImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer* renderer) +{ + //return the actor corresponding to the renderer + return m_LSH.GetLocalStorage(renderer)->m_Actors; +} + +void mitk::DoseImageVtkMapper2D::GenerateDataForRenderer( mitk::BaseRenderer *renderer ) +{ + + LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); + + mitk::Image *input = const_cast< mitk::Image * >( this->GetInput() ); + mitk::DataNode* datanode = this->GetDataNode(); + + if ( input == NULL || input->IsInitialized() == false ) + { + return; + } + + //check if there is a valid worldGeometry + const PlaneGeometry *worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); + if( ( worldGeometry == NULL ) || ( !worldGeometry->IsValid() ) || ( !worldGeometry->HasReferenceGeometry() )) + { + return; + } + + input->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, input->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_ReslicedImage = NULL; + localStorage->m_Mapper->SetInputData( localStorage->m_EmptyPolyData ); + return; + } + + //set main input for ExtractSliceFilter + localStorage->m_Reslicer->SetInput(input); + 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( input->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 ( (input->GetDimension() >= 3) && (input->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); + } + + //set the vtk output property to true, makes sure that no unneeded mitk image convertion + //is done. + localStorage->m_Reslicer->SetVtkOutputRequest(true); + + //Thickslicing + int thickSlicesMode = 0; + int thickSlicesNum = 1; + // Thick slices parameters + if( input->GetPixelType().GetNumberOfComponents() == 1 ) // for now only single component are allowed + { + DataNode *dn=renderer->GetCurrentWorldPlaneGeometryNode(); + 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"; + } + } + + const PlaneGeometry *planeGeometry = dynamic_cast< const PlaneGeometry * >( worldGeometry ); + + if(thickSlicesMode > 0) + { + double dataZSpacing = 1.0; + + Vector3D normInIndex, normal; + + if ( planeGeometry != NULL ){ + normal = planeGeometry->GetNormal(); + }else{ + const mitk::AbstractTransformGeometry* abstractGeometry = dynamic_cast< const AbstractTransformGeometry * >(worldGeometry); + if(abstractGeometry != NULL) + normal = abstractGeometry->GetPlane()->GetNormal(); + else + return; //no fitting geometry set + } + normal.Normalize(); + + input->GetTimeGeometry()->GetGeometryForTimeStep( this->GetTimestep() )->WorldToIndex( normal, normInIndex ); + + dataZSpacing = 1.0 / normInIndex.GetNorm(); + + localStorage->m_Reslicer->SetOutputDimensionality( 3 ); + localStorage->m_Reslicer->SetOutputSpacingZDirection(dataZSpacing); + localStorage->m_Reslicer->SetOutputExtentZDirection( -thickSlicesNum, 0+thickSlicesNum ); + + // 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. + localStorage->m_TSFilter->SetThickSliceMode( thickSlicesMode-1 ); + localStorage->m_TSFilter->SetInputData( localStorage->m_Reslicer->GetVtkOutput() ); + + //vtkFilter=>mitkFilter=>vtkFilter update mechanism will fail without calling manually + localStorage->m_Reslicer->Modified(); + localStorage->m_Reslicer->Update(); + + localStorage->m_TSFilter->Modified(); + localStorage->m_TSFilter->Update(); + localStorage->m_ReslicedImage = localStorage->m_TSFilter->GetOutput(); + } + else + { + //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_ReslicedImage = localStorage->m_Reslicer->GetVtkOutput(); + } + + // Bounds information for reslicing (only reuqired if reference geometry + // is present) + //this used for generating a vtkPLaneSource with the right size + double sliceBounds[6]; + for ( int i = 0; i < 6; ++i ) + { + sliceBounds[i] = 0.0; + } + 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]; + for ( int i = 0; i < 6; ++i ) + { + textureClippingBounds[i] = 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. + mitk::PlaneClipping::CalculateClippedPlaneBounds( input->GetGeometry(), planeGeometry, textureClippingBounds ); + + textureClippingBounds[0] = static_cast< int >( textureClippingBounds[0] / localStorage->m_mmPerPixel[0] + 0.5 ); + textureClippingBounds[1] = static_cast< int >( textureClippingBounds[1] / localStorage->m_mmPerPixel[0] + 0.5 ); + textureClippingBounds[2] = static_cast< int >( textureClippingBounds[2] / localStorage->m_mmPerPixel[1] + 0.5 ); + textureClippingBounds[3] = static_cast< int >( textureClippingBounds[3] / localStorage->m_mmPerPixel[1] + 0.5 ); + + //clipping bounds for cutting the image + localStorage->m_LevelWindowFilter->SetClippingBounds(textureClippingBounds); + } + + //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; + datanode->GetBoolProperty( "binary", binary, renderer ); + datanode->GetBoolProperty( "outline binary", binaryOutline, renderer ); + if(binaryOutline) //contour rendering + { + //generate contours/outlines + localStorage->m_OutlinePolyData = CreateOutlinePolyData(renderer); + + float binaryOutlineWidth(1.0); + if ( datanode->GetFloatProperty( "outline width", binaryOutlineWidth, renderer ) ) + { + if ( localStorage->m_Actors->GetNumberOfPaths() > 1 ) + { + float binaryOutlineShadowWidth(1.5); + datanode->GetFloatProperty( "outline shadow width", binaryOutlineShadowWidth, renderer ); + + dynamic_cast(localStorage->m_Actors->GetParts()->GetItemAsObject(0)) + ->GetProperty()->SetLineWidth( binaryOutlineWidth * binaryOutlineShadowWidth ); + } + + localStorage->m_Actor->GetProperty()->SetLineWidth( binaryOutlineWidth ); + } + } + + this->ApplyOpacity( renderer ); + this->ApplyRenderingMode(renderer); + + // do not use a VTK lookup table (we do that ourselves in m_LevelWindowFilter) + localStorage->m_Texture->MapColorScalarsThroughLookupTableOff(); + + int displayedComponent = 0; + + if (datanode->GetIntProperty("Image.Displayed Component", displayedComponent, renderer) && numberOfComponents > 1) + { + localStorage->m_VectorComponentExtractor->SetComponents(displayedComponent); + localStorage->m_VectorComponentExtractor->SetInputData(localStorage->m_ReslicedImage); + + localStorage->m_LevelWindowFilter->SetInputConnection(localStorage->m_VectorComponentExtractor->GetOutputPort(0)); + } + else + { + //connect the input with the levelwindow filter + localStorage->m_LevelWindowFilter->SetInputData(localStorage->m_ReslicedImage); + } + + // 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->SetInputConnection(localStorage->m_LevelWindowFilter->GetOutputPort()); + + this->TransformActor( renderer ); + + vtkActor* contourShadowActor = dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0)); + + if(binaryOutline) //connect the mapper with the polyData which contains the lines + { + //We need the contour for the binary outline property as actor + localStorage->m_Mapper->SetInputData(localStorage->m_OutlinePolyData); + localStorage->m_Actor->SetTexture(NULL); //no texture for contours + + bool binaryOutlineShadow( false ); + datanode->GetBoolProperty( "outline binary shadow", binaryOutlineShadow, renderer ); + + if ( binaryOutlineShadow ) + contourShadowActor->SetVisibility( true ); + else + contourShadowActor->SetVisibility( false ); + } + 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); + contourShadowActor->SetVisibility( false ); + } + + // We have been modified => save this for next Update() + localStorage->m_LastUpdateTime.Modified(); +} + +void mitk::DoseImageVtkMapper2D::ApplyLevelWindow(mitk::BaseRenderer *renderer) +{ + LocalStorage *localStorage = this->GetLocalStorage( renderer ); + + LevelWindow levelWindow; + this->GetDataNode()->GetLevelWindow( levelWindow, renderer, "levelwindow" ); + localStorage->m_LevelWindowFilter->GetLookupTable()->SetRange( levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound() ); + + mitk::LevelWindow opacLevelWindow; + if( this->GetDataNode()->GetLevelWindow( opacLevelWindow, renderer, "opaclevelwindow" ) ) + { + //pass the opaque level window to the filter + localStorage->m_LevelWindowFilter->SetMinOpacity(opacLevelWindow.GetLowerWindowBound()); + localStorage->m_LevelWindowFilter->SetMaxOpacity(opacLevelWindow.GetUpperWindowBound()); + } + else + { + //no opaque level window + localStorage->m_LevelWindowFilter->SetMinOpacity(0.0); + localStorage->m_LevelWindowFilter->SetMaxOpacity(255.0); + } +} + +void mitk::DoseImageVtkMapper2D::ApplyColor( mitk::BaseRenderer* renderer ) +{ + LocalStorage *localStorage = this->GetLocalStorage( renderer ); + + 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 + { + GetDataNode()->GetColor( rgb, renderer, "color" ); + } + } + 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 + { + GetDataNode()->GetColor(rgb, renderer, "color"); + } + } + if(!hover && !selected) + { + GetDataNode()->GetColor( rgb, renderer, "color" ); + } + + double rgbConv[3] = {(double)rgb[0], (double)rgb[1], (double)rgb[2]}; //conversion to double for VTK + dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0))->GetProperty()->SetColor(rgbConv); + localStorage->m_Actor->GetProperty()->SetColor(rgbConv); + + if ( localStorage->m_Actors->GetParts()->GetNumberOfItems() > 1 ) + { + float rgb[3]= { 1.0f, 1.0f, 1.0f }; + mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty + ("outline binary shadow color", renderer)); + if(colorprop.IsNotNull()) + { + memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); + } + double rgbConv[3] = {(double)rgb[0], (double)rgb[1], (double)rgb[2]}; //conversion to double for VTK + dynamic_cast( localStorage->m_Actors->GetParts()->GetItemAsObject(0) )->GetProperty()->SetColor(rgbConv); + } +} + +void mitk::DoseImageVtkMapper2D::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::DoseImageVtkMapper2D::ApplyRenderingMode( mitk::BaseRenderer* renderer ) +{ + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + + bool binary = false; + this->GetDataNode()->GetBoolProperty( "binary", binary, renderer ); + if(binary) // is it a binary image? + { + //for binary images, we always use our default LuT and map every value to (0,1) + //the opacity of 0 will always be 0.0. We never a apply a LuT/TfF nor a level window. + localStorage->m_LevelWindowFilter->SetLookupTable(localStorage->m_BinaryLookupTable); + } + else + { + //all other image types can make use of the rendering mode + int renderingMode = mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR; + mitk::RenderingModeProperty::Pointer mode = dynamic_cast(this->GetDataNode()->GetProperty( "Image Rendering.Mode", renderer )); + if(mode.IsNotNull()) + { + renderingMode = mode->GetRenderingMode(); + } + switch(renderingMode) + { + case mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR: + MITK_DEBUG << "'Image Rendering.Mode' = LevelWindow_LookupTable_Color"; + this->ApplyLookuptable( renderer ); + this->ApplyLevelWindow( renderer ); + break; + case mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_LEVELWINDOW_COLOR: + MITK_DEBUG << "'Image Rendering.Mode' = LevelWindow_ColorTransferFunction_Color"; + this->ApplyColorTransferFunction( renderer ); + this->ApplyLevelWindow( renderer ); + break; + case mitk::RenderingModeProperty::LOOKUPTABLE_COLOR: + MITK_DEBUG << "'Image Rendering.Mode' = LookupTable_Color"; + this->ApplyLookuptable( renderer ); + break; + case mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_COLOR: + MITK_DEBUG << "'Image Rendering.Mode' = ColorTransferFunction_Color"; + this->ApplyColorTransferFunction( renderer ); + break; + default: + MITK_ERROR << "No valid 'Image Rendering.Mode' set. Using LOOKUPTABLE_LEVELWINDOW_COLOR instead."; + this->ApplyLookuptable( renderer ); + this->ApplyLevelWindow( renderer ); + break; + } + } + //we apply color for all images (including binaries). + this->ApplyColor( renderer ); +} + +void mitk::DoseImageVtkMapper2D::ApplyLookuptable( mitk::BaseRenderer* renderer ) +{ + 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(this->GetDataNode()->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. + } + localStorage->m_LevelWindowFilter->SetLookupTable(usedLookupTable); +} + +void mitk::DoseImageVtkMapper2D::ApplyColorTransferFunction(mitk::BaseRenderer *renderer) +{ + mitk::TransferFunctionProperty::Pointer transferFunctionProp = dynamic_cast(this->GetDataNode()->GetProperty("Image Rendering.Transfer Function",renderer )); + + if( transferFunctionProp.IsNull() ) + { + MITK_ERROR << "'Image Rendering.Mode'' was set to use a color transfer function but there is no property 'Image Rendering.Transfer Function'. Nothing will be done."; + return; + } + LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); + //pass the transfer function to our level window filter + localStorage->m_LevelWindowFilter->SetLookupTable(transferFunctionProp->GetValue()->GetColorTransferFunction()); +} + +void mitk::DoseImageVtkMapper2D::Update(mitk::BaseRenderer* renderer) +{ + + bool visible = true; + GetDataNode()->GetVisibility(visible, renderer, "visible"); + + if ( !visible ) + { + 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 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->GetCurrentWorldPlaneGeometryUpdateTime()) //was the geometry modified? + || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->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::DoseImageVtkMapper2D::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( "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 ); + node->AddProperty( "outline binary shadow", mitk::BoolProperty::New( false ), renderer, overwrite ); + node->AddProperty( "outline binary shadow color", ColorProperty::New(0.0,0.0,0.0), renderer, overwrite ); + node->AddProperty( "outline shadow width", mitk::FloatProperty::New( 1.5 ), 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 ) ); + + 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(); + mitkLut->SetType(mitk::LookupTable::GRAYSCALE); + mitk::LookupTableProperty::Pointer mitkLutProp = mitk::LookupTableProperty::New(); + mitkLutProp->SetLookupTable(mitkLut); + node->SetProperty("LookupTable", mitkLutProp); + + std::string photometricInterpretation; // DICOM tag telling us how pixel values should be displayed + if ( node->GetStringProperty( "dicom.pixel.PhotometricInterpretation", photometricInterpretation ) ) + { + // modality provided by DICOM or other reader + if ( photometricInterpretation.find("MONOCHROME1") != std::string::npos ) // meaning: display MINIMUM pixels as WHITE + { + // Set inverse grayscale look-up table + mitkLut->SetType(mitk::LookupTable::INVERSE_GRAYSCALE); + mitkLutProp->SetLookupTable(mitkLut); + node->SetProperty("LookupTable", mitkLutProp); + } + // Otherwise do nothing - the default grayscale look-up table has already been set + } + + 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->GetStatistics()->GetScalarValueMin(); + maxValue = centralSliceImage->GetStatistics()->GetScalarValueMax(); + min2ndValue = centralSliceImage->GetStatistics()->GetScalarValue2ndMin(); + max2ndValue = centralSliceImage->GetStatistics()->GetScalarValue2ndMax(); + } + if ((maxValue == min2ndValue && minValue == max2ndValue) || minValue == maxValue) + { + // centralSlice is strange, lets look at all data + minValue = image->GetStatistics()->GetScalarValueMin(); + maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(); + min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(); + max2ndValue = image->GetStatistics()->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); + + std::string className = image->GetNameOfClass(); + + if (className != "TensorImage" && className != "QBallImage") + { + PixelType pixelType = image->GetPixelType(); + size_t numComponents = pixelType.GetNumberOfComponents(); + + if ((pixelType.GetPixelTypeAsString() == "vector" && numComponents > 1) || numComponents == 2 || numComponents > 4) + node->AddProperty("Image.Displayed Component", 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 ( image->GetPropertyList()->GetStringProperty( "dicom.voilut.WindowCenter", sLevel ) + && image->GetPropertyList()->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 ( image->GetPropertyList()->GetStringProperty( "dicom.series.SmallestPixelValueInSeries", sSmallestPixelValueInSeries ) + && image->GetPropertyList()->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 + } + else + { + contrast.SetAuto( static_cast(node->GetData()), false, true ); // we need this as a fallback + } + + contrast.SetLevelWindow( level, window, true ); + node->SetProperty( "levelwindow", LevelWindowProperty::New( contrast ), renderer ); + } + } + if(((overwrite) || (node->GetProperty("opaclevelwindow", renderer)==NULL)) + && (image->GetPixelType().GetPixelType() == itk::ImageIOBase::RGBA) + && (image->GetPixelType().GetComponentType() == itk::ImageIOBase::UCHAR) ) + { + mitk::LevelWindow opaclevwin; + opaclevwin.SetRangeMinMax(0,255); + opaclevwin.SetWindowBounds(0,255); + mitk::LevelWindowProperty::Pointer prop = mitk::LevelWindowProperty::New(opaclevwin); + node->SetProperty( "opaclevelwindow", prop, renderer ); + } + } + Superclass::SetDefaultProperties(node, renderer, overwrite); +} + +mitk::DoseImageVtkMapper2D::LocalStorage* mitk::DoseImageVtkMapper2D::GetLocalStorage(mitk::BaseRenderer* renderer) +{ + return m_LSH.GetLocalStorage(renderer); +} + +vtkSmartPointer mitk::DoseImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer* renderer ){ + LocalStorage* localStorage = this->GetLocalStorage(renderer); + + //get the min and max index values of each direction + int* extent = localStorage->m_ReslicedImage->GetExtent(); + int xMin = extent[0]; + int xMax = extent[1]; + int yMin = extent[2]; + int yMax = extent[3]; + + int* dims = localStorage->m_ReslicedImage->GetDimensions(); //dimensions of the image + int line = dims[0]; //how many pixels per line? + //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 + vtkSmartPointer colors = vtkSmartPointer::New(); + colors->SetNumberOfComponents(3); + colors->SetName("Colors"); + + float pref; + this->GetDataNode()->GetFloatProperty(mitk::rt::Constants::REFERENCE_DOSE_PROPERTY_NAME.c_str(),pref); + + mitk::IsoDoseLevelSetProperty::Pointer propIsoSet = dynamic_cast(GetDataNode()->GetProperty(mitk::rt::Constants::DOSE_ISO_LEVELS_PROPERTY_NAME.c_str())); + mitk::IsoDoseLevelSet::Pointer isoDoseLevelSet = propIsoSet->GetValue(); + + for(mitk::IsoDoseLevelSet::ConstIterator doseIT = isoDoseLevelSet->Begin(); doseIT!=isoDoseLevelSet->End();++doseIT) + { + if(doseIT->GetVisibleIsoLine()) + { + double doseValue = doseIT->GetDoseValue()*pref; + mitk::IsoDoseLevel::ColorType isoColor = doseIT->GetColor(); + unsigned char colorLine[3] = {isoColor.GetRed()*255, isoColor.GetGreen()*255, isoColor.GetBlue()*255}; + + int x = xMin; //pixel index x + int y = yMin; //pixel index y + unsigned short* currentPixel; + + // We take the pointer to the first pixel of the image + currentPixel = static_cast(localStorage->m_ReslicedImage->GetScalarPointer() ); + + while (y <= yMax) + { + //if the current pixel value is set to something + if ((currentPixel) && (*currentPixel >= doseValue)) + { + //check in which direction a line is necessary + //a line is added if the neighbor of the current pixel has the value 0 + //and if the pixel is located at the edge of the image + + //if vvvvv not the first line vvvvv + if (y > yMin && *(currentPixel-line) < doseValue) + { //x direction - bottom edge of the pixel + //add the 2 points + 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); + colors->InsertNextTupleValue(colorLine); + } + + //if vvvvv not the last line vvvvv + if (y < yMax && *(currentPixel+line) < doseValue) + { //x direction - top edge of the pixel + 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); + colors->InsertNextTupleValue(colorLine); + } + + //if vvvvv not the first pixel vvvvv + if ( (x > xMin || y > yMin) && *(currentPixel-1) < doseValue) + { //y direction - left edge of the pixel + 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); + colors->InsertNextTupleValue(colorLine); + } + + //if vvvvv not the last pixel vvvvv + if ( (y < yMax || (x < xMax) ) && *(currentPixel+1) < doseValue) + { //y direction - right edge of the pixel + 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); + colors->InsertNextTupleValue(colorLine); + } + + /* now consider pixels at the edge of the image */ + + //if vvvvv left edge of image vvvvv + if (x == xMin) + { //draw left edge of the pixel + 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); + colors->InsertNextTupleValue(colorLine); + } + + //if vvvvv right edge of image vvvvv + if (x == xMax) + { //draw right edge of the pixel + 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); + colors->InsertNextTupleValue(colorLine); + } + + //if vvvvv bottom edge of image vvvvv + if (y == yMin) + { //draw bottom edge of the pixel + 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); + lines->InsertNextCell(2); + lines->InsertCellPoint(p1); + lines->InsertCellPoint(p2); + colors->InsertNextTupleValue(colorLine); + } + + //if vvvvv top edge of image vvvvv + if (y == yMax) + { //draw top edge of the pixel + 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); + colors->InsertNextTupleValue(colorLine); + } + }//end if currentpixel is set + + x++; + + if (x > xMax) + { //reached end of line + x = xMin; + y++; + } + + // Increase the pointer-position to the next pixel. + // This is safe, as the while-loop and the x-reset logic above makes + // sure we do not exceed the bounds of the image + currentPixel++; + }//end of while + }//end of if visible dose value + }//end of loop over all does values + + // 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); + polyData->GetCellData()->SetScalars(colors); + return polyData; +} + +void mitk::DoseImageVtkMapper2D::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::DoseImageVtkMapper2D::RenderingGeometryIntersectsImage( const PlaneGeometry* 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::DoseImageVtkMapper2D::LocalStorage::~LocalStorage() +{ +} + +mitk::DoseImageVtkMapper2D::LocalStorage::LocalStorage() +: m_VectorComponentExtractor(vtkSmartPointer::New()) +{ + + m_LevelWindowFilter = vtkSmartPointer::New(); + + //Do as much actions as possible in here to avoid double executions. + m_Plane = vtkSmartPointer::New(); + m_Texture = vtkSmartPointer::New().GetPointer(); + m_DefaultLookupTable = vtkSmartPointer::New(); + m_BinaryLookupTable = vtkSmartPointer::New(); + m_ColorLookupTable = vtkSmartPointer::New(); + m_Mapper = vtkSmartPointer::New(); + m_Actor = vtkSmartPointer::New(); + m_Actors = vtkSmartPointer::New(); + m_Reslicer = mitk::ExtractSliceFilter::New(); + m_TSFilter = vtkSmartPointer::New(); + m_OutlinePolyData = vtkSmartPointer::New(); + m_ReslicedImage = vtkSmartPointer::New(); + m_EmptyPolyData = 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(); + + mitk::LookupTable::Pointer mitkLUT = mitk::LookupTable::New(); + //built a default lookuptable + mitkLUT->SetType(mitk::LookupTable::GRAYSCALE); + m_DefaultLookupTable = mitkLUT->GetVtkLookupTable(); + + mitkLUT->SetType(mitk::LookupTable::LEGACY_BINARY); + m_BinaryLookupTable = mitkLUT->GetVtkLookupTable(); + + mitkLUT->SetType(mitk::LookupTable::LEGACY_RAINBOW_COLOR); + 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/DicomRT/Rendering/mitkDoseImageVtkMapper2D.h b/Modules/DicomRT/Rendering/mitkDoseImageVtkMapper2D.h new file mode 100644 index 0000000000..f986c550a1 --- /dev/null +++ b/Modules/DicomRT/Rendering/mitkDoseImageVtkMapper2D.h @@ -0,0 +1,308 @@ +/*=================================================================== + +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 MITKDoseImageVtkMapper2D2D_H_HEADER_INCLUDED +#define MITKDoseImageVtkMapper2D2D_H_HEADER_INCLUDED + +//MITK +#include +#include + +//MITK Rendering +#include "mitkBaseRenderer.h" +#include "mitkVtkMapper.h" +#include "mitkExtractSliceFilter.h" + +//VTK +#include +#include + +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 a 3D image. + * + * The following image gives a brief overview of the mapping and the involved parts. + * + * \image html DoseImageVtkMapper2Darchitecture.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 put into a vtkMitkLevelWindowFilter + * and the scalar levelwindow, opacity levelwindow and optional clipping to + * local image bounds are applied + * + * Next, the output of the vtkMitkLevelWindowFilter 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 "LookupTable": (mitkLookupTableProperty) If this property is set, + * the default lookuptable will be ignored and the "LookupTable" value + * will be used instead. + * - \b "Image Rendering.Mode": This property decides which mode is used to render images. (E.g. if a lookup table or a transferfunction is applied). Detailed documentation about the modes can be found here: \link mitk::RenderingerModeProperty \endlink + * - \b "Image Rendering.Transfer Function": (mitkTransferFunctionProperty) If this + * property is set, a color transferfunction will be used to color the image. + * - \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 "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) + * - \b "Image Rendering.Transfer Function": Default color transfer function for CTs + * - \b "LookupTable": Rainbow color. + + * 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 MitkDicomRT_EXPORT DoseImageVtkMapper2D : public VtkMapper + { + + public: + /** Standard class typedefs. */ + mitkClassMacro( DoseImageVtkMapper2D,VtkMapper ); + + /** Method for creation through the object factory. */ + itkFactorylessNewMacro(Self) + itkCloneMacro(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); + //### 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; + + vtkSmartPointer m_Actors; + /** \brief Mapper of a 2D render window. */ + vtkSmartPointer m_Mapper; + vtkSmartPointer m_VectorComponentExtractor; + /** \brief Current slice of a 2D render window.*/ + vtkSmartPointer m_ReslicedImage; + /** \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_DefaultLookupTable; + vtkSmartPointer m_BinaryLookupTable; + vtkSmartPointer m_ColorLookupTable; + /** \brief The actual reslicer (one per renderer) */ + mitk::ExtractSliceFilter::Pointer m_Reslicer; + /** \brief Filter for thick slices */ + vtkSmartPointer m_TSFilter; + /** \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 mmPerPixel relation between pixel and mm. (World spacing).*/ + mitk::ScalarType* m_mmPerPixel; + + /** \brief This filter is used to apply the level window to Grayvalue and RBG(A) images. */ + vtkSmartPointer m_LevelWindowFilter; + + /** \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); + + /** \brief This method switches between different rendering modes (e.g. use a lookup table or a transfer function). + * Detailed documentation about the modes can be found here: \link mitk::RenderingerModeProperty \endlink + */ + void ApplyRenderingMode(mitk::BaseRenderer *renderer); + + 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]); + + /** \brief Generates a vtkPolyData object containing the outline of a given binary slice. + \param renderer: Pointer to the renderer containing the needed information + \note This code is based on code from the iil library. + */ + vtkSmartPointer CreateOutlinePolyData(mitk::BaseRenderer* renderer); + + /** Default constructor */ + DoseImageVtkMapper2D(); + /** Default deconstructor */ + virtual ~DoseImageVtkMapper2D(); + + /** \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 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); + + /** \brief This method applies a color transfer function. + * Internally, a vtkColorTransferFunction is used. This is usefull for coloring continous + * images (e.g. float) + * \warning To use the color transfer function, the property 'Image Rendering.Transfer Function' must be set and a 'Image Rendering.Mode' which uses the color transfer function must be set. + */ + void ApplyColorTransferFunction(mitk::BaseRenderer* renderer); + + /** + * @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); + + /** \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 ); + + /** + * \brief Calculates whether the given rendering geometry intersects the + * given SlicedGeometry3D. + * + * This method checks if the given PlaneGeometry intersects the given + * SlicedGeometry3D. It calculates the distance of the PlaneGeometry 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 PlaneGeometry* renderingGeometry, SlicedGeometry3D* imageGeometry ); + }; + +} // namespace mitk + +#endif /* MITKDoseImageVtkMapper2D_H_HEADER_INCLUDED_C10E906E */ diff --git a/Modules/DicomRT/files.cmake b/Modules/DicomRT/files.cmake index f638320b1f..93f4918a6a 100644 --- a/Modules/DicomRT/files.cmake +++ b/Modules/DicomRT/files.cmake @@ -1,26 +1,28 @@ SET(CPP_FILES mitkRTDoseReader.cpp mitkRTStructureSetReader.cpp DataStructures/mitkRTConstants.cpp DataStructures/mitkIsoDoseLevel.cpp DataStructures/mitkIsoDoseLevelCollections.cpp DataStructures/mitkIsoDoseLevelSetProperty.cpp DataStructures/mitkIsoDoseLevelVectorProperty.cpp + Rendering/mitkDoseImageVtkMapper2D.cpp ) set(H_FILES mitkRTDoseReader.h mitkRTStructureSetReader.h DataStructures/mitkIsoDoseLevel.h DataStructures/mitkIsoDoseLevelCollections.h DataStructures/mitkIsoDoseLevelSetProperty.h DataStructures/mitkIsoDoseLevelVectorProperty.h DataStructures/mitkDoseValueType.h DataStructures/mitkRTConstants.h + Rendering/mitkDoseImageVtkMapper2D.h ) set(TPP_FILES ) set(MOC_H_FILES )