diff --git a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp index 7d6a88dfef..fb4bd00898 100644 --- a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp +++ b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp @@ -1,1077 +1,1078 @@ /*=================================================================== 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 "mitkImageStatisticsHolder.h" #include "mitkPlaneClipping.h" //MITK Rendering #include "mitkImageVtkMapper2D.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 //ITK #include #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. 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::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 * >( GetDataNode()->GetData() ); } vtkProp* mitk::ImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer* renderer) { //return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } void mitk::ImageVtkMapper2D::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 Geometry2D *worldGeometry = renderer->GetCurrentWorldGeometry2D(); 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->SetInput( 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->GetTimeSlicedGeometry()->GetGeometry3D( 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->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"; } } 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->GetTimeSlicedGeometry()->GetGeometry3D( 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->SetInput( 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 vtkFloatingPointType 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 { vtkFloatingPointType 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 ); if(binary) //binary image { datanode->GetBoolProperty( "outline binary", binaryOutline, renderer ); if(binaryOutline) //contour rendering { if ( input->GetPixelType().GetBpe() <= 8 ) { //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 ); } } 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!"; } } } if (!(numberOfComponents == 1 || numberOfComponents == 3 || numberOfComponents == 4)) { MITK_WARN << "Unknown number of components!"; } this->ApplyOpacity( renderer ); this->ApplyRenderingMode(renderer); // do not use a VTK lookup table (we do that ourselves in m_LevelWindowFilter) localStorage->m_Texture->MapColorScalarsThroughLookupTableOff(); //connect the input with the levelwindow filter localStorage->m_LevelWindowFilter->SetInput(localStorage->m_ReslicedImage); //connect the texture with the output of the levelwindow filter // 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); localStorage->m_Texture->SetInputConnection(localStorage->m_LevelWindowFilter->GetOutputPort()); this->TransformActor( renderer ); vtkActor* contourShadowActor = dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0)); if(binary && 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->SetInput(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::ImageVtkMapper2D::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::ImageVtkMapper2D::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::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 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::ImageVtkMapper2D::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::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::LEVELWINDOW_COLOR: MITK_DEBUG << "'Image Rendering.Mode' = LevelWindow_Color"; localStorage->m_LevelWindowFilter->SetLookupTable( localStorage->m_DefaultLookupTable ); this->ApplyLevelWindow( renderer ); break; 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"; break; } } //we apply color for all images (including binaries). this->ApplyColor( renderer ); } void mitk::ImageVtkMapper2D::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 { MITK_WARN << "Image Rendering.Mode was set to use a lookup table but there is no property 'LookupTable'. A default (rainbow) lookup table will be used."; } localStorage->m_LevelWindowFilter->SetLookupTable(usedLookupTable); } void mitk::ImageVtkMapper2D::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::ImageVtkMapper2D::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 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( "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); 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 { // generate LUT (white to black) mitk::LookupTable::Pointer mitkLut = mitk::LookupTable::New(); vtkLookupTable* bwLut = mitkLut->GetVtkLookupTable(); bwLut->SetTableRange (0, 1); bwLut->SetSaturationRange (0, 0); bwLut->SetHueRange (0, 0); bwLut->SetValueRange (1, 0); bwLut->SetAlphaRange (1, 1); bwLut->SetRampToLinear(); bwLut->Build(); mitk::LookupTableProperty::Pointer mitkLutProp = mitk::LookupTableProperty::New(); mitkLutProp->SetLookupTable(mitkLut); node->SetProperty( "LookupTable", mitkLutProp ); + renderingModeProperty->SetValue( mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR ); // USE lookuptable } else if ( photometricInterpretation.find("MONOCHROME2") != std::string::npos ) // meaning: display MINIMUM pixels as BLACK { // apply default LUT (black to white) node->SetProperty( "color", mitk::ColorProperty::New( 1,1,1 ), renderer ); } // PALETTE interpretation should be handled ok by RGB loading } 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); } 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::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); //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? int x = xMin; //pixel index x int y = yMin; //pixel index y char* currentPixel; //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 // 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 != 0)) { //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) == 0) { //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); } //if vvvvv not the last line vvvvv if (y < yMax && *(currentPixel+line) == 0) { //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); } //if vvvvv not the first pixel vvvvv if ( (x > xMin || y > yMin) && *(currentPixel-1) == 0) { //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); } //if vvvvv not the last pixel vvvvv if ( (y < yMax || (x < xMax) ) && *(currentPixel+1) == 0) { //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); } /* 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); } //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); } //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); } //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); } }//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 // 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::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::ImageVtkMapper2D::RenderingGeometryIntersectsImage( const Geometry2D* renderingGeometry, SlicedGeometry3D* imageGeometry ) { // if either one of the two geometries is NULL we return true // for safety reasons if ( renderingGeometry == NULL || imageGeometry == NULL ) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance( imageGeometry->GetCornerPoint( 0 ) ); for( int i=1; i<8; i++ ) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint( i ); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance( cornerPoint ); // if it has not the same signing as the distance of the first point if ( initialDistance * distance < 0 ) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } mitk::ImageVtkMapper2D::LocalStorage::~LocalStorage() { } mitk::ImageVtkMapper2D::LocalStorage::LocalStorage() { 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(); //built a default lookuptable m_DefaultLookupTable->SetRampToLinear(); 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_BinaryLookupTable->SetRampToLinear(); m_BinaryLookupTable->SetSaturationRange( 0.0, 0.0 ); m_BinaryLookupTable->SetHueRange( 0.0, 0.0 ); m_BinaryLookupTable->SetValueRange( 0.0, 1.0 ); m_BinaryLookupTable->SetRange(0.0, 1.0); m_BinaryLookupTable->Build(); // add a default rainbow lookup table for color mapping m_ColorLookupTable->SetRampToLinear(); m_ColorLookupTable->SetHueRange(0.6667, 0.0); m_ColorLookupTable->SetTableRange(0.0, 20.0); m_ColorLookupTable->Build(); // make first value transparent { double rgba[4]; m_BinaryLookupTable->GetTableValue(0, rgba); m_BinaryLookupTable->SetTableValue(0, rgba[0], rgba[1], rgba[2], 0.0); // background to 0 } //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/DICOMReader/TODOs.txt b/Modules/DICOMReader/TODOs.txt index 75370af496..a6e2b3eee8 100644 --- a/Modules/DICOMReader/TODOs.txt +++ b/Modules/DICOMReader/TODOs.txt @@ -1,37 +1,37 @@ Important [ ] ONLY load a pre-sorted list [ ] Either NRRD cache option or slice-by-slice loading [x] Performance of mitkdump / DICOMReaderSelector (TagCache?) - the note in GDCM..Reader::GetTagValue() is correct. This piece of code is taking lots of time. Approx. half of the time is tag scanning, the other half is construction of block describing properties, which accesses GetTagValue. [x] - maybe we can evaluate these properties in a lazy way (only when asked for). Solution: Yes, this helps, implemented. [x] Sorting by "Image Time" seems undeterministic (clarkson test data) [x] - Numeric conversion of "1.2.3.4" yields 1.2 on Windows, seems to fail on Linux(?) - need to verify that the whole string (except whitespace at the begin/end) was converted Solution: GetTagValue as String does a right-trim plus we check after conversion! Works. [x] Implement Configurator::GetBuiltIn3DReaders() (don't ignore "classic reader") -[ ] Complete MITK properties of images: level/window and color type (MONOCHROME1/2) +[x] Complete MITK properties of images: level/window and color type (MONOCHROME1/2) [ ] Check input images fo DICOMness and non-multi-frameness [ ] Use CanReadFile() in selection! [x] Check TODO in mitkDICOMTag.cpp - operator< for DICOMTag has been re-implemented in a readable and safer way. [ ] Configurable precision for tag value comparisons [x] Images are upside-down in some cases - error was hidden assumption somewhere: filenames for ImageSeriesReader need to be in an order that goes along the image normals, not in the opposite direction Questionable [ ] Fallback to filename instead of instance UID? Nice-to-have [ ] Multi-Frame images (DCMTK) [ ] ... diff --git a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp index 71b162f2dc..9939060689 100644 --- a/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOMReader/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,557 +1,563 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include #include #include mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader() :DICOMFileReader() ,m_FixTiltByShearing(true) { this->EnsureMandatorySortersArePresent(); } mitk::DICOMITKSeriesGDCMReader ::DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other ) :DICOMFileReader(other) ,m_FixTiltByShearing(false) ,m_Sorter( other.m_Sorter ) // TODO should clone the list items ,m_EquiDistantBlocksSorter( other.m_EquiDistantBlocksSorter->Clone() ) ,m_NormalDirectionConsistencySorter( other.m_NormalDirectionConsistencySorter->Clone() ) { this->EnsureMandatorySortersArePresent(); } mitk::DICOMITKSeriesGDCMReader ::~DICOMITKSeriesGDCMReader() { } mitk::DICOMITKSeriesGDCMReader& mitk::DICOMITKSeriesGDCMReader ::operator=(const DICOMITKSeriesGDCMReader& other) { if (this != &other) { DICOMFileReader::operator=(other); this->m_FixTiltByShearing = other.m_FixTiltByShearing; this->m_Sorter = other.m_Sorter; // TODO should clone the list items this->m_EquiDistantBlocksSorter = other.m_EquiDistantBlocksSorter->Clone(); this->m_NormalDirectionConsistencySorter = other.m_NormalDirectionConsistencySorter->Clone(); } return *this; } void mitk::DICOMITKSeriesGDCMReader ::SetFixTiltByShearing(bool on) { m_FixTiltByShearing = on; } mitk::DICOMGDCMImageFrameList mitk::DICOMITKSeriesGDCMReader ::FromDICOMDatasetList(const DICOMDatasetList& input) { DICOMGDCMImageFrameList output; output.reserve(input.size()); for(DICOMDatasetList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMGDCMImageFrameInfo* gfi = dynamic_cast(*inputIter); assert(gfi); output.push_back(gfi); } return output; } mitk::DICOMDatasetList mitk::DICOMITKSeriesGDCMReader ::ToDICOMDatasetList(const DICOMGDCMImageFrameList& input) { DICOMDatasetList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMDatasetAccess* da = inputIter->GetPointer(); assert(da); output.push_back(da); } return output; } mitk::DICOMImageFrameList mitk::DICOMITKSeriesGDCMReader ::ToDICOMImageFrameList(const DICOMGDCMImageFrameList& input) { DICOMImageFrameList output; output.reserve(input.size()); for(DICOMGDCMImageFrameList::const_iterator inputIter = input.begin(); inputIter != input.end(); ++inputIter) { DICOMImageFrameInfo::Pointer fi = (*inputIter)->GetFrameInfo(); assert(fi.IsNotNull()); output.push_back(fi); } return output; } void mitk::DICOMITKSeriesGDCMReader ::InternalPrintConfiguration(std::ostream& os) const { unsigned int sortIndex(1); for(SorterList::const_iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sortIndex, ++sorterIter) { os << "Sorting step " << sortIndex << ":" << std::endl; (*sorterIter)->PrintConfiguration(os, " "); } os << "Sorting step " << sortIndex << ":" << std::endl; m_EquiDistantBlocksSorter->PrintConfiguration(os, " "); } std::string mitk::DICOMITKSeriesGDCMReader ::GetActiveLocale() const { return setlocale(LC_NUMERIC, NULL); } void mitk::DICOMITKSeriesGDCMReader ::PushLocale() { std::string currentCLocale = setlocale(LC_NUMERIC, NULL); m_ReplacedCLocales.push( currentCLocale ); setlocale(LC_NUMERIC, "C"); std::locale currentCinLocale( std::cin.getloc() ); m_ReplacedCinLocales.push( currentCinLocale ); std::locale l( "C" ); std::cin.imbue(l); } void mitk::DICOMITKSeriesGDCMReader ::PopLocale() { if (!m_ReplacedCLocales.empty()) { setlocale(LC_NUMERIC, m_ReplacedCLocales.top().c_str()); m_ReplacedCLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } if (!m_ReplacedCinLocales.empty()) { std::cin.imbue( m_ReplacedCinLocales.top() ); m_ReplacedCinLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader ::Condense3DBlocks(SortingBlockList& input) { return input; // to be implemented differently by sub-classes } void mitk::DICOMITKSeriesGDCMReader ::AnalyzeInputFiles() { itk::TimeProbesCollectorBase timer; timer.Start("Reset"); this->ClearOutputs(); m_InputFrameList.clear(); m_GDCMScanner.ClearTags(); timer.Stop("Reset"); // prepare initial sorting (== list of input files) StringList inputFilenames = this->GetInputFiles(); // scan files for sorting-relevant tags timer.Start("Setup scanning"); for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIter) { assert(sorterIter->IsNotNull()); DICOMTagList tags = (*sorterIter)->GetTagsOfInterest(); for(DICOMTagList::const_iterator tagIter = tags.begin(); tagIter != tags.end(); ++tagIter) { MITK_DEBUG << "Sorting uses tag " << tagIter->GetGroup() << "," << tagIter->GetElement(); m_GDCMScanner.AddTag( gdcm::Tag(tagIter->GetGroup(), tagIter->GetElement()) ); } } // Add some of our own interest // TODO all tags that are needed in DICOMImageBlockDescriptor should be added by DICOMFileReader (this code location here should query all superclasses for tags) m_GDCMScanner.AddTag( gdcm::Tag(0x0018,0x1164) ); // pixel spacing m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x0030) ); // imager pixel spacing + m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x1050) ); // window center + m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x1051) ); // window width + m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0008) ); // image type + m_GDCMScanner.AddTag( gdcm::Tag(0x0028,0x0004) ); // photometric interpretation + m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x1041) ); // slice location m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0013) ); // instance number m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0016) ); // sop class UID m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0018) ); // sop instance UID m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0011) ); // series number + m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x1030) ); // study description m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x103e) ); // series description m_GDCMScanner.AddTag( gdcm::Tag(0x0008,0x0060) ); // modality m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0012) ); // acquisition number m_GDCMScanner.AddTag( gdcm::Tag(0x0018,0x0024) ); // sequence name m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0037) ); // image orientation m_GDCMScanner.AddTag( gdcm::Tag(0x0020,0x0032) ); // ipp timer.Stop("Setup scanning"); timer.Start("Tag scanning"); PushLocale(); m_GDCMScanner.Scan( inputFilenames ); PopLocale(); timer.Stop("Tag scanning"); timer.Start("Setup sorting"); for (StringList::const_iterator inputIter = inputFilenames.begin(); inputIter != inputFilenames.end(); ++inputIter) { m_InputFrameList.push_back( DICOMGDCMImageFrameInfo::New( DICOMImageFrameInfo::New(*inputIter, 0), m_GDCMScanner.GetMapping(inputIter->c_str()) ) ); } m_SortingResultInProgress.clear(); m_SortingResultInProgress.push_back( m_InputFrameList ); timer.Stop("Setup sorting"); // sort and split blocks as configured timer.Start("Sorting frames"); unsigned int sorterIndex = 0; for(SorterList::iterator sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sorterIndex, ++sorterIter) { m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex, *sorterIter, m_SortingResultInProgress, &timer); } // a last extra-sorting step: ensure equidistant slices m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex++, m_EquiDistantBlocksSorter.GetPointer(), m_SortingResultInProgress, &timer); m_SortingResultInProgress = this->InternalExecuteSortingStep(sorterIndex, m_NormalDirectionConsistencySorter.GetPointer(), m_SortingResultInProgress, &timer); timer.Stop("Sorting frames"); timer.Start("Condensing 3D blocks (3D+t or vector values)"); m_SortingResultInProgress = this->Condense3DBlocks( m_SortingResultInProgress ); timer.Stop("Condensing 3D blocks (3D+t or vector values)"); // provide final result as output timer.Start("Output"); unsigned int o = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( o + m_SortingResultInProgress.size() ); // Condense3DBlocks may already have added outputs! for (SortingBlockList::iterator blockIter = m_SortingResultInProgress.begin(); blockIter != m_SortingResultInProgress.end(); ++o, ++blockIter) { DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; DICOMImageFrameList frameList = ToDICOMImageFrameList( gdcmFrameInfoList ); assert(!gdcmFrameInfoList.empty()); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetImageFrameList( frameList ); const GantryTiltInformation& tiltInfo = m_EquiDistantBlocksSorter->GetTiltInformation( (gdcmFrameInfoList.front())->GetFilenameIfAvailable() ); block.SetFlag("gantryTilt", tiltInfo.IsRegularGantryTilt()); block.SetTiltInformation( tiltInfo ); static const DICOMTag tagPixelSpacing(0x0028,0x0030); static const DICOMTag tagImagerPixelSpacing(0x0018,0x1164); std::string pixelSpacingString = (gdcmFrameInfoList.front())->GetTagValueAsString( tagPixelSpacing ); std::string imagerPixelSpacingString = gdcmFrameInfoList.front()->GetTagValueAsString( tagImagerPixelSpacing ); block.SetPixelSpacingTagValues(pixelSpacingString, imagerPixelSpacingString); static const DICOMTag tagSOPClassUID(0x0008,0x0016); std::string sopClassUID = (gdcmFrameInfoList.front())->GetTagValueAsString( tagSOPClassUID ); block.SetSOPClassUID(sopClassUID); block.SetReaderImplementationLevel( this->GetReaderImplementationLevel(sopClassUID) ); this->SetOutput( o, block ); } timer.Stop("Output"); #ifdef MBILOG_ENABLE_DEBUG std::cout << "---------------------------------------------------------------" << std::endl; timer.Report( std::cout ); std::cout << "---------------------------------------------------------------" << std::endl; #endif } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader ::InternalExecuteSortingStep( unsigned int sortingStepIndex, DICOMDatasetSorter::Pointer sorter, const SortingBlockList& input, itk::TimeProbesCollectorBase* timer) { SortingBlockList nextStepSorting; // we should not modify our input list while processing it std::stringstream ss; ss << "Sorting step " << sortingStepIndex; timer->Start( ss.str().c_str() ); nextStepSorting.clear(); MITK_DEBUG << "================================================================================"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ": " << input.size() << " groups input"; unsigned int groupIndex = 0; for(SortingBlockList::const_iterator blockIter = input.begin(); blockIter != input.end(); ++groupIndex, ++blockIter) { const DICOMGDCMImageFrameList& gdcmInfoFrameList = *blockIter; DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmInfoFrameList ); MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; for (DICOMDatasetList::iterator oi = datasetList.begin(); oi != datasetList.end(); ++oi) { MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable(); } sorter->SetInput(datasetList); sorter->Sort(); unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); for (unsigned int b = 0; b < numberOfResultingBlocks; ++b) { DICOMDatasetList blockResult = sorter->GetOutput(b); for (DICOMDatasetList::iterator oi = blockResult.begin(); oi != blockResult.end(); ++oi) { MITK_DEBUG << " OUTPUT(" << b << ") :" << (*oi)->GetFilenameIfAvailable(); } DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList(blockResult); nextStepSorting.push_back( sortedGdcmInfoFrameList ); } } timer->Stop( ss.str().c_str() ); return nextStepSorting; } mitk::ReaderImplementationLevel mitk::DICOMITKSeriesGDCMReader ::GetReaderImplementationLevel(const std::string sopClassUID) const { if (sopClassUID.empty()) { return SOPClassUnknown; } gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( sopClassUID.c_str() ); gdcm::UIDs::TSType gdcmType = uidKnowledge; switch (gdcmType) { case gdcm::UIDs::CTImageStorage: case gdcm::UIDs::MRImageStorage: case gdcm::UIDs::PositronEmissionTomographyImageStorage: case gdcm::UIDs::ComputedRadiographyImageStorage: case gdcm::UIDs::DigitalXRayImageStorageForPresentation: case gdcm::UIDs::DigitalXRayImageStorageForProcessing: return SOPClassSupported; case gdcm::UIDs::NuclearMedicineImageStorage: return SOPClassPartlySupported; case gdcm::UIDs::SecondaryCaptureImageStorage: return SOPClassImplemented; default: return SOPClassUnsupported; } } // void AllocateOutputImages(); bool mitk::DICOMITKSeriesGDCMReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { success &= this->LoadMitkImageForOutput(o); } return success; } bool mitk::DICOMITKSeriesGDCMReader ::LoadMitkImageForOutput(unsigned int o) { PushLocale(); DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = block.GetFlag("gantryTilt", false); if (hasTilt) { MITK_DEBUG << "When loading image " << o << ": got tilt info:"; //tiltInfo.Print(std::cout); } else { MITK_DEBUG << "When loading image " << o << ": has NO info."; } ITKDICOMSeriesReaderHelper::StringContainer filenames; for (DICOMImageFrameList::const_iterator frameIter = frames.begin(); frameIter != frames.end(); ++frameIter) { filenames.push_back( (*frameIter)->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); // TODO preloaded images, caching..? block.SetMitkImage( mitkImage ); PopLocale(); return true; } bool mitk::DICOMITKSeriesGDCMReader ::CanHandleFile(const std::string& itkNotUsed(filename)) { return true; // can handle all // TODO peek into file, check DCM // TODO nice-to-have: check multi-framedness } void mitk::DICOMITKSeriesGDCMReader ::AddSortingElement(DICOMDatasetSorter* sorter, bool atFront) { assert(sorter); if (atFront) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } } void mitk::DICOMITKSeriesGDCMReader ::EnsureMandatorySortersArePresent() { // TODO do just once! Do it in c'tor and handle this step extra! DICOMTagBasedSorter::Pointer splitter = DICOMTagBasedSorter::New(); splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(5) ); // Image Orientation (Patient) // TODO: configurable! splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames this->AddSortingElement( splitter, true ); // true = at front if (m_EquiDistantBlocksSorter.IsNull()) { m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); } m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); if (m_NormalDirectionConsistencySorter.IsNull()) { m_NormalDirectionConsistencySorter = mitk::NormalDirectionConsistencySorter::New(); } } std::string mitk::DICOMITKSeriesGDCMReader ::GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const { // TODO inefficient. if (m_InputFrameList.contains(frame)) return frame->GetTagValueAsString(tag); for(DICOMGDCMImageFrameList::const_iterator frameIter = m_InputFrameList.begin(); frameIter != m_InputFrameList.end(); ++frameIter) { if ( (*frameIter)->GetFrameInfo().IsNotNull() && (*((*frameIter)->GetFrameInfo()) == *frame ) ) { return (*frameIter)->GetTagValueAsString(tag); } } return ""; } diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp index 410296aa3c..52239c5da5 100644 --- a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.cpp @@ -1,603 +1,655 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMImageBlockDescriptor.h" #include "mitkStringProperty.h" +#include "mitkLevelWindowProperty.h" #include mitk::DICOMImageBlockDescriptor ::DICOMImageBlockDescriptor() :m_ReaderImplementationLevel(SOPClassUnknown) ,m_PixelSpacing("") ,m_ImagerPixelSpacing("") ,m_SOPClassUID("") ,m_PropertyList(PropertyList::New()) ,m_TagCache(NULL) ,m_PropertiesOutOfDate(true) { } mitk::DICOMImageBlockDescriptor ::~DICOMImageBlockDescriptor() { } mitk::DICOMImageBlockDescriptor ::DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other) :m_ImageFrameList( other.m_ImageFrameList ) ,m_MitkImage( other.m_MitkImage ) ,m_SliceIsLoaded( other.m_SliceIsLoaded ) ,m_ReaderImplementationLevel( other.m_ReaderImplementationLevel ) ,m_PixelSpacing( other.m_PixelSpacing ) ,m_ImagerPixelSpacing( other.m_ImagerPixelSpacing ) ,m_SOPClassUID( other.m_SOPClassUID ) ,m_TiltInformation( other.m_TiltInformation ) ,m_PropertyList( other.m_PropertyList->Clone() ) ,m_TagCache( other.m_TagCache ) ,m_PropertiesOutOfDate( other.m_PropertiesOutOfDate ) { if (m_MitkImage) { m_MitkImage = m_MitkImage->Clone(); } } mitk::DICOMImageBlockDescriptor& mitk::DICOMImageBlockDescriptor ::operator=(const DICOMImageBlockDescriptor& other) { if (this != &other) { m_ImageFrameList = other.m_ImageFrameList; m_MitkImage = other.m_MitkImage; m_SliceIsLoaded = other.m_SliceIsLoaded; m_ReaderImplementationLevel = other.m_ReaderImplementationLevel; m_PixelSpacing = other.m_PixelSpacing; m_ImagerPixelSpacing = other.m_ImagerPixelSpacing; m_SOPClassUID = other.m_SOPClassUID; m_TiltInformation = other.m_TiltInformation; if (other.m_PropertyList) { m_PropertyList = other.m_PropertyList->Clone(); } if (other.m_MitkImage) { m_MitkImage = other.m_MitkImage->Clone(); } m_TagCache = other.m_TagCache; m_PropertiesOutOfDate = other.m_PropertiesOutOfDate; } return *this; } void mitk::DICOMImageBlockDescriptor ::SetTiltInformation(const GantryTiltInformation& info) { m_TiltInformation = info; } const mitk::GantryTiltInformation mitk::DICOMImageBlockDescriptor ::GetTiltInformation() const { return m_TiltInformation; } void mitk::DICOMImageBlockDescriptor ::SetImageFrameList(const DICOMImageFrameList& framelist) { m_ImageFrameList = framelist; m_SliceIsLoaded.resize(framelist.size()); m_SliceIsLoaded.assign(framelist.size(), false); m_PropertiesOutOfDate = true; } const mitk::DICOMImageFrameList& mitk::DICOMImageBlockDescriptor ::GetImageFrameList() const { return m_ImageFrameList; } void mitk::DICOMImageBlockDescriptor ::SetMitkImage(Image::Pointer image) { if (m_MitkImage != image) { m_MitkImage = this->DescribeImageWithProperties( this->FixupSpacing(image) ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::GetMitkImage() const { return m_MitkImage; } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::FixupSpacing(Image* mitkImage) { if (mitkImage) { Vector3D imageSpacing = mitkImage->GetGeometry()->GetSpacing(); ScalarType desiredSpacingX = imageSpacing[0]; ScalarType desiredSpacingY = imageSpacing[1]; this->GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); // prefer pixel spacing over imager pixel spacing MITK_DEBUG << "Loaded image with spacing " << imageSpacing[0] << ", " << imageSpacing[1]; MITK_DEBUG << "Found correct spacing info " << desiredSpacingX << ", " << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; mitkImage->GetGeometry()->SetSpacing( imageSpacing ); } return mitkImage; } void mitk::DICOMImageBlockDescriptor ::SetSliceIsLoaded(unsigned int index, bool isLoaded) { if (index < m_SliceIsLoaded.size()) { m_SliceIsLoaded[index] = isLoaded; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor ::IsSliceLoaded(unsigned int index) const { if (index < m_SliceIsLoaded.size()) { return m_SliceIsLoaded[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor ::AllSlicesAreLoaded() const { bool allLoaded = true; for (BoolList::const_iterator iter = m_SliceIsLoaded.begin(); iter != m_SliceIsLoaded.end(); ++iter) { allLoaded &= *iter; } return allLoaded; } void mitk::DICOMImageBlockDescriptor ::SetPixelSpacingTagValues(const std::string& pixelSpacing, const std::string& imagerPixelSpacing) { m_PixelSpacing = pixelSpacing; m_ImagerPixelSpacing = imagerPixelSpacing; } /* PS defined IPS defined PS==IPS 0 0 --> UNKNOWN spacing, loader will invent 0 1 --> spacing as at detector surface 1 0 --> spacing as in patient 1 1 0 --> detector surface spacing CORRECTED for geometrical magnifications: spacing as in patient 1 1 1 --> detector surface spacing NOT corrected for geometrical magnifications: spacing as at detector */ mitk::PixelSpacingInterpretation mitk::DICOMImageBlockDescriptor ::GetPixelSpacingInterpretation() const { if (m_PixelSpacing.empty()) { if (m_ImagerPixelSpacing.empty()) { return SpacingUnknown; } else { return SpacingAtDetector; } } else // Pixel Spacing defined { if (m_ImagerPixelSpacing.empty()) { return SpacingInPatient; } else if (m_PixelSpacing != m_ImagerPixelSpacing) { return SpacingInPatient; } else { return SpacingAtDetector; } } } void mitk::DICOMImageBlockDescriptor ::GetDesiredMITKImagePixelSpacing( ScalarType& spacingX, ScalarType& spacingY) const { // preference for "in patient" pixel spacing if ( !DICOMStringToSpacing( m_PixelSpacing, spacingX, spacingY ) ) { // fallback to "on detector" spacing if ( !DICOMStringToSpacing( m_ImagerPixelSpacing, spacingX, spacingY ) ) { // last resort: invent something spacingX = spacingY = 1.0; } } } void mitk::DICOMImageBlockDescriptor ::SetProperty(const std::string& key, BaseProperty* value) { m_PropertyList->SetProperty(key, value); } mitk::BaseProperty* mitk::DICOMImageBlockDescriptor ::GetProperty(const std::string& key) const { this->UpdateImageDescribingProperties(); return m_PropertyList->GetProperty(key); } std::string mitk::DICOMImageBlockDescriptor ::GetPropertyAsString(const std::string& key) const { this->UpdateImageDescribingProperties(); mitk::BaseProperty::Pointer property = m_PropertyList->GetProperty(key); if (property.IsNotNull()) { return property->GetValueAsString(); } else { return std::string(""); } } void mitk::DICOMImageBlockDescriptor ::SetFlag(const std::string& key, bool value) { m_PropertyList->ReplaceProperty(key, BoolProperty::New(value)); } bool mitk::DICOMImageBlockDescriptor ::GetFlag(const std::string& key, bool defaultValue) const { this->UpdateImageDescribingProperties(); BoolProperty::ConstPointer boolProp = dynamic_cast( this->GetProperty(key) ); if (boolProp.IsNotNull()) { return boolProp->GetValue(); } else { return defaultValue; } } void mitk::DICOMImageBlockDescriptor ::SetIntProperty(const std::string& key, int value) { m_PropertyList->ReplaceProperty(key, IntProperty::New(value)); } int mitk::DICOMImageBlockDescriptor ::GetIntProperty(const std::string& key, int defaultValue) const { this->UpdateImageDescribingProperties(); IntProperty::ConstPointer intProp = dynamic_cast( this->GetProperty(key) ); if (intProp.IsNotNull()) { return intProp->GetValue(); } else { return defaultValue; } } +double +mitk::DICOMImageBlockDescriptor +::stringtodouble(const std::string& str) const +{ + double d; + std::string trimmedstring(str); + trimmedstring = trimmedstring.erase(trimmedstring.find_last_not_of(" \n\r\t")+1); + + std::istringstream converter(trimmedstring); + if ( !trimmedstring.empty() && (converter >> d) && converter.eof() ) + { + return d; + } + else + { + throw std::invalid_argument("Argument is not a convertable number"); + } +} + mitk::Image::Pointer mitk::DICOMImageBlockDescriptor ::DescribeImageWithProperties(Image* mitkImage) { + // TODO: this is a collection of properties that have been provided by the + // legacy DicomSeriesReader. + // We should at some point clean up this collection and name them in a more + // consistent way! + if (!mitkImage) return mitkImage; // first part: add some tags that describe individual slices // these propeties are defined at analysis time (see UpdateImageDescribingProperties()) std::string propertyKeySliceLocation = "dicom.image.0020.1041"; std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; std::string propertyKeySOPInstanceUID = "dicom.image.0008.0018"; mitkImage->SetProperty( propertyKeySliceLocation.c_str(), this->GetProperty("sliceLocationForSlices") ); mitkImage->SetProperty( propertyKeyInstanceNumber.c_str(), this->GetProperty("instanceNumberForSlices") ); mitkImage->SetProperty( propertyKeySOPInstanceUID.c_str(), this->GetProperty("SOPInstanceUIDForSlices") ); // second part: add properties that describe the whole image block mitkImage->SetProperty("dicomseriesreader.SOPClassUID", StringProperty::New( m_SOPClassUID ) ); mitkImage->SetProperty("dicomseriesreader.SOPClass", StringProperty::New( this->GetSOPClassUIDAsName() )); mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New( PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() )) ); mitkImage->SetProperty("dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New( this->GetPixelSpacingInterpretation() )); mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevelString", StringProperty::New( ReaderImplementationLevelToString( m_ReaderImplementationLevel ) )); mitkImage->SetProperty("dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( m_ReaderImplementationLevel )); mitkImage->SetProperty("dicomseriesreader.GantyTiltCorrected", BoolProperty::New( this->GetFlag("gantryTilt",false) )); mitkImage->SetProperty("dicomseriesreader.3D+t", BoolProperty::New( this->GetFlag("3D+t",false) )); + // level window + std::string windowCenter = this->GetPropertyAsString("windowCenter"); + std::string windowWidth = this->GetPropertyAsString("windowWidth"); + try + { + double level = stringtodouble( windowCenter ); + double window = stringtodouble( windowWidth ); + mitkImage->SetProperty("levelwindow", LevelWindowProperty::New( LevelWindow(level,window) ) ); + } + catch (...) + { + // nothing, no levelwindow to be predicted... + } + + mitkImage->SetProperty("dicom.pixel.PhotometricInterpretation", this->GetProperty("photometricInterpretation") ); + mitkImage->SetProperty("dicom.image.imagetype", this->GetProperty("imagetype") ); + + mitkImage->SetProperty("dicom.study.StudyDescription", this->GetProperty("studyDescription") ); + mitkImage->SetProperty("dicom.series.SeriesDescription", this->GetProperty("seriesDescription") ); + // third part: get something from ImageIO. BUT this needs to be created elsewhere. or not at all! return mitkImage; } void mitk::DICOMImageBlockDescriptor ::SetReaderImplementationLevel(const ReaderImplementationLevel& level) { m_ReaderImplementationLevel = level; } mitk::ReaderImplementationLevel mitk::DICOMImageBlockDescriptor ::GetReaderImplementationLevel() const { return m_ReaderImplementationLevel; } void mitk::DICOMImageBlockDescriptor ::SetSOPClassUID(const std::string& uid) { m_SOPClassUID = uid; } std::string mitk::DICOMImageBlockDescriptor ::GetSOPClassUID() const { return m_SOPClassUID; } std::string mitk::DICOMImageBlockDescriptor ::GetSOPClassUIDAsName() const { gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( m_SOPClassUID.c_str() ); const char* name = uidKnowledge.GetName(); if (name) { return std::string(name); } else { return std::string(""); } } void mitk::DICOMImageBlockDescriptor ::SetTagCache(DICOMTagCache* privateCache) { // TODO this must only be used during loading and never afterwards // TODO better: somehow make possible to have smartpointers here m_TagCache = privateCache; } #define printPropertyRange(label, property_name) \ { \ std::string first = this->GetPropertyAsString( #property_name "First"); \ std::string last = this->GetPropertyAsString( #property_name "Last"); \ if (!first.empty() || !last.empty()) \ { \ if (first == last) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ else \ { \ os << " " label ": '" << first << "' - '" << last << "'" << std::endl; \ } \ } \ } #define printProperty(label, property_name) \ { \ std::string first = this->GetPropertyAsString( #property_name ); \ if (!first.empty()) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ } #define printBool(label, commands) \ { \ os << " " label ": '" << (commands?"yes":"no") << "'" << std::endl; \ } void mitk::DICOMImageBlockDescriptor ::Print(std::ostream& os, bool filenameDetails) const { os << " Number of Frames: '" << m_ImageFrameList.size() << "'" << std::endl; os << " SOP class: '" << this->GetSOPClassUIDAsName() << "'" << std::endl; printProperty("Series Number", seriesNumber); + printProperty("Study Description", studyDescription); printProperty("Series Description", seriesDescription); printProperty("Modality", modality); printProperty("Sequence Name", sequenceName); printPropertyRange("Slice Location", sliceLocation); printPropertyRange("Acquisition Number", acquisitionNumber); printPropertyRange("Instance Number", instanceNumber); printPropertyRange("Image Position", imagePositionPatient); printProperty("Image Orientation", orientation); os << " Pixel spacing interpretation: '" << PixelSpacingInterpretationToString(this->GetPixelSpacingInterpretation()) << "'" << std::endl; printBool("Gantry Tilt", this->GetFlag("gantryTilt",false)) //printBool("3D+t", this->GetFlag("3D+t",false)) //os << " MITK image loaded: '" << (this->GetMitkImage().IsNotNull() ? "yes" : "no") << "'" << std::endl; if (filenameDetails) { os << " Files in this image block:" << std::endl; for (DICOMImageFrameList::const_iterator frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++frameIter) { os << " " << (*frameIter)->Filename; if ((*frameIter)->FrameNo > 0) { os << ", " << (*frameIter)->FrameNo; } os << std::endl; } } } #define storeTagValueToProperty(tag_name, tag_g, tag_e) \ { \ const DICOMTag t(tag_g, tag_e); \ std::string tagValue = m_TagCache->GetTagValue( firstFrame, t ); \ const_cast(this)->SetProperty(#tag_name, StringProperty::New( tagValue ) ); \ } #define storeTagValueRangeToProperty(tag_name, tag_g, tag_e) \ { \ const DICOMTag t(tag_g, tag_e); \ std::string tagValueFirst = m_TagCache->GetTagValue( firstFrame, t ); \ std::string tagValueLast = m_TagCache->GetTagValue( lastFrame, t ); \ const_cast(this)->SetProperty(#tag_name "First", StringProperty::New( tagValueFirst ) ); \ const_cast(this)->SetProperty(#tag_name "Last", StringProperty::New( tagValueLast ) ); \ } void mitk::DICOMImageBlockDescriptor ::UpdateImageDescribingProperties() const { if (!m_PropertiesOutOfDate) return; if (m_TagCache && !m_ImageFrameList.empty()) { - DICOMImageFrameInfo::Pointer firstFrame = m_ImageFrameList.front();; - DICOMImageFrameInfo::Pointer lastFrame = m_ImageFrameList.back();; + DICOMImageFrameInfo::Pointer firstFrame = m_ImageFrameList.front(); + DICOMImageFrameInfo::Pointer lastFrame = m_ImageFrameList.back(); // see macros above storeTagValueToProperty(seriesNumber,0x0020,0x0011) + storeTagValueToProperty(studyDescription,0x0008,0x1030) storeTagValueToProperty(seriesDescription,0x0008,0x103e) storeTagValueToProperty(modality,0x0008,0x0060) storeTagValueToProperty(sequenceName,0x0018,0x0024) storeTagValueToProperty(orientation,0x0020,0x0037) storeTagValueRangeToProperty(sliceLocation,0x0020,0x1041) storeTagValueRangeToProperty(acquisitionNumber,0x0020,0x0012) storeTagValueRangeToProperty(instanceNumber,0x0020,0x0013) storeTagValueRangeToProperty(imagePositionPatient,0x0020,0x0032) + storeTagValueToProperty(windowCenter,0x0028,0x1050) + storeTagValueToProperty(windowWidth,0x0028,0x1051) + storeTagValueToProperty(imageType,0x0008,0x0008) + storeTagValueToProperty(photometricInterpretation,0x0028,0x0004) + // some per-image attributes // frames are just numbered starting from 0. timestep 1 (the second time-step) has frames starting at (number-of-frames-per-timestep) std::string propertyKeySliceLocation = "dicom.image.0020.1041"; std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; StringLookupTable sliceLocationForSlices; StringLookupTable instanceNumberForSlices; StringLookupTable SOPInstanceUIDForSlices; const DICOMTag tagSliceLocation(0x0020,0x1041); const DICOMTag tagInstanceNumber(0x0020,0x0013); const DICOMTag tagSOPInstanceNumber(0x0008,0x0018); unsigned int slice(0); for (DICOMImageFrameList::const_iterator frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++slice, ++frameIter) { std::string sliceLocation = m_TagCache->GetTagValue( *frameIter, tagSliceLocation ); sliceLocationForSlices.SetTableValue(slice, sliceLocation); std::string instanceNumber = m_TagCache->GetTagValue( *frameIter, tagInstanceNumber ); instanceNumberForSlices.SetTableValue(slice, instanceNumber); std::string sopInstanceUID = m_TagCache->GetTagValue( *frameIter, tagSOPInstanceNumber ); SOPInstanceUIDForSlices.SetTableValue(slice, sopInstanceUID); MITK_DEBUG << "Tag info for slice " << slice << ": SL '" << sliceLocation << "' IN '" << instanceNumber << "' SOP instance UID '" << sopInstanceUID << "'"; // add property or properties with proper names const_cast(this)->SetProperty( "sliceLocationForSlices", StringLookupTableProperty::New( sliceLocationForSlices ) ); const_cast(this)->SetProperty( "instanceNumberForSlices", StringLookupTableProperty::New( instanceNumberForSlices ) ); const_cast(this)->SetProperty( "SOPInstanceUIDForSlices", StringLookupTableProperty::New( SOPInstanceUIDForSlices ) ); } m_PropertiesOutOfDate = false; } } diff --git a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h index 473af17152..e132a3c566 100644 --- a/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h +++ b/Modules/DICOMReader/mitkDICOMImageBlockDescriptor.h @@ -1,165 +1,167 @@ /*=================================================================== 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 mitkDICOMImageBlockDescriptor_h #define mitkDICOMImageBlockDescriptor_h #include "mitkDICOMEnums.h" #include "mitkDICOMImageFrameInfo.h" #include "mitkDICOMTag.h" #include "mitkDICOMTagCache.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkGantryTiltInformation.h" namespace mitk { //TODO describe attributes common and different for all blocks of a series (perhaps not here) /** \ingroup DICOMReaderModule \brief Output descriptor for DICOMFileReader. As a result of analysis by a mitk::DICOMFileReader, this class describes the properties of a single mitk::Images that could be loaded by the file reader. The descriptor contains the following information: - the mitk::Image itself. This will be NULL after analysis and only be present after actual loading. - a list of frames (mostly: filenames) that went into composition of the mitk::Image. - an assessment of the reader's ability to load this set of files (ReaderImplementationLevel) - this can be used for reader selection when one reader is able to load an image with correct colors and the other is able to produce only gray values, for example - description of aspects of the image. Mostly a key-value list implemented by means of mitk::PropertyList. - for specific keys and possible values, see documentation of specific readers. \note an mitk::Image may both consist of multiple files (the "old" DICOM way) or a mitk::Image may be described by a single DICOM file or even only parts of a DICOM file (the newer multi-frame DICOM classes). To reflect this DICOMImageFrameList describes a list of frames from different or a single file. Described aspects of an image are: - whether pixel spacing is meant to be in-patient or on-detector (mitk::PixelSpacingInterpretation) - details about a possible gantry tilt (intended for use by file readers, may be hidden later) */ class DICOMReader_EXPORT DICOMImageBlockDescriptor { public: DICOMImageBlockDescriptor(); ~DICOMImageBlockDescriptor(); DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other); DICOMImageBlockDescriptor& operator=(const DICOMImageBlockDescriptor& other); /// List of frames that constitute the mitk::Image (DICOMImageFrame%s) void SetImageFrameList(const DICOMImageFrameList& framelist); /// List of frames that constitute the mitk::Image (DICOMImageFrame%s) const DICOMImageFrameList& GetImageFrameList() const; /// The 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList void SetMitkImage(Image::Pointer image); /// the 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList Image::Pointer GetMitkImage() const; /// Reader's capability to appropriately load this set of frames ReaderImplementationLevel GetReaderImplementationLevel() const; /// Reader's capability to appropriately load this set of frames void SetReaderImplementationLevel(const ReaderImplementationLevel& level); /// Key-value store describing aspects of the image to be loaded void SetProperty(const std::string& key, BaseProperty* value); /// Key-value store describing aspects of the image to be loaded BaseProperty* GetProperty(const std::string& key) const; /// Convenience function around GetProperty() std::string GetPropertyAsString(const std::string&) const; /// Convenience function around SetProperty() void SetFlag(const std::string& key, bool value); /// Convenience function around GetProperty() bool GetFlag(const std::string& key, bool defaultValue) const; /// Convenience function around SetProperty() void SetIntProperty(const std::string& key, int value); /// Convenience function around GetProperty() int GetIntProperty(const std::string& key, int defaultValue) const; private: // For future implementation: load slice-by-slice, mark this using these methods void SetSliceIsLoaded(unsigned int index, bool isLoaded); // For future implementation: load slice-by-slice, mark this using these methods bool IsSliceLoaded(unsigned int index) const; // For future implementation: load slice-by-slice, mark this using these methods bool AllSlicesAreLoaded() const; public: /// Takes the raw value of the DICOM tags (0028,0030) Pixel Spacing and (0018,1164) Imager Pixel Spacing void SetPixelSpacingTagValues(const std::string& pixelSpacing, const std::string& imagerPixelSpacing); /// Describe how the mitk::Image's pixel spacing should be interpreted PixelSpacingInterpretation GetPixelSpacingInterpretation() const; /// Describe the correct x/y pixel spacing of the mitk::Image (which some readers might need to adjust after loading) void GetDesiredMITKImagePixelSpacing( ScalarType& spacingXinMM, ScalarType& spacingYinMM) const; /// Describe the gantry tilt of the acquisition void SetTiltInformation(const GantryTiltInformation& info); /// Describe the gantry tilt of the acquisition const GantryTiltInformation GetTiltInformation() const; /// SOP Class UID of this set of frames void SetSOPClassUID(const std::string& uid); /// SOP Class UID of this set of frames std::string GetSOPClassUID() const; /// SOP Class as human readable name (e.g. "CT Image Storage") std::string GetSOPClassUIDAsName() const; void SetTagCache(DICOMTagCache* privateCache); /// Print information about this image block to given stream void Print(std::ostream& os, bool filenameDetails) const; private: Image::Pointer FixupSpacing(Image* mitkImage); Image::Pointer DescribeImageWithProperties(Image* mitkImage); void UpdateImageDescribingProperties() const; + double stringtodouble(const std::string& str) const; + DICOMImageFrameList m_ImageFrameList; Image::Pointer m_MitkImage; BoolList m_SliceIsLoaded; PixelSpacingInterpretation m_PixelSpacingInterpretation; ReaderImplementationLevel m_ReaderImplementationLevel; std::string m_PixelSpacing; std::string m_ImagerPixelSpacing; std::string m_SOPClassUID; GantryTiltInformation m_TiltInformation; PropertyList::Pointer m_PropertyList; const DICOMTagCache* m_TagCache; mutable bool m_PropertiesOutOfDate; }; } #endif