diff --git a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp index 35e3f12605..96f122d406 100644 --- a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp +++ b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp @@ -1,1081 +1,1080 @@ /*=================================================================== 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 "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 #include //ITK #include #include mitk::ImageVtkMapper2D::ImageVtkMapper2D() { } mitk::ImageVtkMapper2D::~ImageVtkMapper2D() { //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::ImageVtkMapper2D::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::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 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", renderer ); 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", renderer ) && resliceMethodEnumProperty ) thickSlicesMode = resliceMethodEnumProperty->GetValueAsId(); IntProperty *intProperty=0; if( dn->GetProperty( intProperty, "reslice.thickslices.num", renderer ) && intProperty ) { thickSlicesNum = intProperty->GetValue(); if(thickSlicesNum < 1) thickSlicesNum=1; } } 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; const mitk::AbstractTransformGeometry* abstractGeometry = dynamic_cast< const AbstractTransformGeometry * >(worldGeometry); if(abstractGeometry != NULL) normal = abstractGeometry->GetPlane()->GetNormal(); else{ if ( planeGeometry != NULL ){ normal = planeGeometry->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 ); 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!"; } } } 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(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->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::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; bool binary = false; GetDataNode()->GetBoolProperty("binaryimage.ishovering", hover, renderer); GetDataNode()->GetBoolProperty("selected", selected, renderer); GetDataNode()->GetBoolProperty("binary", binary, renderer); if(binary && 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(binary && 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(!binary || (!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::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::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 { //"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::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()); localStorage->m_LevelWindowFilter->SetOpacityPiecewiseFunction(transferFunctionProp->GetValue()->GetScalarOpacityFunction()); } 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 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::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( false ) ); 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 ); renderingModeProperty->SetValue( mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR ); // USE lookuptable - } // 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.GetPixelType() == itk::ImageIOBase::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::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 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::ImageVtkMapper2D::LocalStorage::~LocalStorage() { } mitk::ImageVtkMapper2D::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/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.cpp b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.cpp index e430bc7a04..f4417097c4 100644 --- a/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.cpp +++ b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.cpp @@ -1,445 +1,444 @@ /*=================================================================== 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. ===================================================================*/ //#define MBILOG_ENABLE_DEBUG #include #include "mitkImage.h" #include "mitkTestDCMLoading.h" - #include mitk::TestDCMLoading::TestDCMLoading() :m_PreviousCLocale(NULL) { } void mitk::TestDCMLoading::SetDefaultLocale() { // remember old locale only once if (m_PreviousCLocale == NULL) { m_PreviousCLocale = setlocale(LC_NUMERIC, NULL); // set to "C" setlocale(LC_NUMERIC, "C"); m_PreviousCppLocale = std::cin.getloc(); std::locale l( "C" ); std::cin.imbue(l); std::cout.imbue(l); } } void mitk::TestDCMLoading::ResetUserLocale() { if (m_PreviousCLocale) { setlocale(LC_NUMERIC, m_PreviousCLocale); std::cin.imbue(m_PreviousCppLocale); std::cout.imbue(m_PreviousCppLocale); m_PreviousCLocale = NULL; } } mitk::TestDCMLoading::ImageList mitk::TestDCMLoading::LoadFiles( const StringContainer& files, itk::SmartPointer preLoadedVolume ) { for (StringContainer::const_iterator iter = files.begin(); iter != files.end(); ++iter) { MITK_DEBUG << "File " << *iter; } ImageList result; DicomSeriesReader::FileNamesGrouping seriesInFiles = DicomSeriesReader::GetSeries( files, true ); // TODO sort series UIDs, implementation of map iterator might differ on different platforms (or verify this is a standard topic??) for (DicomSeriesReader::FileNamesGrouping::const_iterator seriesIter = seriesInFiles.begin(); seriesIter != seriesInFiles.end(); ++seriesIter) { StringContainer files = seriesIter->second.GetFilenames(); DataNode::Pointer node = DicomSeriesReader::LoadDicomSeries( files, true, true, true, 0, preLoadedVolume ); // true, true, true ist just a copy of the default values if (node.IsNotNull()) { Image::Pointer image = dynamic_cast( node->GetData() ); result.push_back( image ); } else { } } return result; } std::string mitk::TestDCMLoading::ComponentTypeToString(int type) { if (type == itk::ImageIOBase::UCHAR) return "UCHAR"; else if (type == itk::ImageIOBase::CHAR) return "CHAR"; else if (type == itk::ImageIOBase::USHORT) return "USHORT"; else if (type == itk::ImageIOBase::SHORT) return "SHORT"; else if (type == itk::ImageIOBase::UINT) return "UINT"; else if (type == itk::ImageIOBase::INT) return "INT"; else if (type == itk::ImageIOBase::ULONG) return "ULONG"; else if (type == itk::ImageIOBase::LONG) return "LONG"; else if (type == itk::ImageIOBase::FLOAT) return "FLOAT"; else if (type == itk::ImageIOBase::DOUBLE) return "DOUBLE"; else return "UNKNOWN"; } // add a line to stringstream result (see DumpImageInformation #define DumpLine(field, data) DumpILine(0, field, data) // add an indented(!) line to stringstream result (see DumpImageInformation #define DumpILine(indent, field, data) \ { \ std::string DumpLine_INDENT; DumpLine_INDENT.resize(indent, ' ' ); \ result << DumpLine_INDENT << field << ": " << data << "\n"; \ } std::string mitk::TestDCMLoading::DumpImageInformation( const Image* image ) { std::stringstream result; if (image == NULL) return result.str(); SetDefaultLocale(); // basic image data DumpLine( "Pixeltype", ComponentTypeToString(image->GetPixelType().GetComponentType()) ); DumpLine( "BitsPerPixel", image->GetPixelType().GetBpe() ); DumpLine( "Dimension", image->GetDimension() ); result << "Dimensions: "; for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) result << image->GetDimension(dim) << " "; result << "\n"; // geometry data result << "Geometry: \n"; BaseGeometry* geometry = image->GetGeometry(); if (geometry) { AffineTransform3D* transform = geometry->GetIndexToWorldTransform(); if (transform) { result << " " << "Matrix: "; const AffineTransform3D::MatrixType& matrix = transform->GetMatrix(); for (unsigned int i = 0; i < 3; ++i) for (unsigned int j = 0; j < 3; ++j) result << matrix[i][j] << " "; result << "\n"; result << " " << "Offset: "; const AffineTransform3D::OutputVectorType& offset = transform->GetOffset(); for (unsigned int i = 0; i < 3; ++i) result << offset[i] << " "; result << "\n"; result << " " << "Center: "; const AffineTransform3D::InputPointType& center = transform->GetCenter(); for (unsigned int i = 0; i < 3; ++i) result << center[i] << " "; result << "\n"; result << " " << "Translation: "; const AffineTransform3D::OutputVectorType& translation = transform->GetTranslation(); for (unsigned int i = 0; i < 3; ++i) result << translation[i] << " "; result << "\n"; result << " " << "Scale: "; const double* scale = transform->GetScale(); for (unsigned int i = 0; i < 3; ++i) result << scale[i] << " "; result << "\n"; result << " " << "Origin: "; const Point3D& origin = geometry->GetOrigin(); for (unsigned int i = 0; i < 3; ++i) result << origin[i] << " "; result << "\n"; result << " " << "Spacing: "; const Vector3D& spacing = geometry->GetSpacing(); for (unsigned int i = 0; i < 3; ++i) result << spacing[i] << " "; result << "\n"; result << " " << "TimeBounds: "; const TimeBounds timeBounds = image->GetTimeGeometry()->GetTimeBounds(); for (unsigned int i = 0; i < 2; ++i) result << timeBounds[i] << " "; result << "\n"; } } ResetUserLocale(); return result.str(); } std::string mitk::TestDCMLoading::trim(const std::string& pString, const std::string& pWhitespace) { const size_t beginStr = pString.find_first_not_of(pWhitespace); if (beginStr == std::string::npos) { // no content return ""; } const size_t endStr = pString.find_last_not_of(pWhitespace); const size_t range = endStr - beginStr + 1; return pString.substr(beginStr, range); } std::string mitk::TestDCMLoading::reduce(const std::string& pString, const std::string& pFill, const std::string& pWhitespace) { // trim first std::string result(trim(pString, pWhitespace)); // replace sub ranges size_t beginSpace = result.find_first_of(pWhitespace); while (beginSpace != std::string::npos) { const size_t endSpace = result.find_first_not_of(pWhitespace, beginSpace); const size_t range = endSpace - beginSpace; result.replace(beginSpace, range, pFill); const size_t newStart = beginSpace + pFill.length(); beginSpace = result.find_first_of(pWhitespace, newStart); } return result; } bool mitk::TestDCMLoading::CompareSpacedValueFields( const std::string& reference, const std::string& test, double /*eps*/ ) { bool result(true); // tokenize string, compare each token, if possible by float comparison std::stringstream referenceStream(reduce(reference)); std::stringstream testStream(reduce(test)); std::string refToken; std::string testToken; while ( std::getline( referenceStream, refToken, ' ' ) && std::getline ( testStream, testToken, ' ' ) ) { float refNumber; float testNumber; if ( this->StringToNumber(refToken, refNumber) ) { if ( this->StringToNumber(testToken, testNumber) ) { // print-out compared tokens if DEBUG output allowed MITK_DEBUG << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; bool old_result = result; result &= ( fabs(refNumber - testNumber) < 0.0001 /*mitk::eps*/ ); // log the token/number which causes the test to fail if( old_result != result) { MITK_ERROR << std::setprecision(16) << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; MITK_ERROR << "[FALSE] - difference: " << std::setprecision(16) << fabs(refNumber - testNumber) << " EPS: " << 0.0001;// mitk::eps; } } else { MITK_ERROR << refNumber << " cannot be compared to '" << testToken << "'"; } } else { MITK_DEBUG << "Token '" << refToken << "'" << " handled as string"; result &= refToken == testToken; } } if ( std::getline( referenceStream, refToken, ' ' ) ) { MITK_ERROR << "Reference string still had values when test string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } else if ( std::getline( testStream, testToken, ' ' ) ) { MITK_ERROR << "Test string still had values when reference string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } return result; } bool mitk::TestDCMLoading::CompareImageInformationDumps( const std::string& referenceDump, const std::string& testDump ) { KeyValueMap reference = ParseDump(referenceDump); KeyValueMap test = ParseDump(testDump); bool testResult(true); // verify all expected values for (KeyValueMap::const_iterator refIter = reference.begin(); refIter != reference.end(); ++refIter) { const std::string& refKey = refIter->first; const std::string& refValue = refIter->second; if ( test.find(refKey) != test.end() ) { const std::string& testValue = test[refKey]; bool thisTestResult = CompareSpacedValueFields( refValue, testValue ); testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << refValue << "' == '" << testValue << "' ? " << (thisTestResult?"YES":"NO"); } else { MITK_ERROR << "Reference dump contains a key'" << refKey << "' (value '" << refValue << "')." ; MITK_ERROR << "This key is expected to be generated for tests (but was not). Most probably you need to update your test data."; return false; } } // now check test dump does not contain any additional keys for (KeyValueMap::const_iterator testIter = test.begin(); testIter != test.end(); ++testIter) { const std::string& key = testIter->first; const std::string& value = testIter->second; if ( reference.find(key) == reference.end() ) { MITK_ERROR << "Test dump contains an unexpected key'" << key << "' (value '" << value << "')." ; MITK_ERROR << "This key is not expected. Most probably you need to update your test data."; return false; } } return testResult; } mitk::TestDCMLoading::KeyValueMap mitk::TestDCMLoading::ParseDump( const std::string& dump ) { KeyValueMap parsedResult; std::string shredder(dump); std::stack surroundingKeys; std::stack expectedIndents; expectedIndents.push(0); while (true) { std::string::size_type newLinePos = shredder.find( '\n' ); if (newLinePos == std::string::npos || newLinePos == 0) break; std::string line = shredder.substr( 0, newLinePos ); shredder = shredder.erase( 0, newLinePos+1 ); std::string::size_type keyPosition = line.find_first_not_of( ' ' ); std::string::size_type colonPosition = line.find( ':' ); std::string key = line.substr(keyPosition, colonPosition - keyPosition); std::string::size_type firstSpacePosition = key.find_first_of(" "); if (firstSpacePosition != std::string::npos) { key.erase(firstSpacePosition); } if ( keyPosition > expectedIndents.top() ) { // more indent than before expectedIndents.push(keyPosition); } else if (keyPosition == expectedIndents.top() ) { if (!surroundingKeys.empty()) { surroundingKeys.pop(); // last of same length } } else { // less indent than before do expectedIndents.pop(); while (expectedIndents.top() != keyPosition); // unwind until current indent is found } if (!surroundingKeys.empty()) { key = surroundingKeys.top() + "." + key; // construct current key name } surroundingKeys.push(key); // this is the new embracing key std::string value = line.substr(colonPosition+1); MITK_DEBUG << " Key: '" << key << "' value '" << value << "'" ; parsedResult[key] = value; // store parsing result } return parsedResult; } diff --git a/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.h b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.h index b0a1552d3a..5ac73a1071 100644 --- a/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.h +++ b/Core/Code/Testing/DCMTesting/mitkTestDCMLoading.h @@ -1,108 +1,109 @@ /*=================================================================== 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 mitkTestDCMLoading_h #define mitkTestDCMLoading_h #include "mitkDicomSeriesReader.h" #include "MitkDCMTestingExports.h" namespace mitk { + class Image; class MitkDCMTesting_EXPORT TestDCMLoading { public: typedef DicomSeriesReader::StringContainer StringContainer; typedef std::list< DataNode::Pointer > NodeList; typedef std::list< itk::SmartPointer > ImageList; TestDCMLoading(); ImageList LoadFiles( const StringContainer& files, itk::SmartPointer preLoadedVolume = NULL ); /** \brief Dump relevant image information for later comparison. \sa CompareImageInformationDumps */ std::string DumpImageInformation( const Image* image ); /** \brief Compare two image information dumps. \return true, if dumps are sufficiently equal (see parameters) \sa DumpImageInformation */ bool CompareImageInformationDumps( const std::string& reference, const std::string& test ); private: typedef std::map KeyValueMap; void SetDefaultLocale(); void ResetUserLocale(); std::string ComponentTypeToString( int type ); KeyValueMap ParseDump( const std::string& dump ); bool CompareSpacedValueFields( const std::string& reference, const std::string& test, double eps = mitk::eps ); /** Compress whitespace in string \param pString input string \param pFill replacement whitespace (only whitespace in string after reduction) \param pWhitespace characters handled as whitespace */ std::string reduce(const std::string& pString, const std::string& pFill = " ", const std::string& pWhitespace = " \t"); /** Remove leading and trailing whitespace \param pString input string \param pWhitespace characters handled as whitespace */ std::string trim(const std::string& pString, const std::string& pWhitespace = " \t"); template bool StringToNumber(const std::string& s, T& value) { std::stringstream stream(s); stream >> value; return (!stream.fail()) && (fabs(value) < std::numeric_limits::max()); } const char* m_PreviousCLocale; std::locale m_PreviousCppLocale; }; } #endif diff --git a/Core/Code/Testing/files.cmake b/Core/Code/Testing/files.cmake index 939ba93d9b..c2aff2b806 100644 --- a/Core/Code/Testing/files.cmake +++ b/Core/Code/Testing/files.cmake @@ -1,199 +1,200 @@ # tests with no extra command line parameter set(MODULE_TESTS # IMPORTANT: If you plan to deactivate / comment out a test please write a bug number to the commented out line of code. # # Example: #mitkMyTest #this test is commented out because of bug 12345 # # It is important that the bug is open and that the test will be activated again before the bug is closed. This assures that # no test is forgotten after it was commented out. If there is no bug for your current problem, please add a new one and # mark it as critical. ################## DISABLED TESTS ################################################# #mitkAbstractTransformGeometryTest.cpp #seems as tested class mitkExternAbstractTransformGeometry doesnt exist any more #mitkStateMachineContainerTest.cpp #rewrite test, indirect since no longer exported Bug 14529 #mitkRegistrationBaseTest.cpp #tested class mitkRegistrationBase doesn't exist any more #mitkSegmentationInterpolationTest.cpp #file doesn't exist! #mitkPipelineSmartPointerCorrectnessTest.cpp #file doesn't exist! #mitkITKThreadingTest.cpp #test outdated because itk::Semaphore was removed from ITK #mitkAbstractTransformPlaneGeometryTest.cpp #mitkVtkAbstractTransformPlaneGeometry doesn't exist any more #mitkTestUtilSharedLibrary.cpp #Linker problem with this test... #mitkTextOverlay2DSymbolsRenderingTest.cpp #Implementation of the tested feature is not finished yet. Ask Christoph or see bug 15104 for details. ################# RUNNING TESTS ################################################### mitkAccessByItkTest.cpp mitkCoreObjectFactoryTest.cpp mitkDataNodeTest.cpp mitkMaterialTest.cpp mitkActionTest.cpp mitkDispatcherTest.cpp mitkEnumerationPropertyTest.cpp mitkEventTest.cpp mitkFileReaderRegistryTest.cpp #mitkFileWriterRegistryTest.cpp mitkFocusManagerTest.cpp mitkGenericPropertyTest.cpp mitkGeometry3DTest.cpp mitkGeometry3DEqualTest.cpp mitkGeometryDataToSurfaceFilterTest.cpp mitkGlobalInteractionTest.cpp mitkImageCastTest.cpp mitkImageEqualTest.cpp mitkImageDataItemTest.cpp mitkImageGeneratorTest.cpp mitkIOUtilTest.cpp mitkBaseDataTest.cpp mitkImportItkImageTest.cpp mitkGrabItkImageMemoryTest.cpp mitkInstantiateAccessFunctionTest.cpp mitkInteractorTest.cpp mitkLevelWindowTest.cpp mitkMessageTest.cpp mitkPixelTypeTest.cpp mitkPlaneGeometryTest.cpp mitkPointSetTest.cpp mitkPointSetEqualTest.cpp mitkPointSetFileIOTest.cpp mitkPointSetOnEmptyTest.cpp mitkPointSetWriterTest.cpp mitkPointSetReaderTest.cpp mitkPointSetInteractorTest.cpp mitkPointSetPointOperationsTest.cpp mitkProgressBarTest.cpp mitkPropertyTest.cpp mitkPropertyListTest.cpp mitkSlicedGeometry3DTest.cpp mitkSliceNavigationControllerTest.cpp mitkStateMachineTest.cpp mitkStateTest.cpp mitkSurfaceTest.cpp mitkSurfaceEqualTest.cpp mitkSurfaceToSurfaceFilterTest.cpp mitkTimeGeometryTest.cpp mitkProportionalTimeGeometryTest.cpp mitkTransitionTest.cpp mitkUndoControllerTest.cpp mitkVtkWidgetRenderingTest.cpp mitkVerboseLimitedLinearUndoTest.cpp mitkWeakPointerTest.cpp mitkTransferFunctionTest.cpp mitkStepperTest.cpp mitkRenderingManagerTest.cpp vtkMitkThickSlicesFilterTest.cpp mitkNodePredicateSourceTest.cpp mitkVectorTest.cpp mitkClippedSurfaceBoundsCalculatorTest.cpp mitkExceptionTest.cpp mitkExtractSliceFilterTest.cpp mitkLogTest.cpp mitkImageDimensionConverterTest.cpp mitkLoggingAdapterTest.cpp mitkUIDGeneratorTest.cpp mitkShaderRepositoryTest.cpp mitkPlanePositionManagerTest.cpp mitkAffineTransformBaseTest.cpp mitkPropertyAliasesTest.cpp mitkPropertyDescriptionsTest.cpp mitkPropertyExtensionsTest.cpp mitkPropertyFiltersTest.cpp mitkTinyXMLTest.cpp mitkRawImageFileReaderTest.cpp mitkInteractionEventTest.cpp mitkLookupTableTest.cpp mitkSTLFileReaderTest.cpp mitkPointTypeConversionTest.cpp mitkVectorTypeConversionTest.cpp mitkMatrixTypeConversionTest.cpp mitkArrayTypeConversionTest.cpp mitkSurfaceToImageFilterTest.cpp mitkBaseGeometryTest.cpp mitkImageToSurfaceFilterTest.cpp mitkEqualTest.cpp mitkLineTest.cpp ) if(MITK_ENABLE_RENDERING_TESTING) #since mitkInteractionTestHelper is currently creating a vtkRenderWindow set(MODULE_TESTS ${MODULE_TESTS} mitkPointSetDataInteractorTest.cpp ) endif() # test with image filename as an extra command line parameter set(MODULE_IMAGE_TESTS mitkImageTimeSelectorTest.cpp #only runs on images mitkImageAccessorTest.cpp #only runs on images ) set(MODULE_SURFACE_TESTS mitkSurfaceVtkWriterTest.cpp #only runs on surfaces ) # list of images for which the tests are run set(MODULE_TESTIMAGES US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) set(MODULE_TESTSURFACES binary.stl ball.stl ) set(MODULE_CUSTOM_TESTS mitkDataStorageTest.cpp mitkDicomSeriesReaderTest.cpp mitkDICOMLocaleTest.cpp + mitkDataNodeTest.cpp mitkEventMapperTest.cpp mitkEventConfigTest.cpp mitkNodeDependentPointSetInteractorTest.cpp mitkStateMachineFactoryTest.cpp mitkPointSetLocaleTest.cpp mitkImageTest.cpp mitkImageWriterTest.cpp mitkImageVtkMapper2DTest.cpp mitkImageVtkMapper2DLevelWindowTest.cpp mitkImageVtkMapper2DOpacityTest.cpp mitkImageVtkMapper2DResliceInterpolationPropertyTest.cpp mitkImageVtkMapper2DColorTest.cpp mitkImageVtkMapper2DSwivelTest.cpp mitkImageVtkMapper2DTransferFunctionTest.cpp mitkImageVtkMapper2DOpacityTransferFunctionTest.cpp mitkImageVtkMapper2DLookupTableTest.cpp mitkSurfaceVtkMapper3DTest mitkSurfaceVtkMapper3DTexturedSphereTest.cpp mitkSurfaceGLMapper2DColorTest.cpp mitkSurfaceGLMapper2DOpacityTest.cpp mitkVolumeCalculatorTest.cpp mitkLevelWindowManagerTest.cpp mitkPointSetVtkMapper2DTest.cpp mitkPointSetVtkMapper2DImageTest.cpp mitkPointSetVtkMapper2DGlyphTypeTest.cpp mitkPointSetVtkMapper2DTransformedPointsTest.cpp mitkVTKRenderWindowSizeTest.cpp mitkMultiComponentImageDataComparisonFilterTest.cpp mitkImageToItkTest.cpp mitkImageSliceSelectorTest.cpp mitkSurfaceDepthPeelingTest.cpp mitkSurfaceDepthSortingTest.cpp ) set(MODULE_RESOURCE_FILES Interactions/AddAndRemovePoints.xml Interactions/globalConfig.xml Interactions/StatemachineTest.xml Interactions/StatemachineConfigTest.xml ) # Embed the resources set(testdriver_resources ) usFunctionEmbedResources(testdriver_resources EXECUTABLE_NAME ${MODULE_NAME}TestDriver ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Resources FILES ${MODULE_RESOURCE_FILES} ) set(TEST_CPP_FILES ${testdriver_init_file} ${testdriver_resources}) diff --git a/Core/Code/Testing/mitkDicomSeriesReaderTest.cpp b/Core/Code/Testing/mitkDicomSeriesReaderTest.cpp index 5f9fdf4f36..e201a48026 100644 --- a/Core/Code/Testing/mitkDicomSeriesReaderTest.cpp +++ b/Core/Code/Testing/mitkDicomSeriesReaderTest.cpp @@ -1,163 +1,162 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkTestingMacros.h" #include #include "mitkDicomSeriesReader.h" #include "mitkProperties.h" #include "mitkImage.h" - static std::map > GetTagInformationFromFile(mitk::DicomSeriesReader::StringContainer files) { gdcm::Scanner scanner; std::map > tagInformations; const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location scanner.AddTag( tagSliceLocation ); const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number scanner.AddTag( tagInstanceNumber ); const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number scanner.AddTag( tagSOPInstanceNumber ); //unsigned int slice(0); scanner.Scan(files); // return const_cast(scanner.GetMappings()); gdcm::Scanner::MappingType& tagValueMappings = const_cast(scanner.GetMappings()); for(std::vector::const_iterator fIter = files.begin(); fIter != files.end(); ++fIter) { std::map tags; tags.insert(std::pair (tagSliceLocation, tagValueMappings[fIter->c_str()][tagSliceLocation])); tags.insert(std::pair (tagInstanceNumber, tagValueMappings[fIter->c_str()][tagInstanceNumber])); tags.insert(std::pair (tagSOPInstanceNumber, tagValueMappings[fIter->c_str()][tagSOPInstanceNumber])); tagInformations.insert(std::pair > (fIter->c_str(), tags)); } return tagInformations; } int mitkDicomSeriesReaderTest(int argc, char* argv[]) { // always start with this! MITK_TEST_BEGIN("DicomSeriesReader") if(argc < 1) { MITK_ERROR << "No directory given!"; return -1; } char* dir; dir = argv[1]; //check if DICOMTags have been set as property for mitk::Image mitk::DicomSeriesReader::FileNamesGrouping seriesInFiles = mitk::DicomSeriesReader::GetSeries( dir, true ); std::list images; std::map fileMap; // TODO sort series UIDs, implementation of map iterator might differ on different platforms (or verify this is a standard topic??) for (mitk::DicomSeriesReader::FileNamesGrouping::const_iterator seriesIter = seriesInFiles.begin(); seriesIter != seriesInFiles.end(); ++seriesIter) { mitk::DicomSeriesReader::StringContainer files = seriesIter->second.GetFilenames(); mitk::DataNode::Pointer node = mitk::DicomSeriesReader::LoadDicomSeries( files ); MITK_TEST_CONDITION_REQUIRED(node.IsNotNull(),"Testing node") if (node.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); images.push_back( image ); fileMap.insert( std::pair(image,files)); } } //Test if DICOM tags have been added correctly to the mitk::image properties const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number for ( std::list::const_iterator imageIter = images.begin(); imageIter != images.end(); ++imageIter ) { const mitk::Image::Pointer image = *imageIter; //Get tag information for all dicom files of this image std::map > tagInformations = GetTagInformationFromFile((*fileMap.find(image)).second); mitk::StringLookupTableProperty* sliceLocation = dynamic_cast(image->GetProperty("dicom.image.0020.1041").GetPointer()); mitk::StringLookupTableProperty* instanceNumber = dynamic_cast(image->GetProperty("dicom.image.0020.0013").GetPointer()); mitk::StringLookupTableProperty* SOPInstnaceNumber = dynamic_cast(image->GetProperty("dicom.image.0008.0018").GetPointer()); mitk::StringLookupTableProperty* files = dynamic_cast(image->GetProperty("files").GetPointer()); MITK_TEST_CONDITION(sliceLocation != NULL, "Test if tag for slice location has been set to mitk image"); if(sliceLocation != NULL) { for(int i = 0; i < (int)sliceLocation->GetValue().GetLookupTable().size(); i++) { if(i < (int)files->GetValue().GetLookupTable().size()) { MITK_INFO << "Table value: " << sliceLocation->GetValue().GetTableValue(i) << " and File value: " << tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSliceLocation] << std::endl; MITK_INFO << "Filename: " << files->GetValue().GetTableValue(i).c_str() << std::endl; MITK_TEST_CONDITION(sliceLocation->GetValue().GetTableValue(i) == tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSliceLocation], "Test if value for slice location is correct"); } } } MITK_TEST_CONDITION(instanceNumber != NULL, "Test if tag for image instance number has been set to mitk image"); if(instanceNumber != NULL) { for(int i = 0; i < (int)instanceNumber->GetValue().GetLookupTable().size(); i++) { if(i < (int)files->GetValue().GetLookupTable().size()) { MITK_INFO << "Table value: " << instanceNumber->GetValue().GetTableValue(i) << " and File value: " << tagInformations[files->GetValue().GetTableValue(i).c_str()][tagInstanceNumber] << std::endl; MITK_INFO << "Filename: " << files->GetValue().GetTableValue(i).c_str() << std::endl; MITK_TEST_CONDITION(instanceNumber->GetValue().GetTableValue(i) == tagInformations[files->GetValue().GetTableValue(i).c_str()][tagInstanceNumber], "Test if value for instance number is correct"); } } } MITK_TEST_CONDITION(SOPInstnaceNumber != NULL, "Test if tag for SOP instance number has been set to mitk image"); if(SOPInstnaceNumber != NULL) { for(int i = 0; i < (int)SOPInstnaceNumber->GetValue().GetLookupTable().size(); i++) { if(i < (int)files->GetValue().GetLookupTable().size()) { MITK_INFO << "Table value: " << instanceNumber->GetValue().GetTableValue(i) << " and File value: " << tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSOPInstanceNumber] << std::endl; MITK_INFO << "Filename: " << files->GetValue().GetTableValue(i).c_str() << std::endl; MITK_TEST_CONDITION(SOPInstnaceNumber->GetValue().GetTableValue(i) == tagInformations[files->GetValue().GetTableValue(i).c_str()][tagSOPInstanceNumber], "Test if value for SOP instance number is correct"); } } } } MITK_TEST_END() } diff --git a/Core/Code/TestingHelper/mitkTestingMacros.h b/Core/Code/TestingHelper/mitkTestingMacros.h index 02edc5fe47..dcf2de6393 100644 --- a/Core/Code/TestingHelper/mitkTestingMacros.h +++ b/Core/Code/TestingHelper/mitkTestingMacros.h @@ -1,386 +1,385 @@ /*=================================================================== 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 mitkTestingMacros_h #define mitkTestingMacros_h #include #include #include #include #include #include #include #include #include namespace mitk { /** @brief Indicate a failed test. */ class TestFailedException : public std::exception { public: TestFailedException() {} }; } /** * @brief Output some text without generating a terminating newline. Include * * @ingroup MITKTestingAPI */ #define MITK_TEST_OUTPUT_NO_ENDL(x) \ std::cout x ; /** * @brief Output some text. * * @ingroup MITKTestingAPI */ #define MITK_TEST_OUTPUT(x) \ MITK_TEST_OUTPUT_NO_ENDL(x << "\n") /** * @brief Do some general test preparations. Must be called first in the * main test function. * * @deprecatedSince{2013_09} Use MITK_TEST_SUITE_REGISTRATION instead. * @ingroup MITKTestingAPI */ #define MITK_TEST_BEGIN(testName) \ std::string mitkTestName(#testName); \ mitk::TestManager::GetInstance()->Initialize(); \ try { /** * @brief Fail and finish test with message MSG * * @deprecatedSince{2013_09} Use CPPUNIT_FAIL instead * @ingroup MITKTestingAPI */ #define MITK_TEST_FAILED_MSG(MSG) \ MITK_TEST_OUTPUT(MSG) \ throw mitk::TestFailedException(); /** * @brief Must be called last in the main test function. * * @deprecatedSince{2013_09} Use MITK_TEST_SUITE_REGISTRATION instead. * @ingroup MITKTestingAPI */ #define MITK_TEST_END() \ } catch (const mitk::TestFailedException&) { \ MITK_TEST_OUTPUT(<< "Further test execution skipped.") \ mitk::TestManager::GetInstance()->TestFailed(); \ } catch (const std::exception& ex) { \ MITK_TEST_OUTPUT(<< "std::exception occured " << ex.what()) \ mitk::TestManager::GetInstance()->TestFailed(); \ } \ if (mitk::TestManager::GetInstance()->NumberOfFailedTests() > 0) { \ MITK_TEST_OUTPUT(<< mitkTestName << ": [DONE FAILED] , subtests passed: " << \ mitk::TestManager::GetInstance()->NumberOfPassedTests() << " failed: " << \ mitk::TestManager::GetInstance()->NumberOfFailedTests() ) \ return EXIT_FAILURE; \ } else { \ MITK_TEST_OUTPUT(<< mitkTestName << ": " \ << mitk::TestManager::GetInstance()->NumberOfPassedTests() \ << " tests [DONE PASSED]") \ return EXIT_SUCCESS; \ } \ /** * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT or CPPUNIT_ASSERT_MESSAGE instead. */ #define MITK_TEST_CONDITION(COND,MSG) \ MITK_TEST_OUTPUT_NO_ENDL(<< MSG) \ if ( ! (COND) ) { \ mitk::TestManager::GetInstance()->TestFailed(); \ MITK_TEST_OUTPUT(<< " [FAILED]\n" << "In " << __FILE__ \ << ", line " << __LINE__ \ << ": " #COND " : [FAILED]") \ } else { \ MITK_TEST_OUTPUT(<< " [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } /** * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT or CPPUNIT_ASSERT_MESSAGE instead. */ #define MITK_TEST_CONDITION_REQUIRED(COND,MSG) \ MITK_TEST_OUTPUT_NO_ENDL(<< MSG) \ if ( ! (COND) ) { \ MITK_TEST_FAILED_MSG(<< " [FAILED]\n" << " +--> in " << __FILE__ \ << ", line " << __LINE__ \ << ", expression is false: \"" #COND "\"") \ } else { \ MITK_TEST_OUTPUT(<< " [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } /** * \brief Begin block which should be checked for exceptions * * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT_THROW instead. * @ingroup MITKTestingAPI * * This macro, together with MITK_TEST_FOR_EXCEPTION_END, can be used * to test whether a code block throws an expected exception. The test FAILS if the * exception is NOT thrown. A simple example: * MITK_TEST_FOR_EXCEPTION_BEGIN(itk::ImageFileReaderException) typedef itk::ImageFileReader< itk::Image > ReaderType; ReaderType::Pointer reader = ReaderType::New(); reader->SetFileName("/tmp/not-existing"); reader->Update(); MITK_TEST_FOR_EXCEPTION_END(itk::ImageFileReaderException) * */ #define MITK_TEST_FOR_EXCEPTION_BEGIN(EXCEPTIONCLASS) \ try { /** * @deprecatedSince{2013_09} */ #define MITK_TEST_FOR_EXCEPTION_END(EXCEPTIONCLASS) \ mitk::TestManager::GetInstance()->TestFailed(); \ MITK_TEST_OUTPUT( << "Expected an '" << #EXCEPTIONCLASS << "' exception. [FAILED]") \ } \ catch (EXCEPTIONCLASS) { \ MITK_TEST_OUTPUT( << "Caught an expected '" << #EXCEPTIONCLASS \ << "' exception. [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } /** * @brief Simplified version of MITK_TEST_FOR_EXCEPTION_BEGIN / END for * a single statement * * @deprecatedSince{2013_09} Use CPPUNIT_ASSERT_THROW instead. * @ingroup MITKTestingAPI */ #define MITK_TEST_FOR_EXCEPTION(EXCEPTIONCLASS, STATEMENT) \ MITK_TEST_FOR_EXCEPTION_BEGIN(EXCEPTIONCLASS) \ STATEMENT ; \ MITK_TEST_FOR_EXCEPTION_END(EXCEPTIONCLASS) /** * @brief Testing macro to test if two objects are equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * Feel free to implement mitk::Equal for your own datatype or purpose. * * @deprecatedSince{2013_09} Use MITK_ASSERT_EQUAL instead. * * @param OBJ1 First object. * @param OBJ2 Second object. * @param MSG Message to appear with the test. */ #define MITK_TEST_EQUAL(OBJ1,OBJ2,MSG) \ MITK_TEST_CONDITION_REQUIRED( mitk::Equal(OBJ1, OBJ2, mitk::eps, true)==true, MSG) /** * @brief Testing macro to test if two objects are equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * Feel free to implement mitk::Equal for your own datatype or purpose. * * @param EXPECTED First object. * @param ACTUAL Second object. * @param MSG Message to appear with the test. * @throw Throws mitkException if a NULL pointer is given as input. */ #define MITK_ASSERT_EQUAL(EXPECTED, ACTUAL, MSG) \ if(((EXPECTED).IsNull()) || ((ACTUAL).IsNull())) { \ mitkThrow() << "mitk::Equal does not work with NULL pointer input."; \ } \ CPPUNIT_ASSERT_MESSAGE(MSG, mitk::Equal(*(EXPECTED), *(ACTUAL), mitk::eps, true)) /** * @brief Testing macro to test if two objects are not equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * * @deprecatedSince{2013_09} Use MITK_ASSERT_NOT_EQUAL instead. * * @param OBJ1 First object. * @param OBJ2 Second object. * @param MSG Message to appear with the test. * * \sa MITK_TEST_EQUAL */ #define MITK_TEST_NOT_EQUAL(OBJ1,OBJ2,MSG) \ CPPUNIT_ASSERT_MESSAGE(MSG, !mitk::Equal(*(OBJ1), *(OBJ2), mitk::eps, true)) /** * @brief Testing macro to test if two objects are not equal. * * @ingroup MITKTestingAPI * * This macro uses mitk::eps and the corresponding mitk::Equal methods for all * comparisons and will give verbose output on the dashboard/console. * * @param OBJ1 First object. * @param OBJ2 Second object. * @param MSG Message to appear with the test. * @throw Throws mitkException if a NULL pointer is given as input. * * \sa MITK_ASSERT_EQUAL */ #define MITK_ASSERT_NOT_EQUAL(OBJ1, OBJ2, MSG) \ if(((OBJ1).IsNull()) || ((OBJ2).IsNull())) { \ mitkThrow() << "mitk::Equal does not work with NULL pointer input."; \ } \ CPPUNIT_ASSERT_MESSAGE(MSG, !mitk::Equal(*(OBJ1), *(OBJ2), mitk::eps, true)) /** * @brief Registers the given test suite. * * @ingroup MITKTestingAPI * * @param TESTSUITE_NAME The name of the test suite class, without "TestSuite" * at the end. */ #define MITK_TEST_SUITE_REGISTRATION(TESTSUITE_NAME) \ int TESTSUITE_NAME ## Test(int /*argc*/, char* /*argv*/[]) \ { \ CppUnit::TextUi::TestRunner runner; \ runner.addTest(TESTSUITE_NAME ## TestSuite::suite()); \ return runner.run() ? 0 : 1; \ } /** * @brief Adds a test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases. * The macro internally just calls the CPPUNIT_TEST macro. * * @param TESTMETHOD The name of the member funtion test. */ #define MITK_TEST(TESTMETHOD) CPPUNIT_TEST(TESTMETHOD) /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need custom parameters. * * @param TESTMETHOD The name of the member function test. * @param ARGS A std::vector object containing test parameter. * * @note Use the macro MITK_PARAMETERIZED_TEST only if you know what * you are doing. If you are not sure, use MITK_TEST instead. */ #define MITK_PARAMETERIZED_TEST(TESTMETHOD, ARGS) \ { \ std::string testName = #TESTMETHOD; \ for (std::size_t i = 0; i < ARGS.size(); ++i) \ { \ testName += "_" + ARGS[i]; \ } \ CPPUNIT_TEST_SUITE_ADD_TEST( \ ( new mitk::TestCaller( \ context.getTestNameFor(testName), \ &TestFixtureType::TESTMETHOD, \ context.makeFixture(), args ) ) ); \ } /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need parameters from the command line. * * @warning Use the macro MITK_PARAMETERIZED_CMD_LINE_TEST only * if you know what you are doing. If you are not sure, use * MITK_TEST instead. MITK_PARAMETERIZED_CMD_LINE_TEST is meant * for migrating from ctest to CppUnit. If you implement new * tests, the MITK_TEST macro will be sufficient. * * @param TESTMETHOD The name of the member function test. */ #define MITK_PARAMETERIZED_CMD_LINE_TEST(TESTMETHOD) \ CPPUNIT_TEST_SUITE_ADD_TEST( \ ( new mitk::TestCaller( \ context.getTestNameFor( #TESTMETHOD), \ &TestFixtureType::TESTMETHOD, \ context.makeFixture() ) ) ); /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need one custom parameter. * * @param TESTMETHOD The name of the member function test. * @param arg1 A custom string parameter being passed to the fixture. * * @note Use the macro MITK_PARAMETERIZED_TEST_1 only if you know what * you are doing. If you are not sure, use MITK_TEST instead. * * @see MITK_PARAMETERIZED_TEST */ #define MITK_PARAMETERIZED_TEST_1(TESTMETHOD, arg1) \ { \ std::vector args; \ args.push_back(arg1); \ MITK_PARAMETERIZED_TEST(TESTMETHOD, args) \ } /** * @brief Adds a parameterized test to the current test suite. * * @ingroup MITKTestingAPI * * Use this macro after the CPPUNIT_TEST_SUITE() macro to add test cases * which need two custom parameter. * * @param TESTMETHOD The name of the member function test. * @param arg1 A custom string parameter being passed to the fixture. * * @note Use the macro MITK_PARAMETERIZED_TEST_2 only if you know what * you are doing. If you are not sure, use MITK_TEST instead. * * @see MITK_PARAMETERIZED_TEST */ #define MITK_PARAMETERIZED_TEST_2(TESTMETHOD, arg1, arg2) \ { \ std::vector args; \ args.push_back(arg1); \ args.push_back(arg2); \ MITK_PARAMETERIZED_TEST(TESTMETHOD, args) \ } - #endif diff --git a/Modules/DICOMReader/CMakeLists.txt b/Modules/DICOMReader/CMakeLists.txt index ca066adc06..d4384faa6b 100644 --- a/Modules/DICOMReader/CMakeLists.txt +++ b/Modules/DICOMReader/CMakeLists.txt @@ -1,6 +1,5 @@ - MITK_CREATE_MODULE( DEPENDS MitkCore ) add_subdirectory(Testing) diff --git a/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp b/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp index 1548a9f895..74cf2d079d 100644 --- a/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp +++ b/Modules/DICOMReader/mitkDICOMFileReaderSelector.cpp @@ -1,260 +1,259 @@ /*=================================================================== 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 "mitkDICOMFileReaderSelector.h" #include "mitkDICOMReaderConfigurator.h" - #include "mitkDICOMGDCMTagScanner.h" #include #include #include #include #include mitk::DICOMFileReaderSelector ::DICOMFileReaderSelector() { } mitk::DICOMFileReaderSelector ::~DICOMFileReaderSelector() { } std::list mitk::DICOMFileReaderSelector ::GetAllConfiguredReaders() const { return m_Readers; } void mitk::DICOMFileReaderSelector ::AddConfigsFromResources(const std::string& path) { std::vector configs = us::GetModuleContext()->GetModule()->FindResources(path, "*.xml", false); for (std::vector::iterator iter = configs.begin(); iter != configs.end(); ++iter) { us::ModuleResource& resource = *iter; if (resource.IsValid()) { us::ModuleResourceStream stream(resource); // read all into string s std::string s; stream.seekg(0, std::ios::end); s.reserve(stream.tellg()); stream.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); this->AddConfig(s); } } } void mitk::DICOMFileReaderSelector ::AddConfigFromResource(us::ModuleResource& resource) { if (resource.IsValid()) { us::ModuleResourceStream stream(resource); // read all into string s std::string s; stream.seekg(0, std::ios::end); s.reserve(stream.tellg()); stream.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); this->AddConfig(s); } } void mitk::DICOMFileReaderSelector ::AddConfigFromResource(const std::string& resourcename) { us::ModuleResource r = us::GetModuleContext()->GetModule()->GetResource(resourcename); this->AddConfigFromResource(r); } void mitk::DICOMFileReaderSelector ::AddFileReaderCanditate(DICOMFileReader::Pointer reader) { if (reader.IsNotNull()) { m_Readers.push_back( reader ); } } void mitk::DICOMFileReaderSelector ::LoadBuiltIn3DConfigs() { //this->AddConfigsFromResources("configurations/3D"); // in this order of preference... this->AddConfigFromResource("configurations/3D/instancenumber.xml"); this->AddConfigFromResource("configurations/3D/instancenumber_soft.xml"); this->AddConfigFromResource("configurations/3D/slicelocation.xml"); this->AddConfigFromResource("configurations/3D/imageposition.xml"); this->AddConfigFromResource("configurations/3D/imageposition_byacquisition.xml"); //this->AddConfigFromResource("configurations/3D/classicreader.xml"); // not the best choice in ANY of the images I came across } void mitk::DICOMFileReaderSelector ::LoadBuiltIn3DnTConfigs() { this->AddConfigsFromResources("configurations/3DnT"); } void mitk::DICOMFileReaderSelector ::AddConfig(const std::string& xmlDescription) { DICOMReaderConfigurator::Pointer configurator = DICOMReaderConfigurator::New(); DICOMFileReader::Pointer reader = configurator->CreateFromUTF8ConfigString(xmlDescription); if (reader.IsNotNull()) { m_Readers.push_back( reader ); m_PossibleConfigurations.push_back(xmlDescription); } else { std::stringstream ss; ss << "Could not parse reader configuration. Ignoring it."; throw std::invalid_argument( ss.str() ); } } void mitk::DICOMFileReaderSelector ::AddConfigFile(const std::string& filename) { std::ifstream file(filename.c_str()); std::string s; file.seekg(0, std::ios::end); s.reserve(file.tellg()); file.seekg(0, std::ios::beg); s.assign((std::istreambuf_iterator(file)), std::istreambuf_iterator()); this->AddConfig(s); } void mitk::DICOMFileReaderSelector ::SetInputFiles(StringList filenames) { m_InputFilenames = filenames; } const mitk::StringList& mitk::DICOMFileReaderSelector ::GetInputFiles() const { return m_InputFilenames; } mitk::DICOMFileReader::Pointer mitk::DICOMFileReaderSelector ::GetFirstReaderWithMinimumNumberOfOutputImages() { ReaderList workingCandidates; // do the tag scanning externally and just ONCE DICOMGDCMTagScanner::Pointer gdcmScanner = DICOMGDCMTagScanner::New(); gdcmScanner->SetInputFiles( m_InputFilenames ); // let all readers analyze the file set for (ReaderList::iterator rIter = m_Readers.begin(); rIter != m_Readers.end(); ++rIter) { gdcmScanner->AddTags( (*rIter)->GetTagsOfInterest() ); } gdcmScanner->Scan(); // let all readers analyze the file set unsigned int readerIndex(0); for (ReaderList::iterator rIter = m_Readers.begin(); rIter != m_Readers.end(); ++readerIndex, ++rIter) { (*rIter)->SetInputFiles( m_InputFilenames ); (*rIter)->SetTagCache( gdcmScanner.GetPointer() ); try { (*rIter)->AnalyzeInputFiles(); workingCandidates.push_back( *rIter ); MITK_INFO << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") suggests " << (*rIter)->GetNumberOfOutputs() << " 3D blocks"; if ((*rIter)->GetNumberOfOutputs() == 1) { MITK_DEBUG << "Early out with reader #" << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << "), less than 1 block is not possible"; return *rIter; } } catch (std::exception& e) { MITK_ERROR << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") threw exception during file analysis, ignoring this reader. Exception: " << e.what(); } catch (...) { MITK_ERROR << "Reader " << readerIndex << " (" << (*rIter)->GetConfigurationLabel() << ") threw unknown exception during file analysis, ignoring this reader."; } } DICOMFileReader::Pointer bestReader; unsigned int minimumNumberOfOutputs = std::numeric_limits::max(); readerIndex = 0; unsigned int bestReaderIndex(0); // select the reader with the minimum number of mitk::Images as output for (ReaderList::iterator rIter = workingCandidates.begin(); rIter != workingCandidates.end(); ++readerIndex, ++rIter) { unsigned int thisReadersNumberOfOutputs = (*rIter)->GetNumberOfOutputs(); if ( thisReadersNumberOfOutputs > 0 // we don't count readers that don't actually produce output && thisReadersNumberOfOutputs < minimumNumberOfOutputs ) { minimumNumberOfOutputs = (*rIter)->GetNumberOfOutputs(); bestReader = *rIter; bestReaderIndex = readerIndex; } } MITK_DEBUG << "Decided for reader #" << bestReaderIndex << " (" << bestReader->GetConfigurationLabel() << ")"; MITK_DEBUG << m_PossibleConfigurations[bestReaderIndex]; return bestReader; } diff --git a/Modules/DICOMReader/mitkDICOMTag.h b/Modules/DICOMReader/mitkDICOMTag.h index fadf3d0dfb..4495dc9744 100644 --- a/Modules/DICOMReader/mitkDICOMTag.h +++ b/Modules/DICOMReader/mitkDICOMTag.h @@ -1,90 +1,91 @@ /*=================================================================== 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 mitkTag_h #define mitkTag_h #include "mitkPoint.h" #include "mitkVector.h" +#include "mitkPoint.h" #include "MitkDICOMReaderExports.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Representation of a DICOM tag. This class is just meant to combine group and element numbers for better readability and make handling tags more easy by offering comparison methods. */ class MitkDICOMReader_EXPORT DICOMTag { public: DICOMTag(unsigned int group, unsigned int element); DICOMTag(const DICOMTag& other); DICOMTag& operator=(const DICOMTag& other); bool operator==(const DICOMTag& other) const; bool operator<(const DICOMTag& other) const; unsigned int GetGroup() const; unsigned int GetElement() const; /// Return the name of this tag (e.g. "SeriesDescription" instead of "(0008,103e)") std::string GetName() const; /// add "(group-id,element-id) name" to given stream void Print(std::ostream& os) const; private: std::string toHexString(unsigned int i) const; unsigned int m_Group; unsigned int m_Element; }; typedef std::vector DICOMTagList; /** \brief Convert DICOM string describing a point two Vector3D. DICOM tags like ImageOrientationPatient contain two vectors as float numbers separated by backslashes: \verbatim 42.7131\13.77\0.7\137.76\0.3 \endverbatim */ void DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful); bool DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY); /** \brief Convert DICOM string describing a point to Point3D. DICOM tags like ImagePositionPatient contain a position as float numbers separated by backslashes: \verbatim 42.7131\13.77\0.7 \endverbatim */ Point3D DICOMStringToPoint3D(const std::string& s, bool& successful); } #endif diff --git a/Modules/DICOMReader/mitkGantryTiltInformation.h b/Modules/DICOMReader/mitkGantryTiltInformation.h index 8c005aefa1..74bdf416f6 100644 --- a/Modules/DICOMReader/mitkGantryTiltInformation.h +++ b/Modules/DICOMReader/mitkGantryTiltInformation.h @@ -1,146 +1,147 @@ /*=================================================================== 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 mitkGantryTiltInformation_h #define mitkGantryTiltInformation_h #include "mitkPoint.h" #include "mitkVector.h" +#include "mitkPoint.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Gantry tilt analysis result. Takes geometry information for two slices of a DICOM series and calculates whether these fit into an orthogonal block or not. If NOT, they can either be the result of an acquisition with gantry tilt OR completly broken by some shearing transformation. Most calculations are done in the constructor, results can then be read via the remaining methods. This class is a helper to DICOMITKSeriesGDCMReader and can not be used outside of \ref DICOMReaderModule */ class GantryTiltInformation { public: // two types to avoid any rounding errors typedef itk::Point Point3Dd; typedef itk::Vector Vector3Dd; /** \brief Just so we can create empty instances for assigning results later. */ GantryTiltInformation(); void Print(std::ostream& os) const; /** \brief THE constructor, which does all the calculations. Determining the amount of tilt is done by checking the distances of origin1 from planes through origin2. Two planes are considered: - normal vector along normal of slices (right x up): gives the slice distance - normal vector along orientation vector "up": gives the shift parallel to the plane orientation The tilt angle can then be calculated from these distances \param origin1 origin of the first slice \param origin2 origin of the second slice \param right right/up describe the orientatation of borth slices \param up right/up describe the orientatation of borth slices \param numberOfSlicesApart how many slices are the given origins apart (1 for neighboring slices) */ GantryTiltInformation( const Point3D& origin1, const Point3D& origin2, const Vector3D& right, const Vector3D& up, unsigned int numberOfSlicesApart); /** \brief Factory method to create GantryTiltInformation from tag values (strings). Parameters as the regular c'tor. */ static GantryTiltInformation MakeFromTagValues( const std::string& origin1String, const std::string& origin2String, const std::string orientationString, unsigned int numberOfSlicesApart); /** \brief Whether the slices were sheared. True if any of the shifts along right or up vector are non-zero. */ bool IsSheared() const; /** \brief Whether the shearing is a gantry tilt or more complicated. Gantry tilt will only produce shifts in ONE orientation, not in both. Since the correction code currently only coveres one tilt direction AND we don't know of medical images with two tilt directions, the loading code wants to check if our assumptions are true. */ bool IsRegularGantryTilt() const; /** \brief The offset distance in Y direction for each slice in mm (describes the tilt result). */ double GetMatrixCoefficientForCorrectionInWorldCoordinates() const; /** \brief The z / inter-slice spacing. Needed to correct ImageSeriesReader's result. */ double GetRealZSpacing() const; /** \brief The shift between first and last slice in mm. Needed to resize an orthogonal image volume. */ double GetTiltCorrectedAdditionalSize(unsigned int imageSizeZ) const; /** \brief Calculated tilt angle in degrees. */ double GetTiltAngleInDegrees() const; private: /** \brief Projection of point p onto line through lineOrigin in direction of lineDirection. */ Point3D projectPointOnLine( Point3Dd p, Point3Dd lineOrigin, Vector3Dd lineDirection ); double m_ShiftUp; double m_ShiftRight; double m_ShiftNormal; double m_ITKAssumedSliceSpacing; unsigned int m_NumberOfSlicesApart; }; } // namespace #endif diff --git a/Modules/DiffusionImaging/DiffusionCore/CMakeLists.txt b/Modules/DiffusionImaging/DiffusionCore/CMakeLists.txt index 517a120410..bd38665a82 100644 --- a/Modules/DiffusionImaging/DiffusionCore/CMakeLists.txt +++ b/Modules/DiffusionImaging/DiffusionCore/CMakeLists.txt @@ -1,11 +1,11 @@ MITK_CREATE_MODULE( SUBPROJECTS MITK-DTI INCLUDE_DIRS Algorithms Algorithms/Reconstruction Algorithms/Registration Algorithms/Reconstruction/MultishellProcessing DicomImport IODataStructures/DiffusionWeightedImages IODataStructures/QBallImages IODataStructures/TensorImages IODataStructures Rendering ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS MitkMapperExt MitkPlanarFigure MitkImageExtraction MitkSceneSerializationBase + DEPENDS MitkMapperExt MitkPlanarFigure MitkImageExtraction MitkSceneSerializationBase MitkDICOMReader PACKAGE_DEPENDS VTK|vtkFiltersProgrammable ITK|ITKDistanceMap+ITKRegistrationCommon+ITKLabelVoting+ITKVTK Boost ITK|ITKMetricsv4+ITKRegistrationMethodsv4 WARNINGS_AS_ERRORS ) add_subdirectory(Testing) diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReader.cpp b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReader.cpp new file mode 100644 index 0000000000..5249399f3b --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReader.cpp @@ -0,0 +1,306 @@ +/*=================================================================== + +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 "mitkDiffusionDICOMFileReader.h" + +#include "mitkDiffusionDICOMFileReaderHelper.h" + +#include "mitkDiffusionHeaderSiemensDICOMFileReader.h" +#include "mitkDiffusionHeaderSiemensMosaicDICOMFileReader.h" +#include "mitkDiffusionHeaderGEDICOMFileReader.h" +#include "mitkDiffusionHeaderPhilipsDICOMFileReader.h" + +#include "mitkStringProperty.h" + +static void PerformHeaderAnalysis( mitk::DiffusionHeaderDICOMFileReader::DICOMHeaderListType headers ) +{ + unsigned int images = headers.size(); + + unsigned int unweighted_images = 0; + unsigned int weighted_images = 0; + + mitk::DiffusionHeaderDICOMFileReader::DICOMHeaderListType::const_iterator c_iter = headers.begin(); + while( c_iter != headers.end() ) + { + const mitk::DiffusionImageDICOMHeaderInformation h = *c_iter; + if( h.baseline ) unweighted_images++; + if( h.b_value > 0 ) weighted_images++; + + ++c_iter; + } + + MITK_INFO << " :: Analyzed volumes " << images << "\n" + << " :: \t"<< unweighted_images << " b = 0" << "\n" + << " :: \t"<< weighted_images << " b > 0"; +} + +mitk::DiffusionDICOMFileReader::DiffusionDICOMFileReader() +{ + +} + +mitk::DiffusionDICOMFileReader::~DiffusionDICOMFileReader() +{ + +} + +bool mitk::DiffusionDICOMFileReader +::LoadImages() +{ + unsigned int numberOfOutputs = this->GetNumberOfOutputs(); + bool success = true; + + for(unsigned int o = 0; o < numberOfOutputs; ++o) + { + success &= this->LoadSingleOutputImage( this->m_OutputHeaderContainer.at(o), + this->InternalGetOutput(o), this->m_IsMosaicData.at(o) ); + } + + return success; +} + +bool mitk::DiffusionDICOMFileReader +::LoadSingleOutputImage( DiffusionHeaderDICOMFileReader::DICOMHeaderListType retrievedHeader, + DICOMImageBlockDescriptor& block, bool is_mosaic) +{ + // prepare data reading + DiffusionDICOMFileReaderHelper helper; + DiffusionDICOMFileReaderHelper::VolumeFileNamesContainer filenames; + + const DICOMImageFrameList& frames = block.GetImageFrameList(); + int numberOfDWImages = block.GetIntProperty("timesteps", 1); + + int numberOfFramesPerDWImage = frames.size() / numberOfDWImages; + + assert( int( double((double) frames.size() / (double) numberOfDWImages)) == numberOfFramesPerDWImage ); + for( int idx = 0; idx < numberOfDWImages; idx++ ) + { + std::vector< std::string > FileNamesPerVolume; + + DICOMImageFrameList::const_iterator timeStepStart = frames.begin() + idx * numberOfFramesPerDWImage; + DICOMImageFrameList::const_iterator timeStepEnd = frames.begin() + (idx+1) * numberOfFramesPerDWImage; + for (DICOMImageFrameList::const_iterator frameIter = timeStepStart; + frameIter != timeStepEnd; + ++frameIter) + { + FileNamesPerVolume.push_back( (*frameIter)->Filename ); + } + + filenames.push_back( FileNamesPerVolume ); + } + + // TODO : only prototyping to test loading of diffusion images + // we need some solution for the different types + typedef mitk::DiffusionImage DiffusionImageType; + DiffusionImageType::Pointer output_image = DiffusionImageType::New(); + + DiffusionImageType::GradientDirectionContainerType::Pointer directions = + DiffusionImageType::GradientDirectionContainerType::New(); + + + double max_bvalue = 0; + for( int idx = 0; idx < numberOfDWImages; idx++ ) + { + DiffusionImageDICOMHeaderInformation header = retrievedHeader.at(idx); + + if( max_bvalue < header.b_value ) + max_bvalue = header.b_value; + } + + // normalize the retrieved gradient directions according to the set b-value (maximal one) + for( int idx = 0; idx < numberOfDWImages; idx++ ) + { + DiffusionImageDICOMHeaderInformation header = retrievedHeader.at(idx); + DiffusionImageType::GradientDirectionType grad = header.g_vector; + + grad.normalize(); + grad *= sqrt( header.b_value / max_bvalue ); + + directions->push_back( grad ); + } + + // initialize the output image + output_image->SetReferenceBValue( max_bvalue ); + output_image->SetDirections( directions ); + + if( is_mosaic && this->m_ResolveMosaic ) + { + mitk::DiffusionHeaderSiemensMosaicDICOMFileReader::Pointer mosaic_reader = + mitk::DiffusionHeaderSiemensMosaicDICOMFileReader::New(); + + // retrieve the remaining meta-information needed for mosaic reconstruction + // it suffices to get it exemplatory from the first file in the file list + mosaic_reader->RetrieveMosaicInformation( filenames.at(0).at(0) ); + + mitk::MosaicDescriptor mdesc = mosaic_reader->GetMosaicDescriptor(); + output_image->SetVectorImage( helper.LoadMosaicToVector( filenames, mdesc ) ); + + } + else + { + output_image->SetVectorImage( helper.LoadToVector( filenames ) ); + } + output_image->InitializeFromVectorImage(); + output_image->SetProperty("diffusion.dicom.importname", mitk::StringProperty::New( helper.GetOutputName(filenames) ) ); + block.SetMitkImage( (mitk::Image::Pointer) output_image ); + + return block.GetMitkImage().IsNotNull(); + +} + +void mitk::DiffusionDICOMFileReader +::AnalyzeInputFiles() +{ + this->SetGroup3DandT(true); + + Superclass::AnalyzeInputFiles(); + + // collect output from superclass + size_t number_of_outputs = this->GetNumberOfOutputs(); + + if(number_of_outputs == 0) + { + MITK_ERROR << "Failed to parse input, retrieved 0 outputs from SeriesGDCMReader "; + } + + MITK_INFO("diffusion.dicomreader") << "Retrieved " << number_of_outputs << "outputs."; + + for( unsigned int outputidx = 0; outputidx < this->GetNumberOfOutputs(); outputidx++ ) + { + DICOMImageBlockDescriptor block_0 = this->GetOutput(outputidx); + + // collect vendor ID from the first output, first image + StringList inputFilename; + DICOMImageFrameInfo::Pointer frame_0 = block_0.GetImageFrameList().at(0); + inputFilename.push_back( frame_0->Filename ); + + + mitk::DiffusionHeaderDICOMFileReader::Pointer headerReader; + + bool isMosaic = false; + gdcm::Scanner gdcmScanner; + + gdcm::Tag t_vendor(0x008, 0x0070); + gdcm::Tag t_imagetype(0x008, 0x008); + + // add DICOM Tag for vendor + gdcmScanner.AddTag( t_vendor ); + // add DICOM Tag for image type + gdcmScanner.AddTag( t_imagetype ); + if( gdcmScanner.Scan( inputFilename ) ) + { + + // retrieve both vendor and image type + std::string vendor = gdcmScanner.GetValue( frame_0->Filename.c_str(), t_vendor ); + std::string image_type = gdcmScanner.GetValue( frame_0->Filename.c_str(), t_imagetype ); + MITK_INFO("diffusion.dicomreader") << "Output " << outputidx+1 << " Got vendor: " << vendor << " image type " << image_type; + + // parse vendor tag + if( vendor.find("SIEMENS") != std::string::npos ) + //&& image_type.find("DIFFUSION") != std::string::npos ) + { + if( image_type.find("MOSAIC") != std::string::npos ) + { + headerReader = mitk::DiffusionHeaderSiemensMosaicDICOMFileReader::New(); + isMosaic = true; + } + else + { + headerReader = mitk::DiffusionHeaderSiemensDICOMFileReader::New(); + } + + } + else if( vendor.find("GE") != std::string::npos ) + { + headerReader = mitk::DiffusionHeaderGEDICOMFileReader::New(); + } + else if( vendor.find("Philips") != std::string::npos ) + { + headerReader = mitk::DiffusionHeaderPhilipsDICOMFileReader::New(); + } + else + { + // unknown vendor + } + + if( headerReader.IsNull() ) + { + MITK_ERROR << "No header reader for given vendor. "; + continue; + } + + } + else + { + continue; + } + + + bool canread = false; + // iterate over the threeD+t block + + int numberOfTimesteps = block_0.GetIntProperty("timesteps", 1); + int framesPerTimestep = block_0.GetImageFrameList().size() / numberOfTimesteps; + + for( int idx = 0; idx < numberOfTimesteps; idx++ ) + { + int access_idx = idx * framesPerTimestep; + DICOMImageFrameInfo::Pointer frame = this->GetOutput( outputidx ).GetImageFrameList().at( access_idx ); + canread = headerReader->ReadDiffusionHeader( frame->Filename ); + } + + if( canread ) + { + // collect the information + mitk::DiffusionHeaderDICOMFileReader::DICOMHeaderListType retrievedHeader = headerReader->GetHeaderInformation(); + + m_IsMosaicData.push_back(isMosaic); + m_OutputHeaderContainer.push_back( retrievedHeader ); + m_OutputReaderContainer.push_back( headerReader ); + } + } + + this->SetNumberOfOutputs( this->m_OutputHeaderContainer.size() ); + + for( unsigned int outputidx = 0; outputidx < this->GetNumberOfOutputs(); outputidx++ ) + { + // TODO : Analyze outputs + header information, i.e. for the loading confidence + MITK_INFO("diffusion.dicomreader") << "---- DICOM Analysis Report ---- :: Output " << outputidx+1 << " of " << this->GetNumberOfOutputs(); + + try{ + PerformHeaderAnalysis( this->m_OutputHeaderContainer.at( outputidx) ); + } + catch( const std::exception& se) + { + MITK_ERROR << "STD Exception " << se.what(); + } + + MITK_INFO("diffusion.dicomreader") << "==========================================="; + + } + + + +} + + +bool mitk::DiffusionDICOMFileReader +::CanHandleFile(const std::string &filename) +{ + //FIXME : + return true; +} + diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReader.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReader.h new file mode 100644 index 0000000000..c8861009e0 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReader.h @@ -0,0 +1,67 @@ +/*=================================================================== + +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 MITKDIFFUSIONDICOMFILEREADER_H +#define MITKDIFFUSIONDICOMFILEREADER_H + +#include "MitkDiffusionCoreExports.h" + +#include "mitkDICOMITKSeriesGDCMReader.h" +#include "mitkDiffusionHeaderDICOMFileReader.h" +#include "mitkThreeDnTDICOMSeriesReader.h" + +namespace mitk +{ + +class MitkDiffusionCore_EXPORT DiffusionDICOMFileReader + : public ThreeDnTDICOMSeriesReader +{ +public: + + mitkClassMacro( DiffusionDICOMFileReader, DICOMITKSeriesGDCMReader ) + mitkCloneMacro( DiffusionDICOMFileReader ) + + itkNewMacro( DiffusionDICOMFileReader ) + + virtual void AnalyzeInputFiles(); + + virtual bool LoadImages(); + + virtual bool CanHandleFile(const std::string &filename); + + void SetResolveMosaic( bool flag ) + { + m_ResolveMosaic = flag; + } + +protected: + DiffusionDICOMFileReader(); + virtual ~DiffusionDICOMFileReader(); + + bool LoadSingleOutputImage( DiffusionHeaderDICOMFileReader::DICOMHeaderListType, DICOMImageBlockDescriptor&, bool); + + //mitk::DiffusionHeaderDICOMFileReader::DICOMHeaderListType m_RetrievedHeader; + + std::vector< mitk::DiffusionHeaderDICOMFileReader::DICOMHeaderListType > m_OutputHeaderContainer; + std::vector< mitk::DiffusionHeaderDICOMFileReader::Pointer> m_OutputReaderContainer; + std::vector< bool > m_IsMosaicData; + + bool m_ResolveMosaic; +}; + +} + +#endif // MITKDIFFUSIONDICOMFILEREADER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReaderHelper.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReaderHelper.h new file mode 100644 index 0000000000..bcaaa97456 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionDICOMFileReaderHelper.h @@ -0,0 +1,283 @@ +#ifndef MITKDIFFUSIONDICOMFILEREADERHELPER_H +#define MITKDIFFUSIONDICOMFILEREADERHELPER_H + +#include "mitkDiffusionImage.h" + +#include "itkImageSeriesReader.h" +#include "itkVectorImage.h" + +#include "itkImageRegionIteratorWithIndex.h" + +namespace mitk +{ + +/** + * @brief The MosaicDescriptor struct is a help struct holding the necessary information for + * loading a mosaic DICOM file into an MITK file with correct geometry information + */ +struct MosaicDescriptor +{ + unsigned int nimages; + bool slicenormalup; + + itk::ImageBase<3>::SpacingType spacing; + itk::ImageBase<3>::DirectionType direction; + float origin[3]; +}; + +class DiffusionDICOMFileReaderHelper +{ +public: + typedef std::vector< std::string > StringContainer; + + typedef std::vector< StringContainer > VolumeFileNamesContainer; + + std::string GetOutputName(const VolumeFileNamesContainer& filenames) + { + typedef itk::Image< short, 3> InputImageType; + typedef itk::ImageSeriesReader< InputImageType > SeriesReaderType; + + SeriesReaderType::Pointer probe_reader = SeriesReaderType::New(); + probe_reader->SetFileNames( filenames.at(0) ); + probe_reader->GenerateOutputInformation(); + probe_reader->Update(); + + std::string seriesDescTag, seriesNumberTag, patientName; + SeriesReaderType::DictionaryArrayRawPointer inputDict = probe_reader->GetMetaDataDictionaryArray(); + + if( ! itk::ExposeMetaData< std::string > ( *(*inputDict)[0], "0008|103e", seriesDescTag ) ) + seriesDescTag = "UNSeries"; + + if( ! itk::ExposeMetaData< std::string > ( *(*inputDict)[0], "0020|0011", seriesNumberTag ) ) + seriesNumberTag = "00000"; + + if( ! itk::ExposeMetaData< std::string > ( *(*inputDict)[0], "0010|0010", patientName ) ) + patientName = "UnknownName"; + + std::stringstream ss; + ss << seriesDescTag << "_" << seriesNumberTag << "_" << patientName; + + return ss.str(); + + } + + template< typename PixelType, unsigned int VecImageDimension> + typename itk::VectorImage< PixelType, VecImageDimension >::Pointer LoadToVector( + const VolumeFileNamesContainer& filenames + //const itk::ImageBase<3>::RegionType requestedRegion + ) + { + typedef itk::Image< PixelType, 3> InputImageType; + typedef itk::ImageSeriesReader< InputImageType > SeriesReaderType; + + typename SeriesReaderType::Pointer probe_reader = SeriesReaderType::New(); + probe_reader->SetFileNames( filenames.at(0) ); + probe_reader->GenerateOutputInformation(); + const itk::ImageBase<3>::RegionType requestedRegion = probe_reader->GetOutput()->GetLargestPossibleRegion(); + + MITK_INFO << " --- Probe reader : \n" << + " Retrieved LPR " << requestedRegion; + + typedef itk::VectorImage< PixelType, 3 > VectorImageType; + + typename VectorImageType::Pointer output_image = VectorImageType::New(); + output_image->SetNumberOfComponentsPerPixel( filenames.size() ); + output_image->SetSpacing( probe_reader->GetOutput()->GetSpacing() ); + output_image->SetOrigin( probe_reader->GetOutput()->GetOrigin() ); + output_image->SetDirection( probe_reader->GetOutput()->GetDirection() ); + output_image->SetLargestPossibleRegion( probe_reader->GetOutput()->GetLargestPossibleRegion() ); + output_image->SetBufferedRegion( requestedRegion ); + output_image->Allocate(); + + itk::ImageRegionIterator< VectorImageType > vecIter( + output_image, requestedRegion ); + + VolumeFileNamesContainer::const_iterator volumesFileNamesIter = filenames.begin(); + + // iterate over the given volumes + unsigned int component = 0; + while( volumesFileNamesIter != filenames.end() ) + { + + MITK_INFO << " ======== Loading volume " << component+1 << " of " << filenames.size(); + + typename SeriesReaderType::Pointer volume_reader = SeriesReaderType::New(); + volume_reader->SetFileNames( *volumesFileNamesIter ); + + try + { + volume_reader->UpdateLargestPossibleRegion(); + } + catch( const itk::ExceptionObject &e) + { + mitkThrow() << " ITK Series reader failed : "<< e.what(); + } + + itk::ImageRegionConstIterator< InputImageType > iRCIter ( + volume_reader->GetOutput(), + volume_reader->GetOutput()->GetLargestPossibleRegion() ); + + // transfer to vector image + iRCIter.GoToBegin(); + vecIter.GoToBegin(); + + + + while( !iRCIter.IsAtEnd() ) + { + typename VectorImageType::PixelType vector_pixel = vecIter.Get(); + vector_pixel.SetElement( component, iRCIter.Get() ); + vecIter.Set( vector_pixel ); + + ++vecIter; + ++iRCIter; + } + + ++volumesFileNamesIter; + component++; + } + + return output_image; + + } + + + /** + * Create the vector image for the resulting diffusion image from a mosaic DICOM image set, + * The method needs to be provided with the MosaicDescriptor struct to be able to compute the + * correct index and to set the geometry information of the image itself. + */ + template< typename PixelType, unsigned int VecImageDimension> + typename itk::VectorImage< PixelType, VecImageDimension >::Pointer LoadMosaicToVector( + const VolumeFileNamesContainer& filenames, + const MosaicDescriptor& mosaicInfo ) + { + typedef itk::Image< PixelType, 3> MosaicImageType; + typedef itk::ImageFileReader< MosaicImageType > SingleImageReaderType; + + // generate output + typedef itk::VectorImage< PixelType, 3 > VectorImageType; + + VolumeFileNamesContainer::const_iterator volumesFileNamesIter = filenames.begin(); + + // probe the first file to retrieve the size of the 2d image + // we need this information to compute the index relation between mosaic and resulting 3d position + // but we need it only once + typename SingleImageReaderType::Pointer mosaic_probe = SingleImageReaderType::New(); + mosaic_probe->SetFileName( (*volumesFileNamesIter).at(0) ); + try + { + mosaic_probe->UpdateLargestPossibleRegion(); + } + catch( const itk::ExceptionObject &e) + { + mitkThrow() << " ITK Image file reader failed : "<< e.what(); + } + + typename MosaicImageType::RegionType mosaic_lpr = mosaic_probe->GetOutput()->GetLargestPossibleRegion(); + MITK_INFO << " == MOSAIC: " << mosaic_lpr; + + itk::ImageBase<3>::SizeValueType images_per_row = ceil( sqrt( (float) mosaicInfo.nimages ) ); + + itk::ImageBase<3>::RegionType requestedRegion; + requestedRegion.SetSize( 0, mosaic_lpr.GetSize()[0]/images_per_row); + requestedRegion.SetSize( 1, mosaic_lpr.GetSize()[1]/images_per_row); + requestedRegion.SetSize( 2, mosaicInfo.nimages); + + typename VectorImageType::Pointer output_image = VectorImageType::New(); + output_image->SetNumberOfComponentsPerPixel( filenames.size() ); + + typename VectorImageType::DirectionType dmatrix; + dmatrix.SetIdentity(); + + std::vector dirx = mosaic_probe->GetImageIO()->GetDirection(0); + std::vector diry = mosaic_probe->GetImageIO()->GetDirection(1); + std::vector dirz = mosaic_probe->GetImageIO()->GetDirection(2); + + dmatrix.GetVnlMatrix().set_column( 0, &dirx[0] ); + dmatrix.GetVnlMatrix().set_column( 1, &diry[0] ); + dmatrix.GetVnlMatrix().set_column( 2, &dirz[0] ); + + + /* + FIXME!!! The struct currently does not provide the geometry information + the loading works as required*/ + output_image->SetSpacing( mosaicInfo.spacing ); + output_image->SetOrigin( mosaic_probe->GetOutput()->GetOrigin() ); + output_image->SetDirection( dmatrix ); + output_image->SetLargestPossibleRegion( requestedRegion ); + output_image->SetBufferedRegion( requestedRegion ); + output_image->Allocate(); + + itk::ImageRegionIteratorWithIndex< VectorImageType > vecIter( + output_image, requestedRegion ); + + // hold the image sizes in an extra variable ( used very often ) + typename MosaicImageType::SizeValueType dx = requestedRegion.GetSize()[0]; + typename MosaicImageType::SizeValueType dy = requestedRegion.GetSize()[1]; + + // iterate over the given volumes + unsigned int component = 0; + while( volumesFileNamesIter != filenames.end() ) + { + + MITK_INFO << " ======== Loading volume " << component+1 << " of " << filenames.size(); + + typename SingleImageReaderType::Pointer mosaic_reader = SingleImageReaderType::New(); + mosaic_reader->SetFileName( (*volumesFileNamesIter).at(0) ); + + try + { + mosaic_reader->UpdateLargestPossibleRegion(); + } + catch( const itk::ExceptionObject &e) + { + mitkThrow() << " ITK Image file reader failed : "<< e.what(); + } + + typename MosaicImageType::Pointer current_mosaic = mosaic_reader->GetOutput(); + + vecIter.GoToBegin(); + while( !vecIter.IsAtEnd() ) + { + typename VectorImageType::PixelType vector_pixel = vecIter.Get(); + typename VectorImageType::IndexType threeD_index = vecIter.GetIndex(); + + typename MosaicImageType::IndexType mosaic_index; + + mosaic_index[2] = 1; + + // first find the corresponding tile in the mosaic + // this is defined by the z-position of the vector (3D) image iterator + // in x : z_index % #images_in_grid + // in y : z_index / #images_in_grid + // + // the remaining is just computing the correct position in the mosaic, done by + // --------- index of (0,0,z) ----- + --- current 2d position --- + mosaic_index[0] = (threeD_index[2] % images_per_row) * dx + threeD_index[0] + images_per_row; + mosaic_index[1] = (threeD_index[2] / images_per_row) * dy + threeD_index[1]; + + typename MosaicImageType::PixelType mosaic_pixel = current_mosaic->GetPixel( mosaic_index ); + + vector_pixel.SetElement( component, mosaic_pixel ); + + vecIter.Set( vector_pixel ); + + ++vecIter; + } + + ++volumesFileNamesIter; + component++; + + } + + return output_image; + + } + +}; + + +} + +#endif // MITKDIFFUSIONDICOMFILEREADERHELPER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderDICOMFileReader.cpp b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderDICOMFileReader.cpp new file mode 100644 index 0000000000..967816360a --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderDICOMFileReader.cpp @@ -0,0 +1,82 @@ +/*=================================================================== + +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 "mitkDiffusionHeaderDICOMFileReader.h" + +mitk::DiffusionHeaderDICOMFileReader +::DiffusionHeaderDICOMFileReader() +{ + +} + +mitk::DiffusionHeaderDICOMFileReader +::~DiffusionHeaderDICOMFileReader() +{ + +} + +mitk::DiffusionHeaderDICOMFileReader::DICOMHeaderListType +mitk::DiffusionHeaderDICOMFileReader +::GetHeaderInformation() +{ + if( m_HeaderInformationList.size() < 1 ) + { + MITK_WARN << "No information retrieved yet. Call AnalyzeInputFiles first!"; + } + + return m_HeaderInformationList; +} + +bool mitk::RevealBinaryTag(const gdcm::Tag tag, const gdcm::DataSet& dataset, std::string& target) +{ + if( dataset.FindDataElement( tag ) ) + { + MITK_DEBUG << "Found tag " << tag.PrintAsPipeSeparatedString(); + + const gdcm::DataElement& de = dataset.GetDataElement( tag ); + target = std::string( de.GetByteValue()->GetPointer(), + de.GetByteValue()->GetLength() ); + return true; + + } + else + { + MITK_DEBUG << "Could not find tag " << tag.PrintAsPipeSeparatedString(); + return false; + } +} + +bool mitk::RevealBinaryTagC(const gdcm::Tag tag, const gdcm::DataSet& dataset, char * target_array ) +{ + if( dataset.FindDataElement( tag ) ) + { + MITK_DEBUG << "Found tag " << tag.PrintAsPipeSeparatedString(); + + const gdcm::DataElement& de = dataset.GetDataElement( tag ); + + size_t bytesize = de.GetValue().GetLength(); // GetLength(); + //target_array = new char[bytesize]; + memcpy( target_array, de.GetByteValue()->GetPointer(), bytesize); + + return true; + + } + else + { + MITK_DEBUG << "Could not find tag " << tag.PrintAsPipeSeparatedString(); + return false; + } +} diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderDICOMFileReader.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderDICOMFileReader.h new file mode 100644 index 0000000000..ed62cf3f1e --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderDICOMFileReader.h @@ -0,0 +1,117 @@ +/*=================================================================== + +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 MITKDIFFUSIONHEADERFILEREADER_H +#define MITKDIFFUSIONHEADERFILEREADER_H + +#include + +#include +#include "mitkDiffusionImage.h" + +#include "gdcmScanner.h" +#include "gdcmReader.h" + +namespace mitk +{ + +/** + * @brief The DiffusionImageHeaderInformation struct + */ +struct DiffusionImageDICOMHeaderInformation +{ + DiffusionImageDICOMHeaderInformation() + : b_value(0), + baseline(false), + isotropic(false) + { + g_vector.fill(0); + } + + void Print() + { + MITK_INFO << " DiffusionImageHeaderInformation : \n" + << " : b value : " << b_value << "\n" + << " : gradient : " << g_vector << "\n" + << " : isotropic : " << isotropic << "\n --- \n"; + } + + unsigned int b_value; + vnl_vector_fixed< double, 3> g_vector; + bool baseline; + bool isotropic; +}; + +struct DiffusionImageMosaicDICOMHeaderInformation + : public DiffusionImageDICOMHeaderInformation +{ + unsigned long n_images; + bool slicenormalup; +}; + + + +/** + * @class DiffusionHeaderDICOMFileReader + * + * @brief Abstract class for all vendor specific diffusion file header reader + * + * To provide a diffusion header reader for a new vendor, reimplement the \sa ReadDiffusionHeader method. + */ +class MitkDiffusionCore_EXPORT DiffusionHeaderDICOMFileReader + : public itk::LightObject +{ +public: + + typedef std::vector< DiffusionImageDICOMHeaderInformation > DICOMHeaderListType; + + mitkClassMacro( DiffusionHeaderDICOMFileReader, itk::LightObject ) + itkSimpleNewMacro( Self ) + + /** + * @brief IsDiffusionHeader Parse the given dicom file and collect the special diffusion image information + * @return + */ + virtual bool ReadDiffusionHeader( std::string ){ return false; } + + DICOMHeaderListType GetHeaderInformation(); + +protected: + DiffusionHeaderDICOMFileReader(); + + virtual ~DiffusionHeaderDICOMFileReader(); + + DICOMHeaderListType m_HeaderInformationList; +}; + +/** + * @brief Retrieve the value of a gdcm tag to the given string + * + * @param tag the gdcm::Tag to be search for + * @param dataset a gdcm::DataSet to look into + * @param target a string to store the value of the given tag if found + * @param verbose make some output + * + * @return true if a string was found, false otherwise + */ +bool RevealBinaryTag(const gdcm::Tag tag, const gdcm::DataSet& dataset, std::string& target); + +bool RevealBinaryTagC(const gdcm::Tag tag, const gdcm::DataSet& dataset, char* target_array ); + + +} // end namespace mitk + +#endif // MITKDIFFUSIONHEADERFILEREADER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderGEDICOMFileReader.cpp b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderGEDICOMFileReader.cpp new file mode 100644 index 0000000000..04f552619e --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderGEDICOMFileReader.cpp @@ -0,0 +1,101 @@ +#include "mitkDiffusionHeaderGEDICOMFileReader.h" + +mitk::DiffusionHeaderGEDICOMFileReader +::DiffusionHeaderGEDICOMFileReader() +{ + +} + +mitk::DiffusionHeaderGEDICOMFileReader +::~DiffusionHeaderGEDICOMFileReader() +{ + +} + +bool mitk::DiffusionHeaderGEDICOMFileReader +::ReadDiffusionHeader(std::string filename) +{ + + gdcm::Reader gdcmReader; + gdcmReader.SetFileName( filename.c_str() ); + + gdcmReader.Read(); + + gdcm::Tag ge_bvalue_tag( 0x0043, 0x1039 ); + gdcm::Tag ge_gradient_x( 0x0019, 0x10bb ); + gdcm::Tag ge_gradient_y( 0x0019, 0x10bc ); + gdcm::Tag ge_gradient_z( 0x0019, 0x10bd ); + + bool success = true; + DiffusionImageDICOMHeaderInformation header_info; + + std::string ge_tagvalue_string; + char* pEnd; + + // start with b-value + success = RevealBinaryTag( ge_bvalue_tag, gdcmReader.GetFile().GetDataSet(), ge_tagvalue_string ); + // b value stored in the first bytes + // typical example: "1000\8\0\0" for bvalue=1000 + // "40\8\0\0" for bvalue=40 + // so we need to cut off the last 6 elements + const char* bval_string = ge_tagvalue_string.substr(0,ge_tagvalue_string.length()-6).c_str(); + header_info.b_value = static_cast(strtod( bval_string, &pEnd )); + + // now retrieve the gradient direction + if(success && + RevealBinaryTag( ge_gradient_x, gdcmReader.GetFile().GetDataSet(), ge_tagvalue_string ) ) + { + header_info.g_vector[0] = strtod( ge_tagvalue_string.c_str(), &pEnd ); + } + else + { + success = false; + } + + if( success && + RevealBinaryTag( ge_gradient_y, gdcmReader.GetFile().GetDataSet(), ge_tagvalue_string ) ) + { + header_info.g_vector[1] = strtod( ge_tagvalue_string.c_str(), &pEnd ); + } + else + { + success = false; + } + + if( success && + RevealBinaryTag( ge_gradient_z, gdcmReader.GetFile().GetDataSet(), ge_tagvalue_string ) ) + { + header_info.g_vector[2] = strtod( ge_tagvalue_string.c_str(), &pEnd ); + } + else + { + success = false; + } + + if( success ) + { + // Fix for (0,0,0) direction in IVIM datasets + if( header_info.b_value > 0 && + header_info.g_vector.two_norm() < vnl_math::eps ) + { + header_info.g_vector.fill(1); + header_info.g_vector.normalize(); + header_info.isotropic = true; + } + + // mark baseline + if( header_info.b_value == 0 ) + header_info.baseline = true; + + this->m_HeaderInformationList.push_back( header_info ); + + header_info.Print(); + } + + return success; + + + +} + + diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderGEDICOMFileReader.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderGEDICOMFileReader.h new file mode 100644 index 0000000000..2f809d5e53 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderGEDICOMFileReader.h @@ -0,0 +1,28 @@ +#ifndef MITKDIFFUSIONHEADERGEDICOMFILEREADER_H +#define MITKDIFFUSIONHEADERGEDICOMFILEREADER_H + +#include "MitkDiffusionCoreExports.h" +#include "mitkDiffusionHeaderDICOMFileReader.h" + +namespace mitk +{ + +class MitkDiffusionCore_EXPORT DiffusionHeaderGEDICOMFileReader + : public DiffusionHeaderDICOMFileReader +{ +public: + + mitkClassMacro( DiffusionHeaderGEDICOMFileReader, DiffusionHeaderDICOMFileReader ) + itkNewMacro( Self ) + + virtual bool ReadDiffusionHeader(std::string filename); + +protected: + DiffusionHeaderGEDICOMFileReader(); + + virtual ~DiffusionHeaderGEDICOMFileReader(); +}; + +} + +#endif // MITKDIFFUSIONHEADERGEDICOMFILEREADER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderPhilipsDICOMFileReader.cpp b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderPhilipsDICOMFileReader.cpp new file mode 100644 index 0000000000..b037bbb3da --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderPhilipsDICOMFileReader.cpp @@ -0,0 +1,95 @@ +/*=================================================================== + +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 "mitkDiffusionHeaderPhilipsDICOMFileReader.h" + +#include + +mitk::DiffusionHeaderPhilipsDICOMFileReader::DiffusionHeaderPhilipsDICOMFileReader() +{ +} + +mitk::DiffusionHeaderPhilipsDICOMFileReader::~DiffusionHeaderPhilipsDICOMFileReader() +{ + +} + +bool mitk::DiffusionHeaderPhilipsDICOMFileReader::ReadDiffusionHeader(std::string filename) +{ + gdcm::Reader gdcmReader; + gdcmReader.SetFileName( filename.c_str() ); + + gdcmReader.Read(); + + gdcm::Tag philips_bvalue_tag( 0x2001, 0x1003 ); + //gdcm::Tag philips_gradient_direction( 0x2001, 0x1004 ); + + DiffusionImageDICOMHeaderInformation header_info; + //std::string tagvalue_string; + //char* pEnd; + + // reveal b-value + float bvalue = 0; + if( RevealBinaryTagC( philips_bvalue_tag, gdcmReader.GetFile().GetDataSet(), (char*) &bvalue) ) + { + + header_info.b_value = std::ceil( bvalue ); + + if( header_info.b_value == 0) + header_info.baseline = true; + } + else + { + MITK_WARN("diffusion.dicomreader.philips") << "No b-value found. Most probably no diffusion-weighted image."; + return false; + } + + gdcm::Tag philips_new_bvalue_tag( 0x0018,0x9087 ); + double dbvalue = 0; + if( RevealBinaryTagC( philips_new_bvalue_tag, gdcmReader.GetFile().GetDataSet(), (char*) &dbvalue) ) + { + MITK_INFO("philips.dicom.diffusion.bvalue") << dbvalue; + } + + if( header_info.baseline ) + { + // no direction in unweighted images + header_info.g_vector.fill(0); + } + else + { + MITK_INFO("philips.dicom.diffusion.gradientdir") << "Parsing gradient direction."; + + gdcm::Tag philips_gradient_direction_new( 0x0018, 0x9089 ); + double gr_dir_arr[3] = {1,0,-1}; + + if( RevealBinaryTagC( philips_gradient_direction_new, gdcmReader.GetFile().GetDataSet(), (char*) &gr_dir_arr ) ) + { + MITK_INFO("philips.dicom.diffusion.gradient") << "(" << gr_dir_arr[0] <<"," << gr_dir_arr[1] <<"," <m_HeaderInformationList.push_back( header_info ); + + return true; +} diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderPhilipsDICOMFileReader.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderPhilipsDICOMFileReader.h new file mode 100644 index 0000000000..899d95a817 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderPhilipsDICOMFileReader.h @@ -0,0 +1,45 @@ +/*=================================================================== + +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 MITKDIFFUSIONHEADERPHILIPSDICOMREADER_H +#define MITKDIFFUSIONHEADERPHILIPSDICOMREADER_H + + +#include +#include "mitkDiffusionHeaderDICOMFileReader.h" + +namespace mitk +{ + +class MitkDiffusionCore_EXPORT DiffusionHeaderPhilipsDICOMFileReader + : public DiffusionHeaderDICOMFileReader +{ +public: + + mitkClassMacro( DiffusionHeaderPhilipsDICOMFileReader, DiffusionHeaderDICOMFileReader ) + itkNewMacro( Self ) + + virtual bool ReadDiffusionHeader(std::string filename); + +protected: + DiffusionHeaderPhilipsDICOMFileReader(); + + virtual ~DiffusionHeaderPhilipsDICOMFileReader(); +}; + +} + +#endif // MITKDIFFUSIONHEADERPHILIPSDICOMREADER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileHelper.cpp b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileHelper.cpp new file mode 100644 index 0000000000..043f5e94f2 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileHelper.cpp @@ -0,0 +1,42 @@ +#include "mitkDiffusionHeaderSiemensDICOMFileHelper.h" + +#include +#include + +mitk::SiemensDiffusionHeaderType mitk::GetHeaderType( std::string header ) +{ + // The CSA2 format begins with the string ‘SV10’, the CSA1 format does not. + if( header.find("SV10") != std::string::npos ) + { + return mitk::SIEMENS_CSA2; + } + else + { + return mitk::SIEMENS_CSA1; + } +} + +bool mitk::ParseInputString( std::string input, std::vector& values, Siemens_Header_Format format_specs ) +{ + + // TODO : Compute offset based on the format_specs, where does the 84 come from??? + int offset = 84; + int vm = *(input.c_str() + format_specs.NameLength ); + + for (int k = 0; k < vm; k++) + { + int itemLength = *(input.c_str() + offset + 4); + + int strideSize = static_cast (ceil(static_cast(itemLength)/4) * 4); + std::string valueString = input.substr( offset+16, itemLength ); + + double value = atof( valueString.c_str() ); + values.push_back( value ); + + offset += 16+strideSize; + } + + return true; +} + + diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileHelper.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileHelper.h new file mode 100644 index 0000000000..26abbfab51 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileHelper.h @@ -0,0 +1,45 @@ +#ifndef MITKDIFFUSIONHEADERSIEMENSDICOMFILEHELPER_H +#define MITKDIFFUSIONHEADERSIEMENSDICOMFILEHELPER_H + +#include +#include + +namespace mitk +{ + +enum SiemensDiffusionHeaderType { + SIEMENS_CSA1 = 0, + SIEMENS_CSA2 +}; + +SiemensDiffusionHeaderType GetHeaderType( std::string header ); + +struct Siemens_Header_Format +{ + Siemens_Header_Format( size_t nlen, + size_t vm, + size_t vr, + size_t syngodt, + size_t nitems ) + : NameLength( nlen ), + Delimiter( "\0" ), + VM( vm ), + VR( vr ), + Syngodt( syngodt ), + NumItems( nitems ) + { + + } + + size_t NameLength; + std::string Delimiter; + size_t VM; + size_t VR; + size_t Syngodt; + size_t NumItems; +}; + +bool ParseInputString( std::string input, std::vector& values, Siemens_Header_Format format_specs ); + +} //end namespace +#endif // MITKDIFFUSIONHEADERSIEMENSDICOMFILEHELPER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileReader.cpp b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileReader.cpp new file mode 100644 index 0000000000..62416888ca --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileReader.cpp @@ -0,0 +1,151 @@ +/*=================================================================== + +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 "mitkDiffusionHeaderSiemensDICOMFileReader.h" +#include "mitkDiffusionHeaderSiemensDICOMFileHelper.h" + +#include "gdcmScanner.h" +#include "gdcmReader.h" + + +/** + * @brief Extract b value from the siemens diffusion tag + */ +bool mitk::DiffusionHeaderSiemensDICOMFileReader +::ExtractSiemensDiffusionTagInformation( std::string tag_value, mitk::DiffusionImageDICOMHeaderInformation& values) +{ + SiemensDiffusionHeaderType hformat = mitk::GetHeaderType( tag_value ); + Siemens_Header_Format specs = this->m_SiemensFormatsCollection.at( hformat ); + + MITK_DEBUG << " Header format: " << hformat; + MITK_DEBUG << " :: Retrieving b value. "; + + std::string::size_type tag_position = + tag_value.find( "B_value", 0 ); + + if( tag_position == std::string::npos ) + { + MITK_ERROR << "No b value information found. "; + return false; + } + + std::string value_string = tag_value.substr( tag_position, tag_value.size() - tag_position + 1 ); + + std::vector value_array; + if( ParseInputString(value_string, value_array, specs) ) + { + values.b_value = value_array.at(0); + } + else + { + MITK_INFO("diffusion.dicomreader.siemens") << "No b-value tag found. "; + return false; + } + + // search for GradientDirectionInformation if the bvalue is not null + if( values.b_value > 0 ) + { + std::string::size_type tag_position = tag_value.find( "DiffusionGradientDirection", 0 ); + // Possibly it is a IVIM dataset, i.e. the gradient direction is not relevant + // and possibly either not set or set to zero + if( tag_position == std::string::npos ) + { + MITK_WARN << "No gradient direction information, but non-zero b-value. Possibly an IVIM dataset. " << "\n" + << "Setting gradient to (1,1,1)."; + + values.isotropic = true; + values.g_vector.fill(1); + return false; + } + + value_array.clear(); + std::string gradient_direction_str = tag_value.substr( tag_position, tag_value.size() - tag_position + 1 ); + + if( ParseInputString(gradient_direction_str, value_array, specs) ) + { + if( value_array.size() != 3 ) + { + MITK_ERROR << " Retrieved gradient information of length " << value_array.size(); + return false; + } + + for( unsigned int i=0; iExtractSiemensDiffusionTagInformation( siemens_diffusionheader_str, values ); + + m_HeaderInformationList.push_back( values ); + } + + return true; + + +} diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileReader.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileReader.h new file mode 100644 index 0000000000..2f9e94dde2 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensDICOMFileReader.h @@ -0,0 +1,49 @@ +/*=================================================================== + +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 MITKDIFFUSIONHEADERSIEMENSDICOMFILEREADER_H +#define MITKDIFFUSIONHEADERSIEMENSDICOMFILEREADER_H + +#include "MitkDiffusionCoreExports.h" + +#include "mitkDiffusionHeaderDICOMFileReader.h" +#include "mitkDiffusionHeaderSiemensDICOMFileHelper.h" + +namespace mitk +{ + +class MitkDiffusionCore_EXPORT DiffusionHeaderSiemensDICOMFileReader + : public DiffusionHeaderDICOMFileReader +{ +public: + + mitkClassMacro( DiffusionHeaderSiemensDICOMFileReader, DiffusionHeaderDICOMFileReader ) + itkNewMacro( Self ) + + virtual bool ReadDiffusionHeader(std::string filename); + +protected: + DiffusionHeaderSiemensDICOMFileReader(); + + ~DiffusionHeaderSiemensDICOMFileReader(); + + bool ExtractSiemensDiffusionTagInformation( std::string tag_value, mitk::DiffusionImageDICOMHeaderInformation& values ); + + std::vector< Siemens_Header_Format > m_SiemensFormatsCollection; +}; + +} +#endif // MITKDIFFUSIONHEADERSIEMENSDICOMFILEREADER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensMosaicDICOMFileReader.cpp b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensMosaicDICOMFileReader.cpp new file mode 100644 index 0000000000..cda9e35bd3 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensMosaicDICOMFileReader.cpp @@ -0,0 +1,143 @@ +#include "mitkDiffusionHeaderSiemensDICOMFileHelper.h" +#include "mitkDiffusionHeaderSiemensMosaicDICOMFileReader.h" + +#include + +mitk::DiffusionHeaderSiemensMosaicDICOMFileReader::DiffusionHeaderSiemensMosaicDICOMFileReader() + : DiffusionHeaderSiemensDICOMFileReader() +{ + +} + +mitk::DiffusionHeaderSiemensMosaicDICOMFileReader +::~DiffusionHeaderSiemensMosaicDICOMFileReader() +{ + +} + +void mitk::DiffusionHeaderSiemensMosaicDICOMFileReader +::RetrieveMosaicInformation(std::string filename) +{ + // retrieve also mosaic information + if( !this->ReadDiffusionHeader( filename ) ) + { + MITK_ERROR << "Using MOSAIC Reader for non-mosaic files "; + } + + gdcm::Reader gdcmReader; + gdcmReader.SetFileName( filename.c_str() ); + + gdcmReader.Read(); + const gdcm::DataSet& dataset = gdcmReader.GetFile().GetDataSet(); + + // (0018,0088) DS [2.5] # 4, 1 SpacingBetweenSlices + // important for mosaic data + std::string spacing_between_slices; + if( RevealBinaryTag( gdcm::Tag(0x0018, 0x0088 ), dataset, spacing_between_slices ) ) + { + std::istringstream ifs_spacing(spacing_between_slices ); + ifs_spacing >> this->m_MosaicDescriptor.spacing[2]; + } + + //(0028,0030) DS [2.5\2.5] # 8, 2 PixelSpacing + std::string inplace_spacing; + if( RevealBinaryTag( gdcm::Tag(0x0028, 0x0030 ), dataset, inplace_spacing ) ) + { + std::stringstream iplss( inplace_spacing ); + std::string buffer; + unsigned int idx=0; + while( std::getline( iplss, buffer, '\\' ) ) + { + std::istringstream substream(buffer); + substream >> this->m_MosaicDescriptor.spacing[idx++]; + } + } + + // (0020,0032) DS [-802.51815986633\-801.18644070625\-30.680992126465] # 50, 3 ImagePositionPatient + std::string position_patient; + if( RevealBinaryTag( gdcm::Tag( 0x0028, 0x0032), dataset, position_patient) ) + { + std::stringstream ppass( inplace_spacing ); + std::string buffer; + unsigned int idx=0; + while( std::getline( ppass, buffer, '\\' ) ) + { + std::istringstream substream(buffer); + substream >> this->m_MosaicDescriptor.origin[idx++]; + } + } + +} + +bool mitk::DiffusionHeaderSiemensMosaicDICOMFileReader +::ReadDiffusionHeader(std::string filename) +{ + gdcm::Reader gdcmReader; + gdcmReader.SetFileName( filename.c_str() ); + + gdcmReader.Read(); + + MITK_INFO << " -- Analyzing: " << filename; + + const gdcm::DataSet& dataset = gdcmReader.GetFile().GetDataSet(); + + const gdcm::Tag t_sie_diffusion( 0x0029,0x1010 ); + const gdcm::Tag t_sie_diffusion_alt( 0x0029,0x1110 ); + std::string siemens_diffusionheader_str; + + if( RevealBinaryTag( t_sie_diffusion, dataset, siemens_diffusionheader_str ) + || RevealBinaryTag( t_sie_diffusion_alt, dataset, siemens_diffusionheader_str) ) + { + mitk::DiffusionImageMosaicDICOMHeaderInformation header_values; + // wait for success + if( !this->ExtractSiemensDiffusionTagInformation( siemens_diffusionheader_str, header_values )) + return false; + + mitk::SiemensDiffusionHeaderType hformat = GetHeaderType( siemens_diffusionheader_str ); + Siemens_Header_Format specs = this->m_SiemensFormatsCollection.at( hformat ); + + std::string::size_type tag_position = siemens_diffusionheader_str.find( "NumberOfImagesInMosaic", 0 ); + if( tag_position != std::string::npos ) + { + std::vector value_array; + ParseInputString( siemens_diffusionheader_str.substr( tag_position, siemens_diffusionheader_str.size() - tag_position + 1 ), + value_array, + specs + ); + + header_values.n_images = value_array[0]; + this->m_MosaicDescriptor.nimages = value_array[0]; + } + + tag_position = siemens_diffusionheader_str.find("SliceNormalVector", 0); + if( tag_position != std::string::npos ) + { + std::vector value_array; + ParseInputString( siemens_diffusionheader_str.substr( tag_position, siemens_diffusionheader_str.size() - tag_position + 1 ), + value_array, + specs + ); + + MITK_DEBUG << "SliceNormal"; + for( unsigned int i=0; i 2 ) + { + header_values.slicenormalup = (value_array[2] > 0); + this->m_MosaicDescriptor.slicenormalup = ( value_array[2] > 0); + } + + } + + + + m_HeaderInformationList.push_back( header_values ); + + } + + return (m_HeaderInformationList.size() > 0); + +} diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensMosaicDICOMFileReader.h b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensMosaicDICOMFileReader.h new file mode 100644 index 0000000000..dd5eb97716 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/DicomImport/mitkDiffusionHeaderSiemensMosaicDICOMFileReader.h @@ -0,0 +1,53 @@ +/*=================================================================== + +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 MITKDIFFUSIONHEADERSIEMENSMOSAICDICOMFILEREADER_H +#define MITKDIFFUSIONHEADERSIEMENSMOSAICDICOMFILEREADER_H + +#include "mitkDiffusionHeaderSiemensDICOMFileReader.h" +#include "mitkDiffusionDICOMFileReaderHelper.h" + +namespace mitk +{ + +class MitkDiffusionCore_EXPORT DiffusionHeaderSiemensMosaicDICOMFileReader + : public DiffusionHeaderSiemensDICOMFileReader +{ +public: + mitkClassMacro( DiffusionHeaderSiemensMosaicDICOMFileReader, + DiffusionHeaderSiemensDICOMFileReader ) + itkNewMacro( Self ) + + virtual bool ReadDiffusionHeader(std::string filename); + + mitk::MosaicDescriptor GetMosaicDescriptor() + { + return m_MosaicDescriptor; + } + + void RetrieveMosaicInformation(std::string filename); + +protected: + DiffusionHeaderSiemensMosaicDICOMFileReader(); + + virtual ~DiffusionHeaderSiemensMosaicDICOMFileReader(); + + mitk::MosaicDescriptor m_MosaicDescriptor; +}; + +} + +#endif // MITKDIFFUSIONHEADERSIEMENSMOSAICDICOMFILEREADER_H diff --git a/Modules/DiffusionImaging/DiffusionCore/DicomImport/test.txt b/Modules/DiffusionImaging/DiffusionCore/DicomImport/test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Modules/DiffusionImaging/DiffusionCore/Testing/files.cmake b/Modules/DiffusionImaging/DiffusionCore/Testing/files.cmake index 43e92e635a..648222eafb 100644 --- a/Modules/DiffusionImaging/DiffusionCore/Testing/files.cmake +++ b/Modules/DiffusionImaging/DiffusionCore/Testing/files.cmake @@ -1,15 +1,17 @@ set(MODULE_TESTS mitkFactoryRegistrationTest.cpp mitkDiffusionImageEqualTest.cpp mitkNonLocalMeansDenoisingTest.cpp mitkDiffusionImageEqualTest.cpp ) set(MODULE_CUSTOM_TESTS mitkPyramidImageRegistrationMethodTest.cpp mitkDWHeadMotionCorrectionTest.cpp mitkImageReconstructionTest.cpp mitkConvertDWITypeTest.cpp mitkExtractSingleShellTest.cpp + mitkNonLocalMeansDenoisingTest.cpp + mitkDiffusionDICOMFileReaderTest.cpp ) diff --git a/Modules/DiffusionImaging/DiffusionCore/Testing/mitkDiffusionDICOMFileReaderTest.cpp b/Modules/DiffusionImaging/DiffusionCore/Testing/mitkDiffusionDICOMFileReaderTest.cpp new file mode 100644 index 0000000000..f9512c8183 --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/Testing/mitkDiffusionDICOMFileReaderTest.cpp @@ -0,0 +1,120 @@ +/*=================================================================== + +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 "mitkDiffusionDICOMFileReader.h" +#include "mitkDiffusionDICOMFileReaderTestHelper.h" +#include "mitkDICOMTagBasedSorter.h" +#include "mitkDICOMSortByTag.h" + +#include "mitkNrrdDiffusionImageWriter.h" + +#include "mitkTestingMacros.h" + +using mitk::DICOMTag; + +int mitkDiffusionDICOMFileReaderTest(int argc, char* argv[]) +{ + MITK_TEST_BEGIN("mitkDiffusionDICOMFileReaderTest"); + + mitk::DiffusionDICOMFileReader::Pointer gdcmReader = mitk::DiffusionDICOMFileReader::New(); + MITK_TEST_CONDITION_REQUIRED(gdcmReader.IsNotNull(), "DICOMITKSeriesGDCMReader can be instantiated."); + + std::string output_filename = "/tmp/dicom_out.dwi"; + if( argc > 3) + { + mitk::DICOMFileReaderTestHelper::SetTestInputFilenames( argc-1,argv ); + output_filename = std::string( argv[argc-1] ); + } + else + { + mitk::DICOMFileReaderTestHelper::SetTestInputFilenames( argc,argv ); + } + + // check the Set/GetInput function + mitk::DICOMFileReaderTestHelper::TestInputFilenames( gdcmReader ); + MITK_INFO << "Test input filenanems"; + + // check that output is a good reproduction of input (no duplicates, no new elements) + mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); + MITK_INFO << "Test output"; + + // repeat test with some more realistic sorting + gdcmReader = mitk::DiffusionDICOMFileReader::New(); // this also tests destruction + mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); + + // Use tags as in Qmitk + // all the things that split by tag in DicomSeriesReader + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037) ); // Image Orientation (Patient) // TODO add tolerance parameter (l. 1572 of original code) + // TODO handle as real vectors! cluster with configurable errors! + //tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID + //tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0010) ); + tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness + tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames + tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID + + // gdcmReader->AddSortingElement( tagSorter ); + //mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); + + mitk::DICOMSortCriterion::ConstPointer sorting = + mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0013), // instance number + mitk::DICOMSortByTag::New( DICOMTag(0x0020, 0x0012), // aqcuisition number + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0032), // aqcuisition time + mitk::DICOMSortByTag::New( DICOMTag(0x0018, 0x1060), // trigger time + mitk::DICOMSortByTag::New( DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer(); + tagSorter->SetSortCriterion( sorting ); + + MITK_INFO << "Created sort"; + + gdcmReader->AddSortingElement( tagSorter ); + mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); + + MITK_INFO << "Created sort"; + + //gdcmReader->PrintOutputs(std::cout, true); + + // really load images + //mitk::DICOMFileReaderTestHelper::TestMitkImagesAreLoaded( gdcmReader ); + gdcmReader->LoadImages(); + + mitk::Image::Pointer loaded_image = gdcmReader->GetOutput(0).GetMitkImage(); + + mitk::DiffusionImage::Pointer d_img = static_cast*>( loaded_image.GetPointer() ); + + mitk::NrrdDiffusionImageWriter::Pointer writer = + mitk::NrrdDiffusionImageWriter::New(); + writer->SetFileName( output_filename.c_str() ); + writer->SetInput(d_img ); + + try + { + writer->Update(); + } + catch( const itk::ExceptionObject& e) + { + MITK_TEST_FAILED_MSG( << "Writer failed : " << e.what() ); + } + + MITK_TEST_END(); +} diff --git a/Modules/DiffusionImaging/DiffusionCore/Testing/mitkDiffusionDICOMFileReaderTestHelper.h b/Modules/DiffusionImaging/DiffusionCore/Testing/mitkDiffusionDICOMFileReaderTestHelper.h new file mode 100644 index 0000000000..9453ba312d --- /dev/null +++ b/Modules/DiffusionImaging/DiffusionCore/Testing/mitkDiffusionDICOMFileReaderTestHelper.h @@ -0,0 +1,153 @@ +/*=================================================================== + +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 mitkDiffusionDICOMFileReaderTestHelper_h +#define mitkDiffusionDICOMFileReaderTestHelper_h + +#include "mitkDICOMFileReader.h" +#include "mitkDICOMEnums.h" + +#include "mitkTestingMacros.h" + +namespace mitk +{ + +class DICOMFileReaderTestHelper +{ + public: + +static StringList& GetInputFilenames() +{ + static StringList inputs; + return inputs; +} + +static void SetTestInputFilenames(int argc, char* argv[]) +{ + mitk::StringList inputFiles; + + for (int a = 1; a < argc; ++a) + { + inputFiles.push_back( argv[a] ); + } + + GetInputFilenames() = inputFiles; +} + + +static void SetTestInputFilenames(const StringList& filenames) +{ + GetInputFilenames() = filenames; +} + +static void TestInputFilenames(DICOMFileReader* reader) +{ + StringList inputFiles = GetInputFilenames(); + reader->SetInputFiles( inputFiles ); + + const StringList& inputFilesReturned = reader->GetInputFiles(); + MITK_TEST_CONDITION( inputFilesReturned.size() == inputFiles.size(), "Input file list is received") + MITK_TEST_CONDITION( reader->GetNumberOfOutputs() == 0, "No outputs without analysis") + + // TODO: check that strings are actually contained +} + +static void TestOutputsContainInputs(DICOMFileReader* reader) +{ + StringList inputFiles = GetInputFilenames(); + reader->SetInputFiles( inputFiles ); + + reader->AnalyzeInputFiles(); + + StringList allSortedInputsFiles; + + unsigned int numberOfOutputs = reader->GetNumberOfOutputs(); + for (unsigned int o = 0; o < numberOfOutputs; ++o) + { + const DICOMImageBlockDescriptor block = reader->GetOutput(o); + + const DICOMImageFrameList& outputFiles = block.GetImageFrameList(); + for(DICOMImageFrameList::const_iterator iter = outputFiles.begin(); + iter != outputFiles.end(); + ++iter) + { + // check that output is part of input + StringList::iterator inputPositionOfCurrentOutput = std::find( inputFiles.begin(), inputFiles.end(), (*iter)->Filename ); + if (inputPositionOfCurrentOutput != inputFiles.end()) + { + // check that output is only part of ONE output + StringList::iterator outputPositionOfCurrentOutput = std::find( allSortedInputsFiles.begin(), allSortedInputsFiles.end(), (*iter)->Filename ); + if (outputPositionOfCurrentOutput == allSortedInputsFiles.end()) + { + // was not in list before + allSortedInputsFiles.push_back( *inputPositionOfCurrentOutput ); + } + else + { + reader->PrintOutputs(std::cout); + MITK_TEST_CONDITION_REQUIRED(false, "File '" << (*iter)->Filename << "' appears in TWO outputs. Readers are expected to use each frame only once." ) + } + } + else + { + reader->PrintOutputs(std::cout); + MITK_TEST_CONDITION_REQUIRED(false, "File '" << (*iter)->Filename << "' appears in output, but it was never part of the input list." ) + } + } + } + + MITK_TEST_CONDITION( allSortedInputsFiles.size() == inputFiles.size(), "Output list size (" << allSortedInputsFiles.size() << ") equals input list size (" << inputFiles.size() << ")" ) + + try + { + const DICOMImageBlockDescriptor block = reader->GetOutput( inputFiles.size() ); + MITK_TEST_CONDITION(false, "Invalid indices for GetOutput() should throw exception") + } + catch( std::invalid_argument& ) + { + MITK_TEST_CONDITION(true, "Invalid indices for GetOutput() should throw exception") + } +} + +static void TestMitkImagesAreLoaded(DICOMFileReader* reader) +{ + StringList inputFiles = GetInputFilenames(); + reader->SetInputFiles( inputFiles ); + + reader->AnalyzeInputFiles(); + reader->LoadImages(); + + unsigned int numberOfOutputs = reader->GetNumberOfOutputs(); + for (unsigned int o = 0; o < numberOfOutputs; ++o) + { + const DICOMImageBlockDescriptor block = reader->GetOutput(o); + + const DICOMImageFrameList& outputFiles = block.GetImageFrameList(); + mitk::Image::Pointer mitkImage = block.GetMitkImage(); + + MITK_INFO << "-------------------------------------------"; + MITK_INFO << "Output " << o << " at " << (void*) mitkImage.GetPointer(); + MITK_INFO << " Number of files: " << outputFiles.size(); + MITK_INFO << " Dimensions: " << mitkImage->GetDimension(0) << " " << mitkImage->GetDimension(1) << " " << mitkImage->GetDimension(2); + } +} + + +}; // end test class + +} // namespace + +#endif diff --git a/Modules/DiffusionImaging/DiffusionCore/files.cmake b/Modules/DiffusionImaging/DiffusionCore/files.cmake index 720e80088c..632bc04330 100644 --- a/Modules/DiffusionImaging/DiffusionCore/files.cmake +++ b/Modules/DiffusionImaging/DiffusionCore/files.cmake @@ -1,133 +1,139 @@ set(CPP_FILES # DicomImport DicomImport/mitkDicomDiffusionImageReader.cpp # DicomImport/mitkGroupDiffusionHeadersFilter.cpp DicomImport/mitkDicomDiffusionImageHeaderReader.cpp DicomImport/mitkGEDicomDiffusionImageHeaderReader.cpp DicomImport/mitkPhilipsDicomDiffusionImageHeaderReader.cpp DicomImport/mitkSiemensDicomDiffusionImageHeaderReader.cpp DicomImport/mitkSiemensMosaicDicomDiffusionImageHeaderReader.cpp + DicomImport/mitkDiffusionDICOMFileReader.cpp + DicomImport/mitkDiffusionHeaderDICOMFileReader.cpp + DicomImport/mitkDiffusionHeaderSiemensDICOMFileReader.cpp + DicomImport/mitkDiffusionHeaderSiemensDICOMFileHelper.cpp + DicomImport/mitkDiffusionHeaderSiemensMosaicDICOMFileReader.cpp + DicomImport/mitkDiffusionHeaderGEDICOMFileReader.cpp + DicomImport/mitkDiffusionHeaderPhilipsDICOMFileReader.cpp + + # DataStructures -> DWI IODataStructures/DiffusionWeightedImages/mitkDiffusionImageHeaderInformation.cpp IODataStructures/DiffusionWeightedImages/mitkDiffusionImageSource.cpp IODataStructures/DiffusionWeightedImages/mitkNrrdDiffusionImageWriter.cpp IODataStructures/DiffusionWeightedImages/mitkImageToDiffusionImageSource.cpp IODataStructures/DiffusionWeightedImages/mitkDiffusionImageCorrectionFilter.cpp # DataStructures -> QBall IODataStructures/QBallImages/mitkQBallImageSource.cpp IODataStructures/QBallImages/mitkQBallImage.cpp # DataStructures -> Tensor IODataStructures/TensorImages/mitkTensorImage.cpp - #IODataStructures/mitkRegistrationObject.cpp - - # Rendering Rendering/vtkMaskedProgrammableGlyphFilter.cpp Rendering/mitkVectorImageVtkGlyphMapper3D.cpp Rendering/vtkOdfSource.cxx Rendering/vtkThickPlane.cxx Rendering/mitkOdfNormalizationMethodProperty.cpp Rendering/mitkOdfScaleByProperty.cpp # Algorithms Algorithms/mitkPartialVolumeAnalysisHistogramCalculator.cpp Algorithms/mitkPartialVolumeAnalysisClusteringCalculator.cpp Algorithms/itkDwiGradientLengthCorrectionFilter.cpp Algorithms/itkElectrostaticRepulsionDiffusionGradientReductionFilter.h # Registration Algorithms & Co. Algorithms/Registration/mitkRegistrationWrapper.cpp Algorithms/Registration/mitkPyramidImageRegistrationMethod.cpp # Algorithms/Registration/mitkRegistrationMethodITK4.cpp # MultishellProcessing Algorithms/Reconstruction/MultishellProcessing/itkADCAverageFunctor.cpp Algorithms/Reconstruction/MultishellProcessing/itkADCFitFunctor.cpp Algorithms/Reconstruction/MultishellProcessing/itkKurtosisFitFunctor.cpp Algorithms/Reconstruction/MultishellProcessing/itkBiExpFitFunctor.cpp # Function Collection mitkDiffusionFunctionCollection.cpp ) set(H_FILES # function Collection mitkDiffusionFunctionCollection.h # Rendering Rendering/mitkDiffusionImageMapper.h Rendering/mitkOdfVtkMapper2D.h # Reconstruction Algorithms/Reconstruction/itkDiffusionQballReconstructionImageFilter.h Algorithms/Reconstruction/mitkTeemDiffusionTensor3DReconstructionImageFilter.h Algorithms/Reconstruction/itkAnalyticalDiffusionQballReconstructionImageFilter.h Algorithms/Reconstruction/itkDiffusionMultiShellQballReconstructionImageFilter.h Algorithms/Reconstruction/itkPointShell.h Algorithms/Reconstruction/itkOrientationDistributionFunction.h Algorithms/Reconstruction/itkDiffusionIntravoxelIncoherentMotionReconstructionImageFilter.h # MultishellProcessing Algorithms/Reconstruction/MultishellProcessing/itkRadialMultishellToSingleshellImageFilter.h Algorithms/Reconstruction/MultishellProcessing/itkDWIVoxelFunctor.h Algorithms/Reconstruction/MultishellProcessing/itkADCAverageFunctor.h Algorithms/Reconstruction/MultishellProcessing/itkKurtosisFitFunctor.h Algorithms/Reconstruction/MultishellProcessing/itkBiExpFitFunctor.h Algorithms/Reconstruction/MultishellProcessing/itkADCFitFunctor.h # IO Datastructures IODataStructures/DiffusionWeightedImages/mitkDiffusionImage.h # Algorithms Algorithms/itkDiffusionQballGeneralizedFaImageFilter.h Algorithms/itkDiffusionQballPrepareVisualizationImageFilter.h Algorithms/itkTensorDerivedMeasurementsFilter.h Algorithms/itkBrainMaskExtractionImageFilter.h Algorithms/itkB0ImageExtractionImageFilter.h Algorithms/itkB0ImageExtractionToSeparateImageFilter.h Algorithms/itkTensorImageToDiffusionImageFilter.h Algorithms/itkTensorToL2NormImageFilter.h Algorithms/itkGaussianInterpolateImageFunction.h Algorithms/mitkPartialVolumeAnalysisHistogramCalculator.h Algorithms/mitkPartialVolumeAnalysisClusteringCalculator.h Algorithms/itkDiffusionTensorPrincipalDirectionImageFilter.h Algorithms/itkCartesianToPolarVectorImageFilter.h Algorithms/itkPolarToCartesianVectorImageFilter.h Algorithms/itkDistanceMapFilter.h Algorithms/itkProjectionFilter.h Algorithms/itkResidualImageFilter.h Algorithms/itkExtractChannelFromRgbaImageFilter.h Algorithms/itkTensorReconstructionWithEigenvalueCorrectionFilter.h Algorithms/itkMergeDiffusionImagesFilter.h Algorithms/itkDwiPhantomGenerationFilter.h Algorithms/itkFiniteDiffOdfMaximaExtractionFilter.h Algorithms/itkMrtrixPeakImageConverter.h Algorithms/itkFslPeakImageConverter.h Algorithms/itkShCoefficientImageImporter.h Algorithms/itkShCoefficientImageExporter.h Algorithms/itkOdfMaximaExtractionFilter.h Algorithms/itkResampleDwiImageFilter.h Algorithms/itkDwiGradientLengthCorrectionFilter.h Algorithms/itkAdcImageFilter.h Algorithms/itkDwiNormilzationFilter.h Algorithms/itkSplitDWImageFilter.h Algorithms/itkRemoveDwiChannelFilter.h Algorithms/itkExtractDwiChannelFilter.h Algorithms/Registration/mitkDWIHeadMotionCorrectionFilter.h Algorithms/mitkDiffusionImageToDiffusionImageFilter.h Algorithms/itkNonLocalMeansDenoisingFilter.h Algorithms/itkVectorImageToImageFilter.h ) set( TOOL_FILES ) diff --git a/Modules/DiffusionImaging/MiniApps/DICOMLoader.cpp b/Modules/DiffusionImaging/MiniApps/DICOMLoader.cpp new file mode 100644 index 0000000000..ed13bd3053 --- /dev/null +++ b/Modules/DiffusionImaging/MiniApps/DICOMLoader.cpp @@ -0,0 +1,285 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "MiniAppManager.h" +#include "mitkBaseDataIOFactory.h" +#include "mitkDiffusionImage.h" +#include "mitkBaseData.h" + +#include +#include +#include "ctkCommandLineParser.h" +#include +#include + +#include "mitkNrrdDiffusionImageWriter.h" +#include "mitkDiffusionDICOMFileReader.h" +#include "mitkDICOMTagBasedSorter.h" +#include "mitkDICOMSortByTag.h" + +#include "itkMergeDiffusionImagesFilter.h" + +static mitk::StringList& GetInputFilenames() +{ + static mitk::StringList inputs; + return inputs; +} + +void SetInputFileNames( std::string input_directory ) +{ + // I. Get all files in directory + itksys::Directory input; + input.Load( input_directory.c_str() ); + + // II. Push back files + mitk::StringList inputlist;//, mergedlist; + for( unsigned long idx=0; idx::Pointer ReadInDICOMFiles( mitk::StringList& input_files, std::string output_file ) +{ + // repeat test with some more realistic sorting + mitk::DiffusionDICOMFileReader::Pointer gdcmReader = mitk::DiffusionDICOMFileReader::New(); // this also tests destruction + mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); + + // Use tags as in Qmitk + // all the things that split by tag in DicomSeriesReader + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0010) ); // Number of Rows + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0011) ); // Number of Columns + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0030) ); // Pixel Spacing + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0020, 0x0037) ); // Image Orientation (Patient) // TODO add tolerance parameter (l. 1572 of original code) + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0018, 0x0050) ); // Slice Thickness + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0008) ); // Number of Frames + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID + + mitk::DICOMSortCriterion::ConstPointer sorting = + mitk::DICOMSortByTag::New( mitk::DICOMTag(0x0020, 0x0013), // instance number + mitk::DICOMSortByTag::New( mitk::DICOMTag(0x0020, 0x0012) //acquisition number + ).GetPointer() + ).GetPointer(); + tagSorter->SetSortCriterion( sorting ); + + MITK_INFO("dicom.loader.read.init") << "[]" ; + MITK_INFO("dicom.loader.read.inputs") << " " << input_files.size(); + + gdcmReader->SetInputFiles( input_files ); + gdcmReader->AddSortingElement( tagSorter ); + gdcmReader->AnalyzeInputFiles(); + gdcmReader->LoadImages(); + + mitk::Image::Pointer loaded_image = gdcmReader->GetOutput(0).GetMitkImage(); + + mitk::DiffusionImage::Pointer d_img = static_cast*>( loaded_image.GetPointer() ); + + return d_img; +} + +typedef short DiffusionPixelType; +typedef itk::VectorImage DwiImageType; +typedef DwiImageType::PixelType DwiPixelType; +typedef DwiImageType::RegionType DwiRegionType; +typedef std::vector< DwiImageType::Pointer > DwiImageContainerType; + +typedef mitk::DiffusionImage DiffusionImageType; +typedef DiffusionImageType::GradientDirectionContainerType GradientContainerType; +typedef std::vector< GradientContainerType::Pointer > GradientListContainerType; + +void SearchForInputInSubdirs( std::string root_directory, std::string subdir_prefix , std::vector& output_container) +{ + // I. Get all dirs in directory + itksys::Directory rootdir; + rootdir.Load( root_directory.c_str() ); + + MITK_INFO("dicom.loader.setinputdirs.start") << "Prefix = " << subdir_prefix; + + for( unsigned int idx=0; idx parsedArgs = parser.parseArguments(argc, argv); + if (parsedArgs.size()==0) + { + MITK_ERROR << "No input arguments were specified. Please provide all non-optional arguments. "; + return 0; + } + + std::string inputDirectory = us::any_cast( parsedArgs["inputdir"] ); + MITK_INFO << "Loading data from directory: " << inputDirectory; + + // retrieve the prefix flag (if set) + bool search_for_subdirs = false; + std::string subdir_prefix; + if( parsedArgs.count("dwprefix")) + { + MITK_INFO << "Prefix specified, will search for subdirs in the input directory!"; + subdir_prefix = us::any_cast( parsedArgs["dwprefix"] ); + search_for_subdirs = true; + } + + // retrieve the output + std::string outputFile = us::any_cast< std::string >( parsedArgs["output"] ); + + // if the executable is called with a single directory, just parse the given folder for files and read them into a diffusion image + if( !search_for_subdirs ) + { + SetInputFileNames( inputDirectory ); + + MITK_INFO << "Got " << GetInputFilenames().size() << " input files."; + mitk::DiffusionImage::Pointer d_img = ReadInDICOMFiles( GetInputFilenames(), outputFile ); + + mitk::NrrdDiffusionImageWriter::Pointer writer = + mitk::NrrdDiffusionImageWriter::New(); + writer->SetFileName( outputFile.c_str() ); + writer->SetInput(d_img ); + + try + { + writer->Update(); + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed to write out the output file. \n\t Reason : ITK Exception " << e.what(); + } + + } + // if the --dwprefix flag is set, then we have to look for the directories, load each of them separately and afterwards merge the images + else + { + std::vector::Pointer> output_container; + + SearchForInputInSubdirs( inputDirectory, subdir_prefix, output_container ); + + // final output image + mitk::DiffusionImage::Pointer image = mitk::DiffusionImage::New(); + if( output_container.size() > 1 ) + { + DwiImageContainerType imageContainer; + GradientListContainerType gradientListContainer; + std::vector< double > bValueContainer; + + for ( std::vector< mitk::DiffusionImage::Pointer >::iterator dwi = output_container.begin(); + dwi != output_container.end(); ++dwi ) + { + imageContainer.push_back((*dwi)->GetVectorImage()); + gradientListContainer.push_back((*dwi)->GetDirections()); + bValueContainer.push_back((*dwi)->GetReferenceBValue()); + } + + typedef itk::MergeDiffusionImagesFilter FilterType; + FilterType::Pointer filter = FilterType::New(); + filter->SetImageVolumes(imageContainer); + filter->SetGradientLists(gradientListContainer); + filter->SetBValues(bValueContainer); + filter->Update(); + + vnl_matrix_fixed< double, 3, 3 > mf; mf.set_identity(); + + image->SetVectorImage( filter->GetOutput() ); + image->SetReferenceBValue(filter->GetB_Value()); + image->SetDirections(filter->GetOutputGradients()); + image->SetMeasurementFrame(mf); + image->InitializeFromVectorImage(); + } + // just output the image if there was only one folder found + else + { + image = output_container.at(0); + } + + mitk::NrrdDiffusionImageWriter::Pointer writer = + mitk::NrrdDiffusionImageWriter::New(); + writer->SetFileName( outputFile.c_str() ); + writer->SetInput( image ); + + MITK_INFO("dicom.import.writeout") << " [OutputFile] " << outputFile.c_str(); + + try + { + writer->Update(); + } + catch( const itk::ExceptionObject& e) + { + MITK_ERROR << "Failed to write out the output file. \n\t Reason : ITK Exception " << e.what(); + } + + } + + return 1; +} +RegisterDiffusionMiniApp(DICOMLoader); diff --git a/Modules/DiffusionImaging/MiniApps/files.cmake b/Modules/DiffusionImaging/MiniApps/files.cmake index 98428da34c..d3eeeb21b1 100644 --- a/Modules/DiffusionImaging/MiniApps/files.cmake +++ b/Modules/DiffusionImaging/MiniApps/files.cmake @@ -1,31 +1,32 @@ set(CPP_FILES mitkDiffusionMiniApps.cpp MiniAppManager.cpp BatchedFolderRegistration.cpp DicomFolderDump.cpp FileFormatConverter.cpp TensorReconstruction.cpp TensorDerivedMapsExtraction.cpp QballReconstruction.cpp DiffusionIndices.cpp ExtractImageStatistics.cpp CopyGeometry.cpp GibbsTracking.cpp StreamlineTracking.cpp FiberProcessing.cpp LocalDirectionalFiberPlausibility.cpp #TractogramAngularError.cpp FiberDirectionExtraction.cpp ImageResampler.cpp PeakExtraction.cpp PeaksAngularError.cpp MultishellMethods.cpp Fiberfox.cpp ExportShImage.cpp NetworkCreation.cpp NetworkStatistics.cpp DwiDenoising.cpp FiberExtraction.cpp FiberJoin.cpp + DICOMLoader.cpp ) diff --git a/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.cpp b/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.cpp index 2d6030aa1f..56386594c1 100644 --- a/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.cpp +++ b/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.cpp @@ -1,817 +1,558 @@ /*=================================================================== 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 "QmitkDiffusionDicomImportView.h" // qt includes #include // itk includes #include "itkTimeProbesCollectorBase.h" #include "itkGDCMSeriesFileNames.h" #include "itksys/SystemTools.hxx" // mitk includes #include "mitkProgressBar.h" #include "mitkStatusBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkMemoryUtilities.h" #include "mitkIOUtil.h" // diffusion module includes #include "mitkDicomDiffusionImageHeaderReader.h" #include "mitkDicomDiffusionImageReader.h" #include "mitkDiffusionImage.h" +#include "mitkDiffusionDICOMFileReader.h" +#include "mitkDICOMTagBasedSorter.h" +#include "mitkDICOMSortByTag.h" +#include "mitkSortByImagePositionPatient.h" + #include "gdcmDirectory.h" #include "gdcmScanner.h" #include "gdcmSorter.h" #include "gdcmIPPSorter.h" #include "gdcmAttribute.h" #include "gdcmVersion.h" +#include +#include + #include const std::string QmitkDiffusionDicomImport::VIEW_ID = "org.mitk.views.diffusiondicomimport"; QmitkDiffusionDicomImport::QmitkDiffusionDicomImport(QObject* /*parent*/, const char* /*name*/) : QmitkFunctionality(), m_Controls(NULL), m_MultiWidget(NULL), - m_OutputFolderName(""), m_OutputFolderNameSet(false) + m_OutputFolderName(""), m_OutputFolderNameSet(false) { } QmitkDiffusionDicomImport::QmitkDiffusionDicomImport(const QmitkDiffusionDicomImport& other) { Q_UNUSED(other) throw std::runtime_error("Copy constructor not implemented"); } QmitkDiffusionDicomImport::~QmitkDiffusionDicomImport() {} void QmitkDiffusionDicomImport::CreateQtPartControl(QWidget *parent) { m_Parent = parent; if (m_Controls == NULL) { m_Controls = new Ui::QmitkDiffusionDicomImportControls; m_Controls->setupUi(parent); this->CreateConnections(); - m_Controls->m_DicomLoadRecursiveCheckbox->setChecked(true); + m_Controls->m_DicomLoadRecursiveCheckbox->setChecked(false); m_Controls->m_DicomLoadAverageDuplicatesCheckbox->setChecked(false); - m_Controls->m_DicomLoadRecursiveCheckbox->setVisible(false); + m_Controls->m_DicomLoadRecursiveCheckbox->setVisible(true); m_Controls->m_OverrideOptionCheckbox->setVisible(false); + m_Controls->m_SubdirPrefixLineEdit->setVisible(false); + m_Controls->m_SetPrefixButton->setVisible(false); + m_Controls->m_ResetPrefixButton->setVisible(false); AverageClicked(); } } void QmitkDiffusionDicomImport::CreateConnections() { if ( m_Controls ) { connect( m_Controls->m_AddFoldersButton, SIGNAL(clicked()), this, SLOT(DicomLoadAddFolderNames()) ); connect( m_Controls->m_DeleteFoldersButton, SIGNAL(clicked()), this, SLOT(DicomLoadDeleteFolderNames()) ); - connect( m_Controls->m_DicomLoadStartLoadButton, SIGNAL(clicked()), this, SLOT(DicomLoadStartLoad()) ); + //connect( m_Controls->m_DicomLoadStartLoadButton, SIGNAL(clicked()), this, SLOT(DicomLoadStartLoad()) ); + connect( m_Controls->m_DicomLoadStartLoadButton, SIGNAL(clicked()), this, SLOT(NewDicomLoadStartLoad()) ); connect( m_Controls->m_DicomLoadAverageDuplicatesCheckbox, SIGNAL(clicked()), this, SLOT(AverageClicked()) ); connect( m_Controls->m_OutputSetButton, SIGNAL(clicked()), this, SLOT(OutputSet()) ); connect( m_Controls->m_OutputClearButton, SIGNAL(clicked()), this, SLOT(OutputClear()) ); connect( m_Controls->m_Remove, SIGNAL(clicked()), this, SLOT(Remove()) ); + connect( m_Controls->m_SetPrefixButton, SIGNAL(clicked()), this, SLOT(SetPrefixButtonPushed())); + connect( m_Controls->m_ResetPrefixButton, SIGNAL(clicked()), this, SLOT(ResetPrefixButtonPushed())); + connect( m_Controls->m_DicomLoadRecursiveCheckbox, SIGNAL(clicked()), this, SLOT(RecursiveSettingsChanged()) ); } } +void QmitkDiffusionDicomImport::RecursiveSettingsChanged() +{ + m_Controls->m_SubdirPrefixLineEdit->setVisible( m_Controls->m_DicomLoadRecursiveCheckbox->isChecked() ); + m_Controls->m_SetPrefixButton->setVisible( m_Controls->m_DicomLoadRecursiveCheckbox->isChecked() ); + m_Controls->m_SubdirPrefixLineEdit->clear(); + this->m_Controls->m_SubdirPrefixLineEdit->setEnabled(true); +} + +void QmitkDiffusionDicomImport::SetPrefixButtonPushed() +{ + m_Prefix = this->m_Controls->m_SubdirPrefixLineEdit->text().toStdString(); + if( !this->m_Controls->m_ResetPrefixButton->isVisible() ) + this->m_Controls->m_ResetPrefixButton->setVisible(true); + + this->m_Controls->m_SubdirPrefixLineEdit->setEnabled(false); + this->m_Controls->m_ResetPrefixButton->setEnabled(true); + this->m_Controls->m_SetPrefixButton->setEnabled(false); +} + +void QmitkDiffusionDicomImport::ResetPrefixButtonPushed() +{ + m_Controls->m_SubdirPrefixLineEdit->clear(); + + this->m_Controls->m_SubdirPrefixLineEdit->setEnabled(true); + + this->m_Controls->m_ResetPrefixButton->setEnabled(false); + this->m_Controls->m_SetPrefixButton->setEnabled(true); +} void QmitkDiffusionDicomImport::Remove() { int i = m_Controls->listWidget->currentRow(); m_Controls->listWidget->takeItem(i); } void QmitkDiffusionDicomImport::OutputSet() { // SELECT FOLDER DIALOG QFileDialog* w = new QFileDialog( m_Parent, QString("Select folders containing DWI data") ); w->setFileMode( QFileDialog::Directory ); // RETRIEVE SELECTION if ( w->exec() != QDialog::Accepted ) return; m_OutputFolderName = w->selectedFiles()[0]; m_OutputFolderNameSet = true; m_Controls->m_OutputLabel->setText(m_OutputFolderName); // show file override option checkbox m_Controls->m_OverrideOptionCheckbox->setVisible(true); } void QmitkDiffusionDicomImport::OutputClear() { m_OutputFolderName = ""; m_OutputFolderNameSet = false; m_Controls->m_OutputLabel->setText("... optional out-folder ..."); // hide file override option checkbox - no output specified m_Controls->m_OverrideOptionCheckbox->setVisible(false); } void QmitkDiffusionDicomImport::AverageClicked() { m_Controls->m_Blur->setEnabled(m_Controls->m_DicomLoadAverageDuplicatesCheckbox->isChecked()); } void QmitkDiffusionDicomImport::Activated() { QmitkFunctionality::Activated(); } void QmitkDiffusionDicomImport::DicomLoadDeleteFolderNames() { m_Controls->listWidget->clear(); } void QmitkDiffusionDicomImport::DicomLoadAddFolderNames() { // SELECT FOLDER DIALOG QFileDialog* w = new QFileDialog( m_Parent, QString("Select folders containing DWI data") ); w->setFileMode( QFileDialog::Directory ); // RETRIEVE SELECTION if ( w->exec() != QDialog::Accepted ) return; m_Controls->listWidget->addItems(w->selectedFiles()); } bool SortBySeriesUID(gdcm::DataSet const & ds1, gdcm::DataSet const & ds2 ) { gdcm::Attribute<0x0020,0x000e> at1; at1.Set( ds1 ); gdcm::Attribute<0x0020,0x000e> at2; at2.Set( ds2 ); return at1 < at2; } bool SortByAcquisitionNumber(gdcm::DataSet const & ds1, gdcm::DataSet const & ds2 ) { gdcm::Attribute<0x0020,0x0012> at1; at1.Set( ds1 ); gdcm::Attribute<0x0020,0x0012> at2; at2.Set( ds2 ); return at1 < at2; } bool SortBySeqName(gdcm::DataSet const & ds1, gdcm::DataSet const & ds2 ) { gdcm::Attribute<0x0018, 0x0024> at1; at1.Set( ds1 ); gdcm::Attribute<0x0018, 0x0024> at2; at2.Set( ds2 ); std::string str1 = at1.GetValue().Trim(); std::string str2 = at2.GetValue().Trim(); return std::lexicographical_compare(str1.begin(), str1.end(), str2.begin(), str2.end() ); } void QmitkDiffusionDicomImport::Status(QString status) { mitk::StatusBar::GetInstance()->DisplayText(status.toAscii()); MITK_INFO << status.toStdString().c_str(); } void QmitkDiffusionDicomImport::Status(std::string status) { mitk::StatusBar::GetInstance()->DisplayText(status.c_str()); MITK_INFO << status.c_str(); } void QmitkDiffusionDicomImport::Status(const char* status) { mitk::StatusBar::GetInstance()->DisplayText(status); MITK_INFO << status; } void QmitkDiffusionDicomImport::Error(QString status) { mitk::StatusBar::GetInstance()->DisplayErrorText(status.toAscii()); MITK_ERROR << status.toStdString().c_str(); } void QmitkDiffusionDicomImport::Error(std::string status) { mitk::StatusBar::GetInstance()->DisplayErrorText(status.c_str()); MITK_ERROR << status.c_str(); } void QmitkDiffusionDicomImport::Error(const char* status) { mitk::StatusBar::GetInstance()->DisplayErrorText(status); MITK_ERROR << status; } void QmitkDiffusionDicomImport::PrintMemoryUsage() { size_t processSize = mitk::MemoryUtilities::GetProcessMemoryUsage(); size_t totalSize = mitk::MemoryUtilities::GetTotalSizeOfPhysicalRam(); float percentage = ( (float) processSize / (float) totalSize ) * 100.0; MITK_INFO << "Current memory usage: " << GetMemoryDescription( processSize, percentage ); } std::string QmitkDiffusionDicomImport::FormatMemorySize( size_t size ) { double val = size; std::string descriptor("B"); if ( val >= 1000.0 ) { val /= 1024.0; descriptor = "KB"; } if ( val >= 1000.0 ) { val /= 1024.0; descriptor = "MB"; } if ( val >= 1000.0 ) { val /= 1024.0; descriptor = "GB"; } std::ostringstream str; str << std::fixed << std::setprecision(2) << val << " " << descriptor; return str.str(); } std::string QmitkDiffusionDicomImport::FormatPercentage( double val ) { std::ostringstream str; str << std::fixed << std::setprecision(2) << val << " " << "%"; return str.str(); } std::string QmitkDiffusionDicomImport::GetMemoryDescription( size_t processSize, float percentage ) { std::ostringstream str; str << FormatMemorySize(processSize) << " (" << FormatPercentage( percentage ) <<")" ; return str.str(); } -void QmitkDiffusionDicomImport::DicomLoadStartLoad() +void QmitkDiffusionDicomImport::NewDicomLoadStartLoad() { itk::TimeProbesCollectorBase clock; bool imageSuccessfullySaved = true; + bool has_prefix = true; + try { const std::string& locale = "C"; const std::string& currLocale = setlocale( LC_ALL, NULL ); if ( locale.compare(currLocale)!=0 ) { try { MITK_INFO << " ** Changing locale from " << setlocale(LC_ALL, NULL) << " to '" << locale << "'"; setlocale(LC_ALL, locale.c_str()); } catch(...) { MITK_INFO << "Could not set locale " << locale; } } int nrFolders = m_Controls->listWidget->count(); if(!nrFolders) { Error(QString("No input folders were selected. ABORTING.")); return; } Status(QString("GDCM %1 used for DICOM parsing and sorting!").arg(gdcm::Version::GetVersion())); PrintMemoryUsage(); QString status; mitk::DataNode::Pointer node; mitk::ProgressBar::GetInstance()->AddStepsToDo(2*nrFolders); - - std::string folder = m_Controls->m_OutputLabel->text().toStdString(); - - - if(berry::Platform::IsWindows()) - { - folder.append("\\import.log"); - } - else - { - folder.append("/import.log"); - } - - ofstream logfile; - if(m_OutputFolderNameSet) logfile.open(folder.c_str()); - + gdcm::Directory::FilenamesType complete_list; while(m_Controls->listWidget->count()) { // RETREIVE FOLDERNAME QListWidgetItem * item = m_Controls->listWidget->takeItem(0); QString folderName = item->text(); - if(m_OutputFolderNameSet) logfile << "Reading " << folderName.toStdString() << '\n'; - - // PARSING DIRECTORY - PrintMemoryUsage(); - clock.Start(folderName.toAscii()); - std::vector seriesUIDs(0); - std::vector > seriesFilenames(0); - - Status("== Initial Directory Scan =="); - if(m_OutputFolderNameSet) logfile << "== Initial Directory Scan ==\n"; - - gdcm::Directory d; - d.Load( folderName.toStdString().c_str(), true ); // recursive ! - const gdcm::Directory::FilenamesType &l1 = d.GetFilenames(); - const unsigned int ntotalfiles = l1.size(); - Status(QString(" ... found %1 different files").arg(ntotalfiles)); - if(m_OutputFolderNameSet)logfile << "...found " << ntotalfiles << " different files\n"; - - Status("Scanning Headers"); - if(m_OutputFolderNameSet) logfile << "Scanning Headers\n"; - - gdcm::Scanner s; - const gdcm::Tag t1(0x0020,0x000d); // Study Instance UID - const gdcm::Tag t2(0x0020,0x000e); // Series Instance UID - const gdcm::Tag t5(0x0028, 0x0010); // number rows - const gdcm::Tag t6(0x0028, 0x0011); // number cols - s.AddTag( t1 ); - s.AddTag( t2 ); - s.AddTag( t5 ); - s.AddTag( t6 ); - - bool b = s.Scan( d.GetFilenames() ); - if( !b ) - { - Error("Scanner failed"); - if(m_OutputFolderNameSet )logfile << "ERROR: scanner failed\n"; - continue; - } - - // Only get the DICOM files: - gdcm::Directory::FilenamesType l2 = s.GetKeys(); - + if( this->m_Controls->m_DicomLoadRecursiveCheckbox->isChecked() ) + { - gdcm::Directory::FilenamesType::iterator it; + std::string subdir_prefix = ""; + if( has_prefix ) + { + subdir_prefix = this->m_Prefix; + } - for (it = l2.begin() ; it != l2.end(); ++it) { - MITK_INFO << "-------FN " << *it; - } + itksys::Directory rootdir; + rootdir.Load( folderName.toStdString().c_str() ); - const int nfiles = l2.size(); - if(nfiles < 1) - { - Error("No DICOM files found"); - if(m_OutputFolderNameSet)logfile << "ERROR: No DICOM files found\n"; - continue; - } - Status(QString(" ... successfully scanned %1 headers.").arg(nfiles)); - if(m_OutputFolderNameSet) logfile << "...succesfully scanned " << nfiles << " headers\n"; + for( unsigned int idx=0; idxm_DuplicateID->isChecked()) - { - nvalues = 1; - } - else - { - nvalues = values1.size(); - } + gdcm::Directory d; + d.Load( itksys::SystemTools::ConvertToOutputPath( directory_path.c_str() ) , false); - if(nvalues>1) - { - Error("Multiple sSeries tudies found. Please limit to 1 study per folder"); - if(m_OutputFolderNameSet) logfile << "Multiple series found. Limit to one. If you are convinced this is an error use the merge duplicate study IDs option \n"; - continue; - } + MITK_INFO("dicom.load.subdir.attempt") << "In directory " << itksys::SystemTools::ConvertToOutputPath( directory_path.c_str() ); - const gdcm::Scanner::ValuesType &values5 = s.GetValues(t5); - const gdcm::Scanner::ValuesType &values6 = s.GetValues(t6); - if(values5.size()>1 || values6.size()>1) - { - Error("Folder contains images of unequal dimensions that cannot be combined in one 3d volume. ABORTING."); - if(m_OutputFolderNameSet) logfile << "Folder contains images of unequal dimensions that cannot be combined in one 3d volume. ABORTING\n."; - continue; - } + const gdcm::Directory::FilenamesType &l1 = d.GetFilenames(); + const unsigned int ntotalfiles = l1.size(); + Status(QString(" ... found %1 different files").arg(ntotalfiles)); - const gdcm::Scanner::ValuesType &values2 = s.GetValues(t2); + for( unsigned int i=0; i< ntotalfiles; i++) + { + complete_list.push_back( l1.at(i) ); + } - int nSeries; - if(m_Controls->m_DuplicateID->isChecked()) - { - nSeries = 1; - } - else - { - nSeries = values2.size(); - } + } - gdcm::Directory::FilenamesType files; - if(nSeries > 1) - { - gdcm::Sorter sorter; - sorter.SetSortFunction( SortBySeriesUID ); - if (sorter.StableSort( l2 )) - { - files = sorter.GetFilenames(); - } - else - { - Error("Loading of at least one DICOM file not successfull!"); - return; } } else { - files = l2; - } - - unsigned int nTotalAcquis = 0; - - if(nfiles % nSeries != 0) - { - Error("Number of files in series not equal, ABORTING"); - if(m_OutputFolderNameSet) logfile << "Number of files in series not equal, Some volumes are probably incomplete. ABORTING \n"; - continue; - } + gdcm::Directory d; + d.Load( folderName.toStdString().c_str(), this->m_Controls->m_DicomLoadRecursiveCheckbox->isChecked() ); // recursive ! + const gdcm::Directory::FilenamesType &l1 = d.GetFilenames(); + const unsigned int ntotalfiles = l1.size(); + Status(QString(" ... found %1 different files").arg(ntotalfiles)); - int filesPerSeries = nfiles / nSeries; - - gdcm::Scanner::ValuesType::const_iterator it2 = values2.begin(); - for(int i=0; i 1) // More than one element must have this tag (Not != ) - { - subsorter.SetSortFunction( SortByAcquisitionNumber ); - it = values3.begin(); - } - else if (values4.size() > 1) - { - nAcquis = values4.size(); - subsorter.SetSortFunction( SortBySeqName ); - it = values4.begin(); - } - - // Hotfix for Bug 14758, better fix by selecting always availible tags. - if( nAcquis == 0 || values4.size() == 0) + for( unsigned int i=0; i< ntotalfiles; i++) { - std::string err_msg = "Sorting tag (0x0020,0x0012) [Acquisition ID] or (0x0018,0x0024) [Sequence Name] missing, ABORTING"; - Error(err_msg); - if(m_OutputFolderNameSet) logfile << err_msg << "\n"; - continue; + complete_list.push_back( l1.at(i) ); } - nTotalAcquis += nAcquis; - subsorter.Sort( sub ); - - if(filesPerSeries % nAcquis != 0) - { - Error("Number of files per acquisition not equal, ABORTING"); - if(m_OutputFolderNameSet) logfile << "Number of files per acquisition not equal, ABORTING \n"; - continue; - } + } - int filesPerAcqu = filesPerSeries / nAcquis; - gdcm::Directory::FilenamesType subfiles = subsorter.GetFilenames(); - for ( unsigned int j = 0 ; j < nAcquis ; ++j ) - { - std::string identifier = "serie_" + *it2 + "_acquis_" + *it++; - gdcm::IPPSorter ippsorter; - gdcm::Directory::FilenamesType ipplist((j)*filesPerAcqu+subfiles.begin(),(j+1)*filesPerAcqu+subfiles.begin()); - ippsorter.SetComputeZSpacing( true ); - if( !ippsorter.Sort( ipplist ) ) - { - Error(QString("Failed to sort acquisition %1, ABORTING").arg(identifier.c_str())); - if(m_OutputFolderNameSet) logfile << "Failed to sort acquisition " << identifier.c_str() << " , Aborting\n"; - continue; - } - const std::vector & list = ippsorter.GetFilenames(); - seriesFilenames.push_back(list); - seriesUIDs.push_back(identifier.c_str()); - } - ++it2; - } + } - // Hot Fix for Bug 14758, checking if no file is acuired. - if (nTotalAcquis < 1) // Test if zero, if true than error because no file was selected + { + mitk::DiffusionDICOMFileReader::Pointer gdcmReader = mitk::DiffusionDICOMFileReader::New(); + mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); + + // Use tags as in Qmitk + // all the things that split by tag in DicomSeriesReader + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0010) ); // Number of Rows + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0011) ); // Number of Columns + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0030) ); // Pixel Spacing + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0020, 0x0037) ); // Image Orientation (Patient) // TODO add tolerance parameter (l. 1572 of original code) + // TODO handle as real vectors! cluster with configurable errors! + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0020, 0x000e) ); // Series Instance UID + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0018, 0x0050) ); // Slice Thickness + tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0028, 0x0008) ); // Number of Frames + //tagSorter->AddDistinguishingTag( mitk::DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID + + // gdcmReader->AddSortingElement( tagSorter ); + //mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); + + mitk::DICOMSortCriterion::ConstPointer sorting = + mitk::SortByImagePositionPatient::New( // Image Position (Patient) + //mitk::DICOMSortByTag::New( mitk::DICOMTag(0x0020, 0x0013), // instance number + mitk::DICOMSortByTag::New( mitk::DICOMTag(0x0020, 0x0012), // aqcuisition number + mitk::DICOMSortByTag::New( mitk::DICOMTag(0x0008, 0x0032), // aqcuisition time + mitk::DICOMSortByTag::New( mitk::DICOMTag(0x0018, 0x1060), // trigger time + mitk::DICOMSortByTag::New( mitk::DICOMTag(0x0008, 0x0018) // SOP instance UID (last resort, not really meaningful but decides clearly) + ).GetPointer() + ).GetPointer() + ).GetPointer() + ).GetPointer() + // ).GetPointer() + ).GetPointer(); + tagSorter->SetSortCriterion( sorting ); + + // mosaic + gdcmReader->SetResolveMosaic( this->m_Controls->m_SplitMosaicCheckBox->isChecked() ); + gdcmReader->AddSortingElement( tagSorter ); + gdcmReader->SetInputFiles( complete_list ); + try { - Error("Nno files in acquisitions, ABORTING"); - if(m_OutputFolderNameSet) logfile << "Nno files in acquisitions, ABORTING \n"; - continue; + gdcmReader->AnalyzeInputFiles(); } - if(nfiles % nTotalAcquis != 0) + catch( const itk::ExceptionObject &e) { - Error("Number of files per acquisition differs between series, ABORTING"); - if(m_OutputFolderNameSet) logfile << "Number of files per acquisition differs between series, ABORTING \n"; - continue; + MITK_ERROR << "Failed to analyze data. " << e.what(); } - - int slices = nfiles/nTotalAcquis; - Status(QString("Series is composed of %1 different 3D volumes with %2 slices.").arg(nTotalAcquis).arg(slices)); - if(m_OutputFolderNameSet) logfile << "Series is composed of " << nTotalAcquis << " different 3D volumes with " << slices << " slices\n"; - - // READING HEADER-INFOS - PrintMemoryUsage(); - Status(QString("Reading Headers %1").arg(folderName)); - if(m_OutputFolderNameSet) logfile << "Reading Headers "<< folderName.toStdString() << "\n"; - - mitk::DicomDiffusionImageHeaderReader::Pointer headerReader; - typedef short PixelValueType; - typedef mitk::DicomDiffusionImageReader< PixelValueType, 3 > VolumesReader; - VolumesReader::HeaderContainer inHeaders; - unsigned int size2 = seriesUIDs.size(); - for ( unsigned int i = 0 ; i < size2 ; ++i ) + catch( const std::exception &se) { - // Hot Fix for Bug 14459, catching if no valid data in datafile. - try - { - Status(QString("Reading header image #%1/%2").arg(i+1).arg(size2)); - headerReader = mitk::DicomDiffusionImageHeaderReader::New(); - headerReader->SetSeriesDicomFilenames(seriesFilenames[i]); - headerReader->Update(); - inHeaders.push_back(headerReader->GetOutput()); - } - catch (mitk::Exception e) - { - Error("Could not read file header, ABORTING"); - if(m_OutputFolderNameSet) logfile << e; - continue; - } - //Status(std::endl; + MITK_ERROR << "Std Exception " << se.what(); } - mitk::ProgressBar::GetInstance()->Progress(); - - // // GROUP HEADERS - // mitk::GroupDiffusionHeadersFilter::Pointer grouper - // = mitk::GroupDiffusionHeadersFilter::New(); - // mitk::GroupDiffusionHeadersFilter::OutputType outHeaders; - // grouper->SetInput(inHeaders); - // grouper->Update(); - // outHeaders = grouper->GetOutput(); - - // READ VOLUMES - PrintMemoryUsage(); - if(m_OutputFolderNameSet) logfile << "Loading volumes\n"; - Status(QString("Loading Volumes %1").arg(folderName)); - - VolumesReader::Pointer vReader = VolumesReader::New(); - VolumesReader::HeaderContainer hc = inHeaders; - - // hc.insert(hc.end(), outHeaders[1].begin(), outHeaders[1].end() ); - // hc.insert(hc.end(), outHeaders[2].begin(), outHeaders[2].end() ); - if(hc.size()>1) - { - vReader->SetHeaders(hc); - vReader->Update(); - VolumesReader::OutputImageType::Pointer vecImage; - vecImage = vReader->GetOutput(); - Status(QString("Volumes Loaded (%1)").arg(folderName)); - - // CONSTRUCT CONTAINER WITH DIRECTIONS - typedef vnl_vector_fixed< double, 3 > GradientDirectionType; - typedef itk::VectorContainer< unsigned int, - GradientDirectionType > GradientDirectionContainerType; - GradientDirectionContainerType::Pointer directions = - GradientDirectionContainerType::New(); - std::vector b_vals; - double maxb = 0; - for(unsigned int i=0; ibValue; - if(maxb vect = hc[i]->DiffusionVector; - if (vect.magnitude()<0.0001 && b_vals[i]>0) - { - vect.fill(0.0); - vect[0] = 1; - } - vect.normalize(); - vect *= sqrt(b_vals[i]/maxb); - directions->push_back(vect); - } + gdcmReader->LoadImages(); - // DWI TO DATATREE - PrintMemoryUsage(); - Status(QString("Initializing Diffusion Image")); - if(m_OutputFolderNameSet) logfile << "Initializing Diffusion Image\n"; - typedef mitk::DiffusionImage DiffVolumesType; - DiffVolumesType::Pointer diffImage = DiffVolumesType::New(); - diffImage->SetVectorImage(vecImage); - diffImage->SetReferenceBValue(maxb); - diffImage->SetDirections(directions); - diffImage->InitializeFromVectorImage(); - Status(QString("Diffusion Image initialized")); - if(m_OutputFolderNameSet) logfile << "Diffusion Image initialized\n"; - - if(m_Controls->m_DicomLoadAverageDuplicatesCheckbox->isChecked()) - { - PrintMemoryUsage(); - Status(QString("Averaging gradient directions")); - logfile << "Averaging gradient directions\n"; - diffImage->AverageRedundantGradients(m_Controls->m_Blur->value()); - } + for( int o = 0; o < gdcmReader->GetNumberOfOutputs(); o++ ) + { + mitk::Image::Pointer loaded_image = gdcmReader->GetOutput(o).GetMitkImage(); + mitk::DiffusionImage::Pointer d_img = static_cast*>( loaded_image.GetPointer() ); - QString descr = QString("%1_%2_%3") - .arg(((inHeaders)[0])->seriesDescription.c_str()) - .arg(((inHeaders)[0])->seriesNumber) - .arg(((inHeaders)[0])->patientName.c_str()); - descr = descr.trimmed(); - descr = descr.replace(" ", "_"); + std::stringstream ss; + ss << "ImportedData_" << o; - if(!m_OutputFolderNameSet) - { - node=mitk::DataNode::New(); - node->SetData( diffImage ); - GetDefaultDataStorage()->Add(node); - SetDwiNodeProperties(node, descr.toStdString().c_str()); - Status(QString("Image %1 added to datastorage").arg(descr)); - } - else - { - QString fullpath = QString("%1/%2.dwi") - .arg(m_OutputFolderName) - .arg(descr); + node = mitk::DataNode::New(); + node->SetData( d_img ); + std::string outname; + d_img->GetPropertyList()->GetStringProperty("diffusion.dicom.importname", outname ); - // if the override option is not checked, we need to make sure that the current filepath - // does not point to an existing file - if( !(m_Controls->m_OverrideOptionCheckbox->isChecked()) ) - { - QFile outputFile( fullpath ); + node->SetName( outname.c_str() ); - // generate new filename if file exists - int file_counter = 0; - while( outputFile.exists() ) - { - // copy base name - QString newdescr = descr; - - file_counter++; - MITK_WARN << "The file "<< fullpath.toStdString() << " exists already."; - QString appendix = QString("_%1").arg( QString::number(file_counter) ); - newdescr.append(appendix); - fullpath = QString("%1/%2.dwi") - .arg(m_OutputFolderName) - .arg(newdescr); - - // set the new generated filename for next check - outputFile.setFileName( fullpath ); - } - } - try - { - mitk::IOUtil::SaveBaseData(diffImage, fullpath.toStdString()); - } - catch (itk::ExceptionObject &ex) - { - imageSuccessfullySaved = false; - Error(QString("%1\n%2\n%3\n%4\n%5\n%6").arg(ex.GetNameOfClass()).arg(ex.GetFile()).arg(ex.GetLine()).arg(ex.GetLocation()).arg(ex.what()).arg(ex.GetDescription())); - logfile << QString("%1\n%2\n%3\n%4\n%5\n%6").arg(ex.GetNameOfClass()).arg(ex.GetFile()).arg(ex.GetLine()).arg(ex.GetLocation()).arg(ex.what()).arg(ex.GetDescription()).toStdString() << "\n"; - - node=mitk::DataNode::New(); - node->SetData( diffImage ); - GetDefaultDataStorage()->Add(node); - SetDwiNodeProperties(node, descr.toStdString().c_str()); - Status(QString("Image %1 added to datastorage").arg(descr)); - logfile << "Image " << descr.toStdString() << " added to datastorage\n"; - continue ; - } - Status(QString("Image %1 written to disc (%1)").arg(fullpath.toStdString().c_str())); - logfile << "Image " << fullpath.toStdString() << "\n"; - } - } - else - { - Status(QString("No diffusion information found (%1)").arg(folderName)); - if(m_OutputFolderNameSet) logfile << "No diffusion information found "<< folderName.toStdString(); + GetDefaultDataStorage()->Add(node); + //SetDwiNodeProperties(node, ss.str() ); + //Status(QString("Image %1 added to datastorage").arg(descr)); } - Status(QString("Finished processing %1 with memory:").arg(folderName)); - if(m_OutputFolderNameSet) logfile << "Finished processing " << folderName.toStdString() << "\n"; - PrintMemoryUsage(); - clock.Stop(folderName.toAscii()); - mitk::ProgressBar::GetInstance()->Progress(); - int lwidget = m_Controls->listWidget->count(); - std::cout << lwidget <GetData(); if (basedata.IsNotNull()) { mitk::RenderingManager::GetInstance()->InitializeViews( - basedata->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true ); + basedata->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true ); } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); try { MITK_INFO << " ** Changing locale back from " << setlocale(LC_ALL, NULL) << " to '" << currLocale << "'"; setlocale(LC_ALL, currLocale.c_str()); } catch(...) { MITK_INFO << "Could not reset locale " << currLocale; } } catch (itk::ExceptionObject &ex) { Error(QString("%1\n%2\n%3\n%4\n%5\n%6").arg(ex.GetNameOfClass()).arg(ex.GetFile()).arg(ex.GetLine()).arg(ex.GetLocation()).arg(ex.what()).arg(ex.GetDescription())); return ; } if (!imageSuccessfullySaved) QMessageBox::warning(NULL,"WARNING","One or more files could not be saved! The according files where moved to the datastorage."); Status(QString("Finished import with memory:")); PrintMemoryUsage(); } void QmitkDiffusionDicomImport::SetDwiNodeProperties(mitk::DataNode::Pointer node, std::string name) { node->SetProperty( "IsDWIRawVolume", mitk::BoolProperty::New( true ) ); // set foldername as string property mitk::StringProperty::Pointer nameProp = mitk::StringProperty::New( name ); node->SetProperty( "name", nameProp ); } diff --git a/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.h b/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.h index 98b6f6b46b..a7a32024a4 100644 --- a/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.h +++ b/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportView.h @@ -1,110 +1,115 @@ /*=================================================================== 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 QmitkDiffusionDicomImportView_H__INCLUDED #define QmitkDiffusionDicomImportView_H__INCLUDED #include "QmitkFunctionality.h" #include "ui_QmitkDiffusionDicomImportViewControls.h" /*! \brief QmitkDiffusionDicomImport \sa QmitkFunctionality \ingroup Functionalities */ class QmitkDiffusionDicomImport : public QmitkFunctionality { Q_OBJECT public: static const std::string VIEW_ID; /*! \ Convenient typedefs */ typedef mitk::DataStorage::SetOfObjects ConstVector; typedef ConstVector::ConstPointer ConstVectorPointer; typedef ConstVector::ConstIterator ConstVectorIterator; /*! \brief default constructor */ QmitkDiffusionDicomImport(QObject *parent=0, const char *name=0); QmitkDiffusionDicomImport(const QmitkDiffusionDicomImport& other); /*! \brief default destructor */ virtual ~QmitkDiffusionDicomImport(); /*! \brief method for creating the widget containing the application controls, like sliders, buttons etc. */ virtual void CreateQtPartControl(QWidget *parent); /*! \brief method for creating the connections of main and control widget */ virtual void CreateConnections(); virtual void Activated(); void SetDwiNodeProperties(mitk::DataNode::Pointer node, std::string name); protected slots: void DicomLoadAddFolderNames(); void DicomLoadDeleteFolderNames(); - void DicomLoadStartLoad() ; + void NewDicomLoadStartLoad() ; void AverageClicked(); void OutputSet(); void OutputClear(); void Remove(); + void RecursiveSettingsChanged(); + void SetPrefixButtonPushed(); + void ResetPrefixButtonPushed(); protected: void Status(QString status); void Status(std::string status); void Status(const char* status); void Error(QString status); void Error(std::string status); void Error(const char* status); void PrintMemoryUsage(); std::string FormatMemorySize( size_t size ); std::string FormatPercentage( double val ); std::string GetMemoryDescription( size_t processSize, float percentage ); /*! * controls containing sliders for scrolling through the slices */ Ui::QmitkDiffusionDicomImportControls *m_Controls; QmitkStdMultiWidget* m_MultiWidget; QWidget *m_Parent; QString m_OutputFolderName; bool m_OutputFolderNameSet; + std::string m_Prefix; + }; #endif // !defined(QmitkDiffusionDicomImport_H__INCLUDED) diff --git a/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportViewControls.ui b/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportViewControls.ui index 6d7d36109e..35c31a6a3d 100644 --- a/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportViewControls.ui +++ b/Plugins/org.mitk.gui.qt.diffusionimaging/src/internal/QmitkDiffusionDicomImportViewControls.ui @@ -1,320 +1,381 @@ QmitkDiffusionDicomImportControls 0 0 - 341 - 559 + 374 + 603 0 0 true QmitkDiffusionDicomImport 16777215 70 QFrame::NoFrame QFrame::Plain 0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> <table border="0" style="-qt-table-type: root; margin-top:4px; margin-bottom:4px; margin-left:4px; margin-right:4px;"> <tr> <td style="border: none;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Each input folder must only contain DICOM-images that can be combined into one vector-valued 3D output volume. Different patients must be loaded from different input-folders. The folders must not contain other acquisitions (e.g. T1,T2,localizer).</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">In case many imports are performed at once, it is recommended to set the the optional output folder argument. This prevents the images from being kept in memory.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p></td></tr></table></body></html> 0 0 QFrame::NoFrame QFrame::Raised 0 - + + 0 + + + 0 + + + 0 + + 0 Add Input Folders Remove Clear 0 0 0 70 QFrame::Box QFrame::Plain 1 0 Qt::ScrollBarAsNeeded Qt::ScrollBarAlwaysOn QListView::Adjust true QFrame::StyledPanel QFrame::Raised Recursive + + + + 8 + + + QLayout::SetNoConstraint + + + + + Set Prefix + + + + + + + + + + Reset + + + + + Multiple acquistions of one gradient direction can be averaged. Due to rounding errors, similar gradients often differ in the last decimal positions. The Merge radius allows to average them anyway by taking into account all directions within a certain radius. QFrame::NoFrame QFrame::Raised - + + 0 + + + 0 + + + 0 + + 0 Merge duplicate gradients: false 4 2.000000000000000 0.000100000000000 0.001000000000000 Select this option if you are sure that your DICOM dataset contains multiple Study Instance UID or Series Instance UID. It will ignore that fact and attempt to import all images as one. Merge duplicate study IDs QFrame::NoFrame QFrame::Raised - + + 0 + + + 0 + + + 0 + + 0 40 25 30 16777215 Files are automaticall saved to disc. If the files can not be written, they are added to the data manager. Set ... optional out-folder ... false true 50 25 30 16777215 Clear Override existing files + + + + Split Mosaic + + + Import DICOM as *.dwi Qt::Vertical 20 40