diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index 50ad9a9713..ab6009196e 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,311 +1,312 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCoreActivator.cpp mitkCoreObjectFactoryBase.cpp mitkCoreObjectFactory.cpp mitkCoreServices.cpp mitkException.cpp Algorithms/mitkBaseDataSource.cpp Algorithms/mitkClippedSurfaceBoundsCalculator.cpp Algorithms/mitkCompareImageDataFilter.cpp Algorithms/mitkConvert2Dto3DImageFilter.cpp Algorithms/mitkDataNodeSource.cpp Algorithms/mitkExtractSliceFilter.cpp Algorithms/mitkHistogramGenerator.cpp Algorithms/mitkImageChannelSelector.cpp Algorithms/mitkImageSliceSelector.cpp Algorithms/mitkImageSource.cpp Algorithms/mitkImageTimeSelector.cpp Algorithms/mitkImageToImageFilter.cpp Algorithms/mitkImageToSurfaceFilter.cpp Algorithms/mitkMultiComponentImageDataComparisonFilter.cpp Algorithms/mitkPlaneGeometryDataToSurfaceFilter.cpp Algorithms/mitkPointSetSource.cpp Algorithms/mitkPointSetToPointSetFilter.cpp Algorithms/mitkRGBToRGBACastImageFilter.cpp Algorithms/mitkSubImageSelector.cpp Algorithms/mitkSurfaceSource.cpp Algorithms/mitkSurfaceToImageFilter.cpp Algorithms/mitkSurfaceToSurfaceFilter.cpp Algorithms/mitkUIDGenerator.cpp Algorithms/mitkVolumeCalculator.cpp Controllers/mitkBaseController.cpp Controllers/mitkCallbackFromGUIThread.cpp Controllers/mitkCameraController.cpp Controllers/mitkCameraRotationController.cpp Controllers/mitkLimitedLinearUndo.cpp Controllers/mitkOperationEvent.cpp Controllers/mitkPlanePositionManager.cpp Controllers/mitkProgressBar.cpp Controllers/mitkRenderingManager.cpp Controllers/mitkSliceNavigationController.cpp Controllers/mitkSlicesCoordinator.cpp Controllers/mitkStatusBar.cpp Controllers/mitkStepper.cpp Controllers/mitkTestManager.cpp Controllers/mitkUndoController.cpp Controllers/mitkVerboseLimitedLinearUndo.cpp Controllers/mitkVtkLayerController.cpp DataManagement/mitkArbitraryTimeGeometry.cpp DataManagement/mitkAbstractTransformGeometry.cpp DataManagement/mitkAnnotationProperty.cpp DataManagement/mitkApplicationCursor.cpp DataManagement/mitkApplyTransformMatrixOperation.cpp DataManagement/mitkBaseData.cpp DataManagement/mitkBaseGeometry.cpp DataManagement/mitkBaseProperty.cpp DataManagement/mitkChannelDescriptor.cpp DataManagement/mitkClippingProperty.cpp DataManagement/mitkColorProperty.cpp DataManagement/mitkDataNode.cpp DataManagement/mitkDataStorage.cpp DataManagement/mitkEnumerationProperty.cpp DataManagement/mitkFloatPropertyExtension.cpp DataManagement/mitkGeometry3D.cpp DataManagement/mitkGeometryData.cpp DataManagement/mitkGeometryTransformHolder.cpp DataManagement/mitkGroupTagProperty.cpp DataManagement/mitkImageAccessorBase.cpp DataManagement/mitkImageCaster.cpp DataManagement/mitkImageCastPart1.cpp DataManagement/mitkImageCastPart2.cpp DataManagement/mitkImageCastPart3.cpp DataManagement/mitkImageCastPart4.cpp DataManagement/mitkImage.cpp DataManagement/mitkImageDataItem.cpp DataManagement/mitkImageDescriptor.cpp DataManagement/mitkImageReadAccessor.cpp DataManagement/mitkImageStatisticsHolder.cpp DataManagement/mitkImageVtkAccessor.cpp DataManagement/mitkImageVtkReadAccessor.cpp DataManagement/mitkImageVtkWriteAccessor.cpp DataManagement/mitkImageWriteAccessor.cpp DataManagement/mitkIntPropertyExtension.cpp DataManagement/mitkIPersistenceService.cpp DataManagement/mitkIPropertyAliases.cpp DataManagement/mitkIPropertyDescriptions.cpp DataManagement/mitkIPropertyExtensions.cpp DataManagement/mitkIPropertyFilters.cpp DataManagement/mitkIPropertyPersistence.cpp DataManagement/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp DataManagement/mitkLine.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp DataManagement/mitkLookupTables.cpp # specializations of GenericLookupTable DataManagement/mitkMaterial.cpp DataManagement/mitkMemoryUtilities.cpp DataManagement/mitkModalityProperty.cpp DataManagement/mitkModifiedLock.cpp DataManagement/mitkNodePredicateAnd.cpp DataManagement/mitkNodePredicateBase.cpp DataManagement/mitkNodePredicateCompositeBase.cpp DataManagement/mitkNodePredicateData.cpp DataManagement/mitkNodePredicateDataType.cpp DataManagement/mitkNodePredicateDimension.cpp DataManagement/mitkNodePredicateFirstLevel.cpp DataManagement/mitkNodePredicateNot.cpp DataManagement/mitkNodePredicateOr.cpp DataManagement/mitkNodePredicateProperty.cpp DataManagement/mitkNodePredicateSource.cpp DataManagement/mitkNumericConstants.cpp DataManagement/mitkPlaneGeometry.cpp DataManagement/mitkPlaneGeometryData.cpp DataManagement/mitkPlaneOperation.cpp DataManagement/mitkPlaneOrientationProperty.cpp DataManagement/mitkPointOperation.cpp DataManagement/mitkPointSet.cpp DataManagement/mitkPointSetShapeProperty.cpp DataManagement/mitkProperties.cpp DataManagement/mitkPropertyAliases.cpp DataManagement/mitkPropertyDescriptions.cpp DataManagement/mitkPropertyExtension.cpp DataManagement/mitkPropertyExtensions.cpp DataManagement/mitkPropertyFilter.cpp DataManagement/mitkPropertyFilters.cpp DataManagement/mitkPropertyList.cpp DataManagement/mitkPropertyListReplacedObserver.cpp + DataManagement/mitkPropertyNameHelper.cpp DataManagement/mitkPropertyObserver.cpp DataManagement/mitkPropertyPersistence.cpp DataManagement/mitkPropertyPersistenceInfo.cpp DataManagement/mitkProportionalTimeGeometry.cpp DataManagement/mitkRenderingModeProperty.cpp DataManagement/mitkResliceMethodProperty.cpp DataManagement/mitkRestorePlanePositionOperation.cpp DataManagement/mitkRotationOperation.cpp DataManagement/mitkScaleOperation.cpp DataManagement/mitkShaderProperty.cpp DataManagement/mitkSlicedData.cpp DataManagement/mitkSlicedGeometry3D.cpp DataManagement/mitkSmartPointerProperty.cpp DataManagement/mitkStandaloneDataStorage.cpp DataManagement/mitkStringProperty.cpp DataManagement/mitkSurface.cpp DataManagement/mitkSurfaceOperation.cpp DataManagement/mitkThinPlateSplineCurvedGeometry.cpp DataManagement/mitkTimeGeometry.cpp DataManagement/mitkTransferFunction.cpp DataManagement/mitkTransferFunctionInitializer.cpp DataManagement/mitkTransferFunctionProperty.cpp DataManagement/mitkTemporoSpatialStringProperty.cpp DataManagement/mitkVector.cpp DataManagement/mitkVectorProperty.cpp DataManagement/mitkVtkInterpolationProperty.cpp DataManagement/mitkVtkRepresentationProperty.cpp DataManagement/mitkVtkResliceInterpolationProperty.cpp DataManagement/mitkVtkScalarModeProperty.cpp DataManagement/mitkVtkVolumeRenderingProperty.cpp DataManagement/mitkWeakPointerProperty.cpp Interactions/mitkAction.cpp Interactions/mitkBindDispatcherInteractor.cpp Interactions/mitkCrosshairPositionEvent.cpp Interactions/mitkDataInteractor.cpp Interactions/mitkDispatcher.cpp Interactions/mitkDisplayCoordinateOperation.cpp Interactions/mitkDisplayInteractor.cpp Interactions/mitkEventConfig.cpp Interactions/mitkEventFactory.cpp Interactions/mitkEventRecorder.cpp Interactions/mitkEventStateMachine.cpp Interactions/mitkInteractionEventConst.cpp Interactions/mitkInteractionEvent.cpp Interactions/mitkInteractionEventHandler.cpp Interactions/mitkInteractionEventObserver.cpp Interactions/mitkInteractionKeyEvent.cpp Interactions/mitkInteractionPositionEvent.cpp Interactions/mitkInternalEvent.cpp Interactions/mitkMouseDoubleClickEvent.cpp Interactions/mitkMouseModeSwitcher.cpp Interactions/mitkMouseMoveEvent.cpp Interactions/mitkMousePressEvent.cpp Interactions/mitkMouseReleaseEvent.cpp Interactions/mitkMouseWheelEvent.cpp Interactions/mitkPointSetDataInteractor.cpp Interactions/mitkSinglePointDataInteractor.cpp Interactions/mitkStateMachineAction.cpp Interactions/mitkStateMachineCondition.cpp Interactions/mitkStateMachineContainer.cpp Interactions/mitkStateMachineState.cpp Interactions/mitkStateMachineTransition.cpp Interactions/mitkVtkEventAdapter.cpp Interactions/mitkVtkInteractorStyle.cxx Interactions/mitkXML2EventParser.cpp IO/mitkAbstractFileIO.cpp IO/mitkAbstractFileReader.cpp IO/mitkAbstractFileWriter.cpp IO/mitkCustomMimeType.cpp IO/mitkDicomSeriesReader.cpp IO/mitkDicomSeriesReaderService.cpp IO/mitkDicomSR_GantryTiltInformation.cpp IO/mitkDicomSR_ImageBlockDescriptor.cpp IO/mitkDicomSR_LoadDICOMRGBPixel4D.cpp IO/mitkDicomSR_LoadDICOMRGBPixel.cpp IO/mitkDicomSR_LoadDICOMScalar4D.cpp IO/mitkDicomSR_LoadDICOMScalar.cpp IO/mitkDicomSR_SliceGroupingResult.cpp IO/mitkFileReader.cpp IO/mitkFileReaderRegistry.cpp IO/mitkFileReaderSelector.cpp IO/mitkFileReaderWriterBase.cpp IO/mitkFileWriter.cpp IO/mitkFileWriterRegistry.cpp IO/mitkFileWriterSelector.cpp IO/mitkGeometry3DToXML.cpp IO/mitkIFileIO.cpp IO/mitkIFileReader.cpp IO/mitkIFileWriter.cpp IO/mitkGeometryDataReaderService.cpp IO/mitkGeometryDataWriterService.cpp IO/mitkImageGenerator.cpp IO/mitkImageVtkLegacyIO.cpp IO/mitkImageVtkXmlIO.cpp IO/mitkIMimeTypeProvider.cpp IO/mitkIOConstants.cpp IO/mitkIOMimeTypes.cpp IO/mitkIOUtil.cpp IO/mitkItkImageIO.cpp IO/mitkItkLoggingAdapter.cpp IO/mitkLegacyFileReaderService.cpp IO/mitkLegacyFileWriterService.cpp IO/mitkLocaleSwitch.cpp IO/mitkLog.cpp IO/mitkMimeType.cpp IO/mitkMimeTypeProvider.cpp IO/mitkOperation.cpp IO/mitkPixelType.cpp IO/mitkPointSetReaderService.cpp IO/mitkPointSetWriterService.cpp IO/mitkProportionalTimeGeometryToXML.cpp IO/mitkRawImageFileReader.cpp IO/mitkStandardFileLocations.cpp IO/mitkSurfaceStlIO.cpp IO/mitkSurfaceVtkIO.cpp IO/mitkSurfaceVtkLegacyIO.cpp IO/mitkSurfaceVtkXmlIO.cpp IO/mitkVtkLoggingAdapter.cpp Rendering/mitkAbstractOverlayLayouter.cpp Rendering/mitkBaseRenderer.cpp #Rendering/mitkGLMapper.cpp Moved to deprecated LegacyGL Module Rendering/mitkGradientBackground.cpp Rendering/mitkImageVtkMapper2D.cpp Rendering/mitkIShaderRepository.cpp Rendering/mitkManufacturerLogo.cpp Rendering/mitkMapper.cpp Rendering/mitkOverlay.cpp Rendering/mitkOverlayManager.cpp Rendering/mitkPlaneGeometryDataMapper2D.cpp Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp Rendering/mitkPointSetVtkMapper2D.cpp Rendering/mitkPointSetVtkMapper3D.cpp Rendering/mitkRenderWindowBase.cpp Rendering/mitkRenderWindow.cpp Rendering/mitkRenderWindowFrame.cpp #Rendering/mitkSurfaceGLMapper2D.cpp Moved to deprecated LegacyGL Module Rendering/mitkSurfaceVtkMapper2D.cpp Rendering/mitkSurfaceVtkMapper3D.cpp Rendering/mitkVtkEventProvider.cpp Rendering/mitkVtkMapper.cpp Rendering/mitkVtkOverlay2D.cpp Rendering/mitkVtkOverlay3D.cpp Rendering/mitkVtkOverlay.cpp Rendering/mitkVtkPropRenderer.cpp Rendering/mitkVtkWidgetRendering.cpp Rendering/vtkMitkLevelWindowFilter.cpp Rendering/vtkMitkRectangleProp.cpp Rendering/vtkMitkRenderProp.cpp Rendering/vtkMitkThickSlicesFilter.cpp Rendering/vtkNeverTranslucentTexture.cpp ) set(RESOURCE_FILES Interactions/globalConfig.xml Interactions/DisplayInteraction.xml Interactions/DisplayConfig.xml Interactions/DisplayConfigPACS.xml Interactions/DisplayConfigPACSPan.xml Interactions/DisplayConfigPACSScroll.xml Interactions/DisplayConfigPACSZoom.xml Interactions/DisplayConfigPACSLevelWindow.xml Interactions/DisplayConfigMITK.xml Interactions/DisplayConfigMITKNoCrosshair.xml Interactions/DisplayConfigMITKRotation.xml Interactions/DisplayConfigMITKRotationUnCoupled.xml Interactions/DisplayConfigMITKSwivel.xml Interactions/DisplayConfigMITKLimited.xml Interactions/PointSet.xml Interactions/Legacy/StateMachine.xml Interactions/Legacy/DisplayConfigMITKTools.xml Interactions/PointSetConfig.xml mitkLevelWindowPresets.xml ) diff --git a/Modules/DICOMReader/src/mitkDICOMTagCache.cpp b/Modules/Core/include/mitkPropertyNameHelper.h similarity index 56% copy from Modules/DICOMReader/src/mitkDICOMTagCache.cpp copy to Modules/Core/include/mitkPropertyNameHelper.h index ede0742826..01f097d5d4 100644 --- a/Modules/DICOMReader/src/mitkDICOMTagCache.cpp +++ b/Modules/Core/include/mitkPropertyNameHelper.h @@ -1,31 +1,32 @@ /*=================================================================== 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 "mitkDICOMTagCache.h" +#ifndef mitkPropertyNameHelper_h +#define mitkPropertyNameHelper_h -mitk::DICOMTagCache::DICOMTagCache() -:itk::Object() -{ -} +#include -mitk::DICOMTagCache::DICOMTagCache( const DICOMTagCache&) -:itk::Object() +#include "MitkCoreExports.h" +namespace mitk { -} -mitk::DICOMTagCache::~DICOMTagCache() -{ + /** returns the correct property name for a simple DICOM tag. The tag is defined + * by the passed group and element number (both in hex). */ + std::string MITKCORE_EXPORT GeneratePropertyNameForDICOMTag(unsigned int group, unsigned int element); + } + +#endif diff --git a/Modules/DICOMReader/src/mitkDICOMTagCache.cpp b/Modules/Core/src/DataManagement/mitkPropertyNameHelper.cpp similarity index 50% copy from Modules/DICOMReader/src/mitkDICOMTagCache.cpp copy to Modules/Core/src/DataManagement/mitkPropertyNameHelper.cpp index ede0742826..cfdd49cebd 100644 --- a/Modules/DICOMReader/src/mitkDICOMTagCache.cpp +++ b/Modules/Core/src/DataManagement/mitkPropertyNameHelper.cpp @@ -1,31 +1,31 @@ /*=================================================================== 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 +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 "mitkDICOMTagCache.h" - -mitk::DICOMTagCache::DICOMTagCache() -:itk::Object() -{ -} +#include "mitkPropertyNameHelper.h" +#include +#include -mitk::DICOMTagCache::DICOMTagCache( const DICOMTagCache&) -:itk::Object() +std::string +mitk::GeneratePropertyNameForDICOMTag(unsigned int group, unsigned int element) { -} + std::ostringstream nameStream; + nameStream << "DICOM." << std::setw(4) << std::setfill('0') << std::hex << group << "." << std::setw(4) << std::setfill('0') << std::hex << element; -mitk::DICOMTagCache::~DICOMTagCache() -{ -} + return nameStream.str(); +}; diff --git a/Modules/Core/src/IO/mitkDicomSeriesReaderService.cpp b/Modules/Core/src/IO/mitkDicomSeriesReaderService.cpp index cc33717a08..70affdd365 100644 --- a/Modules/Core/src/IO/mitkDicomSeriesReaderService.cpp +++ b/Modules/Core/src/IO/mitkDicomSeriesReaderService.cpp @@ -1,132 +1,149 @@ /*=================================================================== 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 "mitkDicomSeriesReaderService.h" #include #include #include #include #include #include +#include #include namespace mitk { DicomSeriesReaderService::DicomSeriesReaderService() : AbstractFileReader(CustomMimeType(IOMimeTypes::DICOM_MIMETYPE()), "MITK DICOM Reader") { this->RegisterService(); } std::vector > DicomSeriesReaderService::Read() { std::vector result; mitk::LocaleSwitch localeSwitch("C"); std::locale previousCppLocale(std::cin.getloc()); std::locale l( "C" ); std::cin.imbue(l); std::string fileName = this->GetLocalFileName(); if ( DicomSeriesReader::IsPhilips3DDicom(fileName) ) { MITK_INFO << "it is a Philips3D US Dicom file" << std::endl; DataNode::Pointer node = DataNode::New(); mitk::DicomSeriesReader::StringContainer stringvec; stringvec.push_back(fileName); if (DicomSeriesReader::LoadDicomSeries(stringvec, *node)) { BaseData::Pointer data = node->GetData(); StringProperty::Pointer nameProp = StringProperty::New(itksys::SystemTools::GetFilenameName(fileName)); data->GetPropertyList()->SetProperty("name", nameProp); result.push_back(data); } std::cin.imbue(previousCppLocale); return result; } DicomSeriesReader::FileNamesGrouping imageBlocks = DicomSeriesReader::GetSeries(itksys::SystemTools::GetFilenamePath(fileName), true); // true = group gantry tilt images const unsigned int size = imageBlocks.size(); ProgressBar::GetInstance()->AddStepsToDo(size); ProgressBar::GetInstance()->Progress(); unsigned int outputIndex = 0u; const DicomSeriesReader::FileNamesGrouping::const_iterator n_end = imageBlocks.end(); for (DicomSeriesReader::FileNamesGrouping::const_iterator n_it = imageBlocks.begin(); n_it != n_end; ++n_it) { const std::string &uid = n_it->first; DataNode::Pointer node = DataNode::New(); const DicomSeriesReader::ImageBlockDescriptor& imageBlockDescriptor( n_it->second ); MITK_INFO << "--------------------------------------------------------------------------------"; MITK_INFO << "DicomSeriesReader: Loading DICOM series " << outputIndex << ": Series UID " << imageBlockDescriptor.GetSeriesInstanceUID() << std::endl; MITK_INFO << " " << imageBlockDescriptor.GetFilenames().size() << " '" << imageBlockDescriptor.GetModality() << "' files (" << imageBlockDescriptor.GetSOPClassUIDAsString() << ") loaded into 1 mitk::Image"; MITK_INFO << " multi-frame: " << (imageBlockDescriptor.IsMultiFrameImage()?"Yes":"No"); MITK_INFO << " reader support: " << DicomSeriesReader::ReaderImplementationLevelToString(imageBlockDescriptor.GetReaderImplementationLevel()); MITK_INFO << " pixel spacing type: " << DicomSeriesReader::PixelSpacingInterpretationToString( imageBlockDescriptor.GetPixelSpacingType() ); MITK_INFO << " gantry tilt corrected: " << (imageBlockDescriptor.HasGantryTiltCorrected()?"Yes":"No"); MITK_INFO << " 3D+t: " << (imageBlockDescriptor.HasMultipleTimePoints()?"Yes":"No"); MITK_INFO << "--------------------------------------------------------------------------------"; if (DicomSeriesReader::LoadDicomSeries(n_it->second.GetFilenames(), *node, true, true, true)) { BaseData::Pointer data = node->GetData(); PropertyList::Pointer dataProps = data->GetPropertyList(); std::string nodeName(uid); std::string studyDescription; - if ( dataProps->GetStringProperty( "dicom.study.StudyDescription", studyDescription ) ) + + BaseProperty* prop = dataProps->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str()); + if (prop) + { //may not be a string property so use the generic access. + studyDescription = prop->GetValueAsString(); + } + + if (!studyDescription.empty() || dataProps->GetStringProperty("dicom.study.StudyDescription", studyDescription)) + //2nd part is for backwards compatibility with the old property naming style { nodeName = studyDescription; std::string seriesDescription; - if ( dataProps->GetStringProperty( "dicom.series.SeriesDescription", seriesDescription ) ) + + prop = dataProps->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str()); + if (prop) + { //may not be a string property so use the generic access. + seriesDescription = prop->GetValueAsString(); + } + + if (!seriesDescription.empty() || dataProps->GetStringProperty("dicom.series.SeriesDescription", seriesDescription)) + //2nd part is for backwards compatibility with the old property naming style { nodeName += "/" + seriesDescription; } } StringProperty::Pointer nameProp = StringProperty::New(nodeName); data->SetProperty("name", nameProp); result.push_back(data); ++outputIndex; } else { MITK_ERROR << "DicomSeriesReader: Skipping series " << outputIndex << " due to some unspecified error..." << std::endl; } ProgressBar::GetInstance()->Progress(); } std::cin.imbue(previousCppLocale); return result; } DicomSeriesReaderService* DicomSeriesReaderService::Clone() const { return new DicomSeriesReaderService(*this); } } diff --git a/Modules/Core/src/Rendering/mitkImageVtkMapper2D.cpp b/Modules/Core/src/Rendering/mitkImageVtkMapper2D.cpp index 7a46d2a905..9296b23a99 100644 --- a/Modules/Core/src/Rendering/mitkImageVtkMapper2D.cpp +++ b/Modules/Core/src/Rendering/mitkImageVtkMapper2D.cpp @@ -1,1081 +1,1122 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //MITK #include #include #include #include #include #include #include #include #include #include +#include + //#include #include #include "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); SetVtkMapperImmediateModeRendering(localStorage->m_Mapper); 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 (auto & sliceBound : sliceBounds) { sliceBound = 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 (auto & textureClippingBound : textureClippingBounds) { textureClippingBound = 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) && image->GetPixelType().GetNumberOfComponents()==1 ) { // 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 ); } 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); } } // some more properties specific for a binary... if (isBinaryImage) { node->AddProperty( "opacity", mitk::FloatProperty::New(0.3f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( true ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(10), renderer, overwrite); } else //...or image type object { node->AddProperty( "opacity", mitk::FloatProperty::New(1.0f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,1.0,1.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(0), renderer, overwrite); } if(image.IsNotNull() && image->IsInitialized()) { if((overwrite) || (node->GetProperty("levelwindow", renderer)==NULL)) { /* initialize level/window from DICOM tags */ - std::string sLevel; - std::string sWindow; - if ( image->GetPropertyList()->GetStringProperty( "dicom.voilut.WindowCenter", sLevel ) - && image->GetPropertyList()->GetStringProperty( "dicom.voilut.WindowWidth", sWindow ) ) + std::string sLevel = ""; + std::string sWindow = ""; + + BaseProperty* prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x1050).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sLevel = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.voilut.WindowCenter", sLevel); + } + + prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x1051).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sWindow = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.voilut.WindowWidth", sWindow); + } + + if (!sLevel.empty() && !sWindow.empty()) { 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 ) ) + prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x0108).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sSmallestPixelValueInSeries = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.series.SmallestPixelValueInSeries", sSmallestPixelValueInSeries); + } + + prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x0109).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sLargestPixelValueInSeries = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.series.LargestPixelValueInSeries", sLargestPixelValueInSeries); + } + + if (!sSmallestPixelValueInSeries.empty() && !sLargestPixelValueInSeries.empty()) { 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/Modules/Core/test/files.cmake b/Modules/Core/test/files.cmake index 73148cff95..0d7a477e06 100644 --- a/Modules/Core/test/files.cmake +++ b/Modules/Core/test/files.cmake @@ -1,195 +1,196 @@ # 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 mitkFileReaderRegistryTest.cpp #mitkFileWriterRegistryTest.cpp mitkFloatToStringTest.cpp mitkGenericPropertyTest.cpp mitkGeometry3DTest.cpp mitkGeometry3DEqualTest.cpp mitkGeometryDataIOTest.cpp mitkGeometryDataToSurfaceFilterTest.cpp mitkImageCastTest.cpp mitkImageEqualTest.cpp mitkImageDataItemTest.cpp mitkImageGeneratorTest.cpp mitkIOUtilTest.cpp mitkBaseDataTest.cpp mitkImportItkImageTest.cpp mitkGrabItkImageMemoryTest.cpp mitkInstantiateAccessFunctionTest.cpp mitkLevelWindowTest.cpp mitkMessageTest.cpp mitkPixelTypeTest.cpp mitkPlaneGeometryTest.cpp mitkPointSetTest.cpp mitkPointSetEqualTest.cpp mitkPointSetFileIOTest.cpp mitkPointSetOnEmptyTest.cpp mitkPointSetLocaleTest.cpp mitkPointSetWriterTest.cpp mitkPointSetReaderTest.cpp mitkPointSetPointOperationsTest.cpp mitkProgressBarTest.cpp mitkPropertyTest.cpp mitkPropertyListTest.cpp mitkPropertyPersistenceTest.cpp mitkPropertyPersistenceInfoTest.cpp mitkSlicedGeometry3DTest.cpp mitkSliceNavigationControllerTest.cpp mitkSurfaceTest.cpp mitkSurfaceEqualTest.cpp mitkSurfaceToSurfaceFilterTest.cpp mitkTimeGeometryTest.cpp mitkProportionalTimeGeometryTest.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 mitkArbitraryTimeGeometryTest mitkItkImageIOTest.cpp mitkRotatedSlice4DTest.cpp mitkLevelWindowManagerCppUnitTest.cpp mitkVectorPropertyTest.cpp mitkTemporoSpatialStringPropertyTest.cpp + mitkPropertyNameHelperTest.cpp ) if(MITK_ENABLE_RENDERING_TESTING) set(MODULE_TESTS ${MODULE_TESTS} mitkPlaneGeometryDataMapper2DTest.cpp mitkPointSetDataInteractorTest.cpp #since mitkInteractionTestHelper is currently creating a vtkRenderWindow mitkSurfaceVtkMapper2DTest.cpp #new rendering test in CppUnit style mitkSurfaceVtkMapper2D3DTest.cpp # comparisons/consistency 2D/3D ) 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_TESTIMAGE US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) set(MODULE_TESTSURFACE binary.stl ball.stl ) set(MODULE_CUSTOM_TESTS mitkDataStorageTest.cpp mitkDicomSeriesReaderTest.cpp mitkDICOMLocaleTest.cpp mitkDataNodeTest.cpp mitkEventConfigTest.cpp mitkPointSetLocaleTest.cpp mitkImageTest.cpp mitkImageVtkMapper2DTest.cpp mitkImageVtkMapper2DLevelWindowTest.cpp mitkImageVtkMapper2DOpacityTest.cpp mitkImageVtkMapper2DResliceInterpolationPropertyTest.cpp mitkImageVtkMapper2DColorTest.cpp mitkImageVtkMapper2DSwivelTest.cpp mitkImageVtkMapper2DTransferFunctionTest.cpp mitkImageVtkMapper2DOpacityTransferFunctionTest.cpp mitkImageVtkMapper2DLookupTableTest.cpp mitkSurfaceVtkMapper3DTest mitkSurfaceVtkMapper3DTexturedSphereTest.cpp mitkVolumeCalculatorTest.cpp mitkLevelWindowManagerTest.cpp mitkPointSetVtkMapper2DTest.cpp mitkPointSetVtkMapper2DImageTest.cpp mitkPointSetVtkMapper2DGlyphTypeTest.cpp mitkPointSetVtkMapper2DTransformedPointsTest.cpp mitkVTKRenderWindowSizeTest.cpp mitkMultiComponentImageDataComparisonFilterTest.cpp mitkImageToItkTest.cpp mitkImageSliceSelectorTest.cpp mitkSurfaceDepthPeelingTest.cpp ) # Currently not working on windows because of a rendering timing issue # see bug 18083 for details if(NOT WIN32) set(MODULE_CUSTOM_TESTS ${MODULE_CUSTOM_TESTS} mitkSurfaceDepthSortingTest.cpp) endif() set(RESOURCE_FILES Interactions/AddAndRemovePoints.xml Interactions/globalConfig.xml Interactions/StatemachineTest.xml Interactions/StatemachineConfigTest.xml ) diff --git a/Modules/DICOMReader/test/mitkDICOMTagHelperTest.cpp b/Modules/Core/test/mitkPropertyNameHelperTest.cpp similarity index 70% rename from Modules/DICOMReader/test/mitkDICOMTagHelperTest.cpp rename to Modules/Core/test/mitkPropertyNameHelperTest.cpp index 99bc234fe0..a7e95f80f9 100644 --- a/Modules/DICOMReader/test/mitkDICOMTagHelperTest.cpp +++ b/Modules/Core/test/mitkPropertyNameHelperTest.cpp @@ -1,59 +1,59 @@ /*=================================================================== 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 "mitkDICOMTagHelper.h" +#include "mitkPropertyNameHelper.h" #include "mitkTestFixture.h" #include "mitkTestingMacros.h" #include -class mitkDICOMTagHelperTestSuite : public mitk::TestFixture +class mitkPropertyNameHelperTestSuite : public mitk::TestFixture { - CPPUNIT_TEST_SUITE(mitkDICOMTagHelperTestSuite); + CPPUNIT_TEST_SUITE(mitkPropertyNameHelperTestSuite); // Test the append method - MITK_TEST(GeneratPropertyNameForDICOMTag); + MITK_TEST(GeneratePropertyNameForDICOMTag); MITK_TEST(GetDefaultDICOMTagsOfInterest); CPPUNIT_TEST_SUITE_END(); private: public: void setUp() override { } void tearDown() override { } - void GeneratPropertyNameForDICOMTag() + void GeneratePropertyNameForDICOMTag() { - std::string result = mitk::GeneratPropertyNameForDICOMTag(mitk::DICOMTag(0x0018, 0x0080)); + std::string result = mitk::GeneratePropertyNameForDICOMTag(0x0018, 0x0080); MITK_TEST_CONDITION_REQUIRED(result == "DICOM.0018.0080", "Testing GeneratPropertyNameForDICOMTag(mitk::DICOMTag(0x0018, 0x0080)"); - result = mitk::GeneratPropertyNameForDICOMTag(mitk::DICOMTag(0x0008, 0x001a)); + result = mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x001a); MITK_TEST_CONDITION_REQUIRED(result == "DICOM.0008.001a", "Testing GeneratPropertyNameForDICOMTag(mitk::DICOMTag(0x0008, 0x001a)"); } void GetDefaultDICOMTagsOfInterest() { } }; -MITK_TEST_SUITE_REGISTRATION(mitkDICOMTagHelper) +MITK_TEST_SUITE_REGISTRATION(mitkPropertyNameHelper) diff --git a/Modules/DICOMReader/files.cmake b/Modules/DICOMReader/files.cmake index 0e31f9d72c..9f27851013 100644 --- a/Modules/DICOMReader/files.cmake +++ b/Modules/DICOMReader/files.cmake @@ -1,42 +1,49 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkDICOMFileReader.cpp + mitkDICOMTagScanner.cpp mitkDICOMGDCMTagScanner.cpp + mitkDICOMDCMTKTagScanner.cpp mitkDICOMImageBlockDescriptor.cpp mitkDICOMITKSeriesGDCMReader.cpp mitkDICOMDatasetSorter.cpp mitkDICOMTagBasedSorter.cpp mitkDICOMGDCMImageFrameInfo.cpp mitkDICOMImageFrameInfo.cpp + mitkDICOMGenericImageFrameInfo.cpp + mitkDICOMDatasetAccessingImageFrameInfo.cpp mitkDICOMSortCriterion.cpp mitkDICOMSortByTag.cpp mitkITKDICOMSeriesReaderHelper.cpp mitkEquiDistantBlocksSorter.cpp mitkNormalDirectionConsistencySorter.cpp mitkSortByImagePositionPatient.cpp mitkGantryTiltInformation.cpp mitkClassicDICOMSeriesReader.cpp mitkThreeDnTDICOMSeriesReader.cpp mitkDICOMTag.cpp - mitkDICOMTagHelper.cpp + mitkDICOMTagsOfInterestHelper.cpp mitkDICOMTagCache.cpp + mitkDICOMGDCMTagCache.cpp + mitkDICOMGenericTagCache.cpp mitkDICOMEnums.cpp mitkDICOMReaderConfigurator.cpp mitkDICOMFileReaderSelector.cpp mitkIDICOMTagsOfInterest.cpp + mitkDICOMTagPath.cpp mitkDICOMProperty.cpp ) set(RESOURCE_FILES configurations/3D/classicreader.xml configurations/3D/imageposition.xml configurations/3D/imageposition_byacquisition.xml configurations/3D/instancenumber.xml configurations/3D/instancenumber_soft.xml configurations/3D/slicelocation.xml configurations/3DnT/classicreader.xml configurations/3DnT/imageposition_byacquisition.xml configurations/3DnT/imageposition_bytriggertime.xml ) diff --git a/Modules/DICOMReader/include/mitkDICOMDCMTKTagScanner.h b/Modules/DICOMReader/include/mitkDICOMDCMTKTagScanner.h new file mode 100644 index 0000000000..b7d2f93419 --- /dev/null +++ b/Modules/DICOMReader/include/mitkDICOMDCMTKTagScanner.h @@ -0,0 +1,99 @@ +/*=================================================================== + +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 mitkDICOMDCMTKTagScanner_h +#define mitkDICOMDCMTKTagScanner_h + +#include + +#include "mitkDICOMTagScanner.h" +#include "mitkDICOMEnums.h" +#include "mitkDICOMGenericTagCache.h" + +namespace mitk +{ + + /** + \ingroup DICOMReaderModule + \brief Encapsulates the tag scanning process for a set of DICOM files. + + For the scanning process it uses DCMTK functionality. + */ + class MITKDICOMREADER_EXPORT DICOMDCMTKTagScanner : public DICOMTagScanner + { + public: + + mitkClassMacro(DICOMDCMTKTagScanner, DICOMTagScanner); + itkFactorylessNewMacro( DICOMDCMTKTagScanner ); + itkCloneMacro(Self); + + /** + \brief Add this tag to the scanning process. + */ + virtual void AddTag(const DICOMTag& tag) override; + /** + \brief Add a list of tags to the scanning process. + */ + virtual void AddTags(const DICOMTagList& tags) override; + /** + \brief Add this tag path to the scanning process. + */ + virtual void AddTagPath(const DICOMTagPath& tag) override; + /** + \brief Add a list of tag pathes to the scanning process. + */ + virtual void AddTagPaths(const DICOMTagPathList& tags) override; + + /** + \brief Define the list of files to scan. + This does not ADD to an internal list, but it replaces the + whole list of files. + */ + virtual void SetInputFiles(const StringList& filenames) override; + + /** + \brief Start the scanning process. + Calling Scan() will invalidate previous scans, forgetting + all about files and tags from files that have been scanned + previously. + */ + virtual void Scan(); + + /** + \brief Retrieve a result list for file-by-file tag access. + */ + virtual DICOMDatasetAccessingImageFrameList GetFrameInfoList() const override; + + /** + \brief Retrieve Pointer to the complete cache of the scan. + */ + virtual DICOMTagCache::Pointer GetScanCache() const override; + + protected: + + DICOMDCMTKTagScanner(); + virtual ~DICOMDCMTKTagScanner(); + + std::set m_ScannedTags; + StringList m_InputFilenames; + DICOMGenericTagCache::Pointer m_Cache; + + private: + DICOMDCMTKTagScanner(const DICOMDCMTKTagScanner&); + }; +} + +#endif diff --git a/Modules/DICOMReader/include/mitkDICOMDatasetAccess.h b/Modules/DICOMReader/include/mitkDICOMDatasetAccess.h index 8b87a52d0d..c816fe0eef 100644 --- a/Modules/DICOMReader/include/mitkDICOMDatasetAccess.h +++ b/Modules/DICOMReader/include/mitkDICOMDatasetAccess.h @@ -1,68 +1,78 @@ /*=================================================================== 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 mitkDICOMDatasetAccess_h #define mitkDICOMDatasetAccess_h #include "mitkDICOMTag.h" +#include "mitkDICOMTagPath.h" #include "MitkDICOMReaderExports.h" namespace mitk { /** Helper class that is used for the result of DICOMDatasetAccess::GetTagValueAsString */ struct MITKDICOMREADER_EXPORT DICOMDatasetFinding { /**Indicates if value is valid or not.*/ bool isValid; /**The found value.*/ std::string value; + /**Tag path of the value*/ + DICOMTagPath path; - DICOMDatasetFinding() : isValid(false), value("") + DICOMDatasetFinding(bool valid = false, const std::string& aValue = "", const DICOMTagPath& aPath = DICOMTagPath()) : isValid(valid), value(aValue), path(aPath) {}; }; /** \ingroup DICOMReaderModule \brief Interface to datasets that is presented to sorting classes such as DICOMDatasetSorter. Minimal interface to hide actual implementation, which might rely on GDCM. */ class MITKDICOMREADER_EXPORT DICOMDatasetAccess { public: + typedef std::list FindingsListType; /// \brief Return a filename if possible. /// If DICOM is not read from file but from somewhere else (network, database), we might not have files. virtual std::string GetFilenameIfAvailable() const = 0; /** \brief Return a DICOMDatasetFinding instance of the tag. The return containes (if valid) the raw value of the tag as a string. \param tag Tag which value should be retreived. */ virtual DICOMDatasetFinding GetTagValueAsString(const DICOMTag& tag) const = 0; + /** \brief Return a list of DICOMDatasetFindings of the passed tag path. + The return containes (if valid) the raw value of the tag as a string. + \param path Tag path which value should be retreived. + */ + virtual FindingsListType GetTagValueAsString(const DICOMTagPath& path) const = 0; + virtual ~DICOMDatasetAccess() {}; }; typedef std::vector DICOMDatasetList; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMDatasetAccessingImageFrameInfo.h b/Modules/DICOMReader/include/mitkDICOMDatasetAccessingImageFrameInfo.h new file mode 100644 index 0000000000..19bcfeb67e --- /dev/null +++ b/Modules/DICOMReader/include/mitkDICOMDatasetAccessingImageFrameInfo.h @@ -0,0 +1,63 @@ +/*=================================================================== + +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 mitkDICOMDatasetAccessingImageFrameInfo_h +#define mitkDICOMDatasetAccessingImageFrameInfo_h + +#include "mitkDICOMImageFrameInfo.h" +#include "mitkDICOMDatasetAccess.h" + +#include "MitkDICOMReaderExports.h" + +namespace mitk +{ + /** + \ingroup DICOMReaderModule + \brief Defines an abstract base class for DICOM image frame infos with data access. + + This abstract base class extends the DICOMImageFrameInfo by the DICOMDatasetAccess interface. + This allows to directly query for tag values of a frame/file specified by the info. + DICOMGDCMImageFrameInfo is an example for a concrete implementation. + */ + class MITKDICOMREADER_EXPORT DICOMDatasetAccessingImageFrameInfo : public DICOMImageFrameInfo, public DICOMDatasetAccess + { + public: + mitkClassMacro(DICOMDatasetAccessingImageFrameInfo, DICOMImageFrameInfo); + + protected: + DICOMDatasetAccessingImageFrameInfo(const std::string& filename = "", unsigned int frameNo = 0); + virtual ~DICOMDatasetAccessingImageFrameInfo(); + + private: + DICOMDatasetAccessingImageFrameInfo(const DICOMDatasetAccessingImageFrameInfo::Pointer& frameinfo); + Self& operator = (const Self& frameinfo); + }; + + + typedef std::vector DICOMDatasetAccessingImageFrameList; + + MITKDICOMREADER_EXPORT mitk::DICOMImageFrameList + ConvertToDICOMImageFrameList(const DICOMDatasetAccessingImageFrameList& input); + + MITKDICOMREADER_EXPORT mitk::DICOMDatasetList + ConvertToDICOMDatasetList(const DICOMDatasetAccessingImageFrameList& input); + + MITKDICOMREADER_EXPORT mitk::DICOMDatasetAccessingImageFrameList + ConvertToDICOMDatasetAccessingImageFrameList(const DICOMDatasetList& input); + +} + +#endif diff --git a/Modules/DICOMReader/include/mitkDICOMFileReader.h b/Modules/DICOMReader/include/mitkDICOMFileReader.h index 22d54c54a5..98414b68ca 100644 --- a/Modules/DICOMReader/include/mitkDICOMFileReader.h +++ b/Modules/DICOMReader/include/mitkDICOMFileReader.h @@ -1,165 +1,173 @@ /*=================================================================== 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 mitkDICOMFileReader_h #define mitkDICOMFileReader_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "MitkDICOMReaderExports.h" #include "mitkDICOMTagCache.h" #include "mitkDICOMImageBlockDescriptor.h" #include namespace mitk { // TODO Philips3D! // TODO http://bugs.mitk.org/show_bug.cgi?id=11572 ? /** \ingroup DICOMReaderModule \brief Interface for DICOM readers that produce mitk::Images. As described in \ref DICOMReaderModule, this class structures the reader's part in the process of analyzing a set of DICOM files and selecting the most appropriate reader. The overall loading process is as follows: - Define input files: a list of absolute filenames - Analyze the potential output: see what can be made of the input files, describe with DICOMImageBlockDescriptor%s - Load pixel data: an application will usually analyze files using multiple readers and only load with a single reader Sub-classes are required to implement a number of methods that reflect above structure. See mitk::DICOMITKSeriesGDCMReader for an example. To help applications in describing different readers to the user, each reader brings a number of methods that describe its configuration/specifics by means of a short label and a (longer) description. */ class MITKDICOMREADER_EXPORT DICOMFileReader : public itk::Object { public: mitkClassMacroItkParent( DICOMFileReader, itk::Object ); /// Test whether a file is DICOM at all static bool IsDICOM( const std::string& filename ); /// Indicate whether this reader can handle given file virtual bool CanHandleFile( const std::string& filename ) = 0; /// This input files void SetInputFiles( const StringList& filenames ); /// This input files const StringList& GetInputFiles() const; /// Analyze input files virtual void AnalyzeInputFiles() = 0; /// Number of outputs, only meaningful after calling AnalyzeInputFiles() unsigned int GetNumberOfOutputs() const; /// Individual outputs, only meaningful after calling AnalyzeInputFiles(). \throws std::invalid_argument const DICOMImageBlockDescriptor& GetOutput( unsigned int index ) const; // void AllocateOutputImages(); TODO for later implementation of slice-by-slice loading /// Load the mitk::Image%s in our outputs, the DICOMImageBlockDescriptor. To be called only after /// AnalyzeInputFiles(). Take care of potential exceptions! virtual bool LoadImages() = 0; - virtual DICOMTagList GetTagsOfInterest() const = 0; + virtual DICOMTagPathList GetTagsOfInterest() const = 0; /// A way to provide external knowledge about files and tag values is appreciated. virtual void SetTagCache( const DICOMTagCache::Pointer& ) = 0; /// Short label/name to describe this reader void SetConfigurationLabel( const std::string& ); /// Short label/name to describe this reader std::string GetConfigurationLabel() const; /// One-sentence description of the reader's loading "strategy" void SetConfigurationDescription( const std::string& ); /// One-sentence description of the reader's loading "strategy" std::string GetConfigurationDescription() const; /// Print configuration description to given stream, for human reader void PrintConfiguration( std::ostream& os ) const; /// Print output description to given stream, for human reader void PrintOutputs( std::ostream& os, bool filenameDetails = false ) const; virtual bool operator==( const DICOMFileReader& other ) const = 0; + /** Type specifies additional tags of interest. Key is the tag path of interest. + * The value is an optional user defined name for the property that should be used to store the tag value(s). + * Empty value is default and will imply to use the found DICOMTagPath as property name.*/ + typedef DICOMImageBlockDescriptor::AdditionalTagsMapType AdditionalTagsMapType; /** - * \brief Set a list of DICOM-Tags that will be copied into the property of the mitk::Image. + * \brief Set a list of DICOMTagPaths that specifiy all DICOM-Tags that will be copied into the property of the mitk::Image. * * This method can be used to specify a list of DICOM-tags that shall be available after the loading. - * The content of the DICOM tags will be stored in a StringLookupTable on the mitk::Image, - * where the property-key equals the key in the unordered_map. + * The value in the tagMap is an optional user defined name for the property key that should be used + * when storing the property). Empty value is default and will imply to use the found DICOMTagPath + * as property key. + * By default the content of the DICOM tags will be stored in a StringLookupTable on the mitk::Image. + * This behaviour can be changed by setting a different TagLookupTableToPropertyFunctor via + * SetTagLookupTableToPropertyFunctor(). */ - virtual void SetAdditionalTagsOfInterest( const std::unordered_map& tagList ); + virtual void SetAdditionalTagsOfInterest(const AdditionalTagsMapType& tagList); /** * \brief Set a functor that defines how the slice-specific tag-values are stored in a Property. * * This method sets a functor that is given a StringLookupTable that contains the values of one DICOM tag * mapped to the slice index. * The functor is supposed to store these values in an mitk Property. * * By default, the StringLookupTable is stored in a StringLookupTableProperty except if all values are * identical. In this case, the unique value is stored only once in a StringProperty. */ virtual void SetTagLookupTableToPropertyFunctor( mitk::DICOMImageBlockDescriptor::TagLookupTableToPropertyFunctor functor ); protected: DICOMFileReader(); virtual ~DICOMFileReader(); DICOMFileReader( const DICOMFileReader& other ); DICOMFileReader& operator=( const DICOMFileReader& other ); void ClearOutputs(); void SetNumberOfOutputs( unsigned int numberOfOutputs ); void SetOutput( unsigned int index, const DICOMImageBlockDescriptor& output ); /// non-const access to the DICOMImageBlockDescriptor DICOMImageBlockDescriptor& InternalGetOutput( unsigned int index ); /// Configuration description for human reader, to be implemented by sub-classes virtual void InternalPrintConfiguration( std::ostream& os ) const = 0; - virtual std::unordered_map GetAdditionalTagsOfInterest() const; + virtual AdditionalTagsMapType GetAdditionalTagsOfInterest() const; mitk::DICOMImageBlockDescriptor::TagLookupTableToPropertyFunctor GetTagLookupTableToPropertyFunctor() const; private: StringList m_InputFilenames; std::vector m_Outputs; std::string m_ConfigLabel; std::string m_ConfigDescription; - std::unordered_map m_AdditionalTagsOfInterest; + AdditionalTagsMapType m_AdditionalTagsOfInterest; mitk::DICOMImageBlockDescriptor::TagLookupTableToPropertyFunctor m_TagLookupTableToPropertyFunctor; }; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMGDCMImageFrameInfo.h b/Modules/DICOMReader/include/mitkDICOMGDCMImageFrameInfo.h index 6ba896df1c..cf0c4d30c3 100644 --- a/Modules/DICOMReader/include/mitkDICOMGDCMImageFrameInfo.h +++ b/Modules/DICOMReader/include/mitkDICOMGDCMImageFrameInfo.h @@ -1,71 +1,64 @@ /*=================================================================== 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 mitkDICOMGDCMImageFrameInfo_h #define mitkDICOMGDCMImageFrameInfo_h -#include "mitkDICOMImageFrameInfo.h" -#include "mitkDICOMDatasetAccess.h" +#include "mitkDICOMDatasetAccessingImageFrameInfo.h" #include "gdcmScanner.h" namespace mitk { /** \ingroup DICOMReaderModule \brief The dataset access implementation for DICOMITKSeriesGDCMReader, based on GDCM. This class combines a DICOMImageFrameInfo object with the scanning results from gdcm::Scanner. The scanning results will be used to implement the tag access methods of DICOMDatasetAccess. */ - class MITKDICOMREADER_EXPORT DICOMGDCMImageFrameInfo : public itk::LightObject, public DICOMDatasetAccess + class MITKDICOMREADER_EXPORT DICOMGDCMImageFrameInfo : public DICOMDatasetAccessingImageFrameInfo { public: - mitkClassMacroItkParent(DICOMGDCMImageFrameInfo, itk::LightObject); + mitkClassMacro(DICOMGDCMImageFrameInfo, DICOMDatasetAccessingImageFrameInfo); itkFactorylessNewMacro( DICOMGDCMImageFrameInfo ); mitkNewMacro1Param( DICOMGDCMImageFrameInfo, const std::string&); mitkNewMacro2Param( DICOMGDCMImageFrameInfo, const std::string&, unsigned int ); mitkNewMacro1Param( DICOMGDCMImageFrameInfo, const DICOMImageFrameInfo::Pointer& ); mitkNewMacro2Param( DICOMGDCMImageFrameInfo, const DICOMImageFrameInfo::Pointer&, gdcm::Scanner::TagToValue const&); virtual ~DICOMGDCMImageFrameInfo(); virtual DICOMDatasetFinding GetTagValueAsString(const DICOMTag&) const override; - std::string GetFilenameIfAvailable() const override; + virtual FindingsListType GetTagValueAsString(const DICOMTagPath& path) const override; - /// The frame that this objects refers to - DICOMImageFrameInfo::Pointer GetFrameInfo() const; - /// The frame that this objects refers to - void SetFrameInfo(DICOMImageFrameInfo::Pointer frameinfo); + std::string GetFilenameIfAvailable() const override; protected: - - DICOMImageFrameInfo::Pointer m_FrameInfo; - DICOMGDCMImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo); DICOMGDCMImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo, gdcm::Scanner::TagToValue const& tagToValueMapping); DICOMGDCMImageFrameInfo(const std::string& filename = "", unsigned int frameNo = 0); const gdcm::Scanner::TagToValue m_TagForValue; }; typedef std::vector DICOMGDCMImageFrameList; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMGDCMTagCache.h b/Modules/DICOMReader/include/mitkDICOMGDCMTagCache.h new file mode 100644 index 0000000000..f6ee4df851 --- /dev/null +++ b/Modules/DICOMReader/include/mitkDICOMGDCMTagCache.h @@ -0,0 +1,68 @@ +/*=================================================================== + +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 mitkDICOMGDCMTagCache_h +#define mitkDICOMGDCMTagCache_h + +#include "mitkDICOMTagCache.h" + +#include +#include + +#include + +namespace mitk +{ + + /** + \ingroup DICOMReaderModule + \brief Tag cache implementation used by the DICOMGDCMTagScanner. + */ + class MITKDICOMREADER_EXPORT DICOMGDCMTagCache : public DICOMTagCache + { + public: + + mitkClassMacro(DICOMGDCMTagCache, DICOMTagCache); + itkFactorylessNewMacro( DICOMGDCMTagCache ); + itkCloneMacro(Self); + + virtual DICOMDatasetFinding GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const override; + + virtual FindingsListType GetTagValue(DICOMImageFrameInfo* frame, const DICOMTagPath& path) const override; + + virtual DICOMDatasetAccessingImageFrameList GetFrameInfoList() const override; + + void InitCache(const std::set& scannedTags, const std::shared_ptr& scanner, const StringList& inputFiles); + + const gdcm::Scanner& GetScanner() const; + + protected: + + DICOMGDCMTagCache(); + virtual ~DICOMGDCMTagCache(); + + std::set m_ScannedTags; + + std::shared_ptr m_Scanner; + + DICOMDatasetAccessingImageFrameList m_ScanResult; + + private: + DICOMGDCMTagCache(const DICOMGDCMTagCache&); + }; +} + +#endif diff --git a/Modules/DICOMReader/include/mitkDICOMGDCMTagScanner.h b/Modules/DICOMReader/include/mitkDICOMGDCMTagScanner.h index cb35c334cf..4a3289bed3 100644 --- a/Modules/DICOMReader/include/mitkDICOMGDCMTagScanner.h +++ b/Modules/DICOMReader/include/mitkDICOMGDCMTagScanner.h @@ -1,107 +1,123 @@ /*=================================================================== 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 mitkDICOMGDCMTagScanner_h #define mitkDICOMGDCMTagScanner_h -#include "mitkDICOMTagCache.h" +#include "mitkDICOMTagScanner.h" #include "mitkDICOMEnums.h" - -#include "mitkDICOMGDCMImageFrameInfo.h" - -#include +#include "mitkDICOMGDCMTagCache.h" namespace mitk { /** \ingroup DICOMReaderModule \brief Encapsulates the tag scanning process for a set of DICOM files. Formerly integrated as a part of DICOMITKSeriesGDCMReader, the tag scanning part has been factored out into this DICOMGDCMTagScanner class in order to allow a single scan for multiple reader alternatives. This helps much in the selection process of e.g. DICOMFileReaderSelector. The class works similar to gdcm::Scanner, just with the MITK set of classes: - add a number of DICOM tags that should be read - set a list of files that should be scanned for named tags - call Scan() - retrieve the scan results - via GetFrameInfoList() or - via GetTagValue() When used in a process where multiple classes will access the scan results, care should be taken that all the tags and files of interest are communicated to DICOMGDCMTagScanner before requesting the results! + + @remark This scanner does only support the scanning for simple value tag. + If you need to scann for sequence items or non-top-level elements, this scanner + will not be sufficient. See i.a. DICOMDCMTKTagScanner for these cases. */ - class MITKDICOMREADER_EXPORT DICOMGDCMTagScanner : public DICOMTagCache + class MITKDICOMREADER_EXPORT DICOMGDCMTagScanner : public DICOMTagScanner { public: - mitkClassMacro( DICOMGDCMTagScanner, DICOMTagCache ); + mitkClassMacro(DICOMGDCMTagScanner, DICOMTagScanner); itkFactorylessNewMacro( DICOMGDCMTagScanner ); itkCloneMacro(Self); /** \brief Add this tag to the scanning process. */ - virtual void AddTag(const DICOMTag& tag); + virtual void AddTag(const DICOMTag& tag) override; /** \brief Add a list of tags to the scanning process. */ - virtual void AddTags(const DICOMTagList& tags); + virtual void AddTags(const DICOMTagList& tags) override; + /** + \brief Add this tag path to the scanning process. + */ + virtual void AddTagPath(const DICOMTagPath& tag) override; + /** + \brief Add a list of tag pathes to the scanning process. + */ + virtual void AddTagPaths(const DICOMTagPathList& tags) override; /** \brief Define the list of files to scan. This does not ADD to an internal list, but it replaces the whole list of files. */ - virtual void SetInputFiles(const StringList& filenames); + virtual void SetInputFiles(const StringList& filenames) override; /** \brief Start the scanning process. Calling Scan() will invalidate previous scans, forgetting all about files and tags from files that have been scanned previously. */ virtual void Scan(); /** \brief Retrieve a result list for file-by-file tag access. */ - virtual DICOMGDCMImageFrameList GetFrameInfoList() const; + virtual DICOMDatasetAccessingImageFrameList GetFrameInfoList() const override; + + /** + \brief Retrieve Pointer to the complete cache of the scan. + */ + virtual DICOMTagCache::Pointer GetScanCache() const override; /** \brief Directly retrieve the tag value for a given frame and tag. + @pre Scan() must have been called before calling this function. */ - virtual DICOMDatasetFinding GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const override; + virtual DICOMDatasetFinding GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const; - protected: + protected: DICOMGDCMTagScanner(); - DICOMGDCMTagScanner(const DICOMGDCMTagScanner&); virtual ~DICOMGDCMTagScanner(); std::set m_ScannedTags; - - gdcm::Scanner m_GDCMScanner; StringList m_InputFilenames; - DICOMGDCMImageFrameList m_ScanResult; + DICOMGDCMTagCache::Pointer m_Cache; + std::shared_ptr m_GDCMScanner; + + private: + DICOMGDCMTagScanner(const DICOMGDCMTagScanner&); }; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMGenericImageFrameInfo.h b/Modules/DICOMReader/include/mitkDICOMGenericImageFrameInfo.h new file mode 100644 index 0000000000..2525ed4398 --- /dev/null +++ b/Modules/DICOMReader/include/mitkDICOMGenericImageFrameInfo.h @@ -0,0 +1,69 @@ +/*=================================================================== + +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 mitkDICOMGenericImageFrameInfo_h +#define mitkDICOMGenericImageFrameInfo_h + +#include "mitkDICOMDatasetAccessingImageFrameInfo.h" + +#include + +namespace mitk +{ + /** + \ingroup DICOMReaderModule + \brief A generic storage class for image frame info with data access. + */ + class MITKDICOMREADER_EXPORT DICOMGenericImageFrameInfo : public DICOMDatasetAccessingImageFrameInfo + { + public: + + mitkClassMacro(DICOMGenericImageFrameInfo, DICOMDatasetAccessingImageFrameInfo); + itkFactorylessNewMacro( DICOMGenericImageFrameInfo ); + mitkNewMacro1Param( DICOMGenericImageFrameInfo, const std::string&); + mitkNewMacro2Param( DICOMGenericImageFrameInfo, const std::string&, unsigned int ); + mitkNewMacro1Param( DICOMGenericImageFrameInfo, const DICOMImageFrameInfo::Pointer& ); + + virtual ~DICOMGenericImageFrameInfo(); + + virtual DICOMDatasetFinding GetTagValueAsString(const DICOMTag&) const override; + + virtual FindingsListType GetTagValueAsString(const DICOMTagPath& path) const override; + + std::string GetFilenameIfAvailable() const override; + + /** Sets the value for a passed tag path. If the tag path is already set, it will be overwritten + with the new value. + @pre Path must be explicit. No wildcards are allowd. + @post The passed value is set for the passed path. + */ + void SetTagValue(const DICOMTagPath& path, const std::string& value); + + protected: + typedef std::map ValueMapType; + ValueMapType m_Values; + + DICOMGenericImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo); + DICOMGenericImageFrameInfo(const std::string& filename = "", unsigned int frameNo = 0); + + private: + Self& operator = (const Self&); + DICOMGenericImageFrameInfo(const Self&); + }; + +} + +#endif diff --git a/Modules/DICOMReader/include/mitkDICOMGenericTagCache.h b/Modules/DICOMReader/include/mitkDICOMGenericTagCache.h new file mode 100644 index 0000000000..6b2cf9a13b --- /dev/null +++ b/Modules/DICOMReader/include/mitkDICOMGenericTagCache.h @@ -0,0 +1,59 @@ +/*=================================================================== + +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 mitkDICOMGenericTagCache_h +#define mitkDICOMGenericTagCache_h + +#include "mitkDICOMTagCache.h" +#include "mitkDICOMGenericImageFrameInfo.h" + +namespace mitk +{ + + /** + \ingroup DICOMReaderModule + \brief Generic tag cache implementation. + */ + class MITKDICOMREADER_EXPORT DICOMGenericTagCache : public DICOMTagCache + { + public: + + mitkClassMacro(DICOMGenericTagCache, DICOMTagCache); + itkFactorylessNewMacro( DICOMGenericTagCache ); + itkCloneMacro(Self); + + virtual DICOMDatasetFinding GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const override; + + virtual FindingsListType GetTagValue(DICOMImageFrameInfo* frame, const DICOMTagPath& path) const override; + + virtual DICOMDatasetAccessingImageFrameList GetFrameInfoList() const override; + + void AddFrameInfo(DICOMDatasetAccessingImageFrameInfo* info); + void Reset(); + + protected: + + DICOMGenericTagCache(); + virtual ~DICOMGenericTagCache(); + + DICOMDatasetAccessingImageFrameList m_ScanResult; + + private: + DICOMGenericTagCache(const DICOMGenericTagCache&); + }; +} + +#endif diff --git a/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h b/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h index 803b07f91e..6bfd9735a4 100644 --- a/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h +++ b/Modules/DICOMReader/include/mitkDICOMITKSeriesGDCMReader.h @@ -1,350 +1,343 @@ /*=================================================================== 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 mitkDICOMITKSeriesGDCMReader_h #define mitkDICOMITKSeriesGDCMReader_h #include #include "itkMutexLock.h" #include "mitkDICOMFileReader.h" #include "mitkDICOMDatasetSorter.h" #include "mitkDICOMGDCMImageFrameInfo.h" #include "mitkEquiDistantBlocksSorter.h" #include "mitkNormalDirectionConsistencySorter.h" #include "MitkDICOMReaderExports.h" namespace itk { class TimeProbesCollectorBase; } namespace mitk { /** \ingroup DICOMReaderModule \brief Flexible reader based on itk::ImageSeriesReader and GDCM, for single-slice modalities like CT, MR, PET, CR, etc. Implements the loading processed as structured by DICOMFileReader offers configuration of its loading strategy. Documentation sections: - \ref DICOMITKSeriesGDCMReader_LoadingStrategy - \ref DICOMITKSeriesGDCMReader_ForcedConfiguration - \ref DICOMITKSeriesGDCMReader_UserConfiguration - \ref DICOMITKSeriesGDCMReader_GantryTilt - \ref DICOMITKSeriesGDCMReader_Testing - \ref DICOMITKSeriesGDCMReader_Internals - \ref DICOMITKSeriesGDCMReader_RelatedClasses - \ref DICOMITKSeriesGDCMReader_TiltInternals - \ref DICOMITKSeriesGDCMReader_Condensing \section DICOMITKSeriesGDCMReader_LoadingStrategy Loading strategy The set of input files is processed by a number of DICOMDatasetSorter objects which may do two sort of things: 1. split a list of input frames into multiple lists, based on DICOM tags such as "Rows", "Columns", which cannot be mixed within a single mitk::Image 2. sort the frames within the input lists, based on the values of DICOM tags such as "Image Position Patient" When the DICOMITKSeriesGDCMReader is configured with DICOMDatasetSorter%s, the list of input files is processed as follows: 1. build an initial set of output groups, simply by grouping all input files. 2. for each configured DICOMDatasetSorter, process: - for each output group: 1. set this group's files as input to the sorter 2. let the sorter sort (and split) 3. integrate the sorter's output groups with our own output groups \section DICOMITKSeriesGDCMReader_ForcedConfiguration Forced Configuration In all cases, the reader will add two DICOMDatasetSorter objects that are required to load mitk::Images properly via itk::ImageSeriesReader: 1. As a \b first step, the input files will be split into groups that are not compatible because they differ in essential aspects: - (0028,0010) Number of Rows - (0028,0011) Number of Columns - (0028,0030) Pixel Spacing - (0018,1164) Imager Pixel Spacing - (0020,0037) %Image Orientation (Patient) - (0018,0050) Slice Thickness - (0028,0008) Number of Frames 2. As are two forced \b last steps: 1. There will always be an instance of EquiDistantBlocksSorter, which ensures that there is an equal distance between all the frames of an Image. This is required to achieve correct geometrical positions in the mitk::Image, i.e. it is essential to be able to make measurements in images. - whether or not the distance is required to be orthogonal to the image planes is configured by SetFixTiltByShearing(). - during this check, we need to tolerate some minor errors in documented vs. calculated image origins. The amount of tolerance can be adjusted by SetToleratedOriginOffset() and SetToleratedOriginOffsetToAdaptive(). Please see EquiDistantBlocksSorter for more details. The default should be good for most cases. 2. There is always an instance of NormalDirectionConsistencySorter, which makes the order of images go along the image normals (see NormalDirectionConsistencySorter) \section DICOMITKSeriesGDCMReader_UserConfiguration User Configuration The user of this class can add more sorting steps (similar to the one described in above section) by calling AddSortingElement(). Usually, an application will add sorting by "Image Position Patient", by "Instance Number", and by other relevant tags here. \section DICOMITKSeriesGDCMReader_GantryTilt Gantry tilt handling When CT gantry tilt is used, the gantry plane (= X-Ray source and detector ring) and the vertical plane do not align anymore. This scanner feature is used for example to reduce metal artifacs (e.g. Lee C , Evaluation of Using CT Gantry Tilt Scan on Head and Neck Cancer Patients with Dental Structure: Scans Show Less Metal Artifacts. Presented at: Radiological Society of North America 2011 Scientific Assembly and Annual Meeting; November 27- December 2, 2011 Chicago IL.). The acquired planes of such CT series do not match the expectations of a orthogonal geometry in mitk::Image: if you stack the slices, they show a small shift along the Y axis: \verbatim without tilt with tilt |||||| ////// |||||| ////// -- |||||| --------- ////// -------- table orientation |||||| ////// |||||| ////// Stacked slices: without tilt with tilt -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- -------------- \endverbatim As such gemetries do not "work" in conjunction with mitk::Image, DICOMITKSeriesGDCMReader is able to perform a correction for such series. Whether or not such correction should be attempted is controlled by SetFixTiltByShearing(), the default being correction. For details, see "Internals" below. \section DICOMITKSeriesGDCMReader_Testing Testing A number of tests is implemented in module DICOMTesting, which is documented at \ref DICOMTesting. \section DICOMITKSeriesGDCMReader_Internals Class internals Internally, the class is based on GDCM and it depends heavily on the gdcm::Scanner class. Since the sorting elements (see DICOMDatasetSorter and DICOMSortCriterion) can access tags only via the DICOMDatasetAccess interface, BUT DICOMITKSeriesGDCMReader holds a list of more specific classes DICOMGDCMImageFrameInfo, we must convert between the two types sometimes. This explains the methods ToDICOMDatasetList(), FromDICOMDatasetList(). The intermediate result of all the sorting efforts is held in m_SortingResultInProgress, which is modified through InternalExecuteSortingStep(). \subsection DICOMITKSeriesGDCMReader_RelatedClasses Overview of related classes The following diagram gives an overview of the related classes: \image html implementeditkseriesgdcmreader.jpg \subsection DICOMITKSeriesGDCMReader_TiltInternals Details about the tilt correction The gantry tilt "correction" algorithm fixes two errors introduced by ITK's ImageSeriesReader: - the plane shift that is ignored by ITK's reader is recreated by applying a shearing transformation using itk::ResampleFilter. - the spacing is corrected (it is calculated by ITK's reader from the distance between two origins, which is NOT the slice distance in this special case) Both errors are introduced in itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20) For the correction, we examine two consecutive slices of a series, both described as a pair (origin/orientation): - we calculate if the first origin is on a line along the normal of the second slice - if this is not the case, the geometry will not fit a normal mitk::Image/mitk::Geometry3D - we then project the second origin into the first slice's coordinate system to quantify the shift - both is done in class GantryTiltInformation with quite some comments. The geometry of image stacks with tilted geometries is illustrated below: - green: the DICOM images as described by their tags: origin as a point with the line indicating the orientation - red: the output of ITK ImageSeriesReader: wrong, larger spacing, no tilt - blue: how much a shear must correct \image html tilt-correction.jpg \subsection DICOMITKSeriesGDCMReader_Condensing Sub-classes can condense multiple blocks into a single larger block The sorting/splitting process described above is helpful for at least two more DICOM readers, which either try to load 3D+t images or which load diffusion data. In both cases, a single pixel of the mitk::Image is made up of multiple values, in one case values over time, in the other case multiple measurements of a single point. The specialized readers for these cases (e.g. ThreeDnTDICOMSeriesReader) can reuse most of the methods in DICOMITKSeriesGDCMReader, except that they need an extra step after the usual sorting, in which they can merge already grouped 3D blocks. What blocks are merged depends on the specialized reader's understanding of these images. To allow for such merging, a method Condense3DBlocks() is called as an absolute last step of AnalyzeInputFiles(). Given this, a sub-class could implement only LoadImages() and Condense3DBlocks() instead repeating most of AnalyzeInputFiles(). */ class MITKDICOMREADER_EXPORT DICOMITKSeriesGDCMReader : public DICOMFileReader { public: mitkClassMacro( DICOMITKSeriesGDCMReader, DICOMFileReader ); mitkCloneMacro( DICOMITKSeriesGDCMReader ); itkFactorylessNewMacro( DICOMITKSeriesGDCMReader ); mitkNewMacro1Param( DICOMITKSeriesGDCMReader, unsigned int ); /** \brief Runs the sorting / splitting process described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. Method required by DICOMFileReader. */ virtual void AnalyzeInputFiles() override; // void AllocateOutputImages(); /** \brief Loads images using itk::ImageSeriesReader, potentially applies shearing to correct gantry tilt. */ virtual bool LoadImages() override; // re-implemented from super-class virtual bool CanHandleFile(const std::string& filename) override; /** \brief Add an element to the sorting procedure described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy. */ virtual void AddSortingElement(DICOMDatasetSorter* sorter, bool atFront = false); typedef const std::list ConstSorterList; ConstSorterList GetFreelyConfiguredSortingElements() const; /** \brief Controls whether to "fix" tilted acquisitions by shearing the output (see \ref DICOMITKSeriesGDCMReader_GantryTilt). */ void SetFixTiltByShearing(bool on); bool GetFixTiltByShearing() const; /** \brief Controls whether groups of only two images are accepted when ensuring consecutive slices via EquiDistantBlocksSorter. */ void SetAcceptTwoSlicesGroups(bool accept) const; bool GetAcceptTwoSlicesGroups() const; /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistanct = 0.3) const; /** \brief See \ref DICOMITKSeriesGDCMReader_ForcedConfiguration. */ void SetToleratedOriginOffset(double millimeters = 0.005) const; double GetToleratedOriginError() const; bool IsToleratedOriginOffsetAbsolute() const; double GetDecimalPlacesForOrientation() const; virtual bool operator==(const DICOMFileReader& other) const override; - virtual DICOMTagList GetTagsOfInterest() const override; + virtual DICOMTagPathList GetTagsOfInterest() const override; protected: virtual void InternalPrintConfiguration(std::ostream& os) const override; /// \brief Return active C locale static std::string GetActiveLocale(); /** \brief Remember current locale on stack, activate "C" locale. "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PushLocale() const; /** \brief Activate last remembered locale from locale stack "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader */ void PopLocale() const; DICOMITKSeriesGDCMReader(unsigned int decimalPlacesForOrientation = 5); virtual ~DICOMITKSeriesGDCMReader(); DICOMITKSeriesGDCMReader(const DICOMITKSeriesGDCMReader& other); DICOMITKSeriesGDCMReader& operator=(const DICOMITKSeriesGDCMReader& other); - /// \brief See \ref DICOMITKSeriesGDCMReader_Internals - static DICOMDatasetList ToDICOMDatasetList(const DICOMGDCMImageFrameList& input); - /// \brief See \ref DICOMITKSeriesGDCMReader_Internals - static DICOMGDCMImageFrameList FromDICOMDatasetList(const DICOMDatasetList& input); - /// \brief See \ref DICOMITKSeriesGDCMReader_Internals - static DICOMImageFrameList ToDICOMImageFrameList(const DICOMGDCMImageFrameList& input); - - typedef std::vector SortingBlockList; + typedef std::vector SortingBlockList; /** \brief "Hook" for sub-classes, see \ref DICOMITKSeriesGDCMReader_Condensing \return REMAINING blocks */ virtual SortingBlockList Condense3DBlocks(SortingBlockList& resultOf3DGrouping); virtual DICOMTagCache::Pointer GetTagCache() const; void SetTagCache( const DICOMTagCache::Pointer& ) override; /// \brief Sorting step as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy static SortingBlockList InternalExecuteSortingStep( unsigned int sortingStepIndex, const DICOMDatasetSorter::Pointer& sorter, const SortingBlockList& input); /// \brief Loads the mitk::Image by means of an itk::ImageSeriesReader virtual bool LoadMitkImageForOutput(unsigned int o); virtual bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const; /// \brief Describe this reader's confidence for given SOP class UID static ReaderImplementationLevel GetReaderImplementationLevel(const std::string sopClassUID); private: /// \brief Creates the required sorting steps described in \ref DICOMITKSeriesGDCMReader_ForcedConfiguration void EnsureMandatorySortersArePresent(unsigned int decimalPlacesForOrientation); protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader due to lack of time bool m_FixTiltByShearing; // could be removed by ITKDICOMSeriesReader NOT flagging tilt unless requested to fix it! private: SortingBlockList m_SortingResultInProgress; typedef std::list SorterList; SorterList m_Sorter; protected: // NOT nice, made available to ThreeDnTDICOMSeriesReader and ClassicDICOMSeriesReader due to lack of time mitk::EquiDistantBlocksSorter::Pointer m_EquiDistantBlocksSorter; mitk::NormalDirectionConsistencySorter::Pointer m_NormalDirectionConsistencySorter; private: static itk::MutexLock::Pointer s_LocaleMutex; mutable std::stack m_ReplacedCLocales; mutable std::stack m_ReplacedCinLocales; double m_DecimalPlacesForOrientation; DICOMTagCache::Pointer m_TagCache; bool m_ExternalCache; }; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMImageBlockDescriptor.h b/Modules/DICOMReader/include/mitkDICOMImageBlockDescriptor.h index e1d387ec30..985392574b 100644 --- a/Modules/DICOMReader/include/mitkDICOMImageBlockDescriptor.h +++ b/Modules/DICOMReader/include/mitkDICOMImageBlockDescriptor.h @@ -1,222 +1,231 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkDICOMImageBlockDescriptor_h #define mitkDICOMImageBlockDescriptor_h #include "mitkDICOMEnums.h" #include "mitkDICOMImageFrameInfo.h" #include "mitkDICOMTag.h" #include "mitkDICOMTagCache.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkWeakPointer.h" #include "mitkGantryTiltInformation.h" #include namespace mitk { struct DICOMCachedValueInfo { unsigned int TimePoint; unsigned int SliceInTimePoint; std::string Value; }; class DICOMCachedValueLookupTable : public GenericLookupTable< DICOMCachedValueInfo > { public: typedef DICOMCachedValueLookupTable Self; typedef GenericLookupTable< DICOMCachedValueInfo > Superclass; virtual const char *GetNameOfClass() const { return "DICOMCachedValueLookupTable"; } DICOMCachedValueLookupTable() {} virtual Superclass& operator=(const Superclass& other) { return Superclass::operator=(other); } virtual ~DICOMCachedValueLookupTable() {} }; /** \ingroup DICOMReaderModule \brief Output descriptor for DICOMFileReader. As a result of analysis by a mitk::DICOMFileReader, this class describes the properties of a single mitk::Images that could be loaded by the file reader. The descriptor contains the following information: - the mitk::Image itself. This will be NULL after analysis and only be present after actual loading. - a list of frames (mostly: filenames) that went into composition of the mitk::Image. - an assessment of the reader's ability to load this set of files (ReaderImplementationLevel) - this can be used for reader selection when one reader is able to load an image with correct colors and the other is able to produce only gray values, for example - description of aspects of the image. Mostly a key-value list implemented by means of mitk::PropertyList. - for specific keys and possible values, see documentation of specific readers. \note an mitk::Image may both consist of multiple files (the "old" DICOM way) or a mitk::Image may be described by a single DICOM file or even only parts of a DICOM file (the newer multi-frame DICOM classes). To reflect this DICOMImageFrameList describes a list of frames from different or a single file. Described aspects of an image are: - whether pixel spacing is meant to be in-patient or on-detector (mitk::PixelSpacingInterpretation) - details about a possible gantry tilt (intended for use by file readers, may be hidden later) */ class MITKDICOMREADER_EXPORT DICOMImageBlockDescriptor { public: DICOMImageBlockDescriptor(); ~DICOMImageBlockDescriptor(); DICOMImageBlockDescriptor(const DICOMImageBlockDescriptor& other); DICOMImageBlockDescriptor& operator=(const DICOMImageBlockDescriptor& other); static DICOMTagList GetTagsOfInterest(); /// List of frames that constitute the mitk::Image (DICOMImageFrame%s) void SetImageFrameList(const DICOMImageFrameList& framelist); /// List of frames that constitute the mitk::Image (DICOMImageFrame%s) const DICOMImageFrameList& GetImageFrameList() const; /// The 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList void SetMitkImage(Image::Pointer image); /// the 3D mitk::Image that is loaded from the DICOM files of a DICOMImageFrameList Image::Pointer GetMitkImage() const; /// Reader's capability to appropriately load this set of frames ReaderImplementationLevel GetReaderImplementationLevel() const; /// Reader's capability to appropriately load this set of frames void SetReaderImplementationLevel(const ReaderImplementationLevel& level); /// Key-value store describing aspects of the image to be loaded void SetProperty(const std::string& key, BaseProperty* value); /// Key-value store describing aspects of the image to be loaded BaseProperty* GetProperty(const std::string& key) const; /// Convenience function around GetProperty() std::string GetPropertyAsString(const std::string&) const; /// Convenience function around SetProperty() void SetFlag(const std::string& key, bool value); /// Convenience function around GetProperty() bool GetFlag(const std::string& key, bool defaultValue) const; /// Convenience function around SetProperty() void SetIntProperty(const std::string& key, int value); /// Convenience function around GetProperty() int GetIntProperty(const std::string& key, int defaultValue) const; private: // For future implementation: load slice-by-slice, mark this using these methods void SetSliceIsLoaded(unsigned int index, bool isLoaded); // For future implementation: load slice-by-slice, mark this using these methods bool IsSliceLoaded(unsigned int index) const; // For future implementation: load slice-by-slice, mark this using these methods bool AllSlicesAreLoaded() const; public: /// Describe how the mitk::Image's pixel spacing should be interpreted PixelSpacingInterpretation GetPixelSpacingInterpretation() const; /// Describe the correct x/y pixel spacing of the mitk::Image (which some readers might need to adjust after loading) void GetDesiredMITKImagePixelSpacing(ScalarType& spacingXinMM, ScalarType& spacingYinMM) const; /// Describe the gantry tilt of the acquisition void SetTiltInformation(const GantryTiltInformation& info); /// Describe the gantry tilt of the acquisition const GantryTiltInformation GetTiltInformation() const; /// SOP Class UID of this set of frames void SetSOPClassUID(const std::string& uid); /// SOP Class UID of this set of frames std::string GetSOPClassUID() const; /// SOP Class as human readable name (e.g. "CT Image Storage") std::string GetSOPClassUIDAsName() const; /**Convinience method that returns the property timesteps*/ int GetNumberOfTimeSteps() const; /**return the number of frames that constitute one timestep.*/ int GetNumberOfFramesPerTimeStep() const; void SetTagCache(DICOMTagCache* privateCache); + /** Type specifies additional tags of interest. Key is the tag path of interest. + * The value is an optional user defined name for the property that should be used to store the tag value(s). + * Empty value is default and will imply to use the found DICOMTagPath as property name.*/ + typedef std::map AdditionalTagsMapType; /** - * \brief Set a list of DICOM-Tags that will be copied into the property of the mitk::Image. + * \brief Set a list of DICOMTagPaths that specifiy all DICOM-Tags that will be copied into the property of the mitk::Image. * * This method can be used to specify a list of DICOM-tags that shall be available after the loading. - * The content of the DICOM tags will be stored in a StringLookupTable on the mitk::Image, - * where the property-key equals the key in the unordered_map. + * The value in the tagMap is an optional user defined name for the property key that should be used + * when storing the property). Empty value is default and will imply to use the found DICOMTagPath + * as property key. + * By default the content of the DICOM tags will be stored in a StringLookupTable on the mitk::Image. + * This behaviour can be changed by setting a different TagLookupTableToPropertyFunctor via + * SetTagLookupTableToPropertyFunctor(). */ - void SetAdditionalTagsOfInterest(const std::unordered_map& tagList); + void SetAdditionalTagsOfInterest(const AdditionalTagsMapType& tagMap); typedef std::function TagLookupTableToPropertyFunctor; /** * \brief Set a functor that defines how the slice-specific tag-values are stored in a Property. * * This method sets a functor that is given a StringLookupTable that contains the values of one DICOM tag * mapped to the slice index. * The functor is supposed to store these values in an mitk Property. * * By default, the StringLookupTable is stored in a StringLookupTableProperty except if all values are * identical. In this case, the unique value is stored only once in a StringProperty. */ void SetTagLookupTableToPropertyFunctor(TagLookupTableToPropertyFunctor); /// Print information about this image block to given stream void Print(std::ostream& os, bool filenameDetails) const; private: // read values from tag cache std::string GetPixelSpacing() const; std::string GetImagerPixelSpacing() const; Image::Pointer FixupSpacing(Image* mitkImage); Image::Pointer DescribeImageWithProperties(Image* mitkImage); void UpdateImageDescribingProperties() const; static mitk::BaseProperty::Pointer GetPropertyForDICOMValues(const DICOMCachedValueLookupTable& cacheLookupTable); double stringtodouble(const std::string& str) const; DICOMImageFrameList m_ImageFrameList; Image::Pointer m_MitkImage; BoolList m_SliceIsLoaded; ReaderImplementationLevel m_ReaderImplementationLevel; GantryTiltInformation m_TiltInformation; PropertyList::Pointer m_PropertyList; mitk::WeakPointer m_TagCache; mutable bool m_PropertiesOutOfDate; - std::unordered_map m_AdditionalTagList; + AdditionalTagsMapType m_AdditionalTagMap; + std::set m_FoundAdditionalTags; TagLookupTableToPropertyFunctor m_PropertyFunctor; }; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMProperty.h b/Modules/DICOMReader/include/mitkDICOMProperty.h index 16d8f4f0d1..3b062dd9aa 100644 --- a/Modules/DICOMReader/include/mitkDICOMProperty.h +++ b/Modules/DICOMReader/include/mitkDICOMProperty.h @@ -1,35 +1,47 @@ /*=================================================================== 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 mitkDICOMProperty_h #define mitkDICOMProperty_h #include "mitkDICOMImageBlockDescriptor.h" #include "mitkTemporoSpatialStringProperty.h" +#include "mitkDICOMTagPath.h" #include "MitkDICOMReaderExports.h" namespace mitk { typedef TemporoSpatialStringProperty DICOMProperty; /** Generation functor for DICOMFileReader classes to convert the collected tag values into DICOMProperty instances. */ MITKDICOMREADER_EXPORT mitk::BaseProperty::Pointer GetDICOMPropertyForDICOMValuesFunctor(const DICOMCachedValueLookupTable& cacheLookupTable); + + class PropertyList; + class BaseData; + + /** Helper function that searches for all properties in a given property list that matches the passed path. + * The result will be the matching properties in a map*/ + MITKDICOMREADER_EXPORT std::map< std::string, BaseProperty::Pointer> GetPropertyByDICOMTagPath(const PropertyList* list, const DICOMTagPath& path); + /** Helper function that searches for all properties in a given base data that matches the passed path. + * The result will be the matching properties in a map*/ + MITKDICOMREADER_EXPORT std::map< std::string, BaseProperty::Pointer> GetPropertyByDICOMTagPath(const BaseData* data, const DICOMTagPath& path); + } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMTagCache.h b/Modules/DICOMReader/include/mitkDICOMTagCache.h index 4c74493c60..3335c2f93b 100644 --- a/Modules/DICOMReader/include/mitkDICOMTagCache.h +++ b/Modules/DICOMReader/include/mitkDICOMTagCache.h @@ -1,53 +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 mitkDICOMTagCache_h #define mitkDICOMTagCache_h #include "itkObjectFactory.h" #include "mitkCommon.h" #include "mitkDICOMTag.h" #include "mitkDICOMDatasetAccess.h" +#include "mitkDICOMEnums.h" +#include "mitkDICOMDatasetAccessingImageFrameInfo.h" #include "MitkDICOMReaderExports.h" namespace mitk { - class DICOMImageFrameInfo; - /** \ingroup DICOMReaderModule \brief ... */ class MITKDICOMREADER_EXPORT DICOMTagCache : public itk::Object { public: - mitkClassMacroItkParent( DICOMTagCache, itk::Object ); + typedef std::list FindingsListType; + + /** + \brief Define the list of files that were scanned to populate the cache. + */ + virtual void SetInputFiles(const StringList& filenames); + virtual DICOMDatasetFinding GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const = 0; - protected: + virtual FindingsListType GetTagValue(DICOMImageFrameInfo* frame, const DICOMTagPath& path) const = 0; + + /** + \brief Retrieve a result list for file-by-file tag access. + */ + virtual DICOMDatasetAccessingImageFrameList GetFrameInfoList() const = 0; + + protected: + StringList m_InputFilenames; DICOMTagCache(); DICOMTagCache(const DICOMTagCache&); virtual ~DICOMTagCache(); }; } #endif diff --git a/Modules/DICOMReader/include/mitkDICOMTagPath.h b/Modules/DICOMReader/include/mitkDICOMTagPath.h new file mode 100644 index 0000000000..c5173ee0fd --- /dev/null +++ b/Modules/DICOMReader/include/mitkDICOMTagPath.h @@ -0,0 +1,180 @@ +/*=================================================================== + +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 mitkDICOMTagPath_h +#define mitkDICOMTagPath_h + +#include + +#include + +#include + + +namespace mitk +{ + /** @brief Class is used to identify (nested) attributes in a DICOM dataset. + * In contrast to the class DICOMTag, which only specifies one specific tag, + * the tag path can identify nested attributes (like items in a DICOM sequence). + * In addition you may also specify wildcards for the selection index or + * complete elements of the path. + * @remark If you want to keep the DICOMTagPath compatible to the dcmtk search path + * format, you may *not* use element wild cards (this IsExplicit() or HasItemSelectionWildcardsOnly() + * must return true). + */ + class MITKDICOMREADER_EXPORT DICOMTagPath + { + public: + typedef int ItemSelectionIndex; + + struct MITKDICOMREADER_EXPORT NodeInfo + { + enum class NodeType + { + Invalid = 0, //*< Node is non existant or invalid. + Element, //*< Selects an specific element given the node name. + SequenceSelection, //*< Selects an specific item in a sequence of items and has a item selector ("[n]"). + AnySelection, //*< Selects all items of a specific element ("[*]"). + AnyElement, //*< Selects any element/item. Node name is wildcarded ("*"); item selection as well implictily. + }; + + NodeType type; + DICOMTag tag; + ItemSelectionIndex selection; + + NodeInfo(); + NodeInfo(const DICOMTag& tag, NodeType type = NodeType::Element, ItemSelectionIndex index = 0); + bool Matches(const NodeInfo& right) const; + + bool operator == (const NodeInfo& right) const; + }; + + typedef std::vector NodeInfoVectorType; + typedef NodeInfoVectorType::size_type PathIndexType; + + /** Returns if the DICOMTagPath is empty.*/ + bool IsEmpty() const; + + /** Returns if the path is explicit (has no wildcards).*/ + bool IsExplicit() const; + + /** Returns if the path has any nodes with item selection wild cards ([*]).*/ + bool HasItemSelectionWildcardsOnly() const; + + /** Number of path nodes the DICOMTagPath contains.*/ + PathIndexType Size() const; + + /** Adds a new node to the end of the path. + \param [in] newNode Reference to the node that should be added. + \return Returns the index of the newly added node.*/ + PathIndexType AddNode(const NodeInfo& newNode); + + /** Function returns the node info of a path node specified by the index + * within the DICOMTagPath. + * \pre Passed index must not be out of bound. + * \param [in] index Index of the node whose info should be retrieved. + * \return Info of the specified path node. If the index is out of bound an InvalidPathNode exception will be thrown.*/ + const NodeInfo& GetNode(const PathIndexType& index) const; + + /** Function returns the node info of a path node specified by the index + * within the DICOMTagPath. + * \pre Passed index must not be out of bound. + * \param [in] index Index of the node whose info should be retrieved. + * \return Info of the specified path node. If the index is out of bound an InvalidPathNode exception will be thrown.*/ + NodeInfo& GetNode(const PathIndexType& index); + + /** Function returns the node info of the first path node within the DICOMTagPath. + * \pre DICOMTagPath must not be empty. + * \return Info of the first path node. If the path is empty, an InvalidPathNode exception will be thrown.*/ + NodeInfo& GetFirstNode(); + + /** Function returns the node info of the first path node within the DICOMTagPath. + * \pre DICOMTagPath must not be empty. + * \return Info of the first path node. If the path is empty, an InvalidPathNode exception will be thrown.*/ + const NodeInfo& GetFirstNode() const; + + /** Function returns the node info of the last path node within the DICOMTagPath. + * \pre DICOMTagPath must not be empty. + * \return Info of the first path node. If the path is empty, an InvalidPathNode exception will be thrown.*/ + NodeInfo& GetLastNode(); + + /** Function returns the node info of the last path node within the DICOMTagPath. + * \pre DICOMTagPath must not be empty. + * \return Info of the first path node. If the path is empty, an InvalidPathNode exception will be thrown.*/ + const NodeInfo& GetLastNode() const; + + const NodeInfoVectorType& GetNodes() const; + + std::string ToStr() const; + + DICOMTagPath& FromStr(const std::string& pathStr); + + /**Compares two DICOMTagPaths for real equality. So its a string compare of their string conversion*/ + bool operator == (const DICOMTagPath& path) const; + + /**Operation equals like comparing the ToStr() results with operator <.*/ + bool operator < (const DICOMTagPath& right) const; + + /**Checks if to DICOMTagPathes are specify the same node. Hence all wildcards will be processed.\n + * E.G.: "item1/child1/grandChild2" == ".//item1//grandChild2" is true. + * \remark If you want to check if to pathes are "truely" equal and not only equal in terms of + * pointing to the same node, use the member function Equals()*/ + bool Equals(const DICOMTagPath& path) const; + + DICOMTagPath& operator = (const DICOMTagPath& path); + + DICOMTagPath& AddAnyElement(); + DICOMTagPath& AddElement(unsigned int group, unsigned int element); + DICOMTagPath& AddAnySelection(unsigned int group, unsigned int element); + DICOMTagPath& AddSelection(unsigned int group, unsigned int element, ItemSelectionIndex index); + + DICOMTagPath(); + DICOMTagPath(const DICOMTagPath& path); + DICOMTagPath(const DICOMTag& tag); + explicit DICOMTagPath(unsigned int group, unsigned int element); + + ~DICOMTagPath(); + + virtual void Reset(); + + protected: + NodeInfoVectorType m_NodeInfos; + + static bool DICOMTagPathesMatch(const DICOMTagPath& left, const DICOMTagPath& right); + }; + + typedef std::vector DICOMTagPathList; + + MITKDICOMREADER_EXPORT std::ostream& operator<<(std::ostream& os, const DICOMTagPath& path); + + MITKDICOMREADER_EXPORT std::string DICOMTagPathToPropertRegEx(const DICOMTagPath& tagPath); + MITKDICOMREADER_EXPORT std::string DICOMTagPathToPersistenceKeyRegEx(const DICOMTagPath& tagPath); + MITKDICOMREADER_EXPORT std::string DICOMTagPathToPersistenceKeyTemplate(const DICOMTagPath& tagPath); + MITKDICOMREADER_EXPORT std::string DICOMTagPathToPersistenceNameTemplate(const DICOMTagPath& tagPath); + + /** Converts a passed path into a search string for the DCMTK DcmPathProcessor. + @pre tagPath must be an explicit (DICOMTagPath::IsExplicit()) path or + must only contain selection wild cards (DICOMTagPath::HasItemSelectionWildcardsOnly()).*/ + MITKDICOMREADER_EXPORT std::string DICOMTagPathToDCMTKSearchPath(const DICOMTagPath& tagPath); + + /** Converts the passed property name into a tag path. If the property name cannot be converted + into a valid path, the returned path is empty.*/ + MITKDICOMREADER_EXPORT DICOMTagPath PropertyNameToDICOMTagPath(const std::string& propertyName); + /** returns the correct property name for a given DICOMTagPath instance. */ + MITKDICOMREADER_EXPORT std::string DICOMTagPathToPropertyName(const DICOMTagPath& tagPath); +} + +#endif diff --git a/Modules/DICOMReader/include/mitkDICOMTagScanner.h b/Modules/DICOMReader/include/mitkDICOMTagScanner.h new file mode 100644 index 0000000000..eccc604dc8 --- /dev/null +++ b/Modules/DICOMReader/include/mitkDICOMTagScanner.h @@ -0,0 +1,122 @@ +/*=================================================================== + +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 mitkDICOMTagScanner_h +#define mitkDICOMTagScanner_h + +#include +#include "itkMutexLock.h" + +#include "mitkDICOMEnums.h" +#include "mitkDICOMTagPath.h" +#include "mitkDICOMTagCache.h" +#include "mitkDICOMDatasetAccessingImageFrameInfo.h" + +namespace mitk +{ + + /** + \ingroup DICOMReaderModule + \brief Abstracts the tag scanning process for a set of DICOM files. + + Formerly integrated as a part of DICOMITKSeriesGDCMReader, the tag + scanning part has been factored out into DICOMTagScanner classes + in order to allow a single scan for multiple reader alternatives. This + helps much in the selection process of e.g. DICOMFileReaderSelector. + + This is an abstract base class for concrete scanner implementations. + + @remark When used in a process where multiple classes will access the scan + results, care should be taken that all the tags and files of interest + are communicated to DICOMTagScanner before requesting the results! + */ + class MITKDICOMREADER_EXPORT DICOMTagScanner : public itk::Object + { + public: + mitkClassMacroItkParent(DICOMTagScanner, itk::Object); + + /** + \brief Add this tag to the scanning process. + */ + virtual void AddTag(const DICOMTag& tag) = 0; + /** + \brief Add a list of tags to the scanning process. + */ + virtual void AddTags(const DICOMTagList& tags) = 0; + /** + \brief Add this tag path to the scanning process. + */ + virtual void AddTagPath(const DICOMTagPath& path) = 0; + /** + \brief Add a list of tag pathes to the scanning process. + */ + virtual void AddTagPaths(const DICOMTagPathList& paths) = 0; + + /** + \brief Define the list of files to scan. + This does not ADD to an internal list, but it replaces the + whole list of files. + */ + virtual void SetInputFiles(const StringList& filenames) = 0; + + /** + \brief Start the scanning process. + Calling Scan() will invalidate previous scans, forgetting + all about files and tags from files that have been scanned + previously. + */ + virtual void Scan() = 0; + + /** + \brief Retrieve a result list for file-by-file tag access. + */ + virtual DICOMDatasetAccessingImageFrameList GetFrameInfoList() const = 0; + + /** + \brief Retrieve Pointer to the complete cache of the scan. + */ + virtual DICOMTagCache::Pointer GetScanCache() const = 0; + + protected: + + /** \brief Return active C locale */ + static std::string GetActiveLocale(); + /** + \brief Remember current locale on stack, activate "C" locale. + "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader + */ + void PushLocale() const; + /** + \brief Activate last remembered locale from locale stack + "C" locale is required for correct parsing of numbers by itk::ImageSeriesReader + */ + void PopLocale() const; + + DICOMTagScanner(); + virtual ~DICOMTagScanner(); + + private: + + static itk::MutexLock::Pointer s_LocaleMutex; + + mutable std::stack m_ReplacedCLocales; + mutable std::stack m_ReplacedCinLocales; + + DICOMTagScanner(const DICOMTagScanner&); + }; +} + +#endif diff --git a/Modules/DICOMReader/include/mitkDICOMTagHelper.h b/Modules/DICOMReader/include/mitkDICOMTagsOfInterestHelper.h similarity index 62% rename from Modules/DICOMReader/include/mitkDICOMTagHelper.h rename to Modules/DICOMReader/include/mitkDICOMTagsOfInterestHelper.h index a98680bc99..9476794681 100644 --- a/Modules/DICOMReader/include/mitkDICOMTagHelper.h +++ b/Modules/DICOMReader/include/mitkDICOMTagsOfInterestHelper.h @@ -1,48 +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 mitkDICOMTagHelper_h -#define mitkDICOMTagHelper_h +#ifndef mitkDICOMTagsOfInterestHelper_h +#define mitkDICOMTagsOfInterestHelper_h #include -#include +#include #include "MitkDICOMReaderExports.h" namespace mitk { - typedef std::unordered_map DICOMTagMapType; + /** Type specifies tags of interest. Key is the tag path of interest. + * The value is an optional user defined name for the property that should be used to store the tag value(s). + * Empty value is default and will imply to use the found DICOMTagPath as property name.*/ + typedef std::map DICOMTagPathMapType; /** Returns the list of tags that are by default of interest and should * be extracted when loading DICOM data. This can for instance be used * to with DICOMFileReader::SetAdditionalTagsOfInterest().*/ - DICOMTagMapType MITKDICOMREADER_EXPORT GetCurrentDICOMTagsOfInterest(); - - typedef std::unordered_map DefaultDICOMTagMapType; + DICOMTagPathMapType MITKDICOMREADER_EXPORT GetCurrentDICOMTagsOfInterest(); /** Returns the list of tags that are by default of interest and should * be extracted when loading DICOM data. This can for instance be used * to with DICOMFileReader::SetAdditionalTagsOfInterest().*/ - DefaultDICOMTagMapType MITKDICOMREADER_EXPORT GetDefaultDICOMTagsOfInterest(); - - /** returns the correct property name for a given DICOMTag instance. */ - std::string MITKDICOMREADER_EXPORT GeneratPropertyNameForDICOMTag(const mitk::DICOMTag& tag); - + DICOMTagPathMapType MITKDICOMREADER_EXPORT GetDefaultDICOMTagsOfInterest(); } #endif diff --git a/Modules/DICOMReader/include/mitkIDICOMTagsOfInterest.h b/Modules/DICOMReader/include/mitkIDICOMTagsOfInterest.h index 7bd160d9fd..72dba79b74 100644 --- a/Modules/DICOMReader/include/mitkIDICOMTagsOfInterest.h +++ b/Modules/DICOMReader/include/mitkIDICOMTagsOfInterest.h @@ -1,70 +1,68 @@ /*=================================================================== 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 mitkIDICOMTagsOfInterest_h #define mitkIDICOMTagsOfInterest_h #include #include #include -#include +#include #include namespace mitk { /** * \ingroup MicroServices_Interfaces * \brief Interface of DICOM tags of interest service. * * This service allows you to manage the tags of interest (toi). * All registred toi will be extracted when loading dicom data and stored as properties in the corresponding * base data object. In addition the service can (if available) use IPropertyPersistance and IPropertyAliases * to ensure that the tags of interests are also persisted and have a human readable alias. */ class MITKDICOMREADER_EXPORT IDICOMTagsOfInterest { public: virtual ~IDICOMTagsOfInterest(); - typedef std::unordered_map DICOMTagMapType; - /** \brief Add an tag to the TOI. * If the tag was already added it will be overwritten with the passed values. * \param[in] tag Tag that should be added. * \param[in] makePersistant Indicates if the tag should be made persistant if possible via the IPropertyPersistence service. */ - virtual void AddTagOfInterest(const DICOMTag& tag, bool makePersistant = true) = 0; + virtual void AddTagOfInterest(const DICOMTagPath& tag, bool makePersistant = true) = 0; /** Returns the map of all tags of interest. Key is the property name. Value is the DICOM tag.*/ - virtual DICOMTagMapType GetTagsOfInterest() const = 0; + virtual DICOMTagPathMapType GetTagsOfInterest() const = 0; /** Indicates if the given tag is already a tag of interest.*/ - virtual bool HasTag(const DICOMTag& tag) const = 0; + virtual bool HasTag(const DICOMTagPath& tag) const = 0; /** \brief Remove specific tag. If it not exists the function will do nothing. * \param[in] tag Tag that should be removed. */ - virtual void RemoveTag(const DICOMTag& tag) = 0; + virtual void RemoveTag(const DICOMTagPath& tag) = 0; /** \brief Remove all tags. */ virtual void RemoveAllTags() = 0; }; } MITK_DECLARE_SERVICE_INTERFACE(mitk::IDICOMTagsOfInterest, "org.mitk.IDICOMTagsOfInterest") #endif diff --git a/Modules/DICOMReader/src/mitkDICOMDCMTKTagScanner.cpp b/Modules/DICOMReader/src/mitkDICOMDCMTKTagScanner.cpp new file mode 100644 index 0000000000..96ab13e175 --- /dev/null +++ b/Modules/DICOMReader/src/mitkDICOMDCMTKTagScanner.cpp @@ -0,0 +1,188 @@ +/*=================================================================== + +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 "mitkDICOMDCMTKTagScanner.h" +#include "mitkDICOMGenericImageFrameInfo.h" + +#include +#include + +mitk::DICOMDCMTKTagScanner::DICOMDCMTKTagScanner() +{ +} + +mitk::DICOMDCMTKTagScanner::~DICOMDCMTKTagScanner() +{ +} + +void mitk::DICOMDCMTKTagScanner::AddTag( const DICOMTag& tag ) +{ + m_ScannedTags.insert( DICOMTagPath(tag) ); +} + +void mitk::DICOMDCMTKTagScanner::AddTags( const DICOMTagList& tags ) +{ + for ( auto tagIter = tags.cbegin(); tagIter != tags.cend(); ++tagIter ) + { + this->AddTag( *tagIter ); + } +} + +void mitk::DICOMDCMTKTagScanner::AddTagPath(const DICOMTagPath& path) +{ + m_ScannedTags.insert(path); +} + +void mitk::DICOMDCMTKTagScanner::AddTagPaths(const DICOMTagPathList& paths) +{ + for (const auto& path : paths) + { + this->AddTagPath(path); + } +} + +void mitk::DICOMDCMTKTagScanner::SetInputFiles( const StringList& filenames ) +{ + m_InputFilenames = filenames; +} + +mitk::DICOMTagPath DcmPathToTagPath(DcmPath * dcmpath) +{ + mitk::DICOMTagPath result; + + OFListConstIterator(DcmPathNode*) it = dcmpath->begin(); + OFListConstIterator(DcmPathNode*) endOfList = dcmpath->end(); + OFString pathStr; DcmEVR vr; DcmObject* obj; + + while (it != endOfList) + { + if (((*it) == NULL) || ((*it)->m_obj == NULL)) + { + mitkThrow() << "Error in DcmPathToTagPath(). Invalid search result"; + } + obj = (*it)->m_obj; + vr = obj->ident(); + + if ((vr == EVR_SQ) || (obj->isLeaf())) + { + result.AddElement(obj->getTag().getGroup(), obj->getTag().getElement()); + } + else if ((vr == EVR_item) || (vr == EVR_dataset)) + { + if (result.Size() > 0) + { + result.GetLastNode().type = mitk::DICOMTagPath::NodeInfo::NodeType::SequenceSelection; + result.GetLastNode().selection = (*it)->m_itemNo; + } + else + { + mitkThrow() << "Error in DcmPathToTagPath(). DCMTK path is illegal due to toplevel sequence item."; + } + } + else + { + result.AddNode(mitk::DICOMTagPath::NodeInfo()); + } + ++it; + } + + return result; +} + +void mitk::DICOMDCMTKTagScanner::Scan() +{ + this->PushLocale(); + + try + { + DcmPathProcessor processor; + processor.setItemWildcardSupport(true); + + DICOMGenericTagCache::Pointer newCache = DICOMGenericTagCache::New(); + + for (const auto& fileName : this->m_InputFilenames) + { + DcmFileFormat dfile; + OFCondition cond = dfile.loadFile(fileName.c_str()); + if (cond.bad()) + { + MITK_ERROR << "Error when scanning for tags. Cannot open given file. File: " << fileName; + } + else + { + DICOMGenericImageFrameInfo::Pointer info = DICOMGenericImageFrameInfo::New(fileName); + + for (const auto& path : this->m_ScannedTags) + { + std::string tagPath = DICOMTagPathToDCMTKSearchPath(path); + cond = processor.findOrCreatePath(dfile.getDataset(), tagPath.c_str()); + if (cond.good()) + { + OFList< DcmPath * > findings; + processor.getResults(findings); + for (const auto& finding : findings) + { + auto element = dynamic_cast(finding->back()->m_obj); + if (!element) + { + auto item = dynamic_cast(finding->back()->m_obj); + if (item) + { + element = item->getElement(finding->back()->m_itemNo); + } + } + + if (element) + { + OFString value; + cond = element->getOFStringArray(value); + if (cond.good()) + { + info->SetTagValue(DcmPathToTagPath(finding), std::string(value.c_str())); + } + } + } + } + } + newCache->AddFrameInfo(info); + } + } + + m_Cache = newCache; + + this->PopLocale(); + } + catch (...) + { + this->PopLocale(); + throw; + } +} + +mitk::DICOMTagCache::Pointer +mitk::DICOMDCMTKTagScanner::GetScanCache() const +{ + return m_Cache.GetPointer(); +} + +mitk::DICOMDatasetAccessingImageFrameList mitk::DICOMDCMTKTagScanner::GetFrameInfoList() const +{ + if (m_Cache.IsNotNull()) + { + return m_Cache->GetFrameInfoList(); + } + return mitk::DICOMDatasetAccessingImageFrameList(); +} diff --git a/Modules/DICOMReader/src/mitkDICOMDatasetAccessingImageFrameInfo.cpp b/Modules/DICOMReader/src/mitkDICOMDatasetAccessingImageFrameInfo.cpp new file mode 100644 index 0000000000..732ac20c0a --- /dev/null +++ b/Modules/DICOMReader/src/mitkDICOMDatasetAccessingImageFrameInfo.cpp @@ -0,0 +1,76 @@ +/*=================================================================== + +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 "mitkDICOMDatasetAccessingImageFrameInfo.h" + +mitk::DICOMDatasetAccessingImageFrameInfo +::DICOMDatasetAccessingImageFrameInfo(const std::string& filename, unsigned int frameNo) +:DICOMImageFrameInfo(filename, frameNo) +{ +} + +mitk::DICOMDatasetAccessingImageFrameInfo +::~DICOMDatasetAccessingImageFrameInfo() +{ +} + +mitk::DICOMImageFrameList +mitk::ConvertToDICOMImageFrameList(const DICOMDatasetAccessingImageFrameList& input) +{ + DICOMImageFrameList output; + output.reserve(input.size()); + + for (auto& inputIter : input) + { + DICOMImageFrameInfo* fi = inputIter.GetPointer(); + assert(fi); + output.push_back(fi); + } + + return output; +} + +mitk::DICOMDatasetList +mitk::ConvertToDICOMDatasetList(const DICOMDatasetAccessingImageFrameList& input) +{ + DICOMDatasetList output; + output.reserve(input.size()); + + for (auto& inputIter : input) + { + DICOMDatasetAccess* da = inputIter.GetPointer(); + assert(da); + output.push_back(da); + } + + return output; +} + +mitk::DICOMDatasetAccessingImageFrameList +mitk::ConvertToDICOMDatasetAccessingImageFrameList(const DICOMDatasetList& input) +{ + DICOMDatasetAccessingImageFrameList output; + output.reserve(input.size()); + + for (auto& inputIter : input) + { + DICOMDatasetAccessingImageFrameInfo* afi = dynamic_cast(inputIter); + assert(afi); + output.push_back(afi); + } + + return output; +} diff --git a/Modules/DICOMReader/src/mitkDICOMFileReader.cpp b/Modules/DICOMReader/src/mitkDICOMFileReader.cpp index 0c5be7bbe9..3639232224 100644 --- a/Modules/DICOMReader/src/mitkDICOMFileReader.cpp +++ b/Modules/DICOMReader/src/mitkDICOMFileReader.cpp @@ -1,229 +1,229 @@ /*=================================================================== 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 "mitkDICOMFileReader.h" #include mitk::DICOMFileReader ::DICOMFileReader() :itk::Object() { } mitk::DICOMFileReader ::~DICOMFileReader() { } mitk::DICOMFileReader ::DICOMFileReader(const DICOMFileReader& other ) :itk::Object() ,m_InputFilenames( other.m_InputFilenames ) ,m_Outputs( other.m_Outputs ) ,m_ConfigLabel( other.m_ConfigLabel ) ,m_ConfigDescription( other.m_ConfigDescription ) { } mitk::DICOMFileReader& mitk::DICOMFileReader ::operator=(const DICOMFileReader& other) { if (this != &other) { m_InputFilenames = other.m_InputFilenames; m_Outputs = other.m_Outputs; m_ConfigLabel = other.m_ConfigLabel; m_ConfigDescription = other.m_ConfigDescription; } return *this; } void mitk::DICOMFileReader ::SetConfigurationLabel(const std::string& label) { m_ConfigLabel = label; this->Modified(); } std::string mitk::DICOMFileReader ::GetConfigurationLabel() const { return m_ConfigLabel; } void mitk::DICOMFileReader ::SetConfigurationDescription(const std::string& desc) { m_ConfigDescription = desc; this->Modified(); } std::string mitk::DICOMFileReader ::GetConfigurationDescription() const { return m_ConfigDescription; } void mitk::DICOMFileReader ::SetInputFiles( const StringList& filenames) { m_InputFilenames = filenames; this->Modified(); } const mitk::StringList& mitk::DICOMFileReader ::GetInputFiles() const { return m_InputFilenames; } unsigned int mitk::DICOMFileReader ::GetNumberOfOutputs() const { return m_Outputs.size(); } void mitk::DICOMFileReader ::ClearOutputs() { m_Outputs.clear(); } void mitk::DICOMFileReader ::SetNumberOfOutputs(unsigned int numberOfOutputs) { m_Outputs.resize(numberOfOutputs); } void mitk::DICOMFileReader ::SetOutput(unsigned int index, const mitk::DICOMImageBlockDescriptor& output) { if (index < m_Outputs.size()) { m_Outputs[index] = output; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } void mitk::DICOMFileReader ::PrintConfiguration(std::ostream& os) const { os << "---- Configuration of " << this->GetNameOfClass() <<" " << (void*)this << " ----"<< std::endl; this->InternalPrintConfiguration(os); os << "---- End of configuration ----" << std::endl; } void mitk::DICOMFileReader ::PrintOutputs(std::ostream& os, bool filenameDetails) const { os << "---- Outputs of DICOMFilereader " << (void*)this << " ----"<< std::endl; for (unsigned int o = 0; o < m_Outputs.size(); ++o) { os << "-- Output " << o << std::endl; const DICOMImageBlockDescriptor& block = m_Outputs[o]; block.Print(os, filenameDetails); } os << "---- End of output list ----" << std::endl; } const mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::GetOutput(unsigned int index) const { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } mitk::DICOMImageBlockDescriptor& mitk::DICOMFileReader ::InternalGetOutput(unsigned int index) { if (index < m_Outputs.size()) { return m_Outputs[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_Outputs.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMFileReader ::IsDICOM(const std::string& filename) { itk::GDCMImageIO::Pointer io = itk::GDCMImageIO::New(); return io->CanReadFile( filename.c_str() ); } -std::unordered_map mitk::DICOMFileReader::GetAdditionalTagsOfInterest() const +mitk::DICOMFileReader::AdditionalTagsMapType mitk::DICOMFileReader::GetAdditionalTagsOfInterest() const { return m_AdditionalTagsOfInterest; } void mitk::DICOMFileReader::SetAdditionalTagsOfInterest( - const std::unordered_map& tagList ) + const AdditionalTagsMapType& tagList) { m_AdditionalTagsOfInterest = tagList; this->Modified(); } void mitk::DICOMFileReader::SetTagLookupTableToPropertyFunctor( mitk::DICOMImageBlockDescriptor::TagLookupTableToPropertyFunctor functor ) { m_TagLookupTableToPropertyFunctor = functor; this->Modified(); } mitk::DICOMImageBlockDescriptor::TagLookupTableToPropertyFunctor mitk::DICOMFileReader::GetTagLookupTableToPropertyFunctor() const { return m_TagLookupTableToPropertyFunctor; } diff --git a/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp b/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp index 503321d8e9..ce5038badc 100644 --- a/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp +++ b/Modules/DICOMReader/src/mitkDICOMFileReaderSelector.cpp @@ -1,252 +1,252 @@ /*=================================================================== 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) { const std::vector configs = us::GetModuleContext()->GetModule()->FindResources( path, "*.xml", false ); for ( auto iter = configs.cbegin(); iter != configs.cend(); ++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 ( auto rIter = m_Readers.cbegin(); rIter != m_Readers.cend(); ++rIter ) { - gdcmScanner->AddTags( (*rIter)->GetTagsOfInterest() ); + gdcmScanner->AddTagPaths((*rIter)->GetTagsOfInterest()); } gdcmScanner->Scan(); // let all readers analyze the file set unsigned int readerIndex(0); for ( auto rIter = m_Readers.cbegin(); rIter != m_Readers.cend(); ++readerIndex, ++rIter ) { (*rIter)->SetInputFiles( m_InputFilenames ); - (*rIter)->SetTagCache( gdcmScanner.GetPointer() ); + (*rIter)->SetTagCache( gdcmScanner->GetScanCache() ); 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 ( const 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 ( auto rIter = workingCandidates.cbegin(); rIter != workingCandidates.cend(); ++readerIndex, ++rIter ) { const 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/src/mitkDICOMGDCMImageFrameInfo.cpp b/Modules/DICOMReader/src/mitkDICOMGDCMImageFrameInfo.cpp index 9dc22af514..e072fe0a84 100644 --- a/Modules/DICOMReader/src/mitkDICOMGDCMImageFrameInfo.cpp +++ b/Modules/DICOMReader/src/mitkDICOMGDCMImageFrameInfo.cpp @@ -1,126 +1,113 @@ /*=================================================================== 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 "mitkDICOMGDCMImageFrameInfo.h" mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(const std::string& filename, unsigned int frameNo) -:itk::LightObject() -,m_FrameInfo( DICOMImageFrameInfo::New(filename, frameNo) ) +:DICOMDatasetAccessingImageFrameInfo(filename, frameNo) ,m_TagForValue() { } mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo) -:itk::LightObject() -,m_FrameInfo(frameinfo) +:DICOMDatasetAccessingImageFrameInfo(frameinfo->Filename, frameinfo->FrameNo) ,m_TagForValue() { } mitk::DICOMGDCMImageFrameInfo ::DICOMGDCMImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo, gdcm::Scanner::TagToValue const& tagToValueMapping) -:itk::LightObject() -,m_FrameInfo(frameinfo) +:DICOMDatasetAccessingImageFrameInfo(frameinfo->Filename, frameinfo->FrameNo) ,m_TagForValue(tagToValueMapping) { } mitk::DICOMGDCMImageFrameInfo:: ~DICOMGDCMImageFrameInfo() { } mitk::DICOMDatasetFinding mitk::DICOMGDCMImageFrameInfo ::GetTagValueAsString(const DICOMTag& tag) const { const auto mappedValue = m_TagForValue.find( gdcm::Tag(tag.GetGroup(), tag.GetElement()) ); DICOMDatasetFinding result; if (mappedValue != m_TagForValue.cend()) { result.isValid = true; if (mappedValue->second != nullptr) { std::string s(mappedValue->second); try { result.value = s.erase(s.find_last_not_of(" \n\r\t")+1); } catch(...) { result.value = s; } } else { result.value = ""; } } else { const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient) const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation if (tag == tagImagePositionPatient) { result.isValid = true; result.value = std::string("0\\0\\0"); } else if (tag == tagImageOrientation) { result.isValid = true; result.value = std::string("1\\0\\0\\0\\1\\0"); } else { result.isValid = false; result.value = ""; } } return result; } -std::string -mitk::DICOMGDCMImageFrameInfo -::GetFilenameIfAvailable() const +mitk::DICOMDatasetAccess::FindingsListType +mitk::DICOMGDCMImageFrameInfo::GetTagValueAsString(const DICOMTagPath& path) const { - if (m_FrameInfo.IsNotNull()) - { - return m_FrameInfo->Filename; - } - else + FindingsListType result; + if (path.Size() == 1 && path.IsExplicit()) { - return std::string(""); + result.push_back(this->GetTagValueAsString(path.GetFirstNode().tag)); } + return result; } -mitk::DICOMImageFrameInfo::Pointer +std::string mitk::DICOMGDCMImageFrameInfo -::GetFrameInfo() const -{ - return m_FrameInfo; -} - -void - mitk::DICOMGDCMImageFrameInfo -::SetFrameInfo(DICOMImageFrameInfo::Pointer frameinfo) +::GetFilenameIfAvailable() const { - m_FrameInfo = frameinfo; + return this->Filename; } diff --git a/Modules/DICOMReader/src/mitkDICOMGDCMTagCache.cpp b/Modules/DICOMReader/src/mitkDICOMGDCMTagCache.cpp new file mode 100644 index 0000000000..1b0eed068c --- /dev/null +++ b/Modules/DICOMReader/src/mitkDICOMGDCMTagCache.cpp @@ -0,0 +1,108 @@ +/*=================================================================== + +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 "mitkDICOMGDCMTagCache.h" +#include "mitkDICOMEnums.h" +#include "mitkDICOMGDCMImageFrameInfo.h" + +mitk::DICOMGDCMTagCache::DICOMGDCMTagCache() +{ +} + +mitk::DICOMGDCMTagCache::~DICOMGDCMTagCache() +{ +} + +mitk::DICOMDatasetFinding mitk::DICOMGDCMTagCache::GetTagValue( DICOMImageFrameInfo* frame, const DICOMTag& tag ) const +{ + assert( frame ); + + for ( auto frameIter = m_ScanResult.cbegin(); frameIter != m_ScanResult.cend(); ++frameIter ) + { + if ( **frameIter == *frame ) + { + return (*frameIter)->GetTagValueAsString(tag); + } + } + + if ( m_ScannedTags.find( tag ) != m_ScannedTags.cend() ) + { + if ( std::find( m_InputFilenames.cbegin(), m_InputFilenames.cend(), frame->Filename ) + == m_InputFilenames.cend() ) + { + // callers are required to tell us about the filenames they are interested in + // this is a helpful reminder for them to inform us + std::stringstream errorstring; + errorstring << "Invalid call to DICOMGDCMTagCache::GetTagValue( " + << "'" << frame->Filename << "', frame " << frame->FrameNo + << " ). Filename was never mentioned before!"; + MITK_ERROR << errorstring.str(); + throw std::invalid_argument( errorstring.str() ); + } + } + else + { + // callers are required to tell us about the tags they are interested in + // this is a helpful reminder for them to inform us + std::stringstream errorstring; + errorstring << "Invalid call to DICOMGDCMTagCache::GetTagValue( "; + tag.Print( errorstring ); + errorstring << " ). Tag was never mentioned before!"; + MITK_ERROR << errorstring.str(); + throw std::invalid_argument( errorstring.str() ); + } + + return DICOMDatasetFinding(); +} + +mitk::DICOMDatasetAccess::FindingsListType +mitk::DICOMGDCMTagCache::GetTagValue(DICOMImageFrameInfo* frame, const DICOMTagPath& path) const +{ + FindingsListType result; + if (path.Size() == 1 && path.IsExplicit()) + { + result.push_back(this->GetTagValue(frame, path.GetFirstNode().tag)); + } + return result; +} + +mitk::DICOMDatasetAccessingImageFrameList mitk::DICOMGDCMTagCache::GetFrameInfoList() const +{ + return m_ScanResult; +} + +void +mitk::DICOMGDCMTagCache::InitCache(const std::set& scannedTags, const std::shared_ptr& scanner, const StringList& inputFiles) +{ + m_ScannedTags = scannedTags; + m_InputFilenames = inputFiles; + m_Scanner = scanner; + + m_ScanResult.clear(); + m_ScanResult.reserve(m_InputFilenames.size()); + + for (auto inputIter = m_InputFilenames.cbegin(); inputIter != m_InputFilenames.cend(); ++inputIter) + { + m_ScanResult.push_back(DICOMGDCMImageFrameInfo::New(DICOMImageFrameInfo::New(*inputIter, 0), + m_Scanner->GetMapping(inputIter->c_str())).GetPointer()); + } +} + +const gdcm::Scanner& +mitk::DICOMGDCMTagCache::GetScanner() const +{ + return *(this->m_Scanner); +} diff --git a/Modules/DICOMReader/src/mitkDICOMGDCMTagScanner.cpp b/Modules/DICOMReader/src/mitkDICOMGDCMTagScanner.cpp index 5c69034198..2eb92fa7cd 100644 --- a/Modules/DICOMReader/src/mitkDICOMGDCMTagScanner.cpp +++ b/Modules/DICOMReader/src/mitkDICOMGDCMTagScanner.cpp @@ -1,123 +1,122 @@ /*=================================================================== 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 "mitkDICOMGDCMTagScanner.h" +#include "mitkDICOMGDCMTagCache.h" +#include "mitkDICOMGDCMImageFrameInfo.h" -mitk::DICOMGDCMTagScanner::DICOMGDCMTagScanner() -{ -} +#include -mitk::DICOMGDCMTagScanner::DICOMGDCMTagScanner( const DICOMGDCMTagScanner& other ) -: DICOMTagCache( other ) +mitk::DICOMGDCMTagScanner::DICOMGDCMTagScanner() { + m_GDCMScanner = std::make_shared(); } mitk::DICOMGDCMTagScanner::~DICOMGDCMTagScanner() { } mitk::DICOMDatasetFinding mitk::DICOMGDCMTagScanner::GetTagValue( DICOMImageFrameInfo* frame, const DICOMTag& tag ) const { assert( frame ); + assert(m_Cache.IsNotNull()); - for ( auto frameIter = m_ScanResult.cbegin(); frameIter != m_ScanResult.cend(); ++frameIter ) + if (m_Cache.IsNull()) { - if ( ( *frameIter )->GetFrameInfo().IsNotNull() && ( *( ( *frameIter )->GetFrameInfo() ) == *frame ) ) - { - return (*frameIter)->GetTagValueAsString(tag); - } + mitkThrow() << "Wrong usage of DICOMGDCMScanner- Called GetTagValue() before scanned at least once. No scanner cache available."; } - if ( m_ScannedTags.find( tag ) != m_ScannedTags.cend() ) - { - if ( std::find( m_InputFilenames.cbegin(), m_InputFilenames.cend(), frame->Filename ) - != m_InputFilenames.cend() ) - { - // precondition of gdcm::Scanner::GetValue() fulfilled - const char* value = m_GDCMScanner.GetValue( frame->Filename.c_str(), gdcm::Tag( tag.GetGroup(), tag.GetElement() ) ); - DICOMDatasetFinding result; - if (value) - { - result.isValid = true; - result.value = value; - } - return result; - } - else - { - // callers are required to tell us about the filenames they are interested in - // this is a helpful reminder for them to inform us - std::stringstream errorstring; - errorstring << "Invalid call to DICOMGDCMTagScanner::GetTagValue( " - << "'" << frame->Filename << "', frame " << frame->FrameNo - << " ). Filename was never mentioned before!"; - MITK_ERROR << errorstring.str(); - throw std::invalid_argument( errorstring.str() ); - } - } - else - { - // callers are required to tell us about the tags they are interested in - // this is a helpful reminder for them to inform us - std::stringstream errorstring; - errorstring << "Invalid call to DICOMGDCMTagScanner::GetTagValue( "; - tag.Print( errorstring ); - errorstring << " ). Tag was never mentioned before!"; - MITK_ERROR << errorstring.str(); - throw std::invalid_argument( errorstring.str() ); - } + return m_Cache->GetTagValue(frame, tag); } void mitk::DICOMGDCMTagScanner::AddTag( const DICOMTag& tag ) { m_ScannedTags.insert( tag ); - m_GDCMScanner.AddTag( + m_GDCMScanner->AddTag( gdcm::Tag( tag.GetGroup(), tag.GetElement() ) ); // also a set, duplicate calls to AddTag don't hurt } void mitk::DICOMGDCMTagScanner::AddTags( const DICOMTagList& tags ) { for ( auto tagIter = tags.cbegin(); tagIter != tags.cend(); ++tagIter ) { this->AddTag( *tagIter ); } } +void mitk::DICOMGDCMTagScanner::AddTagPath(const DICOMTagPath& path) +{ + if (path.Size() != 1 || !path.IsExplicit()) + { + std::stringstream errorstring; + errorstring << "Invalid call to DICOMGDCMTagScanner::AddTagPath(). " + << "Scanner does only support pathes that are explicitly specify one tag. " + << "Invalid path: "<AddTag(path.GetFirstNode().tag); +} + +void mitk::DICOMGDCMTagScanner::AddTagPaths(const DICOMTagPathList& paths) +{ + for (const auto& path : paths) + { + if (path.Size() != 1 || !path.IsExplicit()) + { + std::stringstream errorstring; + errorstring << "Invalid call to DICOMGDCMTagScanner::AddTagPaths(). " + << "Scanner does only support pathes that are explicitly specify one tag. " + << "Invalid path: " << path.ToStr(); + MITK_ERROR << errorstring.str(); + throw std::invalid_argument(errorstring.str()); + } + this->AddTag(path.GetFirstNode().tag); + } +} + void mitk::DICOMGDCMTagScanner::SetInputFiles( const StringList& filenames ) { m_InputFilenames = filenames; } void mitk::DICOMGDCMTagScanner::Scan() { // TODO integrate push/pop locale?? - m_GDCMScanner.Scan( m_InputFilenames ); + m_GDCMScanner->Scan( m_InputFilenames ); - m_ScanResult.clear(); - m_ScanResult.reserve( m_InputFilenames.size() ); + DICOMGDCMTagCache::Pointer newCache = DICOMGDCMTagCache::New(); + newCache->InitCache(m_ScannedTags, m_GDCMScanner, m_InputFilenames); - for ( auto inputIter = m_InputFilenames.cbegin(); inputIter != m_InputFilenames.cend(); ++inputIter ) - { - m_ScanResult.push_back( DICOMGDCMImageFrameInfo::New( DICOMImageFrameInfo::New( *inputIter, 0 ), - m_GDCMScanner.GetMapping( inputIter->c_str() ) ) ); - } + m_Cache = newCache; } -mitk::DICOMGDCMImageFrameList mitk::DICOMGDCMTagScanner::GetFrameInfoList() const +mitk::DICOMTagCache::Pointer +mitk::DICOMGDCMTagScanner::GetScanCache() const { - return m_ScanResult; + return m_Cache.GetPointer(); +} + +mitk::DICOMDatasetAccessingImageFrameList mitk::DICOMGDCMTagScanner::GetFrameInfoList() const +{ + mitk::DICOMDatasetAccessingImageFrameList result; + if (m_Cache.IsNotNull()) + { + result = m_Cache->GetFrameInfoList(); + } + return result; } diff --git a/Modules/DICOMReader/src/mitkDICOMGenericImageFrameInfo.cpp b/Modules/DICOMReader/src/mitkDICOMGenericImageFrameInfo.cpp new file mode 100644 index 0000000000..48f0e51476 --- /dev/null +++ b/Modules/DICOMReader/src/mitkDICOMGenericImageFrameInfo.cpp @@ -0,0 +1,87 @@ +/*=================================================================== + + 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 "mitkDICOMGenericImageFrameInfo.h" +#include "mitkException.h" + +mitk::DICOMGenericImageFrameInfo +::DICOMGenericImageFrameInfo(const std::string& filename, unsigned int frameNo) +:DICOMDatasetAccessingImageFrameInfo(filename, frameNo) +{ +} + +mitk::DICOMGenericImageFrameInfo +::DICOMGenericImageFrameInfo(const DICOMImageFrameInfo::Pointer& frameinfo) +:DICOMDatasetAccessingImageFrameInfo(frameinfo->Filename, frameinfo->FrameNo) +{ +} + +mitk::DICOMGenericImageFrameInfo:: +~DICOMGenericImageFrameInfo() +{ +} + +mitk::DICOMDatasetFinding +mitk::DICOMGenericImageFrameInfo +::GetTagValueAsString(const DICOMTag& tag) const +{ + DICOMTagPath path(tag); + DICOMDatasetFinding result; + + const auto finding = m_Values.find(path); + if (finding != m_Values.cend()) + { + result.isValid = true; + result.value = finding->second; + result.path = path; + } + + return result; +} + +mitk::DICOMDatasetAccess::FindingsListType +mitk::DICOMGenericImageFrameInfo::GetTagValueAsString(const DICOMTagPath& path) const +{ + FindingsListType result; + + for (const auto& iter : m_Values) + { + if (path.Equals(iter.first)) + { + result.emplace_back(true, iter.second, iter.first); + } + } + + return result; +} + +void +mitk::DICOMGenericImageFrameInfo::SetTagValue(const DICOMTagPath& path, const std::string& value) +{ + if (!path.IsExplicit()) + { + mitkThrow() << "Only explicit tag paths (no wildcards) are allowed for tag values in DICOMGenericImageFrameInfo. Passed tag path:" << path.ToStr(); + } + + m_Values[path] = value; +} + +std::string +mitk::DICOMGenericImageFrameInfo +::GetFilenameIfAvailable() const +{ + return this->Filename; +} diff --git a/Modules/DICOMReader/src/mitkDICOMGenericTagCache.cpp b/Modules/DICOMReader/src/mitkDICOMGenericTagCache.cpp new file mode 100644 index 0000000000..0fc821a369 --- /dev/null +++ b/Modules/DICOMReader/src/mitkDICOMGenericTagCache.cpp @@ -0,0 +1,71 @@ +/*=================================================================== + +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 "mitkDICOMGenericTagCache.h" +#include "mitkDICOMEnums.h" +#include "mitkDICOMGenericImageFrameInfo.h" + +mitk::DICOMGenericTagCache::DICOMGenericTagCache() +{ +} + +mitk::DICOMGenericTagCache::~DICOMGenericTagCache() +{ +} + +mitk::DICOMDatasetFinding mitk::DICOMGenericTagCache::GetTagValue( DICOMImageFrameInfo* frame, const DICOMTag& tag ) const +{ + auto findings = GetTagValue(frame, DICOMTagPath(tag)); + + DICOMDatasetFinding result; + if (!findings.empty()) + { + result = findings.front(); + } + return result; +} + +mitk::DICOMDatasetAccess::FindingsListType +mitk::DICOMGenericTagCache::GetTagValue(DICOMImageFrameInfo* frame, const DICOMTagPath& path) const +{ + FindingsListType result; + + for (auto info : m_ScanResult) + { + if (info == frame) + { + result = info->GetTagValueAsString(path); + } + } + return result; +} + +mitk::DICOMDatasetAccessingImageFrameList mitk::DICOMGenericTagCache::GetFrameInfoList() const +{ + return m_ScanResult; +} + +void +mitk::DICOMGenericTagCache::AddFrameInfo(DICOMDatasetAccessingImageFrameInfo* info) +{ + m_ScanResult.push_back(info); +}; + +void +mitk::DICOMGenericTagCache::Reset() +{ + m_ScanResult.clear(); +}; diff --git a/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp b/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp index 40124a8e1d..7f71f51123 100644 --- a/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp +++ b/Modules/DICOMReader/src/mitkDICOMITKSeriesGDCMReader.cpp @@ -1,685 +1,614 @@ /*=================================================================== 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 #define ENABLE_TIMING #include #include #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkGantryTiltInformation.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMGDCMTagScanner.h" itk::MutexLock::Pointer mitk::DICOMITKSeriesGDCMReader::s_LocaleMutex = itk::MutexLock::New(); mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( unsigned int decimalPlacesForOrientation ) : DICOMFileReader() , m_FixTiltByShearing( true ) , m_DecimalPlacesForOrientation( decimalPlacesForOrientation ) , m_ExternalCache(false) { this->EnsureMandatorySortersArePresent( decimalPlacesForOrientation ); } mitk::DICOMITKSeriesGDCMReader::DICOMITKSeriesGDCMReader( const DICOMITKSeriesGDCMReader& other ) : DICOMFileReader( other ) , m_FixTiltByShearing( false ) , m_SortingResultInProgress( other.m_SortingResultInProgress ) , m_Sorter( other.m_Sorter ) , m_EquiDistantBlocksSorter( other.m_EquiDistantBlocksSorter->Clone() ) , m_NormalDirectionConsistencySorter( other.m_NormalDirectionConsistencySorter->Clone() ) , m_ReplacedCLocales( other.m_ReplacedCLocales ) , m_ReplacedCinLocales( other.m_ReplacedCinLocales ) , m_DecimalPlacesForOrientation( other.m_DecimalPlacesForOrientation ) , m_TagCache( other.m_TagCache ) , m_ExternalCache(other.m_ExternalCache) { } mitk::DICOMITKSeriesGDCMReader::~DICOMITKSeriesGDCMReader() { } mitk::DICOMITKSeriesGDCMReader& mitk::DICOMITKSeriesGDCMReader:: operator=( const DICOMITKSeriesGDCMReader& other ) { if ( this != &other ) { DICOMFileReader::operator =( other ); this->m_FixTiltByShearing = other.m_FixTiltByShearing; this->m_SortingResultInProgress = other.m_SortingResultInProgress; this->m_Sorter = other.m_Sorter; // TODO should clone the list items this->m_EquiDistantBlocksSorter = other.m_EquiDistantBlocksSorter->Clone(); this->m_NormalDirectionConsistencySorter = other.m_NormalDirectionConsistencySorter->Clone(); this->m_ReplacedCLocales = other.m_ReplacedCLocales; this->m_ReplacedCinLocales = other.m_ReplacedCinLocales; this->m_DecimalPlacesForOrientation = other.m_DecimalPlacesForOrientation; this->m_TagCache = other.m_TagCache; } return *this; } bool mitk::DICOMITKSeriesGDCMReader::operator==( const DICOMFileReader& other ) const { if ( const Self* otherSelf = dynamic_cast( &other ) ) { if ( this->m_FixTiltByShearing == otherSelf->m_FixTiltByShearing && *( this->m_EquiDistantBlocksSorter ) == *( otherSelf->m_EquiDistantBlocksSorter ) && ( fabs( this->m_DecimalPlacesForOrientation - otherSelf->m_DecimalPlacesForOrientation ) < eps ) ) { // test sorters for equality if ( this->m_Sorter.size() != otherSelf->m_Sorter.size() ) return false; auto mySorterIter = this->m_Sorter.cbegin(); auto oSorterIter = otherSelf->m_Sorter.cbegin(); for ( ; mySorterIter != this->m_Sorter.cend() && oSorterIter != otherSelf->m_Sorter.cend(); ++mySorterIter, ++oSorterIter ) { if ( !( **mySorterIter == **oSorterIter ) ) return false; // this sorter differs } // nothing differs ==> all is equal return true; } else { return false; } } else { return false; } } void mitk::DICOMITKSeriesGDCMReader::SetFixTiltByShearing( bool on ) { this->Modified(); m_FixTiltByShearing = on; } bool mitk::DICOMITKSeriesGDCMReader::GetFixTiltByShearing() const { return m_FixTiltByShearing; } void mitk::DICOMITKSeriesGDCMReader::SetAcceptTwoSlicesGroups( bool accept ) const { this->Modified(); m_EquiDistantBlocksSorter->SetAcceptTwoSlicesGroups( accept ); } bool mitk::DICOMITKSeriesGDCMReader::GetAcceptTwoSlicesGroups() const { return m_EquiDistantBlocksSorter->GetAcceptTwoSlicesGroups(); } - -mitk::DICOMGDCMImageFrameList - mitk::DICOMITKSeriesGDCMReader::FromDICOMDatasetList( const DICOMDatasetList& input ) -{ - DICOMGDCMImageFrameList output; - output.reserve( input.size() ); - - for ( auto inputIter = input.cbegin(); inputIter != input.cend(); ++inputIter ) - { - DICOMGDCMImageFrameInfo* gfi = dynamic_cast( *inputIter ); - assert( gfi ); - output.push_back( gfi ); - } - - return output; -} - -mitk::DICOMDatasetList - mitk::DICOMITKSeriesGDCMReader::ToDICOMDatasetList( const DICOMGDCMImageFrameList& input ) -{ - DICOMDatasetList output; - output.reserve( input.size() ); - - for ( auto inputIter = input.cbegin(); inputIter != input.cend(); ++inputIter ) - { - DICOMDatasetAccess* da = inputIter->GetPointer(); - assert( da ); - output.push_back( da ); - } - - return output; -} - -mitk::DICOMImageFrameList - mitk::DICOMITKSeriesGDCMReader::ToDICOMImageFrameList( const DICOMGDCMImageFrameList& input ) -{ - DICOMImageFrameList output; - output.reserve( input.size() ); - - for ( auto inputIter = input.cbegin(); inputIter != input.cend(); ++inputIter ) - { - DICOMImageFrameInfo::Pointer fi = ( *inputIter )->GetFrameInfo(); - assert( fi.IsNotNull() ); - output.push_back( fi ); - } - - return output; -} - void mitk::DICOMITKSeriesGDCMReader::InternalPrintConfiguration( std::ostream& os ) const { unsigned int sortIndex( 1 ); for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sortIndex, ++sorterIter ) { os << "Sorting step " << sortIndex << ":" << std::endl; ( *sorterIter )->PrintConfiguration( os, " " ); } os << "Sorting step " << sortIndex << ":" << std::endl; m_EquiDistantBlocksSorter->PrintConfiguration( os, " " ); } std::string mitk::DICOMITKSeriesGDCMReader::GetActiveLocale() { return setlocale( LC_NUMERIC, nullptr ); } void mitk::DICOMITKSeriesGDCMReader::PushLocale() const { s_LocaleMutex->Lock(); std::string currentCLocale = setlocale( LC_NUMERIC, nullptr ); m_ReplacedCLocales.push( currentCLocale ); setlocale( LC_NUMERIC, "C" ); std::locale currentCinLocale( std::cin.getloc() ); m_ReplacedCinLocales.push( currentCinLocale ); std::locale l( "C" ); std::cin.imbue( l ); s_LocaleMutex->Unlock(); } void mitk::DICOMITKSeriesGDCMReader::PopLocale() const { s_LocaleMutex->Lock(); if ( !m_ReplacedCLocales.empty() ) { setlocale( LC_NUMERIC, m_ReplacedCLocales.top().c_str() ); m_ReplacedCLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } if ( !m_ReplacedCinLocales.empty() ) { std::cin.imbue( m_ReplacedCinLocales.top() ); m_ReplacedCinLocales.pop(); } else { MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; } s_LocaleMutex->Unlock(); } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader::Condense3DBlocks( SortingBlockList& input ) { return input; // to be implemented differently by sub-classes } #if defined( MBILOG_ENABLE_DEBUG ) || defined( ENABLE_TIMING ) #define timeStart( part ) timer.Start( part ); #define timeStop( part ) timer.Stop( part ); #else #define timeStart( part ) #define timeStop( part ) #endif void mitk::DICOMITKSeriesGDCMReader::AnalyzeInputFiles() { itk::TimeProbesCollectorBase timer; timeStart( "Reset" ); this->ClearOutputs(); timeStop( "Reset" ); // prepare initial sorting (== list of input files) const StringList inputFilenames = this->GetInputFiles(); timeStart( "Check input for DCM" ); if ( inputFilenames.empty() || !this->CanHandleFile( inputFilenames.front() ) // first || !this->CanHandleFile( inputFilenames.back() ) // last || !this->CanHandleFile( inputFilenames[inputFilenames.size() / 2] ) // roughly central file ) { // TODO a read-as-many-as-possible fallback could be implemented here MITK_DEBUG << "Reader unable to process files.."; return; } timeStop( "Check input for DCM" ); // scan files for sorting-relevant tags if ( m_TagCache.IsNull() || ( m_TagCache->GetMTime()GetMTime() && !m_ExternalCache )) { timeStart( "Tag scanning" ); DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New(); - m_TagCache = filescanner.GetPointer(); // keep alive and make accessible to sub-classes filescanner->SetInputFiles( inputFilenames ); - filescanner->AddTags( this->GetTagsOfInterest() ); + filescanner->AddTagPaths( this->GetTagsOfInterest() ); PushLocale(); filescanner->Scan(); PopLocale(); - timeStop( "Tag scanning" ); + m_TagCache = filescanner->GetScanCache(); // keep alive and make accessible to sub-classes + + timeStop("Tag scanning"); } else { // ensure that the tag cache contains our required tags AND files and has scanned! } m_SortingResultInProgress.clear(); - // TODO We should remove the following cast - // DICOMImageFrameInfo would need to inherit DICOMDatasetAccess! - // - then the DICOMGDCMTagScanner class could create a DICOMGDCMImageFrameList internally - // - and return it as a DICOMImageFrameList - // - like this, DICOMITKSeriesGDCMReader could use the DICOMImageFrameInfoList to feed its sorters - // - problem: - // - DICOMImageFrameInfo is also part of DICOMImageBlockDescriptor, which is meant - // to describe the scanner output, even after the reader (and its scanner) is deleted. - // - if DICOMImageFrameInfo now inherits DICOMDatasetAccess, it would also need to implement - // GetTagValueAsString(). - // - so this could all work if we implement a default response in - // DICOMImageFrameInfo::GetTagValueAsString() (like in GetFilenameIfAvailable) - // and overwrite it in DICOMGDCMImageFrameInfo, which also knows about a specific GDCM scanner result - // (which again COULD (no need to?) be hidden as a point to a DICOMGDCMTagScanner class) - // - if ( DICOMGDCMTagScanner* tagCache = dynamic_cast( m_TagCache.GetPointer() ) ) - { - m_SortingResultInProgress.push_back( tagCache->GetFrameInfoList() ); - } - else - { - throw std::logic_error( "Bad implementation error: DICOMITKSeriesGDCMReader now unable to find " - "dataset/tag information for its input." ); - } + m_SortingResultInProgress.push_back(m_TagCache->GetFrameInfoList()); // sort and split blocks as configured timeStart( "Sorting frames" ); unsigned int sorterIndex = 0; for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sorterIndex, ++sorterIter ) { std::stringstream ss; ss << "Sorting step " << sorterIndex; timeStart( ss.str().c_str() ); m_SortingResultInProgress = this->InternalExecuteSortingStep( sorterIndex, *sorterIter, m_SortingResultInProgress ); timeStop( ss.str().c_str() ); } // a last extra-sorting step: ensure equidistant slices timeStart( "EquiDistantBlocksSorter" ); m_SortingResultInProgress = this->InternalExecuteSortingStep( sorterIndex++, m_EquiDistantBlocksSorter.GetPointer(), m_SortingResultInProgress ); timeStop( "EquiDistantBlocksSorter" ); timeStop( "Sorting frames" ); timeStart( "Condensing 3D blocks" ); m_SortingResultInProgress = this->Condense3DBlocks( m_SortingResultInProgress ); timeStop( "Condensing 3D blocks" ); // provide final result as output timeStart( "Output" ); unsigned int o = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( o + m_SortingResultInProgress.size() ); // Condense3DBlocks may already have added outputs! for ( auto blockIter = m_SortingResultInProgress.cbegin(); blockIter != m_SortingResultInProgress.cend(); ++o, ++blockIter ) { - const DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter; + const DICOMDatasetAccessingImageFrameList& gdcmFrameInfoList = *blockIter; assert( !gdcmFrameInfoList.empty() ); // reverse frames if necessary // update tilt information from absolute last sorting - const DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmFrameInfoList ); + const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); - const DICOMGDCMImageFrameList sortedGdcmInfoFrameList = - FromDICOMDatasetList( m_NormalDirectionConsistencySorter->GetOutput( 0 ) ); + const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = + ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput( 0 ) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block - const DICOMImageFrameList frameList = ToDICOMImageFrameList( sortedGdcmInfoFrameList ); + const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert( !frameList.empty() ); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because // SetImageFrameList will trigger reading of lots of interesting // tags! block.SetAdditionalTagsOfInterest( GetAdditionalTagsOfInterest() ); block.SetTagLookupTableToPropertyFunctor( GetTagLookupTableToPropertyFunctor() ); block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetReaderImplementationLevel( this->GetReaderImplementationLevel( block.GetSOPClassUID() ) ); this->SetOutput( o, block ); } timeStop( "Output" ); #if defined( MBILOG_ENABLE_DEBUG ) || defined( ENABLE_TIMING ) std::cout << "---------------------------------------------------------------" << std::endl; timer.Report( std::cout ); std::cout << "---------------------------------------------------------------" << std::endl; #endif } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::DICOMITKSeriesGDCMReader::InternalExecuteSortingStep( unsigned int sortingStepIndex, const DICOMDatasetSorter::Pointer& sorter, const SortingBlockList& input ) { SortingBlockList nextStepSorting; // we should not modify our input list while processing it std::stringstream ss; ss << "Sorting step " << sortingStepIndex << " '"; #if defined( MBILOG_ENABLE_DEBUG ) sorter->PrintConfiguration( ss ); #endif ss << "'"; nextStepSorting.clear(); MITK_DEBUG << "================================================================================"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ": " << input.size() << " groups input"; unsigned int groupIndex = 0; for ( auto blockIter = input.cbegin(); blockIter != input.cend(); ++groupIndex, ++blockIter ) { - const DICOMGDCMImageFrameList& gdcmInfoFrameList = *blockIter; - const DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmInfoFrameList ); + const DICOMDatasetAccessingImageFrameList& gdcmInfoFrameList = *blockIter; + const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmInfoFrameList ); #if defined( MBILOG_ENABLE_DEBUG ) MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOMITKSeriesGDCMReader: " << ss.str() << ", dataset group " << groupIndex << " (" << datasetList.size() << " datasets): "; for ( auto oi = datasetList.cbegin(); oi != datasetList.cend(); ++oi ) { MITK_DEBUG << " INPUT : " << ( *oi )->GetFilenameIfAvailable(); } #endif sorter->SetInput( datasetList ); sorter->Sort(); unsigned int numberOfResultingBlocks = sorter->GetNumberOfOutputs(); for ( unsigned int b = 0; b < numberOfResultingBlocks; ++b ) { const DICOMDatasetList blockResult = sorter->GetOutput( b ); for ( auto oi = blockResult.cbegin(); oi != blockResult.cend(); ++oi ) { MITK_DEBUG << " OUTPUT(" << b << ") :" << ( *oi )->GetFilenameIfAvailable(); } - DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList( blockResult ); + DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( blockResult ); nextStepSorting.push_back( sortedGdcmInfoFrameList ); } } return nextStepSorting; } mitk::ReaderImplementationLevel mitk::DICOMITKSeriesGDCMReader::GetReaderImplementationLevel( const std::string sopClassUID ) { if ( sopClassUID.empty() ) { return SOPClassUnknown; } gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( sopClassUID.c_str() ); gdcm::UIDs::TSType gdcmType = uidKnowledge; switch ( gdcmType ) { case gdcm::UIDs::CTImageStorage: case gdcm::UIDs::MRImageStorage: case gdcm::UIDs::PositronEmissionTomographyImageStorage: case gdcm::UIDs::ComputedRadiographyImageStorage: case gdcm::UIDs::DigitalXRayImageStorageForPresentation: case gdcm::UIDs::DigitalXRayImageStorageForProcessing: return SOPClassSupported; case gdcm::UIDs::NuclearMedicineImageStorage: return SOPClassPartlySupported; case gdcm::UIDs::SecondaryCaptureImageStorage: return SOPClassImplemented; default: return SOPClassUnsupported; } } // void AllocateOutputImages(); bool mitk::DICOMITKSeriesGDCMReader::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for ( unsigned int o = 0; o < numberOfOutputs; ++o ) { success &= this->LoadMitkImageForOutput( o ); } return success; } bool mitk::DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor( DICOMImageBlockDescriptor& block ) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); bool hasTilt = tiltInfo.IsRegularGantryTilt(); ITKDICOMSeriesReaderHelper::StringContainer filenames; filenames.reserve( frames.size() ); for ( auto frameIter = frames.cbegin(); frameIter != frames.cend(); ++frameIter ) { filenames.push_back( ( *frameIter )->Filename ); } mitk::ITKDICOMSeriesReaderHelper helper; bool success( true ); try { mitk::Image::Pointer mitkImage = helper.Load( filenames, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); } catch ( const std::exception& e ) { success = false; MITK_ERROR << "Exception during image loading: " << e.what(); } PopLocale(); return success; } bool mitk::DICOMITKSeriesGDCMReader::LoadMitkImageForOutput( unsigned int o ) { DICOMImageBlockDescriptor& block = this->InternalGetOutput( o ); return this->LoadMitkImageForImageBlockDescriptor( block ); } bool mitk::DICOMITKSeriesGDCMReader::CanHandleFile( const std::string& filename ) { return ITKDICOMSeriesReaderHelper::CanHandleFile( filename ); } void mitk::DICOMITKSeriesGDCMReader::AddSortingElement( DICOMDatasetSorter* sorter, bool atFront ) { assert( sorter ); if ( atFront ) { m_Sorter.push_front( sorter ); } else { m_Sorter.push_back( sorter ); } this->Modified(); } mitk::DICOMITKSeriesGDCMReader::ConstSorterList mitk::DICOMITKSeriesGDCMReader::GetFreelyConfiguredSortingElements() const { std::list result; unsigned int sortIndex( 0 ); for ( auto sorterIter = m_Sorter.begin(); sorterIter != m_Sorter.end(); ++sortIndex, ++sorterIter ) { if ( sortIndex > 0 ) // ignore first element (see EnsureMandatorySortersArePresent) { result.push_back( ( *sorterIter ).GetPointer() ); } } return result; } void mitk::DICOMITKSeriesGDCMReader::EnsureMandatorySortersArePresent( unsigned int decimalPlacesForOrientation ) { DICOMTagBasedSorter::Pointer splitter = DICOMTagBasedSorter::New(); splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0010) ); // Number of Rows splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0011) ); // Number of Columns splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0030) ); // Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x1164) ); // Imager Pixel Spacing splitter->AddDistinguishingTag( DICOMTag(0x0020, 0x0037), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(decimalPlacesForOrientation) ); // Image Orientation (Patient) splitter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness splitter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames this->AddSortingElement( splitter, true ); // true = at front if ( m_EquiDistantBlocksSorter.IsNull() ) { m_EquiDistantBlocksSorter = mitk::EquiDistantBlocksSorter::New(); } m_EquiDistantBlocksSorter->SetAcceptTilt( m_FixTiltByShearing ); if ( m_NormalDirectionConsistencySorter.IsNull() ) { m_NormalDirectionConsistencySorter = mitk::NormalDirectionConsistencySorter::New(); } } void mitk::DICOMITKSeriesGDCMReader::SetToleratedOriginOffsetToAdaptive( double fractionOfInterSliceDistance ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffsetToAdaptive( fractionOfInterSliceDistance ); this->Modified(); } void mitk::DICOMITKSeriesGDCMReader::SetToleratedOriginOffset( double millimeters ) const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); m_EquiDistantBlocksSorter->SetToleratedOriginOffset( millimeters ); this->Modified(); } double mitk::DICOMITKSeriesGDCMReader::GetToleratedOriginError() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->GetToleratedOriginOffset(); } bool mitk::DICOMITKSeriesGDCMReader::IsToleratedOriginOffsetAbsolute() const { assert( m_EquiDistantBlocksSorter.IsNotNull() ); return m_EquiDistantBlocksSorter->IsToleratedOriginOffsetAbsolute(); } double mitk::DICOMITKSeriesGDCMReader::GetDecimalPlacesForOrientation() const { return m_DecimalPlacesForOrientation; } mitk::DICOMTagCache::Pointer mitk::DICOMITKSeriesGDCMReader::GetTagCache() const { return m_TagCache; } void mitk::DICOMITKSeriesGDCMReader::SetTagCache( const DICOMTagCache::Pointer& tagCache ) { m_TagCache = tagCache; m_ExternalCache = tagCache.IsNotNull(); } -mitk::DICOMTagList mitk::DICOMITKSeriesGDCMReader::GetTagsOfInterest() const +mitk::DICOMTagPathList mitk::DICOMITKSeriesGDCMReader::GetTagsOfInterest() const { - DICOMTagList completeList; + DICOMTagPathList completeList; // check all configured sorters for ( auto sorterIter = m_Sorter.cbegin(); sorterIter != m_Sorter.cend(); ++sorterIter ) { assert( sorterIter->IsNotNull() ); const DICOMTagList tags = ( *sorterIter )->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); } // check our own forced sorters DICOMTagList tags = m_EquiDistantBlocksSorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); tags = m_NormalDirectionConsistencySorter->GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); // add the tags for DICOMImageBlockDescriptor tags = DICOMImageBlockDescriptor::GetTagsOfInterest(); completeList.insert( completeList.end(), tags.cbegin(), tags.cend() ); - const std::unordered_map tagList = GetAdditionalTagsOfInterest(); + const AdditionalTagsMapType tagList = GetAdditionalTagsOfInterest(); for ( auto iter = tagList.cbegin(); iter != tagList.cend(); ++iter ) { - completeList.push_back( iter->second ) ; + completeList.push_back( iter->first ) ; } return completeList; } diff --git a/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp b/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp index 06e9f7866e..4fb30c696e 100644 --- a/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp +++ b/Modules/DICOMReader/src/mitkDICOMImageBlockDescriptor.cpp @@ -1,844 +1,858 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMImageBlockDescriptor.h" #include "mitkStringProperty.h" #include "mitkLevelWindowProperty.h" #include mitk::DICOMImageBlockDescriptor::DICOMImageBlockDescriptor() : m_ReaderImplementationLevel( SOPClassUnknown ) , m_PropertyList( PropertyList::New() ) , m_TagCache( nullptr ) , m_PropertiesOutOfDate( true ) { m_PropertyFunctor = &mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues; } mitk::DICOMImageBlockDescriptor::~DICOMImageBlockDescriptor() { } mitk::DICOMImageBlockDescriptor::DICOMImageBlockDescriptor( const DICOMImageBlockDescriptor& other ) : m_ImageFrameList( other.m_ImageFrameList ) , m_MitkImage( other.m_MitkImage ) , m_SliceIsLoaded( other.m_SliceIsLoaded ) , m_ReaderImplementationLevel( other.m_ReaderImplementationLevel ) , m_TiltInformation( other.m_TiltInformation ) , m_PropertyList( other.m_PropertyList->Clone() ) , m_TagCache( other.m_TagCache ) , m_PropertiesOutOfDate( other.m_PropertiesOutOfDate ) -, m_AdditionalTagList( other.m_AdditionalTagList ) -, m_PropertyFunctor( other.m_PropertyFunctor ) +, m_AdditionalTagMap(other.m_AdditionalTagMap) +, m_FoundAdditionalTags(other.m_FoundAdditionalTags) +, m_PropertyFunctor(other.m_PropertyFunctor) { if ( m_MitkImage ) { m_MitkImage = m_MitkImage->Clone(); } m_PropertyFunctor = &mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues; } mitk::DICOMImageBlockDescriptor& mitk::DICOMImageBlockDescriptor:: operator=( const DICOMImageBlockDescriptor& other ) { if ( this != &other ) { m_ImageFrameList = other.m_ImageFrameList; m_MitkImage = other.m_MitkImage; m_SliceIsLoaded = other.m_SliceIsLoaded; m_ReaderImplementationLevel = other.m_ReaderImplementationLevel; m_TiltInformation = other.m_TiltInformation; - m_AdditionalTagList = other.m_AdditionalTagList; + m_AdditionalTagMap = other.m_AdditionalTagMap; + m_FoundAdditionalTags = other.m_FoundAdditionalTags; m_PropertyFunctor = other.m_PropertyFunctor; if ( other.m_PropertyList ) { m_PropertyList = other.m_PropertyList->Clone(); } if ( other.m_MitkImage ) { m_MitkImage = other.m_MitkImage->Clone(); } m_TagCache = other.m_TagCache; m_PropertiesOutOfDate = other.m_PropertiesOutOfDate; } return *this; } mitk::DICOMTagList mitk::DICOMImageBlockDescriptor::GetTagsOfInterest() { DICOMTagList completeList; completeList.push_back( DICOMTag( 0x0018, 0x1164 ) ); // pixel spacing completeList.push_back( DICOMTag( 0x0028, 0x0030 ) ); // imager pixel spacing completeList.push_back( DICOMTag( 0x0008, 0x0018 ) ); // sop instance UID completeList.push_back( DICOMTag( 0x0008, 0x0016 ) ); // sop class UID completeList.push_back( DICOMTag( 0x0020, 0x0011 ) ); // series number completeList.push_back( DICOMTag( 0x0008, 0x1030 ) ); // study description completeList.push_back( DICOMTag( 0x0008, 0x103e ) ); // series description completeList.push_back( DICOMTag( 0x0008, 0x0060 ) ); // modality completeList.push_back( DICOMTag( 0x0018, 0x0024 ) ); // sequence name completeList.push_back( DICOMTag( 0x0020, 0x0037 ) ); // image orientation completeList.push_back( DICOMTag( 0x0020, 0x1041 ) ); // slice location completeList.push_back( DICOMTag( 0x0020, 0x0012 ) ); // acquisition number completeList.push_back( DICOMTag( 0x0020, 0x0013 ) ); // instance number completeList.push_back( DICOMTag( 0x0020, 0x0032 ) ); // image position patient completeList.push_back( DICOMTag( 0x0028, 0x1050 ) ); // window center completeList.push_back( DICOMTag( 0x0028, 0x1051 ) ); // window width completeList.push_back( DICOMTag( 0x0008, 0x0008 ) ); // image type completeList.push_back( DICOMTag( 0x0028, 0x0004 ) ); // photometric interpretation return completeList; } void mitk::DICOMImageBlockDescriptor::SetAdditionalTagsOfInterest( - const std::unordered_map& tagList ) + const AdditionalTagsMapType& tagMap) { - m_AdditionalTagList = tagList; + m_AdditionalTagMap = tagMap; } void mitk::DICOMImageBlockDescriptor::SetTiltInformation( const GantryTiltInformation& info ) { m_TiltInformation = info; } const mitk::GantryTiltInformation mitk::DICOMImageBlockDescriptor::GetTiltInformation() const { return m_TiltInformation; } void mitk::DICOMImageBlockDescriptor::SetImageFrameList( const DICOMImageFrameList& framelist ) { m_ImageFrameList = framelist; m_SliceIsLoaded.resize( framelist.size() ); m_SliceIsLoaded.assign( framelist.size(), false ); m_PropertiesOutOfDate = true; } const mitk::DICOMImageFrameList& mitk::DICOMImageBlockDescriptor::GetImageFrameList() const { return m_ImageFrameList; } void mitk::DICOMImageBlockDescriptor::SetMitkImage( Image::Pointer image ) { if ( m_MitkImage != image ) { if ( m_TagCache.IsNull() ) { MITK_ERROR << "Unable to describe MITK image with properties without a tag-cache object!"; m_MitkImage = nullptr; return; } if ( m_ImageFrameList.empty() ) { MITK_ERROR << "Unable to describe MITK image with properties without a frame list!"; m_MitkImage = nullptr; return; } // Should verify that the image matches m_ImageFrameList and m_TagCache // however, this is hard to do without re-analyzing all // TODO we should at least make sure that the number of frames is identical (plus rows/columns, // orientation) // without gantry tilt correction, we can also check image origin m_MitkImage = this->DescribeImageWithProperties( this->FixupSpacing( image ) ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::GetMitkImage() const { return m_MitkImage; } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::FixupSpacing( Image* mitkImage ) { if ( mitkImage ) { Vector3D imageSpacing = mitkImage->GetGeometry()->GetSpacing(); ScalarType desiredSpacingX = imageSpacing[0]; ScalarType desiredSpacingY = imageSpacing[1]; this->GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); // prefer pixel spacing over imager pixel spacing if ( desiredSpacingX <= 0 || desiredSpacingY <= 0 ) { return mitkImage; } MITK_DEBUG << "Loaded image with spacing " << imageSpacing[0] << ", " << imageSpacing[1]; MITK_DEBUG << "Found correct spacing info " << desiredSpacingX << ", " << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; mitkImage->GetGeometry()->SetSpacing( imageSpacing ); } return mitkImage; } void mitk::DICOMImageBlockDescriptor::SetSliceIsLoaded( unsigned int index, bool isLoaded ) { if ( index < m_SliceIsLoaded.size() ) { m_SliceIsLoaded[index] = isLoaded; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor::IsSliceLoaded( unsigned int index ) const { if ( index < m_SliceIsLoaded.size() ) { return m_SliceIsLoaded[index]; } else { std::stringstream ss; ss << "Index " << index << " out of range (" << m_SliceIsLoaded.size() << " indices reserved)"; throw std::invalid_argument( ss.str() ); } } bool mitk::DICOMImageBlockDescriptor::AllSlicesAreLoaded() const { bool allLoaded = true; for ( auto iter = m_SliceIsLoaded.cbegin(); iter != m_SliceIsLoaded.cend(); ++iter ) { allLoaded &= *iter; } return allLoaded; } /* PS defined IPS defined PS==IPS 0 0 --> UNKNOWN spacing, loader will invent 0 1 --> spacing as at detector surface 1 0 --> spacing as in patient 1 1 0 --> detector surface spacing CORRECTED for geometrical magnifications: spacing as in patient 1 1 1 --> detector surface spacing NOT corrected for geometrical magnifications: spacing as at detector */ mitk::PixelSpacingInterpretation mitk::DICOMImageBlockDescriptor::GetPixelSpacingInterpretation() const { if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetPixelSpacingInterpretation. Need to have initialized tag-cache!"; return SpacingUnknown; } const std::string pixelSpacing = this->GetPixelSpacing(); const std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); if ( pixelSpacing.empty() ) { if ( imagerPixelSpacing.empty() ) { return SpacingUnknown; } else { return SpacingAtDetector; } } else // Pixel Spacing defined { if ( imagerPixelSpacing.empty() ) { return SpacingInPatient; } else if ( pixelSpacing != imagerPixelSpacing ) { return SpacingInPatient; } else { return SpacingAtDetector; } } } std::string mitk::DICOMImageBlockDescriptor::GetPixelSpacing() const { if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetPixelSpacing. Need to have initialized tag-cache!"; return std::string( "" ); } static const DICOMTag tagPixelSpacing( 0x0028, 0x0030 ); return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagPixelSpacing ).value; } std::string mitk::DICOMImageBlockDescriptor::GetImagerPixelSpacing() const { if ( m_ImageFrameList.empty() || m_TagCache.IsNull() ) { MITK_ERROR << "Invalid call to GetImagerPixelSpacing. Need to have initialized tag-cache!"; return std::string( "" ); } static const DICOMTag tagImagerPixelSpacing( 0x0018, 0x1164 ); return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagImagerPixelSpacing ).value; } void mitk::DICOMImageBlockDescriptor::GetDesiredMITKImagePixelSpacing( ScalarType& spacingX, ScalarType& spacingY ) const { const std::string pixelSpacing = this->GetPixelSpacing(); // preference for "in patient" pixel spacing if ( !DICOMStringToSpacing( pixelSpacing, spacingX, spacingY ) ) { const std::string imagerPixelSpacing = this->GetImagerPixelSpacing(); // fallback to "on detector" spacing if ( !DICOMStringToSpacing( imagerPixelSpacing, spacingX, spacingY ) ) { // last resort: invent something spacingX = spacingY = 1.0; } } } void mitk::DICOMImageBlockDescriptor::SetProperty( const std::string& key, BaseProperty* value ) { m_PropertyList->SetProperty( key, value ); } mitk::BaseProperty* mitk::DICOMImageBlockDescriptor::GetProperty( const std::string& key ) const { this->UpdateImageDescribingProperties(); return m_PropertyList->GetProperty( key ); } std::string mitk::DICOMImageBlockDescriptor::GetPropertyAsString( const std::string& key ) const { this->UpdateImageDescribingProperties(); const mitk::BaseProperty::Pointer property = m_PropertyList->GetProperty( key ); if ( property.IsNotNull() ) { return property->GetValueAsString(); } else { return std::string( "" ); } } void mitk::DICOMImageBlockDescriptor::SetFlag( const std::string& key, bool value ) { m_PropertyList->ReplaceProperty( key, BoolProperty::New( value ) ); } bool mitk::DICOMImageBlockDescriptor::GetFlag( const std::string& key, bool defaultValue ) const { this->UpdateImageDescribingProperties(); BoolProperty::ConstPointer boolProp = dynamic_cast( this->GetProperty( key ) ); if ( boolProp.IsNotNull() ) { return boolProp->GetValue(); } else { return defaultValue; } } void mitk::DICOMImageBlockDescriptor::SetIntProperty( const std::string& key, int value ) { m_PropertyList->ReplaceProperty( key, IntProperty::New( value ) ); } int mitk::DICOMImageBlockDescriptor::GetIntProperty( const std::string& key, int defaultValue ) const { this->UpdateImageDescribingProperties(); IntProperty::ConstPointer intProp = dynamic_cast( this->GetProperty( key ) ); if ( intProp.IsNotNull() ) { return intProp->GetValue(); } else { return defaultValue; } } double mitk::DICOMImageBlockDescriptor::stringtodouble( const std::string& str ) const { double d; std::string trimmedstring( str ); try { trimmedstring = trimmedstring.erase( trimmedstring.find_last_not_of( " \n\r\t" ) + 1 ); } catch ( ... ) { // no last not of } std::string firstcomponent( trimmedstring ); try { firstcomponent = trimmedstring.erase( trimmedstring.find_first_of( "\\" ) ); } catch ( ... ) { // no last not of } std::istringstream converter( firstcomponent ); if ( !firstcomponent.empty() && ( converter >> d ) && converter.eof() ) { return d; } else { throw std::invalid_argument( "Argument is not a convertable number" ); } } mitk::Image::Pointer mitk::DICOMImageBlockDescriptor::DescribeImageWithProperties( Image* mitkImage ) { // TODO: this is a collection of properties that have been provided by the // legacy DicomSeriesReader. // We should at some point clean up this collection and name them in a more // consistent way! if ( !mitkImage ) return mitkImage; // first part: add some tags that describe individual slices // these propeties are defined at analysis time (see UpdateImageDescribingProperties()) const char* propertyKeySliceLocation = "dicom.image.0020.1041"; const char* propertyKeyInstanceNumber = "dicom.image.0020.0013"; const char* propertyKeySOPInstanceUID = "dicom.image.0008.0018"; mitkImage->SetProperty( propertyKeySliceLocation, this->GetProperty( "sliceLocationForSlices" ) ); mitkImage->SetProperty( propertyKeyInstanceNumber, this->GetProperty( "instanceNumberForSlices" ) ); mitkImage->SetProperty( propertyKeySOPInstanceUID, this->GetProperty( "SOPInstanceUIDForSlices" ) ); mitkImage->SetProperty( "files", this->GetProperty( "filenamesForSlices" ) ); - for ( auto iter = m_AdditionalTagList.cbegin(); iter != m_AdditionalTagList.cend(); ++iter ) - { - mitkImage->SetProperty( iter->first, this->GetProperty( iter->first ) ); - } - - // second part: add properties that describe the whole image block mitkImage->SetProperty( "dicomseriesreader.SOPClassUID", StringProperty::New( this->GetSOPClassUID() ) ); mitkImage->SetProperty( "dicomseriesreader.SOPClass", StringProperty::New( this->GetSOPClassUIDAsName() ) ); mitkImage->SetProperty( "dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New( PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() ) ) ); mitkImage->SetProperty( "dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New( this->GetPixelSpacingInterpretation() ) ); mitkImage->SetProperty( "dicomseriesreader.ReaderImplementationLevelString", StringProperty::New( ReaderImplementationLevelToString( m_ReaderImplementationLevel ) ) ); mitkImage->SetProperty( "dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( m_ReaderImplementationLevel ) ); mitkImage->SetProperty( "dicomseriesreader.GantyTiltCorrected", BoolProperty::New( this->GetTiltInformation().IsRegularGantryTilt() ) ); mitkImage->SetProperty( "dicomseriesreader.3D+t", BoolProperty::New( this->GetFlag( "3D+t", false ) ) ); // level window const std::string windowCenter = this->GetPropertyAsString( "windowCenter" ); const std::string windowWidth = this->GetPropertyAsString( "windowWidth" ); try { const double level = stringtodouble( windowCenter ); const double window = stringtodouble( windowWidth ); mitkImage->SetProperty( "levelwindow", LevelWindowProperty::New( LevelWindow( level, window ) ) ); } catch ( ... ) { // nothing, no levelwindow to be predicted... } const std::string modality = this->GetPropertyAsString( "modality" ); mitkImage->SetProperty( "modality", StringProperty::New( modality ) ); mitkImage->SetProperty( "dicom.pixel.PhotometricInterpretation", this->GetProperty( "photometricInterpretation" ) ); mitkImage->SetProperty( "dicom.image.imagetype", this->GetProperty( "imagetype" ) ); mitkImage->SetProperty( "dicom.study.StudyDescription", this->GetProperty( "studyDescription" ) ); mitkImage->SetProperty( "dicom.series.SeriesDescription", this->GetProperty( "seriesDescription" ) ); mitkImage->SetProperty( "dicom.pixel.Rows", this->GetProperty( "rows" ) ); mitkImage->SetProperty( "dicom.pixel.Columns", this->GetProperty( "columns" ) ); + // third part: get all found additional tags of interest - // third part: get something from ImageIO. BUT this needs to be created elsewhere. or not at all! + for (auto tag : m_FoundAdditionalTags) + { + BaseProperty* prop = this->GetProperty(tag); + if (prop) + { + mitkImage->SetProperty(tag.c_str(), prop); + } + } + + // fourth part: get something from ImageIO. BUT this needs to be created elsewhere. or not at all! return mitkImage; } void mitk::DICOMImageBlockDescriptor::SetReaderImplementationLevel( const ReaderImplementationLevel& level ) { m_ReaderImplementationLevel = level; } mitk::ReaderImplementationLevel mitk::DICOMImageBlockDescriptor::GetReaderImplementationLevel() const { return m_ReaderImplementationLevel; } std::string mitk::DICOMImageBlockDescriptor::GetSOPClassUID() const { if ( !m_ImageFrameList.empty() && m_TagCache.IsNotNull() ) { static const DICOMTag tagSOPClassUID( 0x0008, 0x0016 ); return m_TagCache->GetTagValue( m_ImageFrameList.front(), tagSOPClassUID ).value; } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUID(). Need to have initialized tag-cache!"; return std::string( "" ); } } std::string mitk::DICOMImageBlockDescriptor::GetSOPClassUIDAsName() const { if ( !m_ImageFrameList.empty() && m_TagCache.IsNotNull() ) { gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( this->GetSOPClassUID().c_str() ); const char* name = uidKnowledge.GetName(); if ( name ) { return std::string( name ); } else { return std::string( "" ); } } else { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::GetSOPClassUIDAsName(). Need to have " "initialized tag-cache!"; return std::string( "" ); } } int mitk::DICOMImageBlockDescriptor::GetNumberOfTimeSteps() const { int result = 1; this->m_PropertyList->GetIntProperty("timesteps", result); return result; }; int mitk::DICOMImageBlockDescriptor::GetNumberOfFramesPerTimeStep() const { const int numberOfTimesteps = this->GetNumberOfTimeSteps(); int numberOfFramesPerTimestep = this->m_ImageFrameList.size() / numberOfTimesteps; assert(int(double((double)this->m_ImageFrameList.size() / (double)numberOfTimesteps)) == numberOfFramesPerTimestep); // this should hold return numberOfFramesPerTimestep; }; void mitk::DICOMImageBlockDescriptor::SetTagCache( DICOMTagCache* privateCache ) { // this must only be used during loading and never afterwards m_TagCache = privateCache; } #define printPropertyRange( label, property_name ) \ \ { \ const std::string first = this->GetPropertyAsString( #property_name "First" ); \ const std::string last = this->GetPropertyAsString( #property_name "Last" ); \ if ( !first.empty() || !last.empty() ) \ { \ if ( first == last ) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ else \ { \ os << " " label ": '" << first << "' - '" << last << "'" << std::endl; \ } \ } \ \ } #define printProperty( label, property_name ) \ \ { \ const std::string first = this->GetPropertyAsString( #property_name ); \ if ( !first.empty() ) \ { \ os << " " label ": '" << first << "'" << std::endl; \ } \ \ } #define printBool( label, commands ) \ \ { \ os << " " label ": '" << ( commands ? "yes" : "no" ) << "'" << std::endl; \ \ } void mitk::DICOMImageBlockDescriptor::Print(std::ostream& os, bool filenameDetails) const { os << " Number of Frames: '" << m_ImageFrameList.size() << "'" << std::endl; os << " SOP class: '" << this->GetSOPClassUIDAsName() << "'" << std::endl; printProperty( "Series Number", seriesNumber ); printProperty( "Study Description", studyDescription ); printProperty( "Series Description", seriesDescription ); printProperty( "Modality", modality ); printProperty( "Sequence Name", sequenceName ); printPropertyRange( "Slice Location", sliceLocation ); printPropertyRange( "Acquisition Number", acquisitionNumber ); printPropertyRange( "Instance Number", instanceNumber ); printPropertyRange( "Image Position", imagePositionPatient ); printProperty( "Image Orientation", orientation ); os << " Pixel spacing interpretation: '" << PixelSpacingInterpretationToString( this->GetPixelSpacingInterpretation() ) << "'" << std::endl; printBool( "Gantry Tilt", this->GetTiltInformation().IsRegularGantryTilt() ) // printBool("3D+t", this->GetFlag("3D+t",false)) // os << " MITK image loaded: '" << (this->GetMitkImage().IsNotNull() ? "yes" : "no") << "'" << // std::endl; if ( filenameDetails ) { os << " Files in this image block:" << std::endl; for ( auto frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++frameIter ) { os << " " << ( *frameIter )->Filename; if ( ( *frameIter )->FrameNo > 0 ) { os << ", " << ( *frameIter )->FrameNo; } os << std::endl; } } } #define storeTagValueToProperty( tag_name, tag_g, tag_e ) \ \ { \ const DICOMTag t( tag_g, tag_e ); \ const std::string tagValue = m_TagCache->GetTagValue( firstFrame, t ).value; \ const_cast( this ) \ ->SetProperty( #tag_name, StringProperty::New( tagValue ) ); \ \ } #define storeTagValueRangeToProperty( tag_name, tag_g, tag_e ) \ \ { \ const DICOMTag t( tag_g, tag_e ); \ const std::string tagValueFirst = m_TagCache->GetTagValue( firstFrame, t ).value; \ const std::string tagValueLast = m_TagCache->GetTagValue( lastFrame, t ).value; \ const_cast( this ) \ ->SetProperty( #tag_name "First", StringProperty::New( tagValueFirst ) ); \ const_cast( this ) \ ->SetProperty( #tag_name "Last", StringProperty::New( tagValueLast ) ); \ \ } void mitk::DICOMImageBlockDescriptor::UpdateImageDescribingProperties() const { if ( !m_PropertiesOutOfDate ) return; if ( !m_ImageFrameList.empty() ) { if ( m_TagCache.IsNull() ) { MITK_ERROR << "Invalid call to DICOMImageBlockDescriptor::UpdateImageDescribingProperties(). Need to " "have initialized tag-cache!"; return; } const DICOMImageFrameInfo::Pointer firstFrame = m_ImageFrameList.front(); const DICOMImageFrameInfo::Pointer lastFrame = m_ImageFrameList.back(); // see macros above storeTagValueToProperty( seriesNumber, 0x0020, 0x0011 ); storeTagValueToProperty( studyDescription, 0x0008, 0x1030 ); storeTagValueToProperty( seriesDescription, 0x0008, 0x103e ); storeTagValueToProperty( modality, 0x0008, 0x0060 ); storeTagValueToProperty( sequenceName, 0x0018, 0x0024 ); storeTagValueToProperty( orientation, 0x0020, 0x0037 ); storeTagValueToProperty( rows, 0x0028, 0x0010 ); storeTagValueToProperty( columns, 0x0028, 0x0011 ); storeTagValueRangeToProperty( sliceLocation, 0x0020, 0x1041 ); storeTagValueRangeToProperty( acquisitionNumber, 0x0020, 0x0012 ); storeTagValueRangeToProperty( instanceNumber, 0x0020, 0x0013 ); storeTagValueRangeToProperty( imagePositionPatient, 0x0020, 0x0032 ); storeTagValueToProperty( windowCenter, 0x0028, 0x1050 ); storeTagValueToProperty( windowWidth, 0x0028, 0x1051 ); storeTagValueToProperty( imageType, 0x0008, 0x0008 ); storeTagValueToProperty( photometricInterpretation, 0x0028, 0x0004 ); // some per-image attributes // frames are just numbered starting from 0. timestep 1 (the second time-step) has frames starting at // (number-of-frames-per-timestep) // std::string propertyKeySliceLocation = "dicom.image.0020.1041"; // std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; // std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; StringLookupTable sliceLocationForSlices; StringLookupTable instanceNumberForSlices; StringLookupTable SOPInstanceUIDForSlices; StringLookupTable filenamesForSlices; const DICOMTag tagSliceLocation( 0x0020, 0x1041 ); const DICOMTag tagInstanceNumber( 0x0020, 0x0013 ); const DICOMTag tagSOPInstanceNumber( 0x0008, 0x0018 ); - std::unordered_map additionalTagResultList; - + std::unordered_map additionalTagResultList; unsigned int slice(0); int timePoint(-1); unsigned int zSlice(0); const int framesPerTimeStep = this->GetNumberOfFramesPerTimeStep(); for ( auto frameIter = m_ImageFrameList.begin(); frameIter != m_ImageFrameList.end(); ++slice, ++frameIter ) { zSlice = slice%framesPerTimeStep; if ( zSlice == 0) { timePoint++; } const std::string sliceLocation = m_TagCache->GetTagValue( *frameIter, tagSliceLocation ).value; sliceLocationForSlices.SetTableValue( slice, sliceLocation ); const std::string instanceNumber = m_TagCache->GetTagValue( *frameIter, tagInstanceNumber ).value; instanceNumberForSlices.SetTableValue( slice, instanceNumber ); const std::string sopInstanceUID = m_TagCache->GetTagValue( *frameIter, tagSOPInstanceNumber ).value; SOPInstanceUIDForSlices.SetTableValue( slice, sopInstanceUID ); const std::string filename = ( *frameIter )->Filename; filenamesForSlices.SetTableValue( slice, filename ); MITK_DEBUG << "Tag info for slice " << slice << ": SL '" << sliceLocation << "' IN '" << instanceNumber << "' SOP instance UID '" << sopInstanceUID << "'"; - for ( auto iter = m_AdditionalTagList.cbegin(); iter != m_AdditionalTagList.cend(); ++iter ) + for (const auto& tag : m_AdditionalTagMap) { - const DICOMDatasetFinding finding = m_TagCache->GetTagValue( *frameIter, iter->second ); - if (finding.isValid) + const DICOMTagCache::FindingsListType findings = m_TagCache->GetTagValue( *frameIter, tag.first ); + for (const auto& finding : findings) { + if (finding.isValid) + { + std::string propKey = (tag.second.empty()) ? DICOMTagPathToPropertyName(finding.path) : tag.second; DICOMCachedValueInfo info{ static_cast(timePoint), zSlice, finding.value }; - additionalTagResultList[iter->first].SetTableValue(slice, info); + additionalTagResultList[propKey].SetTableValue(slice, info); + } } } } + // add property or properties with proper names DICOMImageBlockDescriptor* thisInstance = const_cast( this ); thisInstance->SetProperty( "sliceLocationForSlices", StringLookupTableProperty::New( sliceLocationForSlices ) ); thisInstance->SetProperty( "instanceNumberForSlices", StringLookupTableProperty::New( instanceNumberForSlices ) ); thisInstance->SetProperty( "SOPInstanceUIDForSlices", StringLookupTableProperty::New( SOPInstanceUIDForSlices ) ); thisInstance->SetProperty( "filenamesForSlices", StringLookupTableProperty::New( filenamesForSlices ) ); + + //add properties for additional tags of interest + for ( auto iter = additionalTagResultList.cbegin(); iter != additionalTagResultList.cend(); ++iter ) { thisInstance->SetProperty( iter->first, m_PropertyFunctor( iter->second ) ); + thisInstance->m_FoundAdditionalTags.insert(m_FoundAdditionalTags.cend(),iter->first); } m_PropertiesOutOfDate = false; } } mitk::BaseProperty::Pointer mitk::DICOMImageBlockDescriptor::GetPropertyForDICOMValues(const DICOMCachedValueLookupTable& cacheLookupTable) { const auto& lookupTable = cacheLookupTable.GetLookupTable(); typedef std::pair PairType; if ( std::adjacent_find( lookupTable.cbegin(), lookupTable.cend(), []( const PairType& lhs, const PairType& rhs ) { return lhs.second.Value != rhs.second.Value; } ) == lookupTable.cend() ) { return static_cast( mitk::StringProperty::New(cacheLookupTable.GetTableValue(0).Value).GetPointer()); } StringLookupTable stringTable; for (auto element : lookupTable) { stringTable.SetTableValue(element.first, element.second.Value); } return static_cast( mitk::StringLookupTableProperty::New(stringTable).GetPointer()); } void mitk::DICOMImageBlockDescriptor::SetTagLookupTableToPropertyFunctor( TagLookupTableToPropertyFunctor functor ) { if ( functor != nullptr ) { m_PropertyFunctor = functor; } } diff --git a/Modules/DICOMReader/src/mitkDICOMProperty.cpp b/Modules/DICOMReader/src/mitkDICOMProperty.cpp index 0606b37504..471b9cb980 100644 --- a/Modules/DICOMReader/src/mitkDICOMProperty.cpp +++ b/Modules/DICOMReader/src/mitkDICOMProperty.cpp @@ -1,32 +1,59 @@ /*=================================================================== 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 "mitkDICOMProperty.h" mitk::BaseProperty::Pointer mitk::GetDICOMPropertyForDICOMValuesFunctor(const mitk::DICOMCachedValueLookupTable& cacheLookupTable) { const auto& lookupTable = cacheLookupTable.GetLookupTable(); mitk::DICOMProperty::Pointer prop = mitk::DICOMProperty::New(); for (auto element : lookupTable) { prop->SetValue(element.second.TimePoint, element.second.SliceInTimePoint, element.second.Value); } return prop.GetPointer(); } + +std::map< std::string, mitk::BaseProperty::Pointer> mitk::GetPropertyByDICOMTagPath(const mitk::PropertyList* list, const mitk::DICOMTagPath& path) +{ + std::map< std::string, mitk::BaseProperty::Pointer> result; + + for (const auto& iter : *(list->GetMap())) + { + DICOMTagPath propPath = PropertyNameToDICOMTagPath(iter.first); + if (!propPath.IsEmpty() && path.Equals(propPath)) + { + result.insert(iter); + } + } + return result; +}; + +std::map< std::string, mitk::BaseProperty::Pointer> mitk::GetPropertyByDICOMTagPath(const mitk::BaseData* data, const mitk::DICOMTagPath& path) +{ + std::map< std::string, mitk::BaseProperty::Pointer> result; + + if (data) + { + result = GetPropertyByDICOMTagPath(data->GetPropertyList(), path); + } + + return result; +}; diff --git a/Modules/DICOMReader/src/mitkDICOMTagCache.cpp b/Modules/DICOMReader/src/mitkDICOMTagCache.cpp index ede0742826..6901a57d58 100644 --- a/Modules/DICOMReader/src/mitkDICOMTagCache.cpp +++ b/Modules/DICOMReader/src/mitkDICOMTagCache.cpp @@ -1,31 +1,37 @@ /*=================================================================== 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 "mitkDICOMTagCache.h" mitk::DICOMTagCache::DICOMTagCache() :itk::Object() { } mitk::DICOMTagCache::DICOMTagCache( const DICOMTagCache&) :itk::Object() { } mitk::DICOMTagCache::~DICOMTagCache() { } + +void mitk::DICOMTagCache::SetInputFiles(const StringList& filenames) +{ + m_InputFilenames = filenames; + this->Modified(); +} diff --git a/Modules/DICOMReader/src/mitkDICOMTagPath.cpp b/Modules/DICOMReader/src/mitkDICOMTagPath.cpp new file mode 100644 index 0000000000..e29ab20794 --- /dev/null +++ b/Modules/DICOMReader/src/mitkDICOMTagPath.cpp @@ -0,0 +1,690 @@ +/*=================================================================== + +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 +#include + +#include +#include + +#include + +namespace mitk +{ + + DICOMTagPath::NodeInfo:: + NodeInfo() : type(NodeType::Invalid), tag(0, 0), selection(0) + { + ; + }; + + DICOMTagPath::NodeInfo:: + NodeInfo(const DICOMTag& aTag, NodeType aType, ItemSelectionIndex index) : type(aType), tag(aTag), selection(index) + {}; + + bool DICOMTagPath::NodeInfo::operator == (const NodeInfo& right) const + { + if (!(this->tag == right.tag)) return false; + if (this->type != right.type) return false; + if (this->selection != right.selection) return false; + + return true; + }; + + bool DICOMTagPath::NodeInfo:: + Matches(const NodeInfo& right) const + { + if (type == NodeType::AnyElement || right.type == NodeType::AnyElement) + { + return true; + } + else if (tag == right.tag && type != NodeType::Invalid && right.type != NodeType::Invalid) + { + if (type == NodeType::Element && right.type == NodeType::Element) + { + return true; + } + else if(selection == right.selection || type == NodeType::AnySelection || right.type == NodeType::AnySelection) + { + return true; + } + } + return false; + }; + + bool DICOMTagPath::IsEmpty() const + { + return m_NodeInfos.empty(); + }; + + bool + DICOMTagPath:: + IsExplicit() const + { + for (const auto & pos : m_NodeInfos) + { + if ((pos.type == NodeInfo::NodeType::AnySelection) || (pos.type == NodeInfo::NodeType::AnyElement)) return false; + } + + return true; + }; + + bool + DICOMTagPath:: + HasItemSelectionWildcardsOnly() const + { + bool result = false; + for (const auto & pos : m_NodeInfos) + { + if (pos.type == NodeInfo::NodeType::AnyElement) return false; + result = result || pos.type == NodeInfo::NodeType::AnySelection; + } + + return result; + }; + + DICOMTagPath::PathIndexType DICOMTagPath::Size() const + { + return m_NodeInfos.size(); + } + + DICOMTagPath::PathIndexType + DICOMTagPath:: + AddNode(const NodeInfo& newNode) + { + m_NodeInfos.push_back(newNode); + return m_NodeInfos.size() - 1; + }; + + const DICOMTagPath::NodeInfo& + DICOMTagPath:: + GetNode(const PathIndexType& index) const + { + if ((index<0) || (index >= Size())) + { + mitkThrow() << "Error. Cannot return info of path node. Node index is out of bound. Index: " << index << "; Path: " << this->ToStr(); + } + + return m_NodeInfos[index]; + }; + + DICOMTagPath::NodeInfo& + DICOMTagPath:: + GetNode(const PathIndexType& index) + { + if ((index<0) || (index >= Size())) + { + mitkThrow() << "Error. Cannot return info of path node. Node index is out of bound. Index: " << index << "; Path: " << this->ToStr(); + } + + return m_NodeInfos[index]; + }; + + const DICOMTagPath::NodeInfo& + DICOMTagPath:: + GetFirstNode() const + { + return GetNode(0); + }; + + const DICOMTagPath::NodeInfo& + DICOMTagPath:: + GetLastNode() const + { + return GetNode(Size() - 1); + }; + + DICOMTagPath::NodeInfo& + DICOMTagPath:: + GetLastNode() + { + return GetNode(Size() - 1); + }; + + const DICOMTagPath::NodeInfoVectorType& + DICOMTagPath:: + GetNodes() const + { + return m_NodeInfos; + }; + + std::string + DICOMTagPath:: + ToStr() const + { + std::ostringstream nameStream; + + if (this->Size() == 0) return nameStream.str(); + + PathIndexType i = 0; + for (const auto& node : m_NodeInfos) + { + if (i) + { + nameStream << "."; + } + ++i; + + if (node.type == NodeInfo::NodeType::AnyElement) + { + nameStream << "*"; + } + else if (node.type != NodeInfo::NodeType::Invalid) + { + nameStream << "(" << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetGroup() << "," << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetElement()<<")"; + + if (node.type == NodeInfo::NodeType::SequenceSelection) + { + nameStream << "[" << node.selection << "]"; + } + else if (node.type == NodeInfo::NodeType::AnySelection) + { + nameStream << "[*]"; + } + } + else + { + nameStream << "INVALID_NODE"; + } + } + + return nameStream.str(); + }; + + bool + DICOMTagPath:: + operator == (const DICOMTagPath& path) const + { + return this->m_NodeInfos == path.m_NodeInfos; + }; + + bool + DICOMTagPath:: + operator < (const DICOMTagPath& right) const + { + auto& rightIter = right.m_NodeInfos.cbegin(); + const auto& rightEnd = right.m_NodeInfos.cend(); + for (const auto& leftPos : m_NodeInfos) + { + if (rightIter == rightEnd) return false; + + if (leftPos.tag.GetElement() < rightIter->tag.GetElement()) return true; + if (rightIter->tag.GetElement() < leftPos.tag.GetElement()) return false; + + if (leftPos.tag.GetGroup() < rightIter->tag.GetGroup()) return true; + if (rightIter->tag.GetGroup()< leftPos.tag.GetGroup()) return false; + + if (leftPos.type < rightIter->type) return true; + if (rightIter->type< leftPos.type) return false; + + if (leftPos.selection < rightIter->selection) return true; + if (rightIter->selection< leftPos.selection) return false; + ++rightIter; + } + return rightIter != rightEnd; + } + + bool + DICOMTagPath:: + Equals(const DICOMTagPath& path) const + { + return DICOMTagPathesMatch(*this, path); + }; + + DICOMTagPath& + DICOMTagPath:: + operator = (const DICOMTagPath& path) + { + if (&path == this) return *this; + + this->m_NodeInfos = path.m_NodeInfos; + + return *this; + }; + + DICOMTagPath& + DICOMTagPath::AddAnyElement() + { + m_NodeInfos.emplace_back(DICOMTag(0,0), NodeInfo::NodeType::AnyElement); + return *this; + }; + + DICOMTagPath& + DICOMTagPath::AddElement(unsigned int group, unsigned int element) + { + m_NodeInfos.emplace_back(DICOMTag(group, element), NodeInfo::NodeType::Element); + return *this; + }; + + DICOMTagPath& + DICOMTagPath::AddAnySelection(unsigned int group, unsigned int element) + { + m_NodeInfos.emplace_back(DICOMTag(group, element), NodeInfo::NodeType::AnySelection); + return *this; + }; + + DICOMTagPath& + DICOMTagPath::AddSelection(unsigned int group, unsigned int element, ItemSelectionIndex index) + { + m_NodeInfos.emplace_back(DICOMTag(group, element), NodeInfo::NodeType::SequenceSelection, index); + return *this; + }; + + DICOMTagPath& + DICOMTagPath:: + FromStr(const std::string& pathStr) + { + NodeInfoVectorType result; + std::istringstream f(pathStr); + std::string subStr; + + while (getline(f, subStr, '.')) + { + NodeInfo info; + + if (subStr == "*") + { + info.type = NodeInfo::NodeType::AnyElement; + } + else + { + std::regex reg_element("\\((\\d{4}),(\\d{4})\\)"); + std::regex reg_anySelection("\\((\\d{4}),(\\d{4})\\)\\[\\*\\]"); + std::regex reg_Selection("\\((\\d{4}),(\\d{4})\\)\\[(\\d+)\\]"); + std::smatch sm; + if (std::regex_match(subStr, sm, reg_anySelection)) + { + info.type = NodeInfo::NodeType::AnySelection; + info.tag = DICOMTag(std::stoul(sm[1], nullptr, 16), std::stoul(sm[2], nullptr, 16)); + } + else if (std::regex_match(subStr, sm, reg_Selection)) + { + info.type = NodeInfo::NodeType::SequenceSelection; + info.tag = DICOMTag(std::stoul(sm[1], nullptr, 16), std::stoul(sm[2], nullptr, 16)); + info.selection = std::stoi(sm[3]); + } + else if (std::regex_match(subStr, sm, reg_element)) + { + info.type = NodeInfo::NodeType::Element; + info.tag = DICOMTag(std::stoul(sm[1], nullptr, 16), std::stoul(sm[2], nullptr, 16)); + } + } + result.push_back(info); + } + + this->m_NodeInfos.swap(result); + return *this; + }; + + DICOMTagPath::DICOMTagPath() + { + Reset(); + }; + + DICOMTagPath:: + DICOMTagPath(const DICOMTagPath& path) + { + *this = path; + }; + + DICOMTagPath:: + DICOMTagPath(const DICOMTag& tag) + { + m_NodeInfos.emplace_back(tag, NodeInfo::NodeType::Element); + }; + + DICOMTagPath::DICOMTagPath(unsigned int group, unsigned int element) + { + m_NodeInfos.emplace_back(DICOMTag(group,element)); + }; + + DICOMTagPath:: + ~DICOMTagPath() {}; + + void + DICOMTagPath:: + Reset() + { + this->m_NodeInfos.clear(); + }; + + bool + DICOMTagPath:: + DICOMTagPathesMatch(const DICOMTagPath& left, const DICOMTagPath& right) + { + NodeInfoVectorType::const_iterator leftPos = left.GetNodes().cbegin(); + NodeInfoVectorType::const_iterator rightPos = right.GetNodes().cbegin(); + NodeInfoVectorType::const_iterator leftEnd = left.GetNodes().cend(); + NodeInfoVectorType::const_iterator rightEnd = right.GetNodes().cend(); + + while (leftPos != leftEnd && rightPos != rightEnd) + { + if (!leftPos->Matches(*rightPos)) break; + ++leftPos; + ++rightPos; + } + + if (leftPos == leftEnd && rightPos == rightEnd) return true; + else return false; + }; + + std::ostream & operator<<(std::ostream &os, const DICOMTagPath &value) + { + os << value.ToStr(); + return os; + }; + + + std::string DICOMTagPathToDCMTKSearchPath(const DICOMTagPath& tagPath) + { + if (!tagPath.IsExplicit() && !tagPath.HasItemSelectionWildcardsOnly()) + { + mitkThrow() << "Cannot convert DICOMTagPath into DCMTK search path. Path has element wild cards. Path: " << tagPath.ToStr(); + } + + return tagPath.ToStr(); + }; + + std::string DICOMTagPathToPropertRegEx(const DICOMTagPath& tagPath) + { + std::ostringstream nameStream; + + nameStream << "DICOM"; + + for (const auto& node : tagPath.GetNodes()) + { + nameStream << "\\."; + + if (node.type == DICOMTagPath::NodeInfo::NodeType::AnyElement) + { + nameStream << "(\\d{4})\\.(\\d{4})"; + } + else if (node.type != DICOMTagPath::NodeInfo::NodeType::Invalid) + { + nameStream << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetGroup() << "\\." << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetElement(); + + if (node.type == DICOMTagPath::NodeInfo::NodeType::SequenceSelection) + { + nameStream << "\\.\\[" << node.selection << "\\]"; + } + else if (node.type == DICOMTagPath::NodeInfo::NodeType::AnySelection) + { + nameStream << "\\.\\[(\\d*)\\]"; + } + } + else + { + nameStream << "INVALIDNODE"; + } + } + + return nameStream.str(); + }; + + std::string DICOMTagPathToPersistenceKeyRegEx(const DICOMTagPath& tagPath) + { + std::ostringstream nameStream; + + nameStream << "DICOM"; + + for (const auto& node : tagPath.GetNodes()) + { + nameStream << "_"; + + if (node.type == DICOMTagPath::NodeInfo::NodeType::AnyElement) + { + nameStream << "(\\d{4})_(\\d{4})"; + } + else if (node.type != DICOMTagPath::NodeInfo::NodeType::Invalid) + { + nameStream << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetGroup() << "_" << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetElement(); + + if (node.type == DICOMTagPath::NodeInfo::NodeType::SequenceSelection) + { + nameStream << "_\\[" << node.selection << "\\]"; + } + else if (node.type == DICOMTagPath::NodeInfo::NodeType::AnySelection) + { + nameStream << "_\\[(\\d*)\\]"; + } + } + else + { + nameStream << "INVALIDNODE"; + } + } + + return nameStream.str(); + }; + + std::string DICOMTagPathToPersistenceKeyTemplate(const DICOMTagPath& tagPath) + { + std::ostringstream nameStream; + + nameStream << "DICOM"; + + int captureGroup = 1; + + for (const auto& node : tagPath.GetNodes()) + { + nameStream << "_"; + + if (node.type == DICOMTagPath::NodeInfo::NodeType::AnyElement) + { + nameStream << "$" << captureGroup++; + nameStream << "_$" << captureGroup++; + } + else if (node.type != DICOMTagPath::NodeInfo::NodeType::Invalid) + { + nameStream << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetGroup() << "_" << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetElement(); + + if (node.type == DICOMTagPath::NodeInfo::NodeType::SequenceSelection) + { + nameStream << "_[" << node.selection << "]"; + } + else if (node.type == DICOMTagPath::NodeInfo::NodeType::AnySelection) + { + nameStream << "_[$" << captureGroup++ << "]"; + } + } + else + { + nameStream << "INVALID_NODE"; + } + } + + return nameStream.str(); + }; + + std::string DICOMTagPathToPersistenceNameTemplate(const DICOMTagPath& tagPath) + { + std::ostringstream nameStream; + + nameStream << "DICOM"; + + int captureGroup = 1; + + for (const auto& node : tagPath.GetNodes()) + { + nameStream << "."; + + if (node.type == DICOMTagPath::NodeInfo::NodeType::AnyElement) + { + nameStream << "$" << captureGroup++; + nameStream << ".$" << captureGroup++; + } + else if (node.type != DICOMTagPath::NodeInfo::NodeType::Invalid) + { + nameStream << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetGroup() << "." << std::setw(4) << std::setfill('0') << std::hex << node.tag.GetElement(); + + if (node.type == DICOMTagPath::NodeInfo::NodeType::SequenceSelection) + { + nameStream << ".[" << node.selection << "]"; + } + else if (node.type == DICOMTagPath::NodeInfo::NodeType::AnySelection) + { + nameStream << ".[$"<Lock(); + + std::string currentCLocale = setlocale(LC_NUMERIC, nullptr); + m_ReplacedCLocales.push(currentCLocale); + setlocale(LC_NUMERIC, "C"); + + std::locale currentCinLocale(std::cin.getloc()); + m_ReplacedCinLocales.push(currentCinLocale); + std::locale l("C"); + std::cin.imbue(l); + + s_LocaleMutex->Unlock(); +} + +void mitk::DICOMTagScanner::PopLocale() const +{ + s_LocaleMutex->Lock(); + + if (!m_ReplacedCLocales.empty()) + { + setlocale(LC_NUMERIC, m_ReplacedCLocales.top().c_str()); + m_ReplacedCLocales.pop(); + } + else + { + MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; + } + + if (!m_ReplacedCinLocales.empty()) + { + std::cin.imbue(m_ReplacedCinLocales.top()); + m_ReplacedCinLocales.pop(); + } + else + { + MITK_WARN << "Mismatched PopLocale on DICOMITKSeriesGDCMReader."; + } + + s_LocaleMutex->Unlock(); +} + +std::string mitk::DICOMTagScanner::GetActiveLocale() +{ + return setlocale(LC_NUMERIC, nullptr); +} diff --git a/Modules/DICOMReader/src/mitkDICOMTagHelper.cpp b/Modules/DICOMReader/src/mitkDICOMTagsOfInterestHelper.cpp similarity index 58% rename from Modules/DICOMReader/src/mitkDICOMTagHelper.cpp rename to Modules/DICOMReader/src/mitkDICOMTagsOfInterestHelper.cpp index 6313afb1ce..461c6d482e 100644 --- a/Modules/DICOMReader/src/mitkDICOMTagHelper.cpp +++ b/Modules/DICOMReader/src/mitkDICOMTagsOfInterestHelper.cpp @@ -1,232 +1,177 @@ /*=================================================================== 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 "mitkDICOMTagHelper.h" +#include "mitkDICOMTagsOfInterestHelper.h" #include #include "usModuleContext.h" #include "usGetModuleContext.h" -mitk::DefaultDICOMTagMapType::value_type MakeEntry(const mitk::DICOMTag& tag) +mitk::DICOMTagPathMapType::value_type MakeLegacyEntry(const std::string& propName, const mitk::DICOMTag& tag) { - return std::make_pair(GeneratPropertyNameForDICOMTag(tag).c_str(), tag); + return std::make_pair(tag, propName); } -mitk::DICOMTagMapType +mitk::DICOMTagPathMapType::value_type MakeEntry(const mitk::DICOMTagPath& tagPath) +{ + return std::make_pair(tagPath, ""); +} + +mitk::DICOMTagPathMapType mitk::GetCurrentDICOMTagsOfInterest() { - mitk::DICOMTagMapType result; + mitk::DICOMTagPathMapType result; std::vector > toiRegisters = us::GetModuleContext()->GetServiceReferences(); if (toiRegisters.empty()) { // bad, no service found, cannot get tags of interest MITK_ERROR << "DICOM tag error: no service for DICOM tags of interest"; return result; } else if (toiRegisters.size() > 1) { MITK_WARN << "DICOM tag error: multiple service for DICOM tags of interest found. Using just one."; } IDICOMTagsOfInterest* toiRegister = us::GetModuleContext()->GetService(toiRegisters.front()); if (!toiRegister) { MITK_ERROR << "Service lookup error, cannot get DICOM tag of interest service "; } return toiRegister->GetTagsOfInterest(); } -mitk::DefaultDICOMTagMapType +mitk::DICOMTagPathMapType mitk::GetDefaultDICOMTagsOfInterest() { - DefaultDICOMTagMapType result; + DICOMTagPathMapType result; //These tags are copied from DICOMSeriesReader. The old naming style (deprecated) //is keept for backwards compatibility until it is removed. //Below we have also already added the properties with the new naming style - // Patient module - result.insert(std::make_pair("dicom.patient.PatientsName", DICOMTag(0x0010, 0x0010))); - result.insert(std::make_pair("dicom.patient.PatientID", DICOMTag(0x0010, 0x0020))); - result.insert(std::make_pair("dicom.patient.PatientsBirthDate", DICOMTag(0x0010, 0x0030))); - result.insert(std::make_pair("dicom.patient.PatientsSex", DICOMTag(0x0010, 0x0040))); - result.insert(std::make_pair("dicom.patient.PatientsBirthTime", DICOMTag(0x0010, 0x0032))); - result.insert(std::make_pair("dicom.patient.OtherPatientIDs", DICOMTag(0x0010, 0x1000))); - result.insert(std::make_pair("dicom.patient.OtherPatientNames", DICOMTag(0x0010, 0x1001))); - result.insert(std::make_pair("dicom.patient.EthnicGroup", DICOMTag(0x0010, 0x2160))); - result.insert(std::make_pair("dicom.patient.PatientComments", DICOMTag(0x0010, 0x4000))); - result.insert(std::make_pair("dicom.patient.PatientIdentityRemoved", DICOMTag(0x0012, 0x0062))); - result.insert(std::make_pair("dicom.patient.DeIdentificationMethod", DICOMTag(0x0012, 0x0063))); - - // General Study module - result.insert(std::make_pair("dicom.study.StudyInstanceUID", DICOMTag(0x0020, 0x000d))); - result.insert(std::make_pair("dicom.study.StudyDate", DICOMTag(0x0008, 0x0020))); - result.insert(std::make_pair("dicom.study.StudyTime", DICOMTag(0x0008, 0x0030))); - result.insert(std::make_pair("dicom.study.ReferringPhysiciansName", DICOMTag(0x0008, 0x0090))); - result.insert(std::make_pair("dicom.study.StudyID", DICOMTag(0x0020, 0x0010))); - result.insert(std::make_pair("dicom.study.AccessionNumber", DICOMTag(0x0008, 0x0050))); - result.insert(std::make_pair("dicom.study.StudyDescription", DICOMTag(0x0008, 0x1030))); - result.insert(std::make_pair("dicom.study.PhysiciansOfRecord", DICOMTag(0x0008, 0x1048))); - result.insert(std::make_pair("dicom.study.NameOfPhysicianReadingStudy", DICOMTag(0x0008, 0x1060))); - - // General Series module - result.insert(std::make_pair("dicom.series.Modality", DICOMTag(0x0008, 0x0060))); - result.insert(std::make_pair("dicom.series.SeriesInstanceUID", DICOMTag(0x0020, 0x000e))); - result.insert(std::make_pair("dicom.series.SeriesNumber", DICOMTag(0x0020, 0x0011))); - result.insert(std::make_pair("dicom.series.Laterality", DICOMTag(0x0020, 0x0060))); - result.insert(std::make_pair("dicom.series.SeriesDate", DICOMTag(0x0008, 0x0021))); - result.insert(std::make_pair("dicom.series.SeriesTime", DICOMTag(0x0008, 0x0031))); - result.insert(std::make_pair("dicom.series.PerformingPhysiciansName", DICOMTag(0x0008, 0x1050))); - result.insert(std::make_pair("dicom.series.ProtocolName", DICOMTag(0x0018, 0x1030))); - result.insert(std::make_pair("dicom.series.SeriesDescription", DICOMTag(0x0008, 0x103e))); - result.insert(std::make_pair("dicom.series.OperatorsName", DICOMTag(0x0008, 0x1070))); - result.insert(std::make_pair("dicom.series.BodyPartExamined", DICOMTag(0x0018, 0x0015))); - result.insert(std::make_pair("dicom.series.PatientPosition", DICOMTag(0x0018, 0x5100))); - result.insert(std::make_pair("dicom.series.SmallestPixelValueInSeries", DICOMTag(0x0028, 0x0108))); - result.insert(std::make_pair("dicom.series.LargestPixelValueInSeries", DICOMTag(0x0028, 0x0109))); - - // VOI LUT module - result.insert(std::make_pair("dicom.voilut.WindowCenter", DICOMTag(0x0028, 0x1050))); - result.insert(std::make_pair("dicom.voilut.WindowWidth", DICOMTag(0x0028, 0x1051))); - result.insert(std::make_pair("dicom.voilut.WindowCenterAndWidthExplanation", DICOMTag(0x0028, 0x1055))); - - // Image Pixel module - result.insert(std::make_pair("dicom.pixel.PhotometricInterpretation", DICOMTag(0x0028, 0x0004))); - result.insert(std::make_pair("dicom.pixel.Rows", DICOMTag(0x0028, 0x0010))); - result.insert(std::make_pair("dicom.pixel.Columns", DICOMTag(0x0028, 0x0011))); - - // Image Plane module - result.insert(std::make_pair("dicom.PixelSpacing", DICOMTag(0x0028, 0x0030))); - result.insert(std::make_pair("dicom.ImagerPixelSpacing", DICOMTag(0x0018, 0x1164))); - - //additional for RT - result.insert(std::make_pair("dicom.RescaleIntercept", DICOMTag(0x0028, 0x1052))); - result.insert(std::make_pair("dicom.RescaleSlope", DICOMTag(0x0028, 0x1053))); - result.insert(std::make_pair("dicom.ManufacturerModelName", DICOMTag(0x0008, 0x1090))); - result.insert(std::make_pair("dicom.ManufacturerName", DICOMTag(0x0008, 0x0070))); - result.insert(std::make_pair("dicom.InstitutionName", DICOMTag(0x0008, 0x0080))); - result.insert(std::make_pair("dicom.StationName", DICOMTag(0x0008, 0x1010))); - result.insert(std::make_pair("dicom.DoseGridScaling", DICOMTag(0x3004, 0x000e))); - // Patient module /*dicom.patient.PatientsName*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x0010))); /*dicom.patient.PatientID*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x0020))); /*dicom.patient.PatientsBirthDate*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x0030))); /*dicom.patient.PatientsSex*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x0040))); /*dicom.patient.PatientsBirthTime*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x0032))); /*dicom.patient.OtherPatientIDs*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x1000))); /*dicom.patient.OtherPatientNames*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x1001))); /*dicom.patient.EthnicGroup*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x2160))); /*dicom.patient.PatientComments*/ result.insert(MakeEntry(DICOMTag(0x0010, 0x4000))); /*dicom.patient.PatientIdentityRemoved*/ result.insert(MakeEntry(DICOMTag(0x0012, 0x0062))); /*dicom.patient.DeIdentificationMethod*/ result.insert(MakeEntry(DICOMTag(0x0012, 0x0063))); // General Study module /*dicom.study.StudyInstanceUID*/ result.insert(MakeEntry(DICOMTag(0x0020, 0x000d))); /*dicom.study.StudyDate*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0020))); /*dicom.study.StudyTime*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0030))); /*dicom.study.ReferringPhysiciansName*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0090))); /*dicom.study.StudyID*/ result.insert(MakeEntry(DICOMTag(0x0020, 0x0010))); /*dicom.study.AccessionNumber*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0050))); /*dicom.study.StudyDescription*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x1030))); /*dicom.study.PhysiciansOfRecord*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x1048))); /*dicom.study.NameOfPhysicianReadingStudy*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x1060))); // General Series module /*dicom.series.Modality*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0060))); /*dicom.series.SeriesInstanceUID*/ result.insert(MakeEntry(DICOMTag(0x0020, 0x000e))); /*dicom.series.SeriesNumber*/ result.insert(MakeEntry(DICOMTag(0x0020, 0x0011))); /*dicom.series.Laterality*/ result.insert(MakeEntry(DICOMTag(0x0020, 0x0060))); /*dicom.series.SeriesDate*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0021))); /*dicom.series.SeriesTime*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0031))); /*dicom.series.PerformingPhysiciansName*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x1050))); /*dicom.series.ProtocolName*/ result.insert(MakeEntry(DICOMTag(0x0018, 0x1030))); /*dicom.series.SeriesDescription*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x103e))); /*dicom.series.OperatorsName*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x1070))); /*dicom.series.BodyPartExamined*/ result.insert(MakeEntry(DICOMTag(0x0018, 0x0015))); /*dicom.series.PatientPosition*/ result.insert(MakeEntry(DICOMTag(0x0018, 0x5100))); /*dicom.series.SmallestPixelValueInSeries*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x0108))); /*dicom.series.LargestPixelValueInSeries*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x0109))); // VOI LUT module /*dicom.voilut.WindowCenter*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x1050))); /*dicom.voilut.WindowWidth*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x1051))); /*dicom.voilut.WindowCenterAndWidthExplanation*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x1055))); // Image Pixel module /*dicom.pixel.PhotometricInterpretation*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x0004))); /*dicom.pixel.Rows*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x0010))); /*dicom.pixel.Columns*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x0011))); // Image Plane module /*dicom.PixelSpacing*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x0030))); /*dicom.ImagerPixelSpacing*/ result.insert(MakeEntry(DICOMTag(0x0018, 0x1164))); //additional for RT /*dicom.RescaleIntercept*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x1052))); /*dicom.RescaleSlope*/ result.insert(MakeEntry(DICOMTag(0x0028, 0x1053))); /*dicom.ManufacturerModelName*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x1090))); /*dicom.ManufacturerName*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0070))); /*dicom.InstitutionName*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x0080))); /*dicom.StationName*/ result.insert(MakeEntry(DICOMTag(0x0008, 0x1010))); /*dicom.DoseGridScaling*/ result.insert(MakeEntry(DICOMTag(0x3004, 0x000e))); //Additions for PET - result.insert(MakeEntry(DICOMTag(0x0018, 0x0031))); //dicom.pet.Tracer - result.insert(MakeEntry(DICOMTag(0x0018, 0x1072))); //dicom.pet.RadiopharmaceuticalStartTime - result.insert(MakeEntry(DICOMTag(0x0018, 0x1074))); //dicom.pet.RadionuclideTotalDose - result.insert(MakeEntry(DICOMTag(0x0018, 0x1075))); //dicom.pet.RadionuclideHalfLife - result.insert(MakeEntry(DICOMTag(0x0010, 0x1030))); //dicom.patient.PatientWeight - result.insert(MakeEntry(DICOMTag(0x0010, 0x1020))); //dicom.patient.PatientSize + DICOMTagPath radioPharmaRootTag; + radioPharmaRootTag.AddAnySelection(0x0054, 0x0016); + DICOMTagPath radioNuclideRootTag(radioPharmaRootTag); + radioNuclideRootTag.AddAnySelection(0x0054, 0x0300); + + result.insert(MakeEntry(DICOMTagPath(radioPharmaRootTag).AddElement(0x0018, 0x0031))); //dicom.pet.Radiopharmaceutical + result.insert(MakeEntry(DICOMTagPath(radioPharmaRootTag).AddElement(0x0018, 0x1072))); //dicom.pet.RadiopharmaceuticalStartTime + result.insert(MakeEntry(DICOMTagPath(radioPharmaRootTag).AddElement(0x0018, 0x1074))); //dicom.pet.RadionuclideTotalDose + result.insert(MakeEntry(DICOMTagPath(radioPharmaRootTag).AddElement(0x0018, 0x1075))); //dicom.pet.RadionuclideHalfLife + result.insert(MakeEntry(DICOMTagPath(radioPharmaRootTag).AddElement(0x0018, 0x1076))); //dicom.pet.RadionuclidePositronFraction + + result.insert(MakeEntry(DICOMTagPath(radioNuclideRootTag).AddElement(0x0008, 0x0100))); //dicom.pet.Radionuclide.CodeValue + result.insert(MakeEntry(DICOMTagPath(radioNuclideRootTag).AddElement(0x0008, 0x0102))); //dicom.pet.Radionuclide.CodingSchemeDesignator + result.insert(MakeEntry(DICOMTagPath(radioNuclideRootTag).AddElement(0x0008, 0x0104))); //dicom.pet.Radionuclide.CodemManing + result.insert(MakeEntry(DICOMTag(0x0054, 0x1001))); //dicom.pet.RadioactivityUnits result.insert(MakeEntry(DICOMTag(0x0054, 0x1102))); //dicom.pet.DecayCorrection result.insert(MakeEntry(DICOMTag(0x0054, 0x1321))); //dicom.pet.DecayFactor result.insert(MakeEntry(DICOMTag(0x0054, 0x1300))); //dicom.pet.FrameReferenceTime + result.insert(MakeEntry(DICOMTag(0x0010, 0x1030))); //dicom.patient.PatientWeight + result.insert(MakeEntry(DICOMTag(0x0010, 0x1020))); //dicom.patient.PatientSize + //Other interesting acquisition correlated information result.insert(MakeEntry(DICOMTag(0x0008, 0x0022))); //dicom.acquisition date result.insert(MakeEntry(DICOMTag(0x0008, 0x0032))); //dicom.acquisition time result.insert(MakeEntry(DICOMTag(0x0008, 0x002a))); //dicom.acquisition datetime result.insert(MakeEntry(DICOMTag(0x0008, 0x0080))); //dicom.Modality result.insert(MakeEntry(DICOMTag(0x0018, 0x002a))); //dicom.Sequence Name result.insert(MakeEntry(DICOMTag(0x0018, 0x0020))); //dicom.Scanning Sequence result.insert(MakeEntry(DICOMTag(0x0018, 0x0021))); //dicom.Sequence Variant result.insert(MakeEntry(DICOMTag(0x0018, 0x0080))); //dicom.TR result.insert(MakeEntry(DICOMTag(0x0018, 0x0081))); //dicom.TE result.insert(MakeEntry(DICOMTag(0x0018, 0x1310))); //dicom.Acquisition Matrix result.insert(MakeEntry(DICOMTag(0x0018, 0x0087))); //dicom.Magnetic Field Strength //SOP result.insert(MakeEntry(DICOMTag(0x0008, 0x0018))); //SOP Instance UID result.insert(MakeEntry(DICOMTag(0x0020, 0x0013))); //Instance number result.insert(MakeEntry(DICOMTag(0x0020, 0x1041))); //Slice location return result; }; - -std::string -mitk::GeneratPropertyNameForDICOMTag(const mitk::DICOMTag& tag) -{ - std::ostringstream nameStream; - nameStream << "DICOM." << std::setw(4) << std::setfill('0') << std::hex << tag.GetGroup() << "." << std::setw(4) << std::setfill('0') << std::hex << tag.GetElement(); - - return nameStream.str(); -}; diff --git a/Modules/DICOMReader/src/mitkITKDICOMSeriesReaderHelper.cpp b/Modules/DICOMReader/src/mitkITKDICOMSeriesReaderHelper.cpp index 1380e78021..f3143fc477 100644 --- a/Modules/DICOMReader/src/mitkITKDICOMSeriesReaderHelper.cpp +++ b/Modules/DICOMReader/src/mitkITKDICOMSeriesReaderHelper.cpp @@ -1,391 +1,391 @@ /*=================================================================== 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 #define BOOST_DATE_TIME_NO_LIB //Prevent unnecessary/unwanted auto link in this compilation when activating boost libraries in the MITK superbuild //It is necessary because BOOST_ALL_DYN_LINK overwrites BOOST_DATE_TIME_NO_LIB #if defined(BOOST_ALL_DYN_LINK) #undef BOOST_ALL_DYN_LINK #endif #include #include "mitkITKDICOMSeriesReaderHelper.h" #include "mitkITKDICOMSeriesReaderHelper.txx" #include "mitkDICOMGDCMTagScanner.h" #include "mitkArbitraryTimeGeometry.h" #define switch3DCase( IOType, T ) \ case IOType: \ return LoadDICOMByITK( filenames, correctTilt, tiltInfo, io ); bool mitk::ITKDICOMSeriesReaderHelper::CanHandleFile( const std::string& filename ) { MITK_DEBUG << "ITKDICOMSeriesReaderHelper::CanHandleFile " << filename; itk::GDCMImageIO::Pointer tester = itk::GDCMImageIO::New(); return tester->CanReadFile( filename.c_str() ); } mitk::Image::Pointer mitk::ITKDICOMSeriesReaderHelper::Load( const StringContainer& filenames, bool correctTilt, const GantryTiltInformation& tiltInfo ) { if ( filenames.empty() ) { MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; return nullptr; // this is not actually an error but the result is very simple } typedef itk::GDCMImageIO DcmIoType; DcmIoType::Pointer io = DcmIoType::New(); try { if ( io->CanReadFile( filenames.front().c_str() ) ) { io->SetFileName( filenames.front().c_str() ); io->ReadImageInformation(); if ( io->GetPixelType() == itk::ImageIOBase::SCALAR ) { switch ( io->GetComponentType() ) { switch3DCase(DcmIoType::UCHAR, unsigned char) switch3DCase(DcmIoType::CHAR, char) switch3DCase( DcmIoType::USHORT, unsigned short) switch3DCase(DcmIoType::SHORT, short) switch3DCase(DcmIoType::UINT, unsigned int) switch3DCase(DcmIoType::INT, int) switch3DCase( DcmIoType::ULONG, long unsigned int) switch3DCase(DcmIoType::LONG, long int) switch3DCase(DcmIoType::FLOAT, float) switch3DCase(DcmIoType::DOUBLE, double) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } else if ( io->GetPixelType() == itk::ImageIOBase::RGB ) { switch ( io->GetComponentType() ) { switch3DCase(DcmIoType::UCHAR, itk::RGBPixel) switch3DCase( DcmIoType::CHAR, itk::RGBPixel) switch3DCase(DcmIoType::USHORT, itk::RGBPixel) switch3DCase(DcmIoType::SHORT, itk::RGBPixel) switch3DCase( DcmIoType::UINT, itk::RGBPixel) switch3DCase(DcmIoType::INT, itk::RGBPixel) switch3DCase(DcmIoType::ULONG, itk::RGBPixel) switch3DCase(DcmIoType::LONG, itk::RGBPixel) switch3DCase( DcmIoType::FLOAT, itk::RGBPixel) switch3DCase(DcmIoType::DOUBLE, itk::RGBPixel) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } MITK_ERROR << "Unsupported DICOM pixel type"; return nullptr; } } catch ( const itk::MemoryAllocationError& e ) { MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); } catch ( const std::exception& e ) { MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); } catch ( ... ) { MITK_ERROR << "Unspecified error encountered when loading DICOM series."; } return nullptr; } #define switch3DnTCase( IOType, T ) \ case IOType: \ return LoadDICOMByITK3DnT( filenamesLists, correctTilt, tiltInfo, io ); mitk::Image::Pointer mitk::ITKDICOMSeriesReaderHelper::Load3DnT( const StringContainerList& filenamesLists, bool correctTilt, const GantryTiltInformation& tiltInfo ) { if ( filenamesLists.empty() || filenamesLists.front().empty() ) { MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; return nullptr; // this is not actually an error but the result is very simple } typedef itk::GDCMImageIO DcmIoType; DcmIoType::Pointer io = DcmIoType::New(); try { if ( io->CanReadFile( filenamesLists.front().front().c_str() ) ) { io->SetFileName( filenamesLists.front().front().c_str() ); io->ReadImageInformation(); if ( io->GetPixelType() == itk::ImageIOBase::SCALAR ) { switch ( io->GetComponentType() ) { switch3DnTCase(DcmIoType::UCHAR, unsigned char) switch3DnTCase(DcmIoType::CHAR, char) switch3DnTCase(DcmIoType::USHORT, unsigned short) switch3DnTCase( DcmIoType::SHORT, short) switch3DnTCase(DcmIoType::UINT, unsigned int) switch3DnTCase(DcmIoType::INT, int) switch3DnTCase(DcmIoType::ULONG, long unsigned int) switch3DnTCase(DcmIoType::LONG, long int) switch3DnTCase(DcmIoType::FLOAT, float) switch3DnTCase(DcmIoType::DOUBLE, double) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } else if ( io->GetPixelType() == itk::ImageIOBase::RGB ) { switch ( io->GetComponentType() ) { switch3DnTCase(DcmIoType::UCHAR, itk::RGBPixel) switch3DnTCase(DcmIoType::CHAR, itk::RGBPixel) switch3DnTCase( DcmIoType::USHORT, itk::RGBPixel) switch3DnTCase(DcmIoType::SHORT, itk::RGBPixel) switch3DnTCase(DcmIoType::UINT, itk::RGBPixel) switch3DnTCase( DcmIoType::INT, itk::RGBPixel) switch3DnTCase(DcmIoType::ULONG, itk::RGBPixel) switch3DnTCase(DcmIoType::LONG, itk::RGBPixel) switch3DnTCase( DcmIoType::FLOAT, itk::RGBPixel) switch3DnTCase(DcmIoType::DOUBLE, itk::RGBPixel) default : MITK_ERROR << "Found unsupported DICOM scalar pixel type: (enum value) " << io->GetComponentType(); } } MITK_ERROR << "Unsupported DICOM pixel type"; return nullptr; } } catch ( const itk::MemoryAllocationError& e ) { MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); } catch ( const std::exception& e ) { MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); } catch ( ... ) { MITK_ERROR << "Unspecified error encountered when loading DICOM series."; } return nullptr; } bool ConvertDICOMDateTimeString( const std::string& dateString, const std::string& timeString, OFDateTime& time ) { OFString content( timeString.c_str() ); if ( !dateString.empty() ) { content = OFString( dateString.c_str() ).append( content ); } const OFCondition result = DcmDateTime::getOFDateTimeFromString( content, time ); return result.good(); } boost::posix_time::ptime ConvertOFDateTimeToPTime( const OFDateTime& time ) { const boost::gregorian::date boostDate( time.getDate().getYear(), time.getDate().getMonth(), time.getDate().getDay() ); const boost::posix_time::time_duration boostTime = boost::posix_time::hours( time.getTime().getHour() ) + boost::posix_time::minutes( time.getTime().getMinute() ) + boost::posix_time::seconds( time.getTime().getSecond() ) + boost::posix_time::milliseconds( time.getTime().getMilliSecond() ); boost::posix_time::ptime result( boostDate, boostTime ); return result; } OFDateTime GetLowerDateTime( const OFDateTime& time1, const OFDateTime& time2 ) { OFDateTime result = time1; if ( ( time2.getDate() < time1.getDate() ) || ( ( time2.getDate() == time1.getDate() ) && ( time2.getTime() < time1.getTime() ) ) ) { result = time2; } return result; } OFDateTime GetUpperDateTime( const OFDateTime& time1, const OFDateTime& time2 ) { OFDateTime result = time1; if ( ( time2.getDate() > time1.getDate() ) || ( ( time2.getDate() == time1.getDate() ) && ( time2.getTime() > time1.getTime() ) ) ) { result = time2; } return result; } double ComputeMiliSecDuration( const OFDateTime& start, const OFDateTime& stop ) { const boost::posix_time::ptime startTime = ConvertOFDateTimeToPTime( start ); const boost::posix_time::ptime stopTime = ConvertOFDateTimeToPTime( stop ); ::boost::posix_time::time_duration duration = stopTime - startTime; double result = duration.total_milliseconds(); return result; } bool mitk::ITKDICOMSeriesReaderHelper::ExtractTimeBoundsOfTimeStep( const StringContainer& filenamesOfTimeStep, DateTimeBounds& bounds ) { const DICOMTag acquisitionDateTag( 0x0008, 0x0022 ); const DICOMTag acquisitionTimeTag( 0x0008, 0x0032 ); DICOMGDCMTagScanner::Pointer filescanner = DICOMGDCMTagScanner::New(); filescanner->SetInputFiles( filenamesOfTimeStep ); filescanner->AddTag( acquisitionDateTag ); filescanner->AddTag( acquisitionTimeTag ); filescanner->Scan(); - const DICOMGDCMImageFrameList frameList = filescanner->GetFrameInfoList(); + const DICOMDatasetAccessingImageFrameList frameList = filescanner->GetFrameInfoList(); bool result = false; bool first = true; - for ( DICOMGDCMImageFrameList::const_iterator pos = frameList.cbegin(); pos != frameList.cend(); ++pos ) + for (DICOMDatasetAccessingImageFrameList::const_iterator pos = frameList.cbegin(); pos != frameList.cend(); ++pos) { const std::string dateStr = ( *pos )->GetTagValueAsString( acquisitionDateTag ).value; const std::string timeStr = ( *pos )->GetTagValueAsString( acquisitionTimeTag ).value; OFDateTime time; const bool convertResult = ConvertDICOMDateTimeString( dateStr, timeStr, time ); if ( convertResult ) { if ( first ) { bounds[0] = time; bounds[1] = time; first = false; } else { bounds[0] = GetLowerDateTime( bounds[0], time ); bounds[1] = GetUpperDateTime( bounds[1], time ); } result = true; } } return result; }; mitk::ITKDICOMSeriesReaderHelper::TimeBoundsList mitk::ITKDICOMSeriesReaderHelper::ExtractTimeBoundsOfTimeSteps( const StringContainerList& filenamesOfTimeSteps ) { TimeBoundsList result; OFDateTime baseLine; bool baseLineSet = false; for ( StringContainerList::const_iterator pos = filenamesOfTimeSteps.cbegin(); pos != filenamesOfTimeSteps.cend(); ++pos ) { TimeBounds bounds( 0.0 ); DateTimeBounds dateTimeBounds; if ( ExtractTimeBoundsOfTimeStep( *pos, dateTimeBounds ) ) { if ( !baseLineSet ) { baseLineSet = true; baseLine = dateTimeBounds[0]; } bounds[0] = ComputeMiliSecDuration( baseLine, dateTimeBounds[0] ); bounds[1] = ComputeMiliSecDuration( baseLine, dateTimeBounds[1] ); } result.push_back( bounds ); } return result; }; mitk::TimeGeometry::Pointer mitk::ITKDICOMSeriesReaderHelper::GenerateTimeGeometry( const BaseGeometry* templateGeometry, const TimeBoundsList& boundsList ) { TimeGeometry::Pointer timeGeometry; double check = 0.0; const auto boundListSize = boundsList.size(); for ( auto pos = 0; pos < boundListSize; ++pos ) { check += boundsList[pos][0]; check += boundsList[pos][1]; } if ( check < mitk::eps ) { // if all bounds are zero we assume that the bounds could not be correctly determined // and as a fallback generate a time geometry in the old mitk style ProportionalTimeGeometry::Pointer newTimeGeometry = ProportionalTimeGeometry::New(); newTimeGeometry->Initialize( templateGeometry, boundListSize ); timeGeometry = newTimeGeometry.GetPointer(); } else { ArbitraryTimeGeometry::Pointer newTimeGeometry = ArbitraryTimeGeometry::New(); newTimeGeometry->ClearAllGeometries(); newTimeGeometry->ReserveSpaceForGeometries( boundListSize ); for ( auto pos = 0; pos < boundListSize; ++pos ) { TimeBounds bounds = boundsList[pos]; if ( pos + 1 < boundListSize ) { bounds[1] = boundsList[pos + 1][0]; } newTimeGeometry->AppendTimeStepClone( templateGeometry, bounds[1], bounds[0] ); } timeGeometry = newTimeGeometry.GetPointer(); } return timeGeometry; }; diff --git a/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp index 0d118f591f..eedf682eb0 100644 --- a/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp +++ b/Modules/DICOMReader/src/mitkThreeDnTDICOMSeriesReader.cpp @@ -1,264 +1,264 @@ /*=================================================================== 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 "mitkThreeDnTDICOMSeriesReader.h" #include "mitkITKDICOMSeriesReaderHelper.h" mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation) :DICOMITKSeriesGDCMReader(decimalPlacesForOrientation) ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other ) :DICOMITKSeriesGDCMReader(other) ,m_Group3DandT(true) { } mitk::ThreeDnTDICOMSeriesReader ::~ThreeDnTDICOMSeriesReader() { } mitk::ThreeDnTDICOMSeriesReader& mitk::ThreeDnTDICOMSeriesReader ::operator=(const ThreeDnTDICOMSeriesReader& other) { if (this != &other) { DICOMITKSeriesGDCMReader::operator=(other); this->m_Group3DandT = other.m_Group3DandT; } return *this; } bool mitk::ThreeDnTDICOMSeriesReader ::operator==(const DICOMFileReader& other) const { if (const Self* otherSelf = dynamic_cast(&other)) { return DICOMITKSeriesGDCMReader::operator==(other) && this->m_Group3DandT == otherSelf->m_Group3DandT; } else { return false; } } void mitk::ThreeDnTDICOMSeriesReader ::SetGroup3DandT(bool on) { m_Group3DandT = on; } bool mitk::ThreeDnTDICOMSeriesReader ::GetGroup3DandT() const { return m_Group3DandT; } mitk::DICOMITKSeriesGDCMReader::SortingBlockList mitk::ThreeDnTDICOMSeriesReader ::Condense3DBlocks(SortingBlockList& resultOf3DGrouping) { if (!m_Group3DandT) { return resultOf3DGrouping; // don't work if nobody asks us to } SortingBlockList remainingBlocks = resultOf3DGrouping; SortingBlockList non3DnTBlocks; SortingBlockList true3DnTBlocks; std::vector true3DnTBlocksTimeStepCount; // we should describe our need for this tag as needed via a function // (however, we currently know that the superclass will always need this tag) const DICOMTag tagImagePositionPatient(0x0020, 0x0032); while (!remainingBlocks.empty()) { // new block to fill up - const DICOMGDCMImageFrameList& firstBlock = remainingBlocks.front(); - DICOMGDCMImageFrameList current3DnTBlock = firstBlock; + const DICOMDatasetAccessingImageFrameList& firstBlock = remainingBlocks.front(); + DICOMDatasetAccessingImageFrameList current3DnTBlock = firstBlock; int current3DnTBlockNumberOfTimeSteps = 1; // get block characteristics of first block const unsigned int currentBlockNumberOfSlices = firstBlock.size(); const std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; const std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; remainingBlocks.erase( remainingBlocks.begin() ); // compare all other blocks against the first one for (auto otherBlockIter = remainingBlocks.begin(); otherBlockIter != remainingBlocks.cend(); /*++otherBlockIter*/) // <-- inside loop { // get block characteristics from first block - const DICOMGDCMImageFrameList otherBlock = *otherBlockIter; + const DICOMDatasetAccessingImageFrameList otherBlock = *otherBlockIter; const unsigned int otherBlockNumberOfSlices = otherBlock.size(); const std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient ).value; const std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient ).value; // add matching blocks to current3DnTBlock // keep other blocks for later if ( otherBlockNumberOfSlices == currentBlockNumberOfSlices && otherBlockFirstOrigin == currentBlockFirstOrigin && otherBlockLastOrigin == currentBlockLastOrigin ) { // matching block ++current3DnTBlockNumberOfTimeSteps; current3DnTBlock.insert( current3DnTBlock.end(), otherBlock.begin(), otherBlock.end() ); // append // remove this block from remainingBlocks otherBlockIter = remainingBlocks.erase(otherBlockIter); // make sure iterator otherBlockIter is valid afterwards } else { ++otherBlockIter; } } // in any case, we now now all about the first block of our list ... // ... and we wither call it 3D o 3D+t if (current3DnTBlockNumberOfTimeSteps > 1) { true3DnTBlocks.push_back(current3DnTBlock); true3DnTBlocksTimeStepCount.push_back(current3DnTBlockNumberOfTimeSteps); } else { non3DnTBlocks.push_back(current3DnTBlock); } } // create output for real 3D+t blocks (other outputs will be created by superclass) // set 3D+t flag on output block this->SetNumberOfOutputs( true3DnTBlocks.size() ); unsigned int o = 0; for (auto blockIter = true3DnTBlocks.cbegin(); blockIter != true3DnTBlocks.cend(); ++o, ++blockIter) { // bad copy&paste code from DICOMITKSeriesGDCMReader, should be handled in a better way - DICOMGDCMImageFrameList gdcmFrameInfoList = *blockIter; + DICOMDatasetAccessingImageFrameList gdcmFrameInfoList = *blockIter; assert(!gdcmFrameInfoList.empty()); // reverse frames if necessary // update tilt information from absolute last sorting - const DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmFrameInfoList ); + const DICOMDatasetList datasetList = ConvertToDICOMDatasetList( gdcmFrameInfoList ); m_NormalDirectionConsistencySorter->SetInput( datasetList ); m_NormalDirectionConsistencySorter->Sort(); - const DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList( m_NormalDirectionConsistencySorter->GetOutput(0) ); + const DICOMDatasetAccessingImageFrameList sortedGdcmInfoFrameList = ConvertToDICOMDatasetAccessingImageFrameList( m_NormalDirectionConsistencySorter->GetOutput(0) ); const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation(); // set frame list for current block - const DICOMImageFrameList frameList = ToDICOMImageFrameList( sortedGdcmInfoFrameList ); + const DICOMImageFrameList frameList = ConvertToDICOMImageFrameList( sortedGdcmInfoFrameList ); assert(!frameList.empty()); DICOMImageBlockDescriptor block; block.SetTagCache( this->GetTagCache() ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags! block.SetAdditionalTagsOfInterest(GetAdditionalTagsOfInterest()); block.SetTagLookupTableToPropertyFunctor(GetTagLookupTableToPropertyFunctor()); block.SetImageFrameList( frameList ); block.SetTiltInformation( tiltInfo ); block.SetFlag("3D+t", true); block.SetIntProperty("timesteps", true3DnTBlocksTimeStepCount[o]); MITK_DEBUG << "Found " << true3DnTBlocksTimeStepCount[o] << " timesteps"; this->SetOutput( o, block ); } return non3DnTBlocks; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadImages() { bool success = true; unsigned int numberOfOutputs = this->GetNumberOfOutputs(); for (unsigned int o = 0; o < numberOfOutputs; ++o) { const DICOMImageBlockDescriptor& block = this->InternalGetOutput(o); if (block.GetFlag("3D+t", false)) { success &= this->LoadMitkImageForOutput(o); } else { success &= DICOMITKSeriesGDCMReader::LoadMitkImageForOutput(o); // let superclass handle non-3D+t } } return success; } bool mitk::ThreeDnTDICOMSeriesReader ::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const { PushLocale(); const DICOMImageFrameList& frames = block.GetImageFrameList(); const GantryTiltInformation tiltInfo = block.GetTiltInformation(); const bool hasTilt = tiltInfo.IsRegularGantryTilt(); const int numberOfTimesteps = block.GetNumberOfTimeSteps(); if (numberOfTimesteps == 1) { return DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block); } const int numberOfFramesPerTimestep = block.GetNumberOfFramesPerTimeStep(); ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep; for (int timeStep = 0; timeStepFilename ); } filenamesPerTimestep.push_back( filenamesOfThisTimeStep ); } mitk::ITKDICOMSeriesReaderHelper helper; mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo ); block.SetMitkImage( mitkImage ); PopLocale(); return true; } diff --git a/Modules/DICOMReader/test/files.cmake b/Modules/DICOMReader/test/files.cmake index a9b2d5666c..94f0d1df07 100644 --- a/Modules/DICOMReader/test/files.cmake +++ b/Modules/DICOMReader/test/files.cmake @@ -1,14 +1,16 @@ set(MODULE_TESTS mitkDICOMReaderConfiguratorTest.cpp - mitkDICOMTagHelperTest.cpp + mitkDICOMDCMTKTagScannerTest.cpp + mitkDICOMTagPathTest.cpp + mitkDICOMPropertyTest.cpp ) set(MODULE_CUSTOM_TESTS mitkDICOMFileReaderTest.cpp mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp ) set(CPP_FILES mitkDICOMNullFileReader.cpp mitkDICOMFilenameSorter.cpp ) diff --git a/Modules/DICOMReader/test/mitkDICOMDCMTKTagScannerTest.cpp b/Modules/DICOMReader/test/mitkDICOMDCMTKTagScannerTest.cpp new file mode 100644 index 0000000000..76263b2d01 --- /dev/null +++ b/Modules/DICOMReader/test/mitkDICOMDCMTKTagScannerTest.cpp @@ -0,0 +1,128 @@ +/*=================================================================== + +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 "mitkDICOMDCMTKTagScanner.h" +#include "mitkDICOMFileReaderTestHelper.h" + +#include "mitkTestFixture.h" +#include "mitkTestingMacros.h" + +#include "mitkStringProperty.h" + +class mitkDICOMDCMTKTagScannerTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkDICOMDCMTKTagScannerTestSuite); + + MITK_TEST(DeepScanning); + MITK_TEST(MultiFileScanning); + + CPPUNIT_TEST_SUITE_END(); + +private: + + mitk::DICOMDCMTKTagScanner::Pointer scanner; + + mitk::StringList doseFiles; + mitk::StringList ctFiles; + +public: + + void setUp() override + { + doseFiles.push_back(GetTestDataFilePath("RT/Dose/RD.dcm")); + ctFiles.push_back(GetTestDataFilePath("TinyCTAbdomen/100")); + ctFiles.push_back(GetTestDataFilePath("TinyCTAbdomen/101")); + ctFiles.push_back(GetTestDataFilePath("TinyCTAbdomen/102")); + ctFiles.push_back(GetTestDataFilePath("TinyCTAbdomen/104")); + + scanner = mitk::DICOMDCMTKTagScanner::New(); + } + + void tearDown() override + { + } + + void DeepScanning() + { + mitk::DICOMTagPath planUIDPath; + planUIDPath.AddAnySelection(0x300C, 0x0002).AddElement(0x0008, 0x1155); + mitk::DICOMTagPath planUIDPathRef; + planUIDPathRef.AddSelection(0x300C, 0x0002, 0).AddElement(0x0008,0x1155); + + mitk::DICOMTagPath patientName(0x0010, 0x0010); + + scanner->SetInputFiles(doseFiles); + scanner->AddTagPath(planUIDPath); + scanner->AddTagPath(patientName); + + scanner->Scan(); + + mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", frames.size() == 1); + + mitk::DICOMDatasetAccess::FindingsListType findings = frames.front()->GetTagValueAsString(planUIDPath); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", findings.size() == 1); + CPPUNIT_ASSERT_MESSAGE("Testing validity of first plan finding", findings.front().isValid); + CPPUNIT_ASSERT_MESSAGE("Testing path of first plan finding", findings.front().path == planUIDPathRef); + CPPUNIT_ASSERT_MESSAGE("Testing value of first plan finding", findings.front().value == "1.2.826.0.1.3680043.8.176.2013826104526987.672.1228523524"); + + findings = frames.front()->GetTagValueAsString(patientName); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", findings.size() == 1); + CPPUNIT_ASSERT_MESSAGE("Testing validity of first plan finding", findings.front().isValid); + CPPUNIT_ASSERT_MESSAGE("Testing path of first plan finding", findings.front().path == patientName); + CPPUNIT_ASSERT_MESSAGE("Testing value of first plan finding", findings.front().value == "L_H"); + } + + void MultiFileScanning() + { + mitk::DICOMTagPath instanceUID(0x0008, 0x0018); + + scanner->SetInputFiles(ctFiles); + scanner->AddTagPath(instanceUID); + + scanner->Scan(); + + mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", frames.size() == 4); + + mitk::DICOMDatasetAccess::FindingsListType findings = frames[0]->GetTagValueAsString(instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", findings.size() == 1); + CPPUNIT_ASSERT_MESSAGE("Testing validity of instance uid finding of frame 0", findings.front().isValid); + CPPUNIT_ASSERT_MESSAGE("Testing path of instance uid finding of frame 0", findings.front().path == instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing value of instance uid finding of frame 0", findings.front().value == "1.2.276.0.99.1.4.8323329.3795.1303917947.940051"); + + findings = frames[1]->GetTagValueAsString(instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", findings.size() == 1); + CPPUNIT_ASSERT_MESSAGE("Testing validity of instance uid finding of frame 1", findings.front().isValid); + CPPUNIT_ASSERT_MESSAGE("Testing path of instance uid finding of frame 1", findings.front().path == instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing value of instance uid finding of frame 1", findings.front().value == "1.2.276.0.99.1.4.8323329.3795.1303917947.940052"); + + findings = frames[2]->GetTagValueAsString(instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", findings.size() == 1); + CPPUNIT_ASSERT_MESSAGE("Testing validity of instance uid finding of frame 2", findings.front().isValid); + CPPUNIT_ASSERT_MESSAGE("Testing path of instance uid finding of frame 2", findings.front().path == instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing value of instance uid finding of frame 2", findings.front().value == "1.2.276.0.99.1.4.8323329.3795.1303917947.940053"); + + findings = frames[3]->GetTagValueAsString(instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing DICOMDCMTKTagScanner::GetFrameInfoList()", findings.size() == 1); + CPPUNIT_ASSERT_MESSAGE("Testing validity of instance uid finding of frame 3", findings.front().isValid); + CPPUNIT_ASSERT_MESSAGE("Testing path of instance uid finding of frame 3", findings.front().path == instanceUID); + CPPUNIT_ASSERT_MESSAGE("Testing value of instance uid finding of frame 3", findings.front().value == "1.2.276.0.99.1.4.8323329.3795.1303917947.940055"); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkDICOMDCMTKTagScanner) diff --git a/Modules/DICOMReader/test/mitkDICOMFileReaderTestHelper.h b/Modules/DICOMReader/test/mitkDICOMFileReaderTestHelper.h index a1fe977ec4..2e1f966c2c 100644 --- a/Modules/DICOMReader/test/mitkDICOMFileReaderTestHelper.h +++ b/Modules/DICOMReader/test/mitkDICOMFileReaderTestHelper.h @@ -1,176 +1,176 @@ /*=================================================================== 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 mitkDICOMFileReaderTestHelper_h #define mitkDICOMFileReaderTestHelper_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") } 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(auto iter = outputFiles.cbegin(); iter != outputFiles.cend(); ++iter) { // check that output is part of input auto inputPositionOfCurrentOutput = std::find( inputFiles.cbegin(), inputFiles.cend(), (*iter)->Filename ); if (inputPositionOfCurrentOutput != inputFiles.cend()) { // check that output is only part of ONE output auto outputPositionOfCurrentOutput = std::find( allSortedInputsFiles.cbegin(), allSortedInputsFiles.cend(), (*iter)->Filename ); if (outputPositionOfCurrentOutput == allSortedInputsFiles.cend()) { // 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, - const std::unordered_map& requestedTags, + const DICOMFileReader::AdditionalTagsMapType& requestedTags, const std::unordered_map& expectedProperties ) { 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(); const mitk::Image::Pointer mitkImage = block.GetMitkImage(); for ( auto iter = requestedTags.cbegin(); iter != requestedTags.cend(); ++iter) { - mitk::BaseProperty* property = mitkImage->GetProperty( iter->first ).GetPointer(); + mitk::BaseProperty* property = mitkImage->GetProperty( iter->second.c_str() ).GetPointer(); MITK_TEST_CONDITION( property != nullptr, "Requested Tag is available as Property in Image" ); if (property) { - MITK_INFO << iter->first << " / " << property->GetNameOfClass(); - auto expectfinding = expectedProperties.find(iter->first); + MITK_INFO << iter->first.ToStr() << " / " << property->GetNameOfClass(); + auto expectfinding = expectedProperties.find(iter->second); if (expectfinding != expectedProperties.end()) { MITK_TEST_CONDITION(std::string(property->GetNameOfClass()) == expectfinding->second, "Property type is as expected"); } } } MITK_DEBUG << "-------------------------------------------"; MITK_DEBUG << "Output " << o << " at " << (void*) mitkImage.GetPointer(); MITK_DEBUG << " Number of files: " << outputFiles.size(); MITK_DEBUG << " Dimensions: " << mitkImage->GetDimension(0) << " " << mitkImage->GetDimension(1) << " " << mitkImage->GetDimension(2); } } static mitk::BaseProperty::Pointer DummyTagToPropertyFunctor( const mitk::StringLookupTable& ) { return mitk::BaseProperty::Pointer(); } }; // end test class } // namespace #endif diff --git a/Modules/DICOMReader/test/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp b/Modules/DICOMReader/test/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp index 8e5e0297c6..d0ca9e6114 100644 --- a/Modules/DICOMReader/test/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp +++ b/Modules/DICOMReader/test/mitkDICOMITKSeriesGDCMReaderBasicsTest.cpp @@ -1,125 +1,125 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkDICOMITKSeriesGDCMReader.h" #include "mitkDICOMFileReaderTestHelper.h" #include "mitkDICOMFilenameSorter.h" #include "mitkDICOMTagBasedSorter.h" #include "mitkDICOMSortByTag.h" #include "mitkTestingMacros.h" #include #include "mitkStringProperty.h" using mitk::DICOMTag; int mitkDICOMITKSeriesGDCMReaderBasicsTest(int argc, char* argv[]) { MITK_TEST_BEGIN("mitkDICOMITKSeriesGDCMReaderBasicsTest"); mitk::DICOMITKSeriesGDCMReader::Pointer gdcmReader = mitk::DICOMITKSeriesGDCMReader::New(); MITK_TEST_CONDITION_REQUIRED(gdcmReader.IsNotNull(), "DICOMITKSeriesGDCMReader can be instantiated."); mitk::DICOMFileReaderTestHelper::SetTestInputFilenames( argc,argv ); std::unordered_map expectedPropertyTypes; - std::unordered_map additionalTags; - additionalTags.insert( std::make_pair( "Test1", DICOMTag( 0x0008, 0x0005 ) ) ); - additionalTags.insert( std::make_pair( "Test2", DICOMTag( 0x0008, 0x0060 ) ) ); - additionalTags.insert( std::make_pair( "Test3", DICOMTag( 0x0020, 0x1041 ) ) ); + mitk::DICOMITKSeriesGDCMReader::AdditionalTagsMapType additionalTags; + additionalTags.insert( std::make_pair(DICOMTag( 0x0008, 0x0005 ), "Test1") ); + additionalTags.insert( std::make_pair(DICOMTag( 0x0008, 0x0060 ), "Test2") ); + additionalTags.insert( std::make_pair(DICOMTag( 0x0020, 0x1041 ), "Test3") ); gdcmReader->SetAdditionalTagsOfInterest( additionalTags ); // check the Set/GetInput function mitk::DICOMFileReaderTestHelper::TestInputFilenames( gdcmReader ); // check that output is a good reproduction of input (no duplicates, no new elements) mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); // repeat test with filename based sorter in-between mitk::DICOMFilenameSorter::Pointer filenameSorter = mitk::DICOMFilenameSorter::New(); gdcmReader->AddSortingElement( filenameSorter ); mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); // repeat test with some more realistic sorting gdcmReader = mitk::DICOMITKSeriesGDCMReader::New(); // this also tests destruction mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); // 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), new mitk::DICOMTagBasedSorter::CutDecimalPlaces(5) ); // Image Orientation (Patient) tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x000e) ); // Series Instance UID tagSorter->AddDistinguishingTag( DICOMTag(0x0018, 0x0050) ); // Slice Thickness tagSorter->AddDistinguishingTag( DICOMTag(0x0028, 0x0008) ); // Number of Frames tagSorter->AddDistinguishingTag( DICOMTag(0x0020, 0x0052) ); // Frame of Reference UID // a sorter... 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 ); gdcmReader->AddSortingElement( tagSorter ); gdcmReader->SetAdditionalTagsOfInterest( additionalTags ); mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); gdcmReader->PrintOutputs(std::cout, true); // really load images mitk::DICOMFileReaderTestHelper::TestMitkImagesAreLoaded( gdcmReader, additionalTags, expectedPropertyTypes ); ////////////////////////////////////////////////////////////////////////// // // Load the images again with another TagLookupTableToPropertyFunctor // ////////////////////////////////////////////////////////////////////////// gdcmReader->SetTagLookupTableToPropertyFunctor( []( const mitk::DICOMCachedValueLookupTable& table ) { return static_cast( mitk::StringProperty::New( table.GetTableValue(0).Value ) ); } ); expectedPropertyTypes.insert(std::make_pair("Test1", "StringProperty")); expectedPropertyTypes.insert(std::make_pair("Test2", "StringProperty")); expectedPropertyTypes.insert(std::make_pair("Test3", "StringProperty")); mitk::DICOMFileReaderTestHelper::TestOutputsContainInputs( gdcmReader ); gdcmReader->PrintOutputs(std::cout, true); // really load images mitk::DICOMFileReaderTestHelper::TestMitkImagesAreLoaded( gdcmReader, additionalTags, expectedPropertyTypes ); MITK_TEST_END(); } diff --git a/Modules/DICOMReader/test/mitkDICOMNullFileReader.h b/Modules/DICOMReader/test/mitkDICOMNullFileReader.h index cb3397a4d4..3ba9da13d5 100644 --- a/Modules/DICOMReader/test/mitkDICOMNullFileReader.h +++ b/Modules/DICOMReader/test/mitkDICOMNullFileReader.h @@ -1,60 +1,60 @@ /*=================================================================== 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 mitkDICOMNullFileReader_h #define mitkDICOMNullFileReader_h #include "mitkDICOMFileReader.h" namespace mitk { class DICOMNullFileReader : public DICOMFileReader { public: mitkClassMacro( DICOMNullFileReader, DICOMFileReader ); mitkCloneMacro( DICOMNullFileReader ); itkNewMacro( DICOMNullFileReader ); virtual void AnalyzeInputFiles() override; // void AllocateOutputImages(); virtual bool LoadImages() override; virtual bool CanHandleFile(const std::string& filename) override; bool operator==(const DICOMFileReader& other) const override; - virtual DICOMTagList GetTagsOfInterest() const override { return DICOMTagList(); } + virtual DICOMTagPathList GetTagsOfInterest() const override { return DICOMTagPathList(); } virtual void SetTagCache( const DICOMTagCache::Pointer& ) override {} protected: DICOMNullFileReader(); virtual ~DICOMNullFileReader(); DICOMNullFileReader(const DICOMNullFileReader& other); DICOMNullFileReader& operator=(const DICOMNullFileReader& other); void InternalPrintConfiguration(std::ostream& os) const override; private: }; } #endif diff --git a/Modules/DICOMReader/test/mitkDICOMPropertyTest.cpp b/Modules/DICOMReader/test/mitkDICOMPropertyTest.cpp new file mode 100644 index 0000000000..7de7a7c08a --- /dev/null +++ b/Modules/DICOMReader/test/mitkDICOMPropertyTest.cpp @@ -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. + +===================================================================*/ + +#include "mitkDICOMProperty.h" + +#include "mitkImage.h" +#include "mitkTestFixture.h" +#include "mitkTestingMacros.h" + +class mitkDICOMPropertyTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkDICOMPropertyTestSuite); + + MITK_TEST(GetPropertyByDICOMTagPath); + MITK_TEST(GetPropertyByDICOMTagPath_2); + + CPPUNIT_TEST_SUITE_END(); + +private: + + mitk::DICOMTagPath simplePath; + mitk::DICOMTagPath deepPath; + mitk::DICOMTagPath deepPath2; + mitk::DICOMTagPath deepPath_withAnyElement; + mitk::DICOMTagPath deepPath_withAnySelection; + mitk::DICOMTagPath deepPath_withSelection; + mitk::DICOMTagPath deepPath_withSelection2; + + mitk::DICOMTagPath emptyPath; + + mitk::Image::Pointer data; + + std::string simplePathStr; + std::string deepPathStr; + std::string deepPath2Str; + std::string deepPath_withSelectionStr; + +public: + + void setUp() override + { + simplePath.AddElement(0x0010, 0x0010); + + deepPath.AddElement(0x0010, 0x0011).AddElement(0x0020, 0x0022).AddElement(0x0030, 0x0033); + + deepPath2.AddElement(0x0010, 0x0011).AddElement(0x0020, 0x0023).AddElement(0x0030, 0x0033); + + deepPath_withAnyElement.AddElement(0x0010, 0x0011).AddAnyElement().AddElement(0x0030, 0x0033); + + deepPath_withAnySelection.AddElement(0x0010, 0x0011).AddAnySelection(0x0020, 0x0024).AddElement(0x0030, 0x0033); + + deepPath_withSelection.AddElement(0x0010, 0x0011).AddSelection(0x0020, 0x0024, 1).AddElement(0x0030, 0x0033); + + deepPath_withSelection2.AddElement(0x0010, 0x0011).AddSelection(0x0020, 0x0024, 2).AddElement(0x0030, 0x0033); + + simplePathStr = mitk::DICOMTagPathToPropertyName(simplePath); + deepPathStr = mitk::DICOMTagPathToPropertyName(deepPath); + deepPath2Str = mitk::DICOMTagPathToPropertyName(deepPath2); + deepPath_withSelectionStr = mitk::DICOMTagPathToPropertyName(deepPath_withSelection); + + data = mitk::Image::New(); + data->GetPropertyList()->SetStringProperty(simplePathStr.c_str(), "simplePath"); + data->GetPropertyList()->SetStringProperty(deepPathStr.c_str(), "deepPath"); + data->GetPropertyList()->SetStringProperty(deepPath2Str.c_str(), "deepPath2"); + data->GetPropertyList()->SetStringProperty(deepPath_withSelectionStr.c_str(), "deepPath_withSelection"); + data->GetPropertyList()->SetStringProperty("DICOM.0003.0003", "otherPath"); + data->GetPropertyList()->SetStringProperty("not_a_dicom_prop", "not_a_dicom_prop"); + } + + void tearDown() override + { + } + + void GetPropertyByDICOMTagPath() + { + std::map< std::string, mitk::BaseProperty::Pointer> result = mitk::GetPropertyByDICOMTagPath(data, simplePath); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("simplePath")); + + result = mitk::GetPropertyByDICOMTagPath(data, deepPath); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath")); + + result = mitk::GetPropertyByDICOMTagPath(data, deepPath2); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath2")); + + result = mitk::GetPropertyByDICOMTagPath(data, deepPath_withAnyElement); + CPPUNIT_ASSERT(result.size() == 3); + CPPUNIT_ASSERT_EQUAL(result[deepPathStr]->GetValueAsString(), std::string("deepPath")); + CPPUNIT_ASSERT_EQUAL(result[deepPath2Str]->GetValueAsString(), std::string("deepPath2")); + CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); + + result = mitk::GetPropertyByDICOMTagPath(data, deepPath_withSelection); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); + + result = mitk::GetPropertyByDICOMTagPath(data, deepPath_withSelection2); + CPPUNIT_ASSERT(result.size() == 0); + + result = mitk::GetPropertyByDICOMTagPath(data, emptyPath); + CPPUNIT_ASSERT(result.size() == 0); + } + + + void GetPropertyByDICOMTagPath_2() + { + std::map< std::string, mitk::BaseProperty::Pointer> result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), simplePath); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("simplePath")); + + result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath")); + + result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath2); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result.begin()->second->GetValueAsString(), std::string("deepPath2")); + + result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath_withAnyElement); + CPPUNIT_ASSERT(result.size() == 3); + CPPUNIT_ASSERT_EQUAL(result[deepPathStr]->GetValueAsString(), std::string("deepPath")); + CPPUNIT_ASSERT_EQUAL(result[deepPath2Str]->GetValueAsString(), std::string("deepPath2")); + CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); + + result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath_withSelection); + CPPUNIT_ASSERT(result.size() == 1); + CPPUNIT_ASSERT_EQUAL(result[deepPath_withSelectionStr]->GetValueAsString(), std::string("deepPath_withSelection")); + + result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), deepPath_withSelection2); + CPPUNIT_ASSERT(result.size() == 0); + + result = mitk::GetPropertyByDICOMTagPath(data->GetPropertyList(), emptyPath); + CPPUNIT_ASSERT(result.size() == 0); + } + + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkDICOMProperty) diff --git a/Modules/DICOMReader/test/mitkDICOMTagPathTest.cpp b/Modules/DICOMReader/test/mitkDICOMTagPathTest.cpp new file mode 100644 index 0000000000..30616335ec --- /dev/null +++ b/Modules/DICOMReader/test/mitkDICOMTagPathTest.cpp @@ -0,0 +1,196 @@ +/*=================================================================== + +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 "mitkDICOMTagPath.h" + +#include "mitkTestFixture.h" +#include "mitkTestingMacros.h" + +class mitkDICOMTagPathTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkDICOMTagPathTestSuite); + + MITK_TEST(DICOMTagPathToPropertRegEx); + MITK_TEST(DICOMTagPathToPersistenceKeyRegEx); + MITK_TEST(DICOMTagPathToPersistenceKeyTemplate); + MITK_TEST(DICOMTagPathToPersistenceNameTemplate); + MITK_TEST(DICOMTagPathToDCMTKSearchPath); + MITK_TEST(PropertyNameToDICOMTagPath); + MITK_TEST(DICOMTagPathToPropertyName); + + CPPUNIT_TEST_SUITE_END(); + +private: + + mitk::DICOMTagPath simplePath; + mitk::DICOMTagPath deepPath; + mitk::DICOMTagPath deepPath_withAnyElement; + mitk::DICOMTagPath deepPath_withAnySelection; + mitk::DICOMTagPath deepPath_withSelection; + mitk::DICOMTagPath verydeepPath; + + mitk::DICOMTagPath emptyPath; + +public: + + void setUp() override + { + simplePath.AddElement(0x0010, 0x0010); + + deepPath.AddElement(0x0010, 0x0011); + deepPath.AddElement(0x0020, 0x0022); + deepPath.AddElement(0x0030, 0x0033); + + deepPath_withAnyElement.AddElement(0x0010, 0x0011); + deepPath_withAnyElement.AddAnyElement(); + deepPath_withAnyElement.AddElement(0x0030, 0x0033); + + deepPath_withAnySelection.AddElement(0x0010, 0x0011); + deepPath_withAnySelection.AddAnySelection(0x0020, 0x0022); + deepPath_withAnySelection.AddElement(0x0030, 0x0033); + + deepPath_withSelection.AddElement(0x0010, 0x0011); + deepPath_withSelection.AddSelection(0x0020, 0x0022, 6); + deepPath_withSelection.AddElement(0x0030, 0x0033); + + verydeepPath.AddAnySelection(0x0010, 0x0011); + verydeepPath.AddAnyElement(); + verydeepPath.AddElement(0x0030, 0x0033); + verydeepPath.AddSelection(0x0040, 0x0044, 4); + verydeepPath.AddElement(0x0050, 0x0055); + } + + void tearDown() override + { + } + + void DICOMTagPathToPropertRegEx() + { + std::string result = mitk::DICOMTagPathToPropertRegEx(simplePath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertRegEx() with '(0010,0010)'", result, std::string("DICOM\\.0010\\.0010")); + result = mitk::DICOMTagPathToPropertRegEx(deepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertRegEx() with '(0010,0010).(0020,0022).(0030,0033)'", result, std::string("DICOM\\.0010\\.0011\\.0020\\.0022\\.0030\\.0033")); + result = mitk::DICOMTagPathToPropertRegEx(deepPath_withAnyElement); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertRegEx() with '(0010,0010).*.(0030,0033)'", result, std::string("DICOM\\.0010\\.0011\\.(\\d{4})\\.(\\d{4})\\.0030\\.0033")); + result = mitk::DICOMTagPathToPropertRegEx(deepPath_withAnySelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertRegEx() with '(0010,0010).(0020,0022)[*].(0030,0033)'", result, std::string("DICOM\\.0010\\.0011\\.0020\\.0022\\.\\[(\\d*)\\]\\.0030\\.0033")); + result = mitk::DICOMTagPathToPropertRegEx(deepPath_withSelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertRegEx() with '(0010,0010).(0020,0022)[6].(0030,0033)'", result, std::string("DICOM\\.0010\\.0011\\.0020\\.0022\\.\\[6\\]\\.0030\\.0033")); + result = mitk::DICOMTagPathToPropertRegEx(verydeepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertRegEx() with '(0010,0010)[*].*.(0030,0033).(0040,0044)[4].(0050,0055)'", result, std::string("DICOM\\.0010\\.0011\\.\\[(\\d*)\\]\\.(\\d{4})\\.(\\d{4})\\.0030\\.0033\\.0040\\.0044\\.\\[4\\]\\.0050\\.0055")); + } + + + void DICOMTagPathToPersistenceKeyRegEx() + { + std::string result = mitk::DICOMTagPathToPersistenceKeyRegEx(simplePath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0010)'", result, std::string("DICOM_0010_0010")); + result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0010).(0020,0022).(0030,0033)'", result, std::string("DICOM_0010_0011_0020_0022_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath_withAnyElement); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0010).*.(0030,0033)'", result, std::string("DICOM_0010_0011_(\\d{4})_(\\d{4})_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath_withAnySelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0010).(0020,0022)[*].(0030,0033)'", result, std::string("DICOM_0010_0011_0020_0022_\\[(\\d*)\\]_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath_withSelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0010).(0020,0022)[6].(0030,0033)'", result, std::string("DICOM_0010_0011_0020_0022_\\[6\\]_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyRegEx(verydeepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0010)[*].*.(0030,0033).(0040,0044)[4].(0050,0055)'", result, std::string("DICOM_0010_0011_\\[(\\d*)\\]_(\\d{4})_(\\d{4})_0030_0033_0040_0044_\\[4\\]_0050_0055")); + } + + void DICOMTagPathToPersistenceKeyTemplate() + { + std::string result = mitk::DICOMTagPathToPersistenceKeyTemplate(simplePath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0010)'", result, std::string("DICOM_0010_0010")); + result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0010).(0020,0022).(0030,0033)'", result, std::string("DICOM_0010_0011_0020_0022_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath_withAnyElement); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0010).*.(0030,0033)'", result, std::string("DICOM_0010_0011_$1_$2_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath_withAnySelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0010).(0020,0022)[*].(0030,0033)'", result, std::string("DICOM_0010_0011_0020_0022_[$1]_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath_withSelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0010).(0020,0022)[6].(0030,0033)'", result, std::string("DICOM_0010_0011_0020_0022_[6]_0030_0033")); + result = mitk::DICOMTagPathToPersistenceKeyTemplate(verydeepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0010)[*].*.(0030,0033).(0040,0044)[4].(0050,0055)'", result, std::string("DICOM_0010_0011_[$1]_$2_$3_0030_0033_0040_0044_[4]_0050_0055")); + } + + void DICOMTagPathToPersistenceNameTemplate() + { + std::string result = mitk::DICOMTagPathToPersistenceNameTemplate(simplePath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0010)'", result, std::string("DICOM.0010.0010")); + result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0010).(0020,0022).(0030,0033)'", result, std::string("DICOM.0010.0011.0020.0022.0030.0033")); + result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath_withAnyElement); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0010).*.(0030,0033)'", result, std::string("DICOM.0010.0011.$1.$2.0030.0033")); + result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath_withAnySelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0010).(0020,0022)[*].(0030,0033)'", result, std::string("DICOM.0010.0011.0020.0022.[$1].0030.0033")); + result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath_withSelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0010).(0020,0022)[6].(0030,0033)'", result, std::string("DICOM.0010.0011.0020.0022.[6].0030.0033")); + result = mitk::DICOMTagPathToPersistenceNameTemplate(verydeepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0010)[*].*.(0030,0033).(0040,0044)[4].(0050,0055)'", result, std::string("DICOM.0010.0011.[$1].$2.$3.0030.0033.0040.0044.[4].0050.0055")); + } + + void DICOMTagPathToDCMTKSearchPath() + { + std::string result = mitk::DICOMTagPathToDCMTKSearchPath(simplePath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0010)'", result, std::string("(0010,0010)")); + result = mitk::DICOMTagPathToDCMTKSearchPath(deepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0011).(0020,0022).(0030,0033)'", result, std::string("(0010,0011).(0020,0022).(0030,0033)")); + CPPUNIT_ASSERT_THROW(mitk::DICOMTagPathToDCMTKSearchPath(deepPath_withAnyElement), mitk::Exception); + result = mitk::DICOMTagPathToDCMTKSearchPath(deepPath_withAnySelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0011).(0020,0022)[*].(0030,0033)'", result, std::string("(0010,0011).(0020,0022)[*].(0030,0033)")); + result = mitk::DICOMTagPathToDCMTKSearchPath(deepPath_withSelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0011).(0020,0022)[6].(0030,0033)'", result, std::string("(0010,0011).(0020,0022)[6].(0030,0033)")); + CPPUNIT_ASSERT_THROW(mitk::DICOMTagPathToDCMTKSearchPath(verydeepPath), mitk::Exception); + } + + void PropertyNameToDICOMTagPath() + { + mitk::DICOMTagPath result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0010"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0010)'", simplePath, result); + result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.0020.0022.0030.0033"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0010).(0020,0022).(0030,0033)'", deepPath, result); + result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.*.0030.0033"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0010).*.(0030,0033)'", deepPath_withAnyElement, result); + result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.0020.0022.[*].0030.0033"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0010).(0020,0022)[*].(0030,0033)'", deepPath_withAnySelection, result); + result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.0020.0022.[6].0030.0033"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0010).(0020,0022)[6].(0030,0033)'", deepPath_withSelection, result); + result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.[*].*.0030.0033.0040.0044.[4].0050.0055"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0010)[*].*.(0030,0033).(0040,0044)[4].(0050,0055)'", verydeepPath, result); + + result = mitk::PropertyNameToDICOMTagPath("WRONG.0010.0011.0020.0022.0030.0033"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with wrong path", emptyPath, result); + } + + void DICOMTagPathToPropertyName() + { + std::string result = mitk::DICOMTagPathToPropertyName(simplePath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0010)'", result, std::string("DICOM.0010.0010")); + result = mitk::DICOMTagPathToPropertyName(deepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0010).(0020,0022).(0030,0033)'", result, std::string("DICOM.0010.0011.0020.0022.0030.0033")); + result = mitk::DICOMTagPathToPropertyName(deepPath_withAnyElement); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0010).*.(0030,0033)'", result, std::string("DICOM.0010.0011.*.0030.0033")); + result = mitk::DICOMTagPathToPropertyName(deepPath_withAnySelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0010).(0020,0022)[*].(0030,0033)'", result, std::string("DICOM.0010.0011.0020.0022.[*].0030.0033")); + result = mitk::DICOMTagPathToPropertyName(deepPath_withSelection); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0010).(0020,0022)[6].(0030,0033)'", result, std::string("DICOM.0010.0011.0020.0022.[6].0030.0033")); + result = mitk::DICOMTagPathToPropertyName(verydeepPath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0010)[*].*.(0030,0033).(0040,0044)[4].(0050,0055)'", result, std::string("DICOM.0010.0011.[*].*.0030.0033.0040.0044.[4].0050.0055")); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkDICOMTagPath) diff --git a/Modules/DICOMReaderServices/include/mitkDICOMTagsOfInterestService.h b/Modules/DICOMReaderServices/include/mitkDICOMTagsOfInterestService.h index 7507824147..87c058f9b1 100644 --- a/Modules/DICOMReaderServices/include/mitkDICOMTagsOfInterestService.h +++ b/Modules/DICOMReaderServices/include/mitkDICOMTagsOfInterestService.h @@ -1,74 +1,68 @@ /*=================================================================== 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 mitkDICOMTagsOfInterestService_h #define mitkDICOMTagsOfInterestService_h #include #include #include #include #include #include namespace mitk { /** * \ingroup MicroServices_Interfaces * \brief DICOM tags of interest service. * * This service allows you to manage the tags of interest (toi). * All registred toi will be extracted when loading dicom data and stored as properties in the corresponding * base data object. In addition the service can (if available) use IPropertyPersistance and IPropertyDescriptions * to ensure that the tags of interests are also persisted and have a human readable descriptions. */ class DICOMTagsOfInterestService: public IDICOMTagsOfInterest { public: DICOMTagsOfInterestService(); virtual ~DICOMTagsOfInterestService(); - virtual void AddTagOfInterest(const DICOMTag& tag, bool makePersistant = true) override; + virtual void AddTagOfInterest(const DICOMTagPath& tag, bool makePersistant = true) override; - virtual DICOMTagMapType GetTagsOfInterest() const override; + virtual DICOMTagPathMapType GetTagsOfInterest() const override; - virtual bool HasTag(const DICOMTag& tag) const override; + virtual bool HasTag(const DICOMTagPath& tag) const override; - virtual void RemoveTag(const DICOMTag& tag) override; + virtual void RemoveTag(const DICOMTagPath& tag) override; virtual void RemoveAllTags() override; private: - typedef std::unordered_map InternalTagMapType; - typedef std::unordered_map InternalPersistanceMapType; - typedef std::set InternalTagSetType; + typedef std::set InternalTagSetType; typedef itk::MutexLockHolder MutexHolder; - /**The set is used to ensure that strings that are passed outside as char* - always stay a valid pointer, even if the tag is removed from the service.*/ - InternalTagSetType m_KnownTags; - InternalTagMapType m_TagMap; - InternalPersistanceMapType m_PersMap; + InternalTagSetType m_Tags; mutable itk::SimpleFastMutexLock m_Lock; DICOMTagsOfInterestService(const DICOMTagsOfInterestService&); DICOMTagsOfInterestService& operator=(const DICOMTagsOfInterestService&); }; } #endif diff --git a/Modules/DICOMReaderServices/src/mitkBaseDICOMReaderService.cpp b/Modules/DICOMReaderServices/src/mitkBaseDICOMReaderService.cpp index 98185a1f78..7d84b5d6eb 100644 --- a/Modules/DICOMReaderServices/src/mitkBaseDICOMReaderService.cpp +++ b/Modules/DICOMReaderServices/src/mitkBaseDICOMReaderService.cpp @@ -1,121 +1,129 @@ /*=================================================================== 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 "mitkBaseDICOMReaderService.h" #include #include #include #include #include -#include +#include #include #include +#include #include #include namespace mitk { BaseDICOMReaderService::BaseDICOMReaderService(const std::string& description) : AbstractFileReader(CustomMimeType(IOMimeTypes::DICOM_MIMETYPE()), description) { } std::vector > BaseDICOMReaderService::Read() { std::vector result; //special handling of Philips 3D US DICOM. //Copied from DICOMSeriesReaderService std::string fileName = this->GetLocalFileName(); if (DicomSeriesReader::IsPhilips3DDicom(fileName)) { MITK_INFO << "it is a Philips3D US Dicom file" << std::endl; mitk::LocaleSwitch localeSwitch("C"); std::locale previousCppLocale(std::cin.getloc()); std::locale l("C"); std::cin.imbue(l); DataNode::Pointer node = DataNode::New(); mitk::DicomSeriesReader::StringContainer stringvec; stringvec.push_back(fileName); if (DicomSeriesReader::LoadDicomSeries(stringvec, *node)) { BaseData::Pointer data = node->GetData(); StringProperty::Pointer nameProp = StringProperty::New(itksys::SystemTools::GetFilenameName(fileName)); data->GetPropertyList()->SetProperty("name", nameProp); result.push_back(data); } std::cin.imbue(previousCppLocale); return result; } //Normal DICOM handling (It wasn't a Philips 3D US) mitk::StringList relevantFiles = this->GetRelevantFiles(); mitk::DICOMFileReader::Pointer reader = this->GetReader(relevantFiles); reader->SetAdditionalTagsOfInterest(mitk::GetCurrentDICOMTagsOfInterest()); reader->SetTagLookupTableToPropertyFunctor(mitk::GetDICOMPropertyForDICOMValuesFunctor); reader->SetInputFiles(relevantFiles); + + mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); + scanner->AddTagPaths(reader->GetTagsOfInterest()); + scanner->SetInputFiles(relevantFiles); + scanner->Scan(); + + reader->SetTagCache(scanner->GetScanCache()); reader->AnalyzeInputFiles(); reader->LoadImages(); for (unsigned int i = 0; i < reader->GetNumberOfOutputs(); ++i) { const mitk::DICOMImageBlockDescriptor& desc = reader->GetOutput(i); mitk::BaseData::Pointer data = desc.GetMitkImage().GetPointer(); std::string nodeName = "Unnamed_DICOM"; std::string studyDescription = desc.GetPropertyAsString("studyDescription"); std::string seriesDescription = desc.GetPropertyAsString("seriesDescription"); if (!studyDescription.empty()) { nodeName = studyDescription; } if (!seriesDescription.empty()) { if (!studyDescription.empty()) { nodeName += "/"; } nodeName += seriesDescription; } StringProperty::Pointer nameProp = StringProperty::New(nodeName); data->SetProperty("name", nameProp); result.push_back(data); } return result; } StringList BaseDICOMReaderService::GetRelevantFiles() const { std::string fileName = this->GetLocalFileName(); mitk::StringList relevantFiles = mitk::GetDICOMFilesInSameDirectory(fileName); return relevantFiles; } } diff --git a/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp b/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp index 908a71b246..6597bda813 100644 --- a/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp +++ b/Modules/DICOMReaderServices/src/mitkDICOMReaderServicesActivator.cpp @@ -1,50 +1,48 @@ /*=================================================================== 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 "mitkDICOMReaderServicesActivator.h" #include "mitkAutoSelectingDICOMReaderService.h" #include "mitkClassicDICOMSeriesReaderService.h" #include "mitkDICOMTagsOfInterestService.h" -#include "mitkDICOMTagHelper.h" - #include namespace mitk { void DICOMReaderServicesActivator::Load(us::ModuleContext* context) { m_AutoSelectingDICOMReader.reset(new AutoSelectingDICOMReaderService()); m_ClassicDICOMSeriesReader.reset(new ClassicDICOMSeriesReaderService()); m_DICOMTagsOfInterestService.reset(new DICOMTagsOfInterestService()); context->RegisterService(m_DICOMTagsOfInterestService.get()); - DefaultDICOMTagMapType tagmap = GetDefaultDICOMTagsOfInterest(); + DICOMTagPathMapType tagmap = GetDefaultDICOMTagsOfInterest(); for (auto tag : tagmap) { - m_DICOMTagsOfInterestService->AddTagOfInterest(tag.second); + m_DICOMTagsOfInterestService->AddTagOfInterest(tag.first); } } void DICOMReaderServicesActivator::Unload(us::ModuleContext*) { } } US_EXPORT_MODULE_ACTIVATOR(mitk::DICOMReaderServicesActivator) diff --git a/Modules/DICOMReaderServices/src/mitkDICOMTagsOfInterestService.cpp b/Modules/DICOMReaderServices/src/mitkDICOMTagsOfInterestService.cpp index 31ec24dd5f..cf2df109f6 100644 --- a/Modules/DICOMReaderServices/src/mitkDICOMTagsOfInterestService.cpp +++ b/Modules/DICOMReaderServices/src/mitkDICOMTagsOfInterestService.cpp @@ -1,161 +1,184 @@ /*=================================================================== 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 "mitkDICOMTagsOfInterestService.h" -#include "mitkDICOMTagHelper.h" #include "usModuleContext.h" #include "usGetModuleContext.h" #include "mitkIPropertyDescriptions.h" #include "mitkIPropertyPersistence.h" #include "mitkTemporoSpatialStringProperty.h" mitk::IPropertyDescriptions* GetDescriptionsService() { mitk::IPropertyDescriptions* result = nullptr; std::vector > descriptionRegisters = us::GetModuleContext()->GetServiceReferences(); if (!descriptionRegisters.empty()) { if (descriptionRegisters.size() > 1) { MITK_WARN << "Multiple property description services found. Using just one."; } result = us::GetModuleContext()->GetService(descriptionRegisters.front()); } return result; }; mitk::IPropertyPersistence* GetPersistenceService() { mitk::IPropertyPersistence* result = nullptr; std::vector > persRegisters = us::GetModuleContext()->GetServiceReferences(); if (!persRegisters.empty()) { if (persRegisters.size() > 1) { MITK_WARN << "Multiple property description services found. Using just one."; } result = us::GetModuleContext()->GetService(persRegisters.front()); } return result; }; mitk::DICOMTagsOfInterestService:: DICOMTagsOfInterestService() { }; mitk::DICOMTagsOfInterestService:: ~DICOMTagsOfInterestService() { }; void mitk::DICOMTagsOfInterestService:: -AddTagOfInterest(const DICOMTag& tag, bool makePersistant) +AddTagOfInterest(const DICOMTagPath& tagPath, bool makePersistant) { + if (tagPath.Size() == 0) + { + MITK_DEBUG << "Indication for wrong DICOMTagsOfInterestService::AddTagOfInterest() usage. Empty DICOM tag path was passed."; + return; + } + MutexHolder lock(m_Lock); - std::string propName = mitk::GeneratPropertyNameForDICOMTag(tag); - this->m_TagMap.insert(std::make_pair(propName, tag)); - this->m_PersMap[propName] = makePersistant; //this must be changed even if the propname already exists. - this->m_KnownTags.insert(propName); + std::string propRegEx = mitk::DICOMTagPathToPropertRegEx(tagPath); + this->m_Tags.insert(tagPath); mitk::IPropertyDescriptions* descriptionSrv = GetDescriptionsService(); if (descriptionSrv) { - descriptionSrv->AddDescription(propName, "DICOM tag: " + tag.GetName()); + descriptionSrv->AddDescriptionRegEx(propRegEx, "DICOM tag: " + tagPath.GetLastNode().tag.GetName()); } mitk::IPropertyPersistence* persSrv = GetPersistenceService(); - if (persSrv) + if (persSrv && makePersistant) { - std::string key = propName; - std::replace(key.begin(), key.end(), '.', '_'); - PropertyPersistenceInfo::Pointer info = PropertyPersistenceInfo::New(); - info->SetNameAndKey(propName, key); + if (tagPath.IsExplicit()) + { + std::string name = mitk::DICOMTagPathToPropertyName(tagPath); + std::string key = name; + std::replace(key.begin(), key.end(), '.', '_'); + info->SetNameAndKey(name, key); + } + else + { + std::string key = mitk::DICOMTagPathToPersistenceKeyRegEx(tagPath); + std::string keyTemplate = mitk::DICOMTagPathToPersistenceKeyTemplate(tagPath); + std::string propTemplate = mitk::DICOMTagPathToPersistenceNameTemplate(tagPath); + info->UseRegEx(propRegEx, propTemplate, key, keyTemplate); + } + info->SetDeserializationFunction(mitk::PropertyPersistenceDeserialization::deserializeJSONToTemporoSpatialStringProperty); info->SetSerializationFunction(mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON); persSrv->AddInfo(info); } }; -mitk::DICOMTagsOfInterestService::DICOMTagMapType +mitk::DICOMTagPathMapType mitk::DICOMTagsOfInterestService:: GetTagsOfInterest() const { MutexHolder lock(m_Lock); - DICOMTagMapType result; + DICOMTagPathMapType result; - for (auto tag : this->m_TagMap) + for (auto tag : this->m_Tags) { - InternalTagSetType::const_iterator finding = this->m_KnownTags.find(tag.first); - if (finding == this->m_KnownTags.cend()) - { - mitkThrow() << "Invalid status. Tag is missing in the known tag set. Problematic tag:" << tag.first; - } - - result.insert(std::make_pair(finding->c_str(), tag.second)); + result.insert(std::make_pair(tag, "")); } return result; }; bool mitk::DICOMTagsOfInterestService:: -HasTag(const DICOMTag& tag) const +HasTag(const DICOMTagPath& tag) const { - std::string propName = mitk::GeneratPropertyNameForDICOMTag(tag); - return this->m_TagMap.find(propName) != this->m_TagMap.cend(); + return this->m_Tags.find(tag) != this->m_Tags.cend(); }; void mitk::DICOMTagsOfInterestService:: -RemoveTag(const DICOMTag& tag) +RemoveTag(const DICOMTagPath& tag) { MutexHolder lock(m_Lock); - std::string propName = mitk::GeneratPropertyNameForDICOMTag(tag); - this->m_PersMap.erase(propName); - this->m_TagMap.erase(propName); + this->m_Tags.erase(tag); + std::string propRegEx = mitk::DICOMTagPathToPropertRegEx(tag); mitk::IPropertyDescriptions* descriptionSrv = GetDescriptionsService(); - if (descriptionSrv && descriptionSrv->HasDescription(propName)) + if (descriptionSrv) { - descriptionSrv->RemoveDescription(propName); + descriptionSrv->RemoveDescription(propRegEx); } mitk::IPropertyPersistence* persSrv = GetPersistenceService(); - if (persSrv && persSrv->HasInfo(propName)) + if (persSrv) { - persSrv->RemoveInfo(propName); + persSrv->RemoveInfo(propRegEx); } }; void mitk::DICOMTagsOfInterestService:: RemoveAllTags() { MutexHolder lock(m_Lock); - this->m_PersMap.clear(); - this->m_TagMap.clear(); + mitk::IPropertyDescriptions* descriptionSrv = GetDescriptionsService(); + mitk::IPropertyPersistence* persSrv = GetPersistenceService(); + + for (const auto& tag : m_Tags) + { + std::string propRegEx = mitk::DICOMTagPathToPropertRegEx(tag); + + if (descriptionSrv) + { + descriptionSrv->RemoveDescription(propRegEx); + } + + if (persSrv) + { + persSrv->RemoveInfo(propRegEx); + } + } + + this->m_Tags.clear(); }; diff --git a/Modules/DicomRT/src/mitkDoseImageVtkMapper2D.cpp b/Modules/DicomRT/src/mitkDoseImageVtkMapper2D.cpp index 2e11fed07a..78b60160be 100644 --- a/Modules/DicomRT/src/mitkDoseImageVtkMapper2D.cpp +++ b/Modules/DicomRT/src/mitkDoseImageVtkMapper2D.cpp @@ -1,1104 +1,1144 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //MITK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkImageStatisticsHolder.h" #include "mitkPlaneClipping.h" +#include "mitkPropertyNameHelper.h" //MITK Rendering #include "mitkDoseImageVtkMapper2D.h" #include "vtkMitkThickSlicesFilter.h" #include "vtkMitkLevelWindowFilter.h" #include "vtkNeverTranslucentTexture.h" //VTK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //ITK #include mitk::DoseImageVtkMapper2D::DoseImageVtkMapper2D() { } mitk::DoseImageVtkMapper2D::~DoseImageVtkMapper2D() { //The 3D RW Mapper (PlaneGeometryDataVtkMapper3D) is listening to this event, //in order to delete the images from the 3D RW. this->InvokeEvent( itk::DeleteEvent() ); } //set the two points defining the textured plane according to the dimension and spacing void mitk::DoseImageVtkMapper2D::GeneratePlane(mitk::BaseRenderer* renderer, double planeBounds[6]) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float depth = this->CalculateLayerDepth(renderer); //Set the origin to (xMin; yMin; depth) of the plane. This is necessary for obtaining the correct //plane size in crosshair rotation and swivel mode. localStorage->m_Plane->SetOrigin(planeBounds[0], planeBounds[2], depth); //These two points define the axes of the plane in combination with the origin. //Point 1 is the x-axis and point 2 the y-axis. //Each plane is transformed according to the view (axial, coronal and saggital) afterwards. localStorage->m_Plane->SetPoint1(planeBounds[1] , planeBounds[2], depth); //P1: (xMax, yMin, depth) localStorage->m_Plane->SetPoint2(planeBounds[0], planeBounds[3], depth); //P2: (xMin, yMax, depth) } float mitk::DoseImageVtkMapper2D::CalculateLayerDepth(mitk::BaseRenderer* renderer) { //get the clipping range to check how deep into z direction we can render images double maxRange = renderer->GetVtkRenderer()->GetActiveCamera()->GetClippingRange()[1]; //Due to a VTK bug, we cannot use the whole clipping range. /100 is empirically determined float depth = -maxRange*0.01; // divide by 100 int layer = 0; GetDataNode()->GetIntProperty( "layer", layer, renderer); //add the layer property for each image to render images with a higher layer on top of the others depth += layer*10; //*10: keep some room for each image (e.g. for QBalls in between) if(depth > 0.0f) { depth = 0.0f; MITK_WARN << "Layer value exceeds clipping range. Set to minimum instead."; } return depth; } const mitk::Image* mitk::DoseImageVtkMapper2D::GetInput( void ) { return static_cast< const mitk::Image * >( GetDataNode()->GetData() ); } vtkProp* mitk::DoseImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer* renderer) { //return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } void mitk::DoseImageVtkMapper2D::GenerateDataForRenderer( mitk::BaseRenderer *renderer ) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::Image *input = const_cast< mitk::Image * >( this->GetInput() ); mitk::DataNode* datanode = this->GetDataNode(); if ( input == NULL || input->IsInitialized() == false ) { return; } //check if there is a valid worldGeometry const PlaneGeometry *worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); if( ( worldGeometry == NULL ) || ( !worldGeometry->IsValid() ) || ( !worldGeometry->HasReferenceGeometry() )) { return; } input->Update(); // early out if there is no intersection of the current rendering geometry // and the geometry of the image that is to be rendered. if ( !RenderingGeometryIntersectsImage( worldGeometry, input->GetSlicedGeometry() ) ) { // set image to NULL, to clear the texture in 3D, because // the latest image is used there if the plane is out of the geometry // see bug-13275 localStorage->m_ReslicedImage = NULL; localStorage->m_Mapper->SetInputData( localStorage->m_EmptyPolyData ); return; } //set main input for ExtractSliceFilter localStorage->m_Reslicer->SetInput(input); localStorage->m_Reslicer->SetWorldGeometry(worldGeometry); localStorage->m_Reslicer->SetTimeStep( this->GetTimestep() ); //set the transformation of the image to adapt reslice axis localStorage->m_Reslicer->SetResliceTransformByGeometry( input->GetTimeGeometry()->GetGeometryForTimeStep( this->GetTimestep() ) ); //is the geometry of the slice based on the input image or the worldgeometry? bool inPlaneResampleExtentByGeometry = false; datanode->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); localStorage->m_Reslicer->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); // Initialize the interpolation mode for resampling; switch to nearest // neighbor if the input image is too small. if ( (input->GetDimension() >= 3) && (input->GetDimension(2) > 1) ) { VtkResliceInterpolationProperty *resliceInterpolationProperty; datanode->GetProperty( resliceInterpolationProperty, "reslice interpolation" ); int interpolationMode = VTK_RESLICE_NEAREST; if ( resliceInterpolationProperty != NULL ) { interpolationMode = resliceInterpolationProperty->GetInterpolation(); } switch ( interpolationMode ) { case VTK_RESLICE_NEAREST: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); break; case VTK_RESLICE_LINEAR: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_LINEAR); break; case VTK_RESLICE_CUBIC: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_CUBIC); break; } } else { localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); } //set the vtk output property to true, makes sure that no unneeded mitk image convertion //is done. localStorage->m_Reslicer->SetVtkOutputRequest(true); //Thickslicing int thickSlicesMode = 0; int thickSlicesNum = 1; // Thick slices parameters if( input->GetPixelType().GetNumberOfComponents() == 1 ) // for now only single component are allowed { DataNode *dn=renderer->GetCurrentWorldPlaneGeometryNode(); if(dn) { ResliceMethodProperty *resliceMethodEnumProperty=0; if( dn->GetProperty( resliceMethodEnumProperty, "reslice.thickslices" ) && resliceMethodEnumProperty ) thickSlicesMode = resliceMethodEnumProperty->GetValueAsId(); IntProperty *intProperty=0; if( dn->GetProperty( intProperty, "reslice.thickslices.num" ) && intProperty ) { thickSlicesNum = intProperty->GetValue(); if(thickSlicesNum < 1) thickSlicesNum=1; if(thickSlicesNum > 10) thickSlicesNum=10; } } else { MITK_WARN << "no associated widget plane data tree node found"; } } const PlaneGeometry *planeGeometry = dynamic_cast< const PlaneGeometry * >( worldGeometry ); if(thickSlicesMode > 0) { double dataZSpacing = 1.0; Vector3D normInIndex, normal; if ( planeGeometry != NULL ){ normal = planeGeometry->GetNormal(); }else{ const mitk::AbstractTransformGeometry* abstractGeometry = dynamic_cast< const AbstractTransformGeometry * >(worldGeometry); if(abstractGeometry != NULL) normal = abstractGeometry->GetPlane()->GetNormal(); else return; //no fitting geometry set } normal.Normalize(); input->GetTimeGeometry()->GetGeometryForTimeStep( this->GetTimestep() )->WorldToIndex( normal, normInIndex ); dataZSpacing = 1.0 / normInIndex.GetNorm(); localStorage->m_Reslicer->SetOutputDimensionality( 3 ); localStorage->m_Reslicer->SetOutputSpacingZDirection(dataZSpacing); localStorage->m_Reslicer->SetOutputExtentZDirection( -thickSlicesNum, 0+thickSlicesNum ); // Do the reslicing. Modified() is called to make sure that the reslicer is // executed even though the input geometry information did not change; this // is necessary when the input /em data, but not the /em geometry changes. localStorage->m_TSFilter->SetThickSliceMode( thickSlicesMode-1 ); localStorage->m_TSFilter->SetInputData( localStorage->m_Reslicer->GetVtkOutput() ); //vtkFilter=>mitkFilter=>vtkFilter update mechanism will fail without calling manually localStorage->m_Reslicer->Modified(); localStorage->m_Reslicer->Update(); localStorage->m_TSFilter->Modified(); localStorage->m_TSFilter->Update(); localStorage->m_ReslicedImage = localStorage->m_TSFilter->GetOutput(); } else { //this is needed when thick mode was enable bevore. These variable have to be reset to default values localStorage->m_Reslicer->SetOutputDimensionality( 2 ); localStorage->m_Reslicer->SetOutputSpacingZDirection(1.0); localStorage->m_Reslicer->SetOutputExtentZDirection( 0, 0 ); localStorage->m_Reslicer->Modified(); //start the pipeline with updating the largest possible, needed if the geometry of the input has changed localStorage->m_Reslicer->UpdateLargestPossibleRegion(); localStorage->m_ReslicedImage = localStorage->m_Reslicer->GetVtkOutput(); } // Bounds information for reslicing (only reuqired if reference geometry // is present) //this used for generating a vtkPLaneSource with the right size double sliceBounds[6]; for ( int i = 0; i < 6; ++i ) { sliceBounds[i] = 0.0; } localStorage->m_Reslicer->GetClippedPlaneBounds(sliceBounds); //get the spacing of the slice localStorage->m_mmPerPixel = localStorage->m_Reslicer->GetOutputSpacing(); // calculate minimum bounding rect of IMAGE in texture { double textureClippingBounds[6]; for ( int i = 0; i < 6; ++i ) { textureClippingBounds[i] = 0.0; } // Calculate the actual bounds of the transformed plane clipped by the // dataset bounding box; this is required for drawing the texture at the // correct position during 3D mapping. mitk::PlaneClipping::CalculateClippedPlaneBounds( input->GetGeometry(), planeGeometry, textureClippingBounds ); textureClippingBounds[0] = static_cast< int >( textureClippingBounds[0] / localStorage->m_mmPerPixel[0] + 0.5 ); textureClippingBounds[1] = static_cast< int >( textureClippingBounds[1] / localStorage->m_mmPerPixel[0] + 0.5 ); textureClippingBounds[2] = static_cast< int >( textureClippingBounds[2] / localStorage->m_mmPerPixel[1] + 0.5 ); textureClippingBounds[3] = static_cast< int >( textureClippingBounds[3] / localStorage->m_mmPerPixel[1] + 0.5 ); //clipping bounds for cutting the image localStorage->m_LevelWindowFilter->SetClippingBounds(textureClippingBounds); } //get the number of scalar components to distinguish between different image types int numberOfComponents = localStorage->m_ReslicedImage->GetNumberOfScalarComponents(); //get the showIsoLines property bool showIsoLines = false; datanode->GetBoolProperty( "dose.showIsoLines", showIsoLines, renderer ); if(showIsoLines) //contour rendering { //generate contours/outlines localStorage->m_OutlinePolyData = CreateOutlinePolyData(renderer); float binaryOutlineWidth(1.0); if ( datanode->GetFloatProperty( "outline width", binaryOutlineWidth, renderer ) ) { if ( localStorage->m_Actors->GetNumberOfPaths() > 1 ) { float binaryOutlineShadowWidth(1.5); datanode->GetFloatProperty( "outline shadow width", binaryOutlineShadowWidth, renderer ); dynamic_cast(localStorage->m_Actors->GetParts()->GetItemAsObject(0)) ->GetProperty()->SetLineWidth( binaryOutlineWidth * binaryOutlineShadowWidth ); } localStorage->m_Actor->GetProperty()->SetLineWidth( binaryOutlineWidth ); } } else { localStorage->m_ReslicedImage = NULL; localStorage->m_Mapper->SetInputData( localStorage->m_EmptyPolyData ); return; } 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(showIsoLines) //connect the mapper with the polyData which contains the lines { //We need the contour for the binary outline property as actor localStorage->m_Mapper->SetInputData(localStorage->m_OutlinePolyData); localStorage->m_Actor->SetTexture(NULL); //no texture for contours bool binaryOutlineShadow( false ); datanode->GetBoolProperty( "outline binary shadow", binaryOutlineShadow, renderer ); if ( binaryOutlineShadow ) contourShadowActor->SetVisibility( true ); else contourShadowActor->SetVisibility( false ); } else { //Connect the mapper with the input texture. This is the standard case. //setup the textured plane this->GeneratePlane( renderer, sliceBounds ); //set the plane as input for the mapper localStorage->m_Mapper->SetInputConnection(localStorage->m_Plane->GetOutputPort()); //set the texture for the actor localStorage->m_Actor->SetTexture(localStorage->m_Texture); contourShadowActor->SetVisibility( false ); } // We have been modified => save this for next Update() localStorage->m_LastUpdateTime.Modified(); } void mitk::DoseImageVtkMapper2D::ApplyLevelWindow(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = this->GetLocalStorage( renderer ); LevelWindow levelWindow; this->GetDataNode()->GetLevelWindow( levelWindow, renderer, "levelwindow" ); localStorage->m_LevelWindowFilter->GetLookupTable()->SetRange( levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound() ); mitk::LevelWindow opacLevelWindow; if( this->GetDataNode()->GetLevelWindow( opacLevelWindow, renderer, "opaclevelwindow" ) ) { //pass the opaque level window to the filter localStorage->m_LevelWindowFilter->SetMinOpacity(opacLevelWindow.GetLowerWindowBound()); localStorage->m_LevelWindowFilter->SetMaxOpacity(opacLevelWindow.GetUpperWindowBound()); } else { //no opaque level window localStorage->m_LevelWindowFilter->SetMinOpacity(0.0); localStorage->m_LevelWindowFilter->SetMaxOpacity(255.0); } } void mitk::DoseImageVtkMapper2D::ApplyColor( mitk::BaseRenderer* renderer ) { LocalStorage *localStorage = this->GetLocalStorage( renderer ); float rgb[3]= { 1.0f, 1.0f, 1.0f }; // check for color prop and use it for rendering if it exists // binary image hovering & binary image selection bool hover = false; bool selected = false; GetDataNode()->GetBoolProperty("binaryimage.ishovering", hover, renderer); GetDataNode()->GetBoolProperty("selected", selected, renderer); if(hover && !selected) { mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty ("binaryimage.hoveringcolor", renderer)); if(colorprop.IsNotNull()) { memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); } else { GetDataNode()->GetColor( rgb, renderer, "color" ); } } if(selected) { mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty ("binaryimage.selectedcolor", renderer)); if(colorprop.IsNotNull()) { memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); } else { GetDataNode()->GetColor(rgb, renderer, "color"); } } if(!hover && !selected) { GetDataNode()->GetColor( rgb, renderer, "color" ); } double rgbConv[3] = {(double)rgb[0], (double)rgb[1], (double)rgb[2]}; //conversion to double for VTK dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0))->GetProperty()->SetColor(rgbConv); localStorage->m_Actor->GetProperty()->SetColor(rgbConv); if ( localStorage->m_Actors->GetParts()->GetNumberOfItems() > 1 ) { float rgb[3]= { 1.0f, 1.0f, 1.0f }; mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty ("outline binary shadow color", renderer)); if(colorprop.IsNotNull()) { memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); } double rgbConv[3] = {(double)rgb[0], (double)rgb[1], (double)rgb[2]}; //conversion to double for VTK dynamic_cast( localStorage->m_Actors->GetParts()->GetItemAsObject(0) )->GetProperty()->SetColor(rgbConv); } } void mitk::DoseImageVtkMapper2D::ApplyOpacity( mitk::BaseRenderer* renderer ) { LocalStorage* localStorage = this->GetLocalStorage( renderer ); float opacity = 1.0f; // check for opacity prop and use it for rendering if it exists GetDataNode()->GetOpacity( opacity, renderer, "opacity" ); //set the opacity according to the properties localStorage->m_Actor->GetProperty()->SetOpacity(opacity); if ( localStorage->m_Actors->GetParts()->GetNumberOfItems() > 1 ) { dynamic_cast( localStorage->m_Actors->GetParts()->GetItemAsObject(0) )->GetProperty()->SetOpacity(opacity); } } void mitk::DoseImageVtkMapper2D::ApplyRenderingMode( mitk::BaseRenderer* renderer ) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); bool binary = false; this->GetDataNode()->GetBoolProperty( "binary", binary, renderer ); if(binary) // is it a binary image? { //for binary images, we always use our default LuT and map every value to (0,1) //the opacity of 0 will always be 0.0. We never a apply a LuT/TfF nor a level window. localStorage->m_LevelWindowFilter->SetLookupTable(localStorage->m_BinaryLookupTable); } else { //all other image types can make use of the rendering mode int renderingMode = mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR; mitk::RenderingModeProperty::Pointer mode = dynamic_cast(this->GetDataNode()->GetProperty( "Image Rendering.Mode", renderer )); if(mode.IsNotNull()) { renderingMode = mode->GetRenderingMode(); } switch(renderingMode) { case mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR: MITK_DEBUG << "'Image Rendering.Mode' = LevelWindow_LookupTable_Color"; this->ApplyLookuptable( renderer ); this->ApplyLevelWindow( renderer ); break; case mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_LEVELWINDOW_COLOR: MITK_DEBUG << "'Image Rendering.Mode' = LevelWindow_ColorTransferFunction_Color"; this->ApplyColorTransferFunction( renderer ); this->ApplyLevelWindow( renderer ); break; case mitk::RenderingModeProperty::LOOKUPTABLE_COLOR: MITK_DEBUG << "'Image Rendering.Mode' = LookupTable_Color"; this->ApplyLookuptable( renderer ); break; case mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_COLOR: MITK_DEBUG << "'Image Rendering.Mode' = ColorTransferFunction_Color"; this->ApplyColorTransferFunction( renderer ); break; default: MITK_ERROR << "No valid 'Image Rendering.Mode' set. Using LOOKUPTABLE_LEVELWINDOW_COLOR instead."; this->ApplyLookuptable( renderer ); this->ApplyLevelWindow( renderer ); break; } } //we apply color for all images (including binaries). this->ApplyColor( renderer ); } void mitk::DoseImageVtkMapper2D::ApplyLookuptable( mitk::BaseRenderer* renderer ) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); vtkLookupTable* usedLookupTable = localStorage->m_ColorLookupTable; // If lookup table or transferfunction use is requested... mitk::LookupTableProperty::Pointer lookupTableProp = dynamic_cast(this->GetDataNode()->GetProperty("LookupTable")); if( lookupTableProp.IsNotNull() ) // is a lookuptable set? { usedLookupTable = lookupTableProp->GetLookupTable()->GetVtkLookupTable(); } else { //"Image Rendering.Mode was set to use a lookup table but there is no property 'LookupTable'. //A default (rainbow) lookup table will be used. //Here have to do nothing. Warning for the user has been removed, due to unwanted console output //in every interation of the rendering. } localStorage->m_LevelWindowFilter->SetLookupTable(usedLookupTable); } void mitk::DoseImageVtkMapper2D::ApplyColorTransferFunction(mitk::BaseRenderer *renderer) { mitk::TransferFunctionProperty::Pointer transferFunctionProp = dynamic_cast(this->GetDataNode()->GetProperty("Image Rendering.Transfer Function",renderer )); if( transferFunctionProp.IsNull() ) { MITK_ERROR << "'Image Rendering.Mode'' was set to use a color transfer function but there is no property 'Image Rendering.Transfer Function'. Nothing will be done."; return; } LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); //pass the transfer function to our level window filter localStorage->m_LevelWindowFilter->SetLookupTable(transferFunctionProp->GetValue()->GetColorTransferFunction()); } void mitk::DoseImageVtkMapper2D::Update(mitk::BaseRenderer* renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if ( !visible ) { return; } mitk::Image* data = const_cast( this->GetInput() ); if ( data == NULL ) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep( renderer ); // Check if time step is valid const TimeGeometry *dataTimeGeometry = data->GetTimeGeometry(); if ( ( dataTimeGeometry == NULL ) || ( dataTimeGeometry->CountTimeSteps() == 0 ) || ( !dataTimeGeometry->IsValidTimeStep( this->GetTimestep() ) ) ) { return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); //check if something important has changed and we need to rerender if ( (localStorage->m_LastUpdateTime < node->GetMTime()) //was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) //Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) //was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) //was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime()) ) { this->GenerateDataForRenderer( renderer ); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } void mitk::DoseImageVtkMapper2D::SetDefaultProperties(mitk::DataNode* node, mitk::BaseRenderer* renderer, bool overwrite) { mitk::Image::Pointer image = dynamic_cast(node->GetData()); // Properties common for both images and segmentations node->AddProperty( "depthOffset", mitk::FloatProperty::New( 0.0 ), renderer, overwrite ); node->AddProperty( "outline binary", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty( "outline width", mitk::FloatProperty::New( 1.0 ), renderer, overwrite ); node->AddProperty( "outline binary shadow", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty( "outline binary shadow color", ColorProperty::New(0.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "outline shadow width", mitk::FloatProperty::New( 1.5 ), renderer, overwrite ); if(image->IsRotated()) node->AddProperty( "reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_CUBIC) ); else node->AddProperty( "reslice interpolation", mitk::VtkResliceInterpolationProperty::New() ); node->AddProperty( "texture interpolation", mitk::BoolProperty::New( mitk::DataNodeFactory::m_TextureInterpolationActive ) ); // set to user configurable default value (see global options) node->AddProperty( "in plane resample extent by geometry", mitk::BoolProperty::New( false ) ); node->AddProperty( "bounding box", mitk::BoolProperty::New( false ) ); mitk::RenderingModeProperty::Pointer renderingModeProperty = mitk::RenderingModeProperty::New(); node->AddProperty( "Image Rendering.Mode", renderingModeProperty); // Set default grayscale look-up table mitk::LookupTable::Pointer mitkLut = mitk::LookupTable::New(); mitkLut->SetType(mitk::LookupTable::GRAYSCALE); mitk::LookupTableProperty::Pointer mitkLutProp = mitk::LookupTableProperty::New(); mitkLutProp->SetLookupTable(mitkLut); node->SetProperty("LookupTable", mitkLutProp); std::string photometricInterpretation; // DICOM tag telling us how pixel values should be displayed if ( node->GetStringProperty( "dicom.pixel.PhotometricInterpretation", photometricInterpretation ) ) { // modality provided by DICOM or other reader if ( photometricInterpretation.find("MONOCHROME1") != std::string::npos ) // meaning: display MINIMUM pixels as WHITE { // Set inverse grayscale look-up table mitkLut->SetType(mitk::LookupTable::INVERSE_GRAYSCALE); mitkLutProp->SetLookupTable(mitkLut); node->SetProperty("LookupTable", mitkLutProp); } // Otherwise do nothing - the default grayscale look-up table has already been set } bool isBinaryImage(false); if ( ! node->GetBoolProperty("binary", isBinaryImage) ) { // ok, property is not set, use heuristic to determine if this // is a binary image mitk::Image::Pointer centralSliceImage; ScalarType minValue = 0.0; ScalarType maxValue = 0.0; ScalarType min2ndValue = 0.0; ScalarType max2ndValue = 0.0; mitk::ImageSliceSelector::Pointer sliceSelector = mitk::ImageSliceSelector::New(); sliceSelector->SetInput(image); sliceSelector->SetSliceNr(image->GetDimension(2)/2); sliceSelector->SetTimeNr(image->GetDimension(3)/2); sliceSelector->SetChannelNr(image->GetDimension(4)/2); sliceSelector->Update(); centralSliceImage = sliceSelector->GetOutput(); if ( centralSliceImage.IsNotNull() && centralSliceImage->IsInitialized() ) { minValue = centralSliceImage->GetStatistics()->GetScalarValueMin(); maxValue = centralSliceImage->GetStatistics()->GetScalarValueMax(); min2ndValue = centralSliceImage->GetStatistics()->GetScalarValue2ndMin(); max2ndValue = centralSliceImage->GetStatistics()->GetScalarValue2ndMax(); } if ((maxValue == min2ndValue && minValue == max2ndValue) || minValue == maxValue) { // centralSlice is strange, lets look at all data minValue = image->GetStatistics()->GetScalarValueMin(); maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(); min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(); max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(); } isBinaryImage = ( maxValue == min2ndValue && minValue == max2ndValue ); } // some more properties specific for a binary... if (isBinaryImage) { node->AddProperty( "opacity", mitk::FloatProperty::New(0.3f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( true ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(10), renderer, overwrite); } else //...or image type object { node->AddProperty( "opacity", mitk::FloatProperty::New(1.0f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,1.0,1.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(0), renderer, overwrite); std::string className = image->GetNameOfClass(); if (className != "TensorImage" && className != "QBallImage") { PixelType pixelType = image->GetPixelType(); size_t numComponents = pixelType.GetNumberOfComponents(); if ((pixelType.GetPixelTypeAsString() == "vector" && numComponents > 1) || numComponents == 2 || numComponents > 4) node->AddProperty("Image.Displayed Component", mitk::IntProperty::New(0), renderer, overwrite); } } if(image.IsNotNull() && image->IsInitialized()) { if((overwrite) || (node->GetProperty("levelwindow", renderer)==NULL)) { /* initialize level/window from DICOM tags */ std::string sLevel; std::string sWindow; - if ( image->GetPropertyList()->GetStringProperty( "dicom.voilut.WindowCenter", sLevel ) - && image->GetPropertyList()->GetStringProperty( "dicom.voilut.WindowWidth", sWindow ) ) + + BaseProperty* prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x1050).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sLevel = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.voilut.WindowCenter", sLevel); + } + + prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x1051).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sWindow = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.voilut.WindowWidth", sWindow); + } + + if (!sLevel.empty() && !sWindow.empty()) { 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 ) ) + prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x0108).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sSmallestPixelValueInSeries = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.series.SmallestPixelValueInSeries", sSmallestPixelValueInSeries); + } + + prop = image->GetPropertyList()->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0028, 0x0109).c_str()); + if (prop) + { //may not be a string property so use the generic access. + sLargestPixelValueInSeries = prop->GetValueAsString(); + } + else + { //this is for backwards compatibility with the old property naming style + image->GetPropertyList()->GetStringProperty("dicom.series.LargestPixelValueInSeries", sLargestPixelValueInSeries); + } + + if (!sSmallestPixelValueInSeries.empty() && !sLargestPixelValueInSeries.empty()) { float smallestPixelValueInSeries = atof( sSmallestPixelValueInSeries.c_str() ); float largestPixelValueInSeries = atof( sLargestPixelValueInSeries.c_str() ); contrast.SetRangeMinMax( smallestPixelValueInSeries-1, largestPixelValueInSeries+1 ); // why not a little buffer? // might remedy some l/w widget challenges } else { contrast.SetAuto( static_cast(node->GetData()), false, true ); // we need this as a fallback } contrast.SetLevelWindow( level, window, true ); node->SetProperty( "levelwindow", LevelWindowProperty::New( contrast ), renderer ); } } if(((overwrite) || (node->GetProperty("opaclevelwindow", renderer)==NULL)) && (image->GetPixelType().GetPixelType() == itk::ImageIOBase::RGBA) && (image->GetPixelType().GetComponentType() == itk::ImageIOBase::UCHAR) ) { mitk::LevelWindow opaclevwin; opaclevwin.SetRangeMinMax(0,255); opaclevwin.SetWindowBounds(0,255); mitk::LevelWindowProperty::Pointer prop = mitk::LevelWindowProperty::New(opaclevwin); node->SetProperty( "opaclevelwindow", prop, renderer ); } } Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::DoseImageVtkMapper2D::LocalStorage* mitk::DoseImageVtkMapper2D::GetLocalStorage(mitk::BaseRenderer* renderer) { return m_LSH.GetLocalStorage(renderer); } vtkSmartPointer mitk::DoseImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer* renderer ) { vtkSmartPointer points = vtkSmartPointer::New(); //the points to draw vtkSmartPointer lines = vtkSmartPointer::New(); //the lines to connect the points vtkSmartPointer colors = vtkSmartPointer::New(); colors->SetNumberOfComponents(3); colors->SetName("Colors"); float pref; this->GetDataNode()->GetFloatProperty(mitk::RTConstants::REFERENCE_DOSE_PROPERTY_NAME.c_str(),pref); mitk::IsoDoseLevelSetProperty::Pointer propIsoSet = dynamic_cast(GetDataNode()->GetProperty(mitk::RTConstants::DOSE_ISO_LEVELS_PROPERTY_NAME.c_str())); mitk::IsoDoseLevelSet::Pointer isoDoseLevelSet = propIsoSet->GetValue(); for(mitk::IsoDoseLevelSet::ConstIterator doseIT = isoDoseLevelSet->Begin(); doseIT!=isoDoseLevelSet->End();++doseIT) { if(doseIT->GetVisibleIsoLine()) { this->CreateLevelOutline(renderer, &(doseIT.Value()), pref, points, lines, colors); }//end of if visible dose value }//end of loop over all does values mitk::IsoDoseLevelVectorProperty::Pointer propfreeIsoVec = dynamic_cast(GetDataNode()->GetProperty(mitk::RTConstants::DOSE_FREE_ISO_VALUES_PROPERTY_NAME.c_str())); mitk::IsoDoseLevelVector::Pointer frereIsoDoseLevelVec = propfreeIsoVec->GetValue(); for(mitk::IsoDoseLevelVector::ConstIterator freeDoseIT = frereIsoDoseLevelVec->Begin(); freeDoseIT!=frereIsoDoseLevelVec->End();++freeDoseIT) { if(freeDoseIT->Value()->GetVisibleIsoLine()) { this->CreateLevelOutline(renderer, freeDoseIT->Value(), pref, points, lines, colors); }//end of if visible dose value }//end of loop over all does values // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); polyData->GetCellData()->SetScalars(colors); return polyData; } void mitk::DoseImageVtkMapper2D::CreateLevelOutline(mitk::BaseRenderer* renderer, const mitk::IsoDoseLevel* level, float pref, vtkSmartPointer points, vtkSmartPointer lines, vtkSmartPointer colors) { LocalStorage* localStorage = this->GetLocalStorage(renderer); //get the min and max index values of each direction int* extent = localStorage->m_ReslicedImage->GetExtent(); int xMin = extent[0]; int xMax = extent[1]; int yMin = extent[2]; int yMax = extent[3]; int* dims = localStorage->m_ReslicedImage->GetDimensions(); //dimensions of the image int line = dims[0]; //how many pixels per line? //get the depth for each contour float depth = CalculateLayerDepth(renderer); double doseValue = level->GetDoseValue()*pref; mitk::IsoDoseLevel::ColorType isoColor = level->GetColor(); unsigned char colorLine[3] = {static_cast(isoColor.GetRed()*255), static_cast(isoColor.GetGreen()*255), static_cast(isoColor.GetBlue()*255)}; int x = xMin; //pixel index x int y = yMin; //pixel index y float* currentPixel; // We take the pointer to the first pixel of the image currentPixel = static_cast(localStorage->m_ReslicedImage->GetScalarPointer() ); while (y <= yMax) { //if the current pixel value is set to something if ((currentPixel) && (*currentPixel >= doseValue)) { //check in which direction a line is necessary //a line is added if the neighbor of the current pixel has the value 0 //and if the pixel is located at the edge of the image //if vvvvv not the first line vvvvv if (y > yMin && *(currentPixel-line) < doseValue) { //x direction - bottom edge of the pixel //add the 2 points vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); //add the line between both points lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } //if vvvvv not the last line vvvvv if (y < yMax && *(currentPixel+line) < doseValue) { //x direction - top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } //if vvvvv not the first pixel vvvvv if ( (x > xMin || y > yMin) && *(currentPixel-1) < doseValue) { //y direction - left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } //if vvvvv not the last pixel vvvvv if ( (y < yMax || (x < xMax) ) && *(currentPixel+1) < doseValue) { //y direction - right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } /* now consider pixels at the edge of the image */ //if vvvvv left edge of image vvvvv if (x == xMin) { //draw left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } //if vvvvv right edge of image vvvvv if (x == xMax) { //draw right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } //if vvvvv bottom edge of image vvvvv if (y == yMin) { //draw bottom edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } //if vvvvv top edge of image vvvvv if (y == yMax) { //draw top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); colors->InsertNextTupleValue(colorLine); } }//end if currentpixel is set x++; if (x > xMax) { //reached end of line x = xMin; y++; } // Increase the pointer-position to the next pixel. // This is safe, as the while-loop and the x-reset logic above makes // sure we do not exceed the bounds of the image currentPixel++; }//end of while } void mitk::DoseImageVtkMapper2D::TransformActor(mitk::BaseRenderer* renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); //get the transformation matrix of the reslicer in order to render the slice as axial, coronal or saggital vtkSmartPointer trans = vtkSmartPointer::New(); vtkSmartPointer matrix = localStorage->m_Reslicer->GetResliceAxes(); trans->SetMatrix(matrix); //transform the plane/contour (the actual actor) to the corresponding view (axial, coronal or saggital) localStorage->m_Actor->SetUserTransform(trans); //transform the origin to center based coordinates, because MITK is center based. localStorage->m_Actor->SetPosition( -0.5*localStorage->m_mmPerPixel[0], -0.5*localStorage->m_mmPerPixel[1], 0.0); if ( localStorage->m_Actors->GetNumberOfPaths() > 1 ) { vtkActor* secondaryActor = dynamic_cast( localStorage->m_Actors->GetParts()->GetItemAsObject(0) ); secondaryActor->SetUserTransform(trans); secondaryActor->SetPosition( -0.5*localStorage->m_mmPerPixel[0], -0.5*localStorage->m_mmPerPixel[1], 0.0); } } bool mitk::DoseImageVtkMapper2D::RenderingGeometryIntersectsImage( const PlaneGeometry* renderingGeometry, SlicedGeometry3D* imageGeometry ) { // if either one of the two geometries is NULL we return true // for safety reasons if ( renderingGeometry == NULL || imageGeometry == NULL ) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance( imageGeometry->GetCornerPoint( 0 ) ); for( int i=1; i<8; i++ ) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint( i ); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance( cornerPoint ); // if it has not the same signing as the distance of the first point if ( initialDistance * distance < 0 ) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } mitk::DoseImageVtkMapper2D::LocalStorage::~LocalStorage() { } mitk::DoseImageVtkMapper2D::LocalStorage::LocalStorage() : m_VectorComponentExtractor(vtkSmartPointer::New()) { m_LevelWindowFilter = vtkSmartPointer::New(); //Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); m_Texture = vtkSmartPointer::New().GetPointer(); m_DefaultLookupTable = vtkSmartPointer::New(); m_BinaryLookupTable = vtkSmartPointer::New(); m_ColorLookupTable = vtkSmartPointer::New(); m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_Actors = vtkSmartPointer::New(); m_Reslicer = mitk::ExtractSliceFilter::New(); m_TSFilter = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_ReslicedImage = vtkSmartPointer::New(); m_EmptyPolyData = vtkSmartPointer::New(); //the following actions are always the same and thus can be performed //in the constructor for each image (i.e. the image-corresponding local storage) m_TSFilter->ReleaseDataFlagOn(); mitk::LookupTable::Pointer mitkLUT = mitk::LookupTable::New(); //built a default lookuptable mitkLUT->SetType(mitk::LookupTable::GRAYSCALE); m_DefaultLookupTable = mitkLUT->GetVtkLookupTable(); mitkLUT->SetType(mitk::LookupTable::LEGACY_BINARY); m_BinaryLookupTable = mitkLUT->GetVtkLookupTable(); mitkLUT->SetType(mitk::LookupTable::LEGACY_RAINBOW_COLOR); m_ColorLookupTable = mitkLUT->GetVtkLookupTable(); //do not repeat the texture (the image) m_Texture->RepeatOff(); //set the mapper for the actor m_Actor->SetMapper( m_Mapper ); vtkSmartPointer outlineShadowActor = vtkSmartPointer::New(); outlineShadowActor->SetMapper( m_Mapper ); m_Actors->AddPart( outlineShadowActor ); m_Actors->AddPart( m_Actor ); } diff --git a/Modules/LegacyIO/mitkDataNodeFactory.cpp b/Modules/LegacyIO/mitkDataNodeFactory.cpp index dcc8c2c68a..69b2e17e5c 100644 --- a/Modules/LegacyIO/mitkDataNodeFactory.cpp +++ b/Modules/LegacyIO/mitkDataNodeFactory.cpp @@ -1,486 +1,503 @@ /*=================================================================== 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 #include #include #include #include #include // C-Standard library includes #include #include // STL-related includes #include #include #include #include #include // VTK-related includes #include #include #include #include #include #include #include #include #include #include #include // ITK-related includes #include #include #include #include #include #include #include #include #include #ifdef NOMINMAX # define DEF_NOMINMAX # undef NOMINMAX #endif #include #ifdef DEF_NOMINMAX # ifndef NOMINMAX # define NOMINMAX # endif # undef DEF_NOMINMAX #endif #include #include // MITK-related includes #include "mitkSurface.h" #include "mitkPointSet.h" #include "mitkStringProperty.h" #include "mitkProperties.h" //#include "mitkMaterialProperty.h" #include "mitkLevelWindowProperty.h" #include "mitkVtkRepresentationProperty.h" #include "mitkVtkInterpolationProperty.h" #include "mitkVtkScalarModeProperty.h" #include "mitkImage.h" #include "mitkLookupTableProperty.h" #include "mitkLookupTable.h" #include "mitkImageChannelSelector.h" #include "mitkImageSliceSelector.h" #include "mitkCoreObjectFactory.h" #include "mitkTransferFunctionProperty.h" #include "mitkVtkResliceInterpolationProperty.h" #include "mitkProgressBar.h" +#include "mitkPropertyNameHelper.h" #include bool mitk::DataNodeFactory::m_TextureInterpolationActive = false; // default value for texture interpolation if nothing is defined in global options (see QmitkMainTemplate.ui.h) mitk::DataNodeFactory::DataNodeFactory() { m_Serie = false; m_OldProgress = 0; this->Modified(); //ensure that a CoreObjectFactory has been instantiated mitk::CoreObjectFactory::GetInstance(); } mitk::DataNodeFactory::~DataNodeFactory() {} void mitk::DataNodeFactory::SetImageSerie(bool serie) { m_Serie = serie; } void mitk::DataNodeFactory::GenerateData() { // IF filename is something.pic, and something.pic does not exist, try to read something.pic.gz // if there are both, something.pic and something.pic.gz, only the requested file is read // not only for images, but for all formats std::ifstream exists(m_FileName.c_str()); if (!exists) { std::string testfilename = m_FileName + ".gz"; std::ifstream exists(testfilename.c_str()); if (exists.good()) { m_FileName += ".gz"; } else { testfilename = m_FileName + ".GZ"; std::ifstream exists(testfilename.c_str()); if (exists.good()) { m_FileName += ".GZ"; } else { std::string message("File does not exist, or cannot be read. Filename = "); message += m_FileName; MITK_ERROR << message; itkExceptionMacro( << message.str() ); } } } // part for DICOM // const char *numbers = "0123456789."; // std::string::size_type first_non_number; // first_non_number = itksys::SystemTools::GetFilenameName(m_FileName).find_first_not_of ( numbers ); if (DicomSeriesReader::IsDicom(this->m_FileName) /*|| first_non_number == std::string::npos*/) { this->ReadFileSeriesTypeDCM(); } else { bool usedNewDTNF = false; // the mitkBaseDataIO class returns a pointer of a vector of BaseData objects std::vector baseDataVector = mitk::BaseDataIO::LoadBaseDataFromFile( m_FileName, m_FilePrefix, m_FilePattern, m_Serie ); if( !baseDataVector.empty() ) this->ResizeOutputs((unsigned int)baseDataVector.size()); for(int i=0; i<(int)baseDataVector.size(); i++) { mitk::BaseData::Pointer baseData = baseDataVector.at(i); if( baseData.IsNotNull() ) { usedNewDTNF = true; mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(baseData); this->SetDefaultCommonProperties( node ); this->SetOutput(this->MakeNameFromOutputIndex(i), node); } } if(!usedNewDTNF && ( m_FileName != "" ) && !(m_Serie == false)) ReadFileSeriesTypeITKImageSeriesReader(); } } void mitk::DataNodeFactory::ResizeOutputs( const unsigned int& num ) { unsigned int prevNum = this->GetNumberOfOutputs(); this->SetNumberOfIndexedOutputs( num ); for ( unsigned int i = prevNum; i < num; ++i ) { this->SetNthOutput( i, this->MakeOutput( i ).GetPointer() ); } } bool mitk::DataNodeFactory::FileNameEndsWith( const std::string& name ) { if (m_FileName.size() < name.size()) return false; return m_FileName.substr(m_FileName.size() - name.size()) == name; } bool mitk::DataNodeFactory::FilePatternEndsWith( const std::string& name ) { return m_FilePattern.find( name ) != std::string::npos; } std::string mitk::DataNodeFactory::GetBaseFileName() { return itksys::SystemTools::GetFilenameName( m_FileName ); } std::string mitk::DataNodeFactory::GetBaseFilePrefix() { return itksys::SystemTools::GetFilenameName( m_FilePrefix ); } std::string mitk::DataNodeFactory::GetDirectory() { if ( !m_FileName.empty() ) return itksys::SystemTools::GetFilenamePath( m_FileName ); if ( !m_FilePrefix.empty() ) return itksys::SystemTools::GetFilenamePath( m_FilePrefix ); return std::string(); } void mitk::DataNodeFactory::ReadFileSeriesTypeDCM() { mitk::LocaleSwitch localeSwitch("C"); std::locale previousCppLocale( std::cin.getloc() ); std::locale l( "C" ); std::cin.imbue(l); if ( DicomSeriesReader::IsPhilips3DDicom(this->GetFileName()) ) { MITK_INFO << "it is a Philips3D US Dicom file" << std::endl; this->ResizeOutputs(1); DataNode::Pointer node = this->GetOutput(); mitk::DicomSeriesReader::StringContainer stringvec; stringvec.push_back(this->GetFileName()); if (DicomSeriesReader::LoadDicomSeries(stringvec, *node)) { node->SetName(this->GetBaseFileName()); } std::cin.imbue(previousCppLocale); return; } DicomSeriesReader::FileNamesGrouping imageBlocks = DicomSeriesReader::GetSeries(this->GetDirectory(), true, this->m_SeriesRestrictions); // true = group gantry tilt images const unsigned int size = imageBlocks.size(); this->ResizeOutputs(size); ProgressBar::GetInstance()->AddStepsToDo(size); ProgressBar::GetInstance()->Progress(); unsigned int outputIndex = 0u; const DicomSeriesReader::FileNamesGrouping::const_iterator n_end = imageBlocks.end(); for (DicomSeriesReader::FileNamesGrouping::const_iterator n_it = imageBlocks.begin(); n_it != n_end; ++n_it) { const std::string &uid = n_it->first; DataNode::Pointer node = this->GetOutput(outputIndex); const DicomSeriesReader::ImageBlockDescriptor& imageBlockDescriptor( n_it->second ); MITK_INFO << "--------------------------------------------------------------------------------"; MITK_INFO << "DataNodeFactory: Loading DICOM series " << outputIndex << ": Series UID " << imageBlockDescriptor.GetSeriesInstanceUID() << std::endl; MITK_INFO << " " << imageBlockDescriptor.GetFilenames().size() << " '" << imageBlockDescriptor.GetModality() << "' files (" << imageBlockDescriptor.GetSOPClassUIDAsString() << ") loaded into 1 mitk::Image"; MITK_INFO << " multi-frame: " << (imageBlockDescriptor.IsMultiFrameImage()?"Yes":"No"); MITK_INFO << " reader support: " << DicomSeriesReader::ReaderImplementationLevelToString(imageBlockDescriptor.GetReaderImplementationLevel()); MITK_INFO << " pixel spacing type: " << DicomSeriesReader::PixelSpacingInterpretationToString( imageBlockDescriptor.GetPixelSpacingType() ); MITK_INFO << " gantry tilt corrected: " << (imageBlockDescriptor.HasGantryTiltCorrected()?"Yes":"No"); MITK_INFO << " 3D+t: " << (imageBlockDescriptor.HasMultipleTimePoints()?"Yes":"No"); MITK_INFO << "--------------------------------------------------------------------------------"; if (DicomSeriesReader::LoadDicomSeries(n_it->second.GetFilenames(), *node, true, true, true)) { std::string nodeName(uid); std::string studyDescription; - if ( node->GetStringProperty( "dicom.study.StudyDescription", studyDescription ) ) + + BaseProperty* prop = node->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str()); + if (prop) + { //may not be a string property so use the generic access. + studyDescription = prop->GetValueAsString(); + } + + if (!studyDescription.empty() || node->GetStringProperty("dicom.study.StudyDescription", studyDescription)) + //2nd part is for backwards compatibility with the old property naming style { nodeName = studyDescription; std::string seriesDescription; - if ( node->GetStringProperty( "dicom.series.SeriesDescription", seriesDescription ) ) + + prop = node->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str()); + if (prop) + { //may not be a string property so use the generic access. + seriesDescription = prop->GetValueAsString(); + } + + if (!seriesDescription.empty() || node->GetStringProperty("dicom.series.SeriesDescription", seriesDescription)) + //2nd part is for backwards compatibility with the old property naming style { nodeName += "/" + seriesDescription; } } node->SetName(nodeName); ++outputIndex; } else { MITK_ERROR << "DataNodeFactory: Skipping series " << outputIndex << " due to some unspecified error..." << std::endl; } ProgressBar::GetInstance()->Progress(); } std::cin.imbue(previousCppLocale); } void mitk::DataNodeFactory::ReadFileSeriesTypeITKImageSeriesReader() { typedef itk::Image ImageType; typedef itk::ImageSeriesReader< ImageType > ReaderType; if ( ! this->GenerateFileList() ) { itkWarningMacro( "Sorry, file list could not be generated!" ); return ; } if ( m_MatchedFileNames.size() == 0 ) { itkWarningMacro( "Sorry, no files matched the given filename ("<< m_FileName <<")!" ); return ; } // // Finally, initialize the ITK-reader and load the files! // ReaderType::Pointer reader = ReaderType::New(); reader->SetFileNames( m_MatchedFileNames ); try { reader->Update(); ResizeOutputs( reader->GetNumberOfOutputs() ); for ( unsigned int i = 0; i < reader->GetNumberOfOutputs(); ++i ) { //Initialize mitk image from itk mitk::Image::Pointer image = mitk::Image::New(); image->InitializeByItk( reader->GetOutput( i ) ); image->SetVolume( reader->GetOutput( i )->GetBufferPointer() ); //add the mitk image to the node mitk::DataNode::Pointer node = this->GetOutput( i ); node->SetData( image ); mitk::StringProperty::Pointer nameProp = mitk::StringProperty::New( m_FileName ); node->SetProperty( "name", nameProp ); } } catch ( const std::exception & e ) { itkWarningMacro( << e.what() ); return ; } } mitk::ColorProperty::Pointer mitk::DataNodeFactory::DefaultColorForOrgan( const std::string& organ ) { static bool initialized = false; static std::map< std::string, std::string > s_ColorMap; if (!initialized) { // all lowercase here, please! s_ColorMap.insert( std::make_pair( "ankle", "0xe38686") ); s_ColorMap.insert( std::make_pair( "appendix", "0xe38686") ); s_ColorMap.insert( std::make_pair( "blood vessels", "0xff3131") ); s_ColorMap.insert( std::make_pair( "bronchial tree", "0x3168ff") ); s_ColorMap.insert( std::make_pair( "bone", "0xd5d5d5") ); s_ColorMap.insert( std::make_pair( "brain", "0xff9cca") ); s_ColorMap.insert( std::make_pair( "coccyx", "0xe38686") ); s_ColorMap.insert( std::make_pair( "colon", "0xe38686") ); s_ColorMap.insert( std::make_pair( "cyst", "0xe38686") ); s_ColorMap.insert( std::make_pair( "elbow", "0xe38686") ); s_ColorMap.insert( std::make_pair( "eye", "0xe38686") ); s_ColorMap.insert( std::make_pair( "fallopian tube", "0xe38686") ); s_ColorMap.insert( std::make_pair( "fat", "0xff2bee") ); s_ColorMap.insert( std::make_pair( "hand", "0xe38686") ); s_ColorMap.insert( std::make_pair( "gall bladder", "0x567f18") ); s_ColorMap.insert( std::make_pair( "heart", "0xeb1d32") ); s_ColorMap.insert( std::make_pair( "hip", "0xe38686") ); s_ColorMap.insert( std::make_pair( "kidney", "0xd33f00") ); s_ColorMap.insert( std::make_pair( "knee", "0xe38686") ); s_ColorMap.insert( std::make_pair( "larynx", "0xe38686") ); s_ColorMap.insert( std::make_pair( "liver", "0xffcc3d") ); s_ColorMap.insert( std::make_pair( "lung", "0x6bdcff") ); s_ColorMap.insert( std::make_pair( "lymph node", "0xff0000") ); s_ColorMap.insert( std::make_pair( "muscle", "0xff456a") ); s_ColorMap.insert( std::make_pair( "nerve", "0xffea4f") ); s_ColorMap.insert( std::make_pair( "nose", "0xe38686") ); s_ColorMap.insert( std::make_pair( "oesophagus", "0xe38686") ); s_ColorMap.insert( std::make_pair( "ovaries", "0xe38686") ); s_ColorMap.insert( std::make_pair( "pancreas", "0xf9ab3d") ); s_ColorMap.insert( std::make_pair( "pelvis", "0xe38686") ); s_ColorMap.insert( std::make_pair( "penis", "0xe38686") ); s_ColorMap.insert( std::make_pair( "pharynx", "0xe38686") ); s_ColorMap.insert( std::make_pair( "prostate", "0xe38686") ); s_ColorMap.insert( std::make_pair( "rectum", "0xe38686") ); s_ColorMap.insert( std::make_pair( "sacrum", "0xe38686") ); s_ColorMap.insert( std::make_pair( "seminal vesicle", "0xe38686") ); s_ColorMap.insert( std::make_pair( "shoulder", "0xe38686") ); s_ColorMap.insert( std::make_pair( "spinal cord", "0xf5f93d") ); s_ColorMap.insert( std::make_pair( "spleen", "0xf96c3d") ); s_ColorMap.insert( std::make_pair( "stomach", "0xf96c3d") ); s_ColorMap.insert( std::make_pair( "teeth", "0xfffcd8") ); s_ColorMap.insert( std::make_pair( "testicles", "0xe38686") ); s_ColorMap.insert( std::make_pair( "thyroid", "0xfff694") ); s_ColorMap.insert( std::make_pair( "tongue", "0xe38686") ); s_ColorMap.insert( std::make_pair( "tumor", "0x937011") ); s_ColorMap.insert( std::make_pair( "urethra", "0xf8ff32") ); s_ColorMap.insert( std::make_pair( "urinary bladder", "0xf8ff32") ); s_ColorMap.insert( std::make_pair( "uterus", "0xe38686") ); s_ColorMap.insert( std::make_pair( "vagina", "0xe38686") ); s_ColorMap.insert( std::make_pair( "vertebra", "0xe38686") ); s_ColorMap.insert( std::make_pair( "wrist", "0xe38686") ); initialized = true; } std::string lowercaseOrgan(organ); for(unsigned int i = 0; i < organ.length(); i++) { lowercaseOrgan[i] = tolower(lowercaseOrgan[i]); } std::map< std::string, std::string >::iterator iter = s_ColorMap.find( lowercaseOrgan ); if ( iter != s_ColorMap.end() ) { std::string hexColor = iter->second; std::string hexRed = std::string("0x") + hexColor.substr( 2, 2 ); std::string hexGreen = std::string("0x") + hexColor.substr( 4, 2 ); std::string hexBlue = std::string("0x") + hexColor.substr( 6, 2 ); long int red = strtol( hexRed.c_str(), NULL, 16 ); long int green = strtol( hexGreen.c_str(), NULL, 16 ); long int blue = strtol( hexBlue.c_str(), NULL, 16 ); return ColorProperty::New( (float)red/ 255.0, (float)green/ 255.0, (float)blue/ 255.0 ); } else { // a default color (green) return ColorProperty::New( 0.0, 1.0, 0.0 ); } } void mitk::DataNodeFactory::SetDefaultCommonProperties(mitk::DataNode::Pointer &node) { // path mitk::StringProperty::Pointer pathProp = mitk::StringProperty::New( itksys::SystemTools::GetFilenamePath( m_FileName ) ); node->SetProperty( StringProperty::PATH, pathProp ); // name already defined? mitk::StringProperty::Pointer nameProp = dynamic_cast(node->GetProperty("name")); if(nameProp.IsNull() || (strcmp(nameProp->GetValue(),"No Name!")==0)) { // name already defined in BaseData mitk::StringProperty::Pointer baseDataNameProp = dynamic_cast(node->GetData()->GetProperty("name").GetPointer() ); if(baseDataNameProp.IsNull() || (strcmp(baseDataNameProp->GetValue(),"No Name!")==0)) { // name neither defined in node, nor in BaseData -> name = filename if (FileNameEndsWith( ".gz" )) m_FileName = m_FileName.substr( 0, m_FileName.length()-3 ); nameProp = mitk::StringProperty::New( itksys::SystemTools::GetFilenameWithoutLastExtension( m_FileName ) ); node->SetProperty( "name", nameProp ); } else { // name defined in BaseData! nameProp = mitk::StringProperty::New( baseDataNameProp->GetValue() ); node->SetProperty( "name", nameProp ); } } // visibility if(!node->GetProperty("visible")) node->SetVisibility(true); } diff --git a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp index 060c913eea..ef74afc618 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageTreeModel.cpp @@ -1,983 +1,1014 @@ /*=================================================================== 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 #include #include #include #include #include #include #include #include #include +#include + #include "QmitkDataStorageTreeModel.h" #include "QmitkNodeDescriptorManager.h" #include #include #include #include #include #include #include #include #include QmitkDataStorageTreeModel::QmitkDataStorageTreeModel( mitk::DataStorage* _DataStorage , bool _PlaceNewNodesOnTop , QObject* parent ) : QAbstractItemModel(parent) , m_DataStorage(0) , m_PlaceNewNodesOnTop(_PlaceNewNodesOnTop) , m_Root(0) , m_BlockDataStorageEvents(false) , m_AllowHierarchyChange(false) { this->SetDataStorage(_DataStorage); } QmitkDataStorageTreeModel::~QmitkDataStorageTreeModel() { // set data storage to 0 = remove all listeners this->SetDataStorage(0); m_Root->Delete(); m_Root = 0; } mitk::DataNode::Pointer QmitkDataStorageTreeModel::GetNode( const QModelIndex &index ) const { return this->TreeItemFromIndex(index)->GetDataNode(); } const mitk::DataStorage::Pointer QmitkDataStorageTreeModel::GetDataStorage() const { return m_DataStorage.GetPointer(); } QModelIndex QmitkDataStorageTreeModel::index( int row, int column, const QModelIndex & parent ) const { TreeItem* parentItem; if (!parent.isValid()) parentItem = m_Root; else parentItem = static_cast(parent.internalPointer()); TreeItem *childItem = parentItem->GetChild(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } int QmitkDataStorageTreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentTreeItem = this->TreeItemFromIndex(parent); return parentTreeItem->GetChildCount(); } Qt::ItemFlags QmitkDataStorageTreeModel::flags( const QModelIndex& index ) const { mitk::DataNode* dataNode = this->TreeItemFromIndex(index)->GetDataNode(); if (index.isValid()) { if(DicomPropertiesExists(*dataNode)) { return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; }else{ return Qt::ItemIsDropEnabled; } } int QmitkDataStorageTreeModel::columnCount( const QModelIndex& /* parent = QModelIndex() */ ) const { return 1; } QModelIndex QmitkDataStorageTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); TreeItem *childItem = this->TreeItemFromIndex(index); TreeItem *parentItem = childItem->GetParent(); if (parentItem == m_Root) return QModelIndex(); return this->createIndex(parentItem->GetIndex(), 0, parentItem); } QmitkDataStorageTreeModel::TreeItem* QmitkDataStorageTreeModel::TreeItemFromIndex( const QModelIndex &index ) const { if (index.isValid()) return static_cast(index.internalPointer()); else return m_Root; } Qt::DropActions QmitkDataStorageTreeModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions QmitkDataStorageTreeModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } bool QmitkDataStorageTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { // Early exit, returning true, but not actually doing anything (ignoring data). if (action == Qt::IgnoreAction) { return true; } // Note, we are returning true if we handled it, and false otherwise bool returnValue = false; if(data->hasFormat("application/x-qabstractitemmodeldatalist")) { returnValue = true; // First we extract a Qlist of TreeItem* pointers. QList listOfItemsToDrop = ToTreeItemPtrList(data); // Retrieve the TreeItem* where we are dropping stuff, and its parent. TreeItem* dropItem = this->TreeItemFromIndex(parent); TreeItem* parentItem = dropItem->GetParent(); // If item was dropped onto empty space, we select the root node if(dropItem == m_Root) { parentItem = m_Root; } // Dragging and Dropping is only allowed within the same parent, so use the first item in list to validate. // (otherwise, you could have a derived image such as a segmentation, and assign it to another image). // NOTE: We are assuming the input list is valid... i.e. when it was dragged, all the items had the same parent. // Determine whether or not the drag and drop operation is a valid one. // Examples of invalid operations include: // - dragging nodes with different parents // - dragging nodes from one parent to another parent, if m_AllowHierarchyChange is false // - dragging a node on one of its child nodes (only relevant if m_AllowHierarchyChange is true) bool isValidDragAndDropOperation(true); // different parents { TreeItem* firstParent = listOfItemsToDrop[0]->GetParent(); QList::iterator diIter; for (diIter = listOfItemsToDrop.begin() +1; diIter != listOfItemsToDrop.end(); diIter++) { if (firstParent != (*diIter)->GetParent()) { isValidDragAndDropOperation = false; break; } } } // dragging from one parent to another if((!m_AllowHierarchyChange) && isValidDragAndDropOperation) { if (row == -1)// drag onto a node { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == parentItem; } else // drag between nodes { isValidDragAndDropOperation = listOfItemsToDrop[0]->GetParent() == dropItem; } } // dragging on a child node of one the dragged nodes { QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem* tempItem = dropItem; while (tempItem != m_Root) { tempItem = tempItem->GetParent(); if (tempItem == *diIter) { isValidDragAndDropOperation = false; } } } } if (!isValidDragAndDropOperation) return isValidDragAndDropOperation; if (listOfItemsToDrop[0] != dropItem && isValidDragAndDropOperation) { // Retrieve the index of where we are dropping stuff. QModelIndex parentModelIndex = this->IndexFromTreeItem(parentItem); int dragIndex = 0; // Iterate through the list of TreeItem (which may be at non-consecutive indexes). QList::iterator diIter; for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { TreeItem* itemToDrop = *diIter; // if the item is dragged down we have to compensate its final position for the // fact it is deleted lateron, this only applies if it is dragged within the same level if ( (itemToDrop->GetIndex() < row) && (itemToDrop->GetParent() == dropItem)) { dragIndex = 1; } // Here we assume that as you remove items, one at a time, that GetIndex() will be valid. this->beginRemoveRows(this->IndexFromTreeItem(itemToDrop->GetParent()), itemToDrop->GetIndex(), itemToDrop->GetIndex()); itemToDrop->GetParent()->RemoveChild(itemToDrop); this->endRemoveRows(); } // row = -1 dropped on an item, row != -1 dropped in between two items // Select the target index position, or put it at the end of the list. int dropIndex = 0; if (row != -1) { if (dragIndex == 0) dropIndex = std::min(row, parentItem->GetChildCount() - 1); else dropIndex = std::min(row - 1, parentItem->GetChildCount() - 1); } else { dropIndex = dropItem->GetIndex(); } QModelIndex dropItemModelIndex = this->IndexFromTreeItem(dropItem); if ((row == -1 && dropItemModelIndex.row() == -1) || dropItemModelIndex.row() > parentItem->GetChildCount()) dropIndex = parentItem->GetChildCount() - 1; // Now insert items again at the drop item position if (m_AllowHierarchyChange) { this->beginInsertRows(dropItemModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } else { this->beginInsertRows(parentModelIndex, dropIndex, dropIndex + listOfItemsToDrop.size() - 1); } for (diIter = listOfItemsToDrop.begin(); diIter != listOfItemsToDrop.end(); diIter++) { // dropped on node, behaviour depends on preference setting if (m_AllowHierarchyChange) { m_BlockDataStorageEvents = true; mitk::DataNode* droppedNode = (*diIter)->GetDataNode(); mitk::DataNode* dropOntoNode = dropItem->GetDataNode(); m_DataStorage->Remove(droppedNode); m_DataStorage->Add(droppedNode, dropOntoNode); m_BlockDataStorageEvents = false; dropItem->InsertChild((*diIter), dropIndex); } else { if (row == -1)// drag onto a node { parentItem->InsertChild((*diIter), dropIndex); } else // drag between nodes { dropItem->InsertChild((*diIter), dropIndex); } } dropIndex++; } this->endInsertRows(); // Change Layers to match. this->AdjustLayerProperty(); } } else if(data->hasFormat("application/x-mitk-datanodes")) { returnValue = true; int numberOfNodesDropped = 0; QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(data); mitk::DataNode* node = NULL; foreach(node, dataNodeList) { if(node && m_DataStorage.IsNotNull() && !m_DataStorage->Exists(node)) { m_DataStorage->Add( node ); mitk::BaseData::Pointer basedata = node->GetData(); if (basedata.IsNotNull()) { mitk::RenderingManager::GetInstance()->InitializeViews( basedata->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true ); numberOfNodesDropped++; } } } // Only do a rendering update, if we actually dropped anything. if (numberOfNodesDropped > 0) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } return returnValue; } QStringList QmitkDataStorageTreeModel::mimeTypes() const { QStringList types = QAbstractItemModel::mimeTypes(); types << "application/x-qabstractitemmodeldatalist"; types << "application/x-mitk-datanodes"; return types; } QMimeData * QmitkDataStorageTreeModel::mimeData(const QModelIndexList & indexes) const { return mimeDataFromModelIndexList(indexes); } QMimeData *QmitkDataStorageTreeModel::mimeDataFromModelIndexList(const QModelIndexList &indexes) { QMimeData * ret = new QMimeData; QString treeItemAddresses(""); QString dataNodeAddresses(""); QByteArray baTreeItemPtrs; QByteArray baDataNodePtrs; QDataStream dsTreeItemPtrs(&baTreeItemPtrs, QIODevice::WriteOnly); QDataStream dsDataNodePtrs(&baDataNodePtrs, QIODevice::WriteOnly); for (int i = 0; i < indexes.size(); i++) { TreeItem* treeItem = static_cast(indexes.at(i).internalPointer()); dsTreeItemPtrs << reinterpret_cast(treeItem); dsDataNodePtrs << reinterpret_cast(treeItem->GetDataNode().GetPointer()); // --------------- deprecated ----------------- unsigned long long treeItemAddress = reinterpret_cast(treeItem); unsigned long long dataNodeAddress = reinterpret_cast(treeItem->GetDataNode().GetPointer()); QTextStream(&treeItemAddresses) << treeItemAddress; QTextStream(&dataNodeAddresses) << dataNodeAddress; if (i != indexes.size() - 1) { QTextStream(&treeItemAddresses) << ","; QTextStream(&dataNodeAddresses) << ","; } // -------------- end deprecated ------------- } // ------------------ deprecated ----------------- ret->setData("application/x-qabstractitemmodeldatalist", QByteArray(treeItemAddresses.toLatin1())); ret->setData("application/x-mitk-datanodes", QByteArray(dataNodeAddresses.toLatin1())); // --------------- end deprecated ----------------- ret->setData(QmitkMimeTypes::DataStorageTreeItemPtrs, baTreeItemPtrs); ret->setData(QmitkMimeTypes::DataNodePtrs, baDataNodePtrs); return ret; } QVariant QmitkDataStorageTreeModel::data( const QModelIndex & index, int role ) const { mitk::DataNode* dataNode = this->TreeItemFromIndex(index)->GetDataNode(); // get name of treeItem (may also be edited) QString nodeName; if(DicomPropertiesExists(*dataNode)) { - mitk::BaseProperty* seriesDescription = (dataNode->GetProperty("dicom.series.SeriesDescription")); - mitk::BaseProperty* studyDescription = (dataNode->GetProperty("dicom.study.StudyDescription")); - mitk::BaseProperty* patientsName = (dataNode->GetProperty("dicom.patient.PatientsName")); + mitk::BaseProperty* seriesDescription = (dataNode->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str())); + mitk::BaseProperty* studyDescription = (dataNode->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str())); + mitk::BaseProperty* patientsName = (dataNode->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); + + mitk::BaseProperty* seriesDescription_deprecated = (dataNode->GetProperty("dicom.series.SeriesDescription")); + mitk::BaseProperty* studyDescription_deprecated = (dataNode->GetProperty("dicom.study.StudyDescription")); + mitk::BaseProperty* patientsName_deprecated = (dataNode->GetProperty("dicom.patient.PatientsName")); - nodeName += QFile::encodeName(patientsName->GetValueAsString().c_str()) + "\n"; - nodeName += QFile::encodeName(studyDescription->GetValueAsString().c_str()) + "\n"; - nodeName += QFile::encodeName(seriesDescription->GetValueAsString().c_str()); + if (patientsName) + { + nodeName += QFile::encodeName(patientsName->GetValueAsString().c_str()) + "\n"; + nodeName += QFile::encodeName(studyDescription->GetValueAsString().c_str()) + "\n"; + nodeName += QFile::encodeName(seriesDescription->GetValueAsString().c_str()); + } + else + { /** Code coveres the deprecated property naming for backwards compatibility */ + nodeName += QFile::encodeName(patientsName_deprecated->GetValueAsString().c_str()) + "\n"; + nodeName += QFile::encodeName(studyDescription_deprecated->GetValueAsString().c_str()) + "\n"; + nodeName += QFile::encodeName(seriesDescription_deprecated->GetValueAsString().c_str()); + } } else { nodeName = QFile::encodeName(dataNode->GetName().c_str()); } if(nodeName.isEmpty()) { nodeName = "unnamed"; } if (role == Qt::DisplayRole) return nodeName; else if(role == Qt::ToolTipRole) return nodeName; else if(role == Qt::DecorationRole) { QmitkNodeDescriptor* nodeDescriptor = QmitkNodeDescriptorManager::GetInstance()->GetDescriptor(dataNode); return nodeDescriptor->GetIcon(); } else if(role == Qt::CheckStateRole) { return dataNode->IsVisible(0); } else if(role == QmitkDataNodeRole) { return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); } else if(role == QmitkDataNodeRawPointerRole) { return QVariant::fromValue(dataNode); } return QVariant(); } bool QmitkDataStorageTreeModel::DicomPropertiesExists(const mitk::DataNode& node) const { bool propertiesExists = false; - mitk::BaseProperty* seriesDescription = (node.GetProperty("dicom.series.SeriesDescription")); - mitk::BaseProperty* studyDescription = (node.GetProperty("dicom.study.StudyDescription")); - mitk::BaseProperty* patientsName = (node.GetProperty("dicom.patient.PatientsName")); + + mitk::BaseProperty* seriesDescription_deprecated = (node.GetProperty("dicom.series.SeriesDescription")); + mitk::BaseProperty* studyDescription_deprecated = (node.GetProperty("dicom.study.StudyDescription")); + mitk::BaseProperty* patientsName_deprecated = (node.GetProperty("dicom.patient.PatientsName")); + mitk::BaseProperty* seriesDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x103e).c_str())); + mitk::BaseProperty* studyDescription = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x1030).c_str())); + mitk::BaseProperty* patientsName = (node.GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); if(patientsName!=NULL && studyDescription!=NULL && seriesDescription!=NULL) { if((!patientsName->GetValueAsString().empty())&& (!studyDescription->GetValueAsString().empty())&& (!seriesDescription->GetValueAsString().empty())) { propertiesExists = true; } } + + /** Code coveres the deprecated property naming for backwards compatibility */ + if (patientsName_deprecated != NULL && studyDescription_deprecated != NULL && seriesDescription_deprecated != NULL) + { + if ((!patientsName_deprecated->GetValueAsString().empty()) && + (!studyDescription_deprecated->GetValueAsString().empty()) && + (!seriesDescription_deprecated->GetValueAsString().empty())) + { + propertiesExists = true; + } + } + return propertiesExists; } QVariant QmitkDataStorageTreeModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole && m_Root) return QString::fromStdString(m_Root->GetDataNode()->GetName()); return QVariant(); } void QmitkDataStorageTreeModel::SetDataStorage( mitk::DataStorage* _DataStorage ) { if(m_DataStorage != _DataStorage) // dont take the same again { if(m_DataStorage.IsNotNull()) { // remove Listener for the data storage itself m_DataStorage.ObjectDelete.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetDataStorageDeleted ) ); // remove listeners for the nodes m_DataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::AddNode ) ); m_DataStorage->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified ) ); m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode ) ); } // take over the new data storage m_DataStorage = _DataStorage; // delete the old root (if necessary, create new) if(m_Root) m_Root->Delete(); mitk::DataNode::Pointer rootDataNode = mitk::DataNode::New(); rootDataNode->SetName("Data Manager"); m_Root = new TreeItem(rootDataNode, 0); this->beginResetModel(); this->endResetModel(); if(m_DataStorage.IsNotNull()) { // add Listener for the data storage itself m_DataStorage.ObjectDelete.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetDataStorageDeleted ) ); // add listeners for the nodes m_DataStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::AddNode ) ); m_DataStorage->ChangedNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::SetNodeModified ) ); m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageTreeModel::RemoveNode ) ); mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = m_DataStorage->GetSubset(m_Predicate); // finally add all nodes to the model this->Update(); } } } void QmitkDataStorageTreeModel::SetDataStorageDeleted( const itk::Object* /*_DataStorage*/ ) { this->SetDataStorage(0); } void QmitkDataStorageTreeModel::AddNodeInternal(const mitk::DataNode *node) { if(node == 0 || m_DataStorage.IsNull() || !m_DataStorage->Exists(node) || m_Root->Find(node) != 0) return; // find out if we have a root node TreeItem* parentTreeItem = m_Root; QModelIndex index; mitk::DataNode* parentDataNode = this->GetParentNode(node); if(parentDataNode) // no top level data node { parentTreeItem = m_Root->Find(parentDataNode); // find the corresponding tree item if(!parentTreeItem) { this->AddNode(parentDataNode); parentTreeItem = m_Root->Find(parentDataNode); if(!parentTreeItem) return; } // get the index of this parent with the help of the grand parent index = this->createIndex(parentTreeItem->GetIndex(), 0, parentTreeItem); } // add node if(m_PlaceNewNodesOnTop) { // emit beginInsertRows event beginInsertRows(index, 0, 0); parentTreeItem->InsertChild(new TreeItem( const_cast(node)), 0); } else { beginInsertRows(index, parentTreeItem->GetChildCount() , parentTreeItem->GetChildCount()); new TreeItem(const_cast(node), parentTreeItem); } // emit endInsertRows event endInsertRows(); this->AdjustLayerProperty(); } void QmitkDataStorageTreeModel::AddNode( const mitk::DataNode* node ) { if(node == 0 || m_BlockDataStorageEvents || m_DataStorage.IsNull() || !m_DataStorage->Exists(node) || m_Root->Find(node) != 0) return; this->AddNodeInternal(node); } void QmitkDataStorageTreeModel::SetPlaceNewNodesOnTop(bool _PlaceNewNodesOnTop) { m_PlaceNewNodesOnTop = _PlaceNewNodesOnTop; } void QmitkDataStorageTreeModel::RemoveNodeInternal( const mitk::DataNode* node ) { if(!m_Root) return; TreeItem* treeItem = m_Root->Find(node); if(!treeItem) return; // return because there is no treeitem containing this node TreeItem* parentTreeItem = treeItem->GetParent(); QModelIndex parentIndex = this->IndexFromTreeItem(parentTreeItem); // emit beginRemoveRows event (QModelIndex is empty because we dont have a tree model) this->beginRemoveRows(parentIndex, treeItem->GetIndex(), treeItem->GetIndex()); // remove node std::vector children = treeItem->GetChildren(); delete treeItem; // emit endRemoveRows event endRemoveRows(); // move all children of deleted node into its parent for ( std::vector::iterator it = children.begin() ; it != children.end(); it++) { // emit beginInsertRows event beginInsertRows(parentIndex, parentTreeItem->GetChildCount(), parentTreeItem->GetChildCount()); // add nodes again parentTreeItem->AddChild(*it); // emit endInsertRows event endInsertRows(); } this->AdjustLayerProperty(); } void QmitkDataStorageTreeModel::RemoveNode(const mitk::DataNode* node ) { if (node == 0 || m_BlockDataStorageEvents) return; this->RemoveNodeInternal(node); } void QmitkDataStorageTreeModel::SetNodeModified( const mitk::DataNode* node ) { TreeItem* treeItem = m_Root->Find(node); if(treeItem) { TreeItem* parentTreeItem = treeItem->GetParent(); // as the root node should not be removed one should always have a parent item if(!parentTreeItem) return; QModelIndex index = this->createIndex(treeItem->GetIndex(), 0, treeItem); // now emit the dataChanged signal emit dataChanged(index, index); } } mitk::DataNode* QmitkDataStorageTreeModel::GetParentNode( const mitk::DataNode* node ) const { mitk::DataNode* dataNode = 0; mitk::DataStorage::SetOfObjects::ConstPointer _Sources = m_DataStorage->GetSources(node); if(_Sources->Size() > 0) dataNode = _Sources->front(); return dataNode; } bool QmitkDataStorageTreeModel::setData( const QModelIndex &index, const QVariant &value, int role ) { mitk::DataNode* dataNode = this->TreeItemFromIndex(index)->GetDataNode(); if(!dataNode) return false; if(role == Qt::EditRole && !value.toString().isEmpty()) { dataNode->SetStringProperty("name", value.toString().toStdString().c_str()); mitk::PlanarFigure* planarFigure = dynamic_cast(dataNode->GetData()); if (planarFigure != NULL) mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else if(role == Qt::CheckStateRole) { // Please note: value.toInt() returns 2, independentely from the actual checkstate of the index element. // Therefore the checkstate is being estimated again here. QVariant qcheckstate = index.data(Qt::CheckStateRole); int checkstate = qcheckstate.toInt(); bool isVisible = bool(checkstate); dataNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } // inform listeners about changes emit dataChanged(index, index); return true; } bool QmitkDataStorageTreeModel::setHeaderData( int /*section*/, Qt::Orientation /*orientation*/, const QVariant& /* value */, int /*role = Qt::EditRole*/ ) { return false; } void QmitkDataStorageTreeModel::AdjustLayerProperty() { /// transform the tree into an array and set the layer property descending std::vector vec; this->TreeToVector(m_Root, vec); int i = vec.size()-1; for(std::vector::const_iterator it = vec.begin(); it != vec.end(); ++it) { mitk::DataNode::Pointer dataNode = (*it)->GetDataNode(); bool fixedLayer = false; if (!(dataNode->GetBoolProperty("fixedLayer", fixedLayer) && fixedLayer)) dataNode->SetIntProperty("layer", i); --i; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkDataStorageTreeModel::TreeToVector(TreeItem* parent, std::vector& vec) const { TreeItem* current; for(int i = 0; iGetChildCount(); ++i) { current = parent->GetChild(i); this->TreeToVector(current, vec); vec.push_back(current); } } QModelIndex QmitkDataStorageTreeModel::IndexFromTreeItem( TreeItem* item ) const { if(item == m_Root) return QModelIndex(); else return this->createIndex(item->GetIndex(), 0, item); } QList QmitkDataStorageTreeModel::GetNodeSet() const { QList res; if(m_Root) this->TreeToNodeSet(m_Root, res); return res; } void QmitkDataStorageTreeModel::TreeToNodeSet( TreeItem* parent, QList& vec ) const { TreeItem* current; for(int i = 0; iGetChildCount(); ++i) { current = parent->GetChild(i); vec.push_back(current->GetDataNode()); this->TreeToNodeSet(current, vec); } } QModelIndex QmitkDataStorageTreeModel::GetIndex( const mitk::DataNode* node ) const { if(m_Root) { TreeItem* item = m_Root->Find(node); if(item) return this->IndexFromTreeItem(item); } return QModelIndex(); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QMimeData* mimeData) { if (mimeData == NULL || !mimeData->hasFormat(QmitkMimeTypes::DataStorageTreeItemPtrs)) { return QList(); } return ToTreeItemPtrList(mimeData->data(QmitkMimeTypes::DataStorageTreeItemPtrs)); } QList QmitkDataStorageTreeModel::ToTreeItemPtrList(const QByteArray& ba) { QList result; QDataStream ds(ba); while(!ds.atEnd()) { quintptr treeItemPtr; ds >> treeItemPtr; result.push_back(reinterpret_cast(treeItemPtr)); } return result; } QmitkDataStorageTreeModel::TreeItem::TreeItem( mitk::DataNode* _DataNode, TreeItem* _Parent ) : m_Parent(_Parent) , m_DataNode(_DataNode) { if(m_Parent) m_Parent->AddChild(this); } QmitkDataStorageTreeModel::TreeItem::~TreeItem() { if(m_Parent) m_Parent->RemoveChild(this); } void QmitkDataStorageTreeModel::TreeItem::Delete() { while(m_Children.size() > 0) delete m_Children.back(); delete this; } QmitkDataStorageTreeModel::TreeItem* QmitkDataStorageTreeModel::TreeItem::Find( const mitk::DataNode* _DataNode ) const { QmitkDataStorageTreeModel::TreeItem* item = 0; if(_DataNode) { if(m_DataNode == _DataNode) item = const_cast(this); else { for(std::vector::const_iterator it = m_Children.begin(); it != m_Children.end(); ++it) { if(item) break; item = (*it)->Find(_DataNode); } } } return item; } int QmitkDataStorageTreeModel::TreeItem::IndexOfChild( const TreeItem* item ) const { std::vector::const_iterator it = std::find(m_Children.begin(), m_Children.end(), item); return it != m_Children.end() ? std::distance(m_Children.begin(), it): -1; } QmitkDataStorageTreeModel::TreeItem* QmitkDataStorageTreeModel::TreeItem::GetChild( int index ) const { return (m_Children.size() > 0 && index >= 0 && index < (int)m_Children.size())? m_Children.at(index): 0; } void QmitkDataStorageTreeModel::TreeItem::AddChild( TreeItem* item ) { this->InsertChild(item); } void QmitkDataStorageTreeModel::TreeItem::RemoveChild( TreeItem* item ) { std::vector::iterator it = std::find(m_Children.begin(), m_Children.end(), item); if(it != m_Children.end()) { m_Children.erase(it); item->SetParent(0); } } int QmitkDataStorageTreeModel::TreeItem::GetChildCount() const { return m_Children.size(); } int QmitkDataStorageTreeModel::TreeItem::GetIndex() const { if (m_Parent) return m_Parent->IndexOfChild(this); return 0; } QmitkDataStorageTreeModel::TreeItem* QmitkDataStorageTreeModel::TreeItem::GetParent() const { return m_Parent; } mitk::DataNode::Pointer QmitkDataStorageTreeModel::TreeItem::GetDataNode() const { return m_DataNode; } void QmitkDataStorageTreeModel::TreeItem::InsertChild( TreeItem* item, int index ) { std::vector::iterator it = std::find(m_Children.begin(), m_Children.end(), item); if(it == m_Children.end()) { if(m_Children.size() > 0 && index >= 0 && index < (int)m_Children.size()) { it = m_Children.begin(); std::advance(it, index); m_Children.insert(it, item); } else m_Children.push_back(item); // add parent if necessary if(item->GetParent() != this) item->SetParent(this); } } std::vector QmitkDataStorageTreeModel::TreeItem::GetChildren() const { return m_Children; } void QmitkDataStorageTreeModel::TreeItem::SetParent( TreeItem* _Parent ) { m_Parent = _Parent; if(m_Parent) m_Parent->AddChild(this); } void QmitkDataStorageTreeModel::Update() { if (m_DataStorage.IsNotNull()) { this->beginResetModel(); this->endResetModel(); mitk::DataStorage::SetOfObjects::ConstPointer _NodeSet = m_DataStorage->GetAll(); for (mitk::DataStorage::SetOfObjects::const_iterator it = _NodeSet->begin(); it != _NodeSet->end(); it++) { // save node this->AddNodeInternal(*it); } } } void QmitkDataStorageTreeModel::SetAllowHierarchyChange(bool allowHierarchyChange) { m_AllowHierarchyChange = allowHierarchyChange; } diff --git a/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp b/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp index a2aedeec1d..df976fdc9a 100644 --- a/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp +++ b/Plugins/org.mitk.gui.qt.dicom/src/internal/DicomEventHandler.cpp @@ -1,375 +1,380 @@ /*=================================================================== 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 "mitkPluginActivator.h" #include "DicomEventHandler.h" #include #include #include #include #include #include #include #include #include #include "mitkImage.h" #include #include +#include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DicomEventHandler::DicomEventHandler() { } DicomEventHandler::~DicomEventHandler() { } void DicomEventHandler::OnSignalAddSeriesToDataManager(const ctkEvent& ctkEvent) { QStringList listOfFilesForSeries; listOfFilesForSeries = ctkEvent.getProperty("FilesForSeries").toStringList(); if (!listOfFilesForSeries.isEmpty()) { //for rt data, if the modality tag isnt defined or is "CT" the image is handled like before if(ctkEvent.containsProperty("Modality") && (ctkEvent.getProperty("Modality").toString().compare("RTDOSE",Qt::CaseInsensitive) == 0 || ctkEvent.getProperty("Modality").toString().compare("RTSTRUCT",Qt::CaseInsensitive) == 0)) { QString modality = ctkEvent.getProperty("Modality").toString(); if(modality.compare("RTDOSE",Qt::CaseInsensitive) == 0) { mitk::RTDoseReader::Pointer doseReader = mitk::RTDoseReader::New(); mitk::DataNode::Pointer doseImageNode = mitk::DataNode::New(); mitk::DataNode::Pointer doseOutlineNode = mitk::DataNode::New(); doseImageNode = doseReader->LoadRTDose(listOfFilesForSeries.at(0).toStdString().c_str()); doseOutlineNode->SetData(doseImageNode->GetData()); if (doseImageNode.IsNotNull() && doseOutlineNode->GetData() != nullptr) { berry::IPreferencesService* prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::RTUIConstants::ROOT_ISO_PRESETS_PREFERENCE_NODE_ID.c_str()); typedef QStringList NamesType; NamesType names = prefNode->ChildrenNames(); std::map presetMap; for (NamesType::const_iterator pos = names.begin(); pos != names.end(); ++pos) { berry::IPreferences::Pointer aPresetNode = prefNode->Node(*pos); if (aPresetNode.IsNull()) { mitkThrow() << "Error in preference interface. Cannot find preset node under given name. Name: " << (*pos).toStdString(); } mitk::IsoDoseLevelSet::Pointer levelSet = mitk::IsoDoseLevelSet::New(); NamesType levelNames = aPresetNode->ChildrenNames(); for (NamesType::const_iterator levelName = levelNames.begin(); levelName != levelNames.end(); ++levelName) { berry::IPreferences::Pointer levelNode = aPresetNode->Node(*levelName); if (aPresetNode.IsNull()) { mitkThrow() << "Error in preference interface. Cannot find level node under given preset name. Name: " << (*pos).toStdString() << "; Level id: " << (*levelName).toStdString(); } mitk::IsoDoseLevel::Pointer isoLevel = mitk::IsoDoseLevel::New(); isoLevel->SetDoseValue(levelNode->GetDouble(mitk::RTUIConstants::ISO_LEVEL_DOSE_VALUE_ID.c_str(), 0.0)); mitk::IsoDoseLevel::ColorType color; color.SetRed(levelNode->GetFloat(mitk::RTUIConstants::ISO_LEVEL_COLOR_RED_ID.c_str(), 1.0)); color.SetGreen(levelNode->GetFloat(mitk::RTUIConstants::ISO_LEVEL_COLOR_GREEN_ID.c_str(), 1.0)); color.SetBlue(levelNode->GetFloat(mitk::RTUIConstants::ISO_LEVEL_COLOR_BLUE_ID.c_str(), 1.0)); isoLevel->SetColor(color); isoLevel->SetVisibleIsoLine(levelNode->GetBool(mitk::RTUIConstants::ISO_LEVEL_VISIBILITY_ISOLINES_ID.c_str(), true)); isoLevel->SetVisibleColorWash(levelNode->GetBool(mitk::RTUIConstants::ISO_LEVEL_VISIBILITY_COLORWASH_ID.c_str(), true)); levelSet->SetIsoDoseLevel(isoLevel); } presetMap.insert(std::make_pair((*pos).toStdString(), levelSet)); } if (presetMap.size() == 0) { presetMap.insert(std::make_pair(std::string("Virtuos"), mitk::GeneratIsoLevels_Virtuos())); } prefNode = prefService->GetSystemPreferences()->Node(mitk::RTUIConstants::ROOT_DOSE_VIS_PREFERENCE_NODE_ID.c_str()); if (prefNode.IsNull()) { mitkThrow() << "Error in preference interface. Cannot find preset node under given name. Name: " << prefNode->ToString().toStdString(); } //set some specific colorwash and isoline properties bool showColorWashGlobal = prefNode->GetBool(mitk::RTUIConstants::GLOBAL_VISIBILITY_COLORWASH_ID.c_str(), true); doseImageNode->SetBoolProperty(mitk::RTConstants::DOSE_SHOW_COLORWASH_PROPERTY_NAME.c_str(), showColorWashGlobal); bool showIsolinesGlobal = prefNode->GetBool(mitk::RTUIConstants::GLOBAL_VISIBILITY_ISOLINES_ID.c_str(), true); doseOutlineNode->SetBoolProperty(mitk::RTConstants::DOSE_SHOW_ISOLINES_PROPERTY_NAME.c_str(), showIsolinesGlobal); //Set reference dose property double referenceDose = prefNode->GetDouble(mitk::RTUIConstants::REFERENCE_DOSE_ID.c_str(), mitk::RTUIConstants::DEFAULT_REFERENCE_DOSE_VALUE); doseImageNode->SetFloatProperty(mitk::RTConstants::REFERENCE_DOSE_PROPERTY_NAME.c_str(), referenceDose); doseOutlineNode->SetFloatProperty(mitk::RTConstants::REFERENCE_DOSE_PROPERTY_NAME.c_str(), referenceDose); QString presetName = prefNode->Get(mitk::RTUIConstants::SELECTED_ISO_PRESET_ID.c_str(), "Virtuos"); mitk::IsoDoseLevelSet::Pointer isoDoseLevelPreset = presetMap[presetName.toStdString()]; mitk::IsoDoseLevelSetProperty::Pointer levelSetProp = mitk::IsoDoseLevelSetProperty::New(isoDoseLevelPreset); doseImageNode->SetProperty(mitk::RTConstants::DOSE_ISO_LEVELS_PROPERTY_NAME.c_str(), levelSetProp); doseOutlineNode->SetProperty(mitk::RTConstants::DOSE_ISO_LEVELS_PROPERTY_NAME.c_str(), levelSetProp); mitk::IsoDoseLevelVector::Pointer levelVector = mitk::IsoDoseLevelVector::New(); mitk::IsoDoseLevelVectorProperty::Pointer levelVecProp = mitk::IsoDoseLevelVectorProperty::New(levelVector); doseImageNode->SetProperty(mitk::RTConstants::DOSE_FREE_ISO_VALUES_PROPERTY_NAME.c_str(), levelVecProp); doseOutlineNode->SetProperty(mitk::RTConstants::DOSE_FREE_ISO_VALUES_PROPERTY_NAME.c_str(), levelVecProp); mitk::RenderingModeProperty::Pointer renderingModeProp = mitk::RenderingModeProperty::New(); if (showColorWashGlobal) { //Generating the Colorwash vtkSmartPointer transferFunction = vtkSmartPointer::New(); for (mitk::IsoDoseLevelSet::ConstIterator itIsoDoseLevel = isoDoseLevelPreset->Begin(); itIsoDoseLevel != isoDoseLevelPreset->End(); ++itIsoDoseLevel) { float *hsv = new float[3]; //used for transfer rgb to hsv vtkSmartPointer cCalc = vtkSmartPointer::New(); if (itIsoDoseLevel->GetVisibleColorWash()){ cCalc->RGBToHSV(itIsoDoseLevel->GetColor()[0], itIsoDoseLevel->GetColor()[1], itIsoDoseLevel->GetColor()[2], &hsv[0], &hsv[1], &hsv[2]); transferFunction->AddHSVPoint(itIsoDoseLevel->GetDoseValue()*referenceDose, hsv[0], hsv[1], hsv[2], 1.0, 1.0); } } mitk::TransferFunction::Pointer mitkTransFunc = mitk::TransferFunction::New(); mitk::TransferFunctionProperty::Pointer mitkTransFuncProp = mitk::TransferFunctionProperty::New(); mitkTransFunc->SetColorTransferFunction(transferFunction); mitkTransFuncProp->SetValue(mitkTransFunc); doseImageNode->SetProperty("Image Rendering.Transfer Function", mitkTransFuncProp); renderingModeProp->SetValue(mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_COLOR); } else { //Set rendering mode to levelwindow color mode renderingModeProp->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); } doseImageNode->SetProperty("Image Rendering.Mode", renderingModeProp); doseImageNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); //set the outline properties doseOutlineNode->SetBoolProperty("outline binary", true); doseOutlineNode->SetProperty("helper object", mitk::BoolProperty::New(true)); doseOutlineNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); dataStorage->Add(doseImageNode); dataStorage->Add(doseOutlineNode, doseImageNode); //set the dose mapper for outline drawing; the colorwash is realized by the imagevtkmapper2D mitk::DoseImageVtkMapper2D::Pointer contourMapper = mitk::DoseImageVtkMapper2D::New(); doseOutlineNode->SetMapper(1, contourMapper); mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); }//END DOSE } else if(modality.compare("RTSTRUCT",Qt::CaseInsensitive) == 0) { mitk::RTStructureSetReader::Pointer structreader = mitk::RTStructureSetReader::New(); std::deque modelVector = structreader->ReadStructureSet(listOfFilesForSeries.at(0).toStdString().c_str()); if(modelVector.empty()) { MITK_ERROR << "No structuresets were created" << endl; } else { ctkServiceReference serviceReference =mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); for(int i=0; iAdd(modelVector.at(i)); } mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); } } } else { mitk::StringList seriesToLoad; QStringListIterator it(listOfFilesForSeries); while (it.hasNext()) { seriesToLoad.push_back(it.next().toStdString()); } //Get Reference for default data storage. ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); //special handling of Philips 3D US DICOM. //Copied from DICOMSeriesReaderService if (!seriesToLoad.empty() && mitk::DicomSeriesReader::IsPhilips3DDicom(seriesToLoad.front())) { MITK_INFO << "it is a Philips3D US Dicom file" << std::endl; mitk::LocaleSwitch localeSwitch("C"); std::locale previousCppLocale(std::cin.getloc()); std::locale l("C"); std::cin.imbue(l); mitk::DataNode::Pointer node = mitk::DataNode::New(); mitk::DicomSeriesReader::StringContainer stringvec; stringvec.push_back(seriesToLoad.front()); if (mitk::DicomSeriesReader::LoadDicomSeries(stringvec, *node)) { mitk::BaseData::Pointer data = node->GetData(); mitk::StringProperty::Pointer nameProp = mitk::StringProperty::New(itksys::SystemTools::GetFilenameName(seriesToLoad.front())); data->GetPropertyList()->SetProperty("name", nameProp); node->SetProperty("name", nameProp); dataStorage->Add(node); } std::cin.imbue(previousCppLocale); return; } //Normal DICOM handling (It wasn't a Philips 3D US) mitk::DICOMFileReaderSelector::Pointer selector = mitk::DICOMFileReaderSelector::New(); selector->LoadBuiltIn3DConfigs(); selector->LoadBuiltIn3DnTConfigs(); selector->SetInputFiles(seriesToLoad); mitk::DICOMFileReader::Pointer reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); - //reset tag cache to ensure that additional tags of interest - //will be regarded by the reader if set later on. - reader->SetTagCache(nullptr); reader->SetAdditionalTagsOfInterest(mitk::GetCurrentDICOMTagsOfInterest()); reader->SetTagLookupTableToPropertyFunctor(mitk::GetDICOMPropertyForDICOMValuesFunctor); reader->SetInputFiles(seriesToLoad); + + mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); + scanner->AddTagPaths(reader->GetTagsOfInterest()); + scanner->SetInputFiles(seriesToLoad); + scanner->Scan(); + + reader->SetTagCache(scanner->GetScanCache()); reader->AnalyzeInputFiles(); reader->LoadImages(); for (unsigned int i = 0; i < reader->GetNumberOfOutputs(); ++i) { const mitk::DICOMImageBlockDescriptor& desc = reader->GetOutput(i); mitk::BaseData::Pointer data = desc.GetMitkImage().GetPointer(); std::string nodeName = "Unnamed_DICOM"; std::string studyDescription = desc.GetPropertyAsString("studyDescription"); std::string seriesDescription = desc.GetPropertyAsString("seriesDescription"); if (!studyDescription.empty()) { nodeName = studyDescription; } if (!seriesDescription.empty()) { if (!studyDescription.empty()) { nodeName += "/"; } nodeName += seriesDescription; } mitk::StringProperty::Pointer nameProp = mitk::StringProperty::New(nodeName); data->SetProperty("name", nameProp); mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(data); nameProp = mitk::StringProperty::New(nodeName); node->SetProperty("name", nameProp); dataStorage->Add(node); } if (reader->GetNumberOfOutputs() < 1) { MITK_ERROR << "Error loading series: " << ctkEvent.getProperty("SeriesName").toString().toStdString() << " id: " << ctkEvent.getProperty("SeriesUID").toString().toStdString(); } } } else { MITK_INFO << "There are no files for the current series"; } } void DicomEventHandler::OnSignalRemoveSeriesFromStorage(const ctkEvent& /*ctkEvent*/) { } void DicomEventHandler::SubscribeSlots() { ctkServiceReference ref = mitk::PluginActivator::getContext()->getServiceReference(); if (ref) { ctkEventAdmin* eventAdmin = mitk::PluginActivator::getContext()->getService(ref); ctkDictionary properties; properties[ctkEventConstants::EVENT_TOPIC] = "org/mitk/gui/qt/dicom/ADD"; eventAdmin->subscribeSlot(this, SLOT(OnSignalAddSeriesToDataManager(ctkEvent)), properties); properties[ctkEventConstants::EVENT_TOPIC] = "org/mitk/gui/qt/dicom/DELETED"; eventAdmin->subscribeSlot(this, SLOT(OnSignalRemoveSeriesFromStorage(ctkEvent)), properties); } }