diff --git a/Core/Code/DataManagement/mitkDataNodeFactory.cpp b/Core/Code/DataManagement/mitkDataNodeFactory.cpp index 79eb751faf..d1cc2c92c8 100644 --- a/Core/Code/DataManagement/mitkDataNodeFactory.cpp +++ b/Core/Code/DataManagement/mitkDataNodeFactory.cpp @@ -1,492 +1,492 @@ /*=================================================================== 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 // 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 #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 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 ); } } } // 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(i, node); } } if(!usedNewDTNF && ( m_FileName != "" ) && !(m_Serie == false)) ReadFileSeriesTypeITKImageSeriesReader(); } } void mitk::DataNodeFactory::ResizeOutputs( const unsigned int& num ) { unsigned int prevNum = this->GetNumberOfOutputs(); this->SetNumberOfOutputs( 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() { const char* previousCLocale = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "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(0); mitk::DicomSeriesReader::StringContainer stringvec; stringvec.push_back(this->GetFileName()); if (DicomSeriesReader::LoadDicomSeries(stringvec, *node)) { node->SetName(this->GetBaseFileName()); } setlocale(LC_NUMERIC, previousCLocale); 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)) + 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 ) ) { nodeName = studyDescription; std::string seriesDescription; if ( node->GetStringProperty( "dicom.series.SeriesDescription", seriesDescription ) ) { nodeName += "/" + seriesDescription; } } node->SetName(nodeName); ++outputIndex; } else { MITK_ERROR << "DataNodeFactory: Skipping series " << outputIndex << " due to some unspecified error..." << std::endl; } ProgressBar::GetInstance()->Progress(); } setlocale(LC_NUMERIC, previousCLocale); std::cin.imbue(previousCppLocale); } void mitk::DataNodeFactory::ReadFileSeriesTypeITKImageSeriesReader() { typedef itk::Image ImageType; typedef itk::ImageSeriesReader< ImageType > ReaderType; typedef itk::NumericSeriesFileNames NameGenerator; 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/Core/Code/IO/mitkDicomSeriesReader.cpp b/Core/Code/IO/mitkDicomSeriesReader.cpp index cb588841c9..07359e3469 100644 --- a/Core/Code/IO/mitkDicomSeriesReader.cpp +++ b/Core/Code/IO/mitkDicomSeriesReader.cpp @@ -1,1900 +1,1900 @@ /*=================================================================== 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. ===================================================================*/ // uncomment for learning more about the internal sorting mechanisms //#define MBILOG_ENABLE_DEBUG #include #include #include #include #include #include #include #include #include #include #include "mitkProperties.h" namespace mitk { typedef itk::GDCMSeriesFileNames DcmFileNamesGeneratorType; DicomSeriesReader::ImageBlockDescriptor::ImageBlockDescriptor() :m_HasGantryTiltCorrected(false) ,m_HasMultipleTimePoints(false) ,m_IsMultiFrameImage(false) { } - + DicomSeriesReader::ImageBlockDescriptor::~ImageBlockDescriptor() { // nothing } DicomSeriesReader::ImageBlockDescriptor::ImageBlockDescriptor(const StringContainer& files) :m_HasGantryTiltCorrected(false) ,m_HasMultipleTimePoints(false) ,m_IsMultiFrameImage(false) { m_Filenames = files; } void DicomSeriesReader::ImageBlockDescriptor::AddFile(const std::string& filename) { m_Filenames.push_back( filename ); } void DicomSeriesReader::ImageBlockDescriptor::AddFiles(const StringContainer& files) { m_Filenames.insert( m_Filenames.end(), files.begin(), files.end() ); } DicomSeriesReader::StringContainer DicomSeriesReader::ImageBlockDescriptor::GetFilenames() const { return m_Filenames; } std::string DicomSeriesReader::ImageBlockDescriptor::GetImageBlockUID() const { return m_ImageBlockUID; } std::string DicomSeriesReader::ImageBlockDescriptor::GetSeriesInstanceUID() const { return m_SeriesInstanceUID; } std::string DicomSeriesReader::ImageBlockDescriptor::GetModality() const { return m_Modality; } std::string DicomSeriesReader::ImageBlockDescriptor::GetSOPClassUIDAsString() const { gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( m_SOPClassUID.c_str() ); return uidKnowledge.GetName(); } std::string DicomSeriesReader::ImageBlockDescriptor::GetSOPClassUID() const { return m_SOPClassUID; } bool DicomSeriesReader::ImageBlockDescriptor::IsMultiFrameImage() const { return m_IsMultiFrameImage; } DicomSeriesReader::ReaderImplementationLevel DicomSeriesReader::ImageBlockDescriptor::GetReaderImplementationLevel() const { if ( this->IsMultiFrameImage() ) return ReaderImplementationLevel_Unsupported; gdcm::UIDs uidKnowledge; uidKnowledge.SetFromUID( m_SOPClassUID.c_str() ); gdcm::UIDs::TSType uid = uidKnowledge; switch (uid) { 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 ReaderImplementationLevel_Supported; case gdcm::UIDs::NuclearMedicineImageStorage: return ReaderImplementationLevel_PartlySupported; case gdcm::UIDs::SecondaryCaptureImageStorage: return ReaderImplementationLevel_Implemented; default: return ReaderImplementationLevel_Unsupported; } } std::string DicomSeriesReader::ReaderImplementationLevelToString( const ReaderImplementationLevel& enumValue ) { switch (enumValue) { case ReaderImplementationLevel_Supported: return "Supported"; case ReaderImplementationLevel_PartlySupported: return "PartlySupported"; case ReaderImplementationLevel_Implemented: return "Implemented"; case ReaderImplementationLevel_Unsupported: return "Unsupported"; default: return ""; }; } std::string DicomSeriesReader::PixelSpacingInterpretationToString( const PixelSpacingInterpretation& enumValue ) { switch (enumValue) { case PixelSpacingInterpretation_SpacingInPatient: return "In Patient"; case PixelSpacingInterpretation_SpacingAtDetector: return "At Detector"; case PixelSpacingInterpretation_SpacingUnknown: return "Unknown spacing"; default: return ""; }; } bool DicomSeriesReader::ImageBlockDescriptor::HasGantryTiltCorrected() const { return m_HasGantryTiltCorrected; } /* 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 */ DicomSeriesReader::PixelSpacingInterpretation DicomSeriesReader::ImageBlockDescriptor::GetPixelSpacingType() const { if (m_PixelSpacing.empty()) { if (m_ImagerPixelSpacing.empty()) { return PixelSpacingInterpretation_SpacingUnknown; } else { return PixelSpacingInterpretation_SpacingAtDetector; } } else // Pixel Spacing defined { if (m_ImagerPixelSpacing.empty()) { return PixelSpacingInterpretation_SpacingInPatient; } else if (m_PixelSpacing != m_ImagerPixelSpacing) { return PixelSpacingInterpretation_SpacingInPatient; } else { return PixelSpacingInterpretation_SpacingAtDetector; } } } bool DicomSeriesReader::ImageBlockDescriptor::PixelSpacingRelatesToPatient() const { return GetPixelSpacingType() == PixelSpacingInterpretation_SpacingInPatient; } bool DicomSeriesReader::ImageBlockDescriptor::PixelSpacingRelatesToDetector() const { return GetPixelSpacingType() == PixelSpacingInterpretation_SpacingAtDetector; } bool DicomSeriesReader::ImageBlockDescriptor::PixelSpacingIsUnknown() const { return GetPixelSpacingType() == PixelSpacingInterpretation_SpacingUnknown; } void DicomSeriesReader::ImageBlockDescriptor::SetPixelSpacingInformation(const std::string& pixelSpacing, const std::string& imagerPixelSpacing) { m_PixelSpacing = pixelSpacing; m_ImagerPixelSpacing = imagerPixelSpacing; } void DicomSeriesReader::ImageBlockDescriptor::GetDesiredMITKImagePixelSpacing( float& spacingX, float& spacingY) const { // preference for "in patient" pixel spacing if ( !DICOMStringToSpacing( m_PixelSpacing, spacingX, spacingY ) ) { // fallback to "on detector" spacing if ( !DICOMStringToSpacing( m_ImagerPixelSpacing, spacingX, spacingY ) ) { // last resort: invent something spacingX = spacingY = 1.0; } } } bool DicomSeriesReader::ImageBlockDescriptor::HasMultipleTimePoints() const { return m_HasMultipleTimePoints; } void DicomSeriesReader::ImageBlockDescriptor::SetImageBlockUID(const std::string& uid) { m_ImageBlockUID = uid; } void DicomSeriesReader::ImageBlockDescriptor::SetSeriesInstanceUID(const std::string& uid) { m_SeriesInstanceUID = uid; } void DicomSeriesReader::ImageBlockDescriptor::SetModality(const std::string& modality) { m_Modality = modality; } void DicomSeriesReader::ImageBlockDescriptor::SetNumberOfFrames(const std::string& numberOfFrames) { m_IsMultiFrameImage = !numberOfFrames.empty(); } void DicomSeriesReader::ImageBlockDescriptor::SetSOPClassUID(const std::string& sopClassUID) { m_SOPClassUID = sopClassUID; } void DicomSeriesReader::ImageBlockDescriptor::SetHasGantryTiltCorrected(bool on) { m_HasGantryTiltCorrected = on; } void DicomSeriesReader::ImageBlockDescriptor::SetHasMultipleTimePoints(bool on) { m_HasMultipleTimePoints = on; } DicomSeriesReader::SliceGroupingAnalysisResult::SliceGroupingAnalysisResult() :m_GantryTilt(false) { } DicomSeriesReader::StringContainer DicomSeriesReader::SliceGroupingAnalysisResult::GetBlockFilenames() { return m_GroupedFiles; } DicomSeriesReader::StringContainer DicomSeriesReader::SliceGroupingAnalysisResult::GetUnsortedFilenames() { return m_UnsortedFiles; } bool DicomSeriesReader::SliceGroupingAnalysisResult::ContainsGantryTilt() { return m_GantryTilt; } void DicomSeriesReader::SliceGroupingAnalysisResult::AddFileToSortedBlock(const std::string& filename) { m_GroupedFiles.push_back( filename ); } void DicomSeriesReader::SliceGroupingAnalysisResult::AddFileToUnsortedBlock(const std::string& filename) { m_UnsortedFiles.push_back( filename ); } void DicomSeriesReader::SliceGroupingAnalysisResult::AddFilesToUnsortedBlock(const StringContainer& filenames) { m_UnsortedFiles.insert( m_UnsortedFiles.end(), filenames.begin(), filenames.end() ); } void DicomSeriesReader::SliceGroupingAnalysisResult::FlagGantryTilt() { m_GantryTilt = true; } void DicomSeriesReader::SliceGroupingAnalysisResult::UndoPrematureGrouping() { assert( !m_GroupedFiles.empty() ); m_UnsortedFiles.insert( m_UnsortedFiles.begin(), m_GroupedFiles.back() ); m_GroupedFiles.pop_back(); } const DicomSeriesReader::TagToPropertyMapType& DicomSeriesReader::GetDICOMTagsToMITKPropertyMap() { static bool initialized = false; static TagToPropertyMapType dictionary; if (!initialized) { /* Selection criteria: - no sequences because we cannot represent that - nothing animal related (specied, breed registration number), MITK focusses on human medical image processing. - only general attributes so far When extending this, we should make use of a real dictionary (GDCM/DCMTK and lookup the tag names there) */ // Patient module dictionary["0010|0010"] = "dicom.patient.PatientsName"; dictionary["0010|0020"] = "dicom.patient.PatientID"; dictionary["0010|0030"] = "dicom.patient.PatientsBirthDate"; dictionary["0010|0040"] = "dicom.patient.PatientsSex"; dictionary["0010|0032"] = "dicom.patient.PatientsBirthTime"; dictionary["0010|1000"] = "dicom.patient.OtherPatientIDs"; dictionary["0010|1001"] = "dicom.patient.OtherPatientNames"; dictionary["0010|2160"] = "dicom.patient.EthnicGroup"; dictionary["0010|4000"] = "dicom.patient.PatientComments"; dictionary["0012|0062"] = "dicom.patient.PatientIdentityRemoved"; dictionary["0012|0063"] = "dicom.patient.DeIdentificationMethod"; // General Study module dictionary["0020|000d"] = "dicom.study.StudyInstanceUID"; dictionary["0008|0020"] = "dicom.study.StudyDate"; dictionary["0008|0030"] = "dicom.study.StudyTime"; dictionary["0008|0090"] = "dicom.study.ReferringPhysiciansName"; dictionary["0020|0010"] = "dicom.study.StudyID"; dictionary["0008|0050"] = "dicom.study.AccessionNumber"; dictionary["0008|1030"] = "dicom.study.StudyDescription"; dictionary["0008|1048"] = "dicom.study.PhysiciansOfRecord"; dictionary["0008|1060"] = "dicom.study.NameOfPhysicianReadingStudy"; // General Series module dictionary["0008|0060"] = "dicom.series.Modality"; dictionary["0020|000e"] = "dicom.series.SeriesInstanceUID"; dictionary["0020|0011"] = "dicom.series.SeriesNumber"; dictionary["0020|0060"] = "dicom.series.Laterality"; dictionary["0008|0021"] = "dicom.series.SeriesDate"; dictionary["0008|0031"] = "dicom.series.SeriesTime"; dictionary["0008|1050"] = "dicom.series.PerformingPhysiciansName"; dictionary["0018|1030"] = "dicom.series.ProtocolName"; dictionary["0008|103e"] = "dicom.series.SeriesDescription"; dictionary["0008|1070"] = "dicom.series.OperatorsName"; dictionary["0018|0015"] = "dicom.series.BodyPartExamined"; dictionary["0018|5100"] = "dicom.series.PatientPosition"; dictionary["0028|0108"] = "dicom.series.SmallestPixelValueInSeries"; dictionary["0028|0109"] = "dicom.series.LargestPixelValueInSeries"; // VOI LUT module dictionary["0028|1050"] = "dicom.voilut.WindowCenter"; dictionary["0028|1051"] = "dicom.voilut.WindowWidth"; dictionary["0028|1055"] = "dicom.voilut.WindowCenterAndWidthExplanation"; // Image Pixel module dictionary["0028|0004"] = "dicom.pixel.PhotometricInterpretation"; // Image Plane module dictionary["0028|0030"] = "dicom.PixelSpacing"; dictionary["0018|1164"] = "dicom.ImagerPixelSpacing"; initialized = true; } return dictionary; } DataNode::Pointer DicomSeriesReader::LoadDicomSeries(const StringContainer &filenames, bool sort, bool check_4d, bool correctTilt, UpdateCallBackMethod callback) { DataNode::Pointer node = DataNode::New(); if (DicomSeriesReader::LoadDicomSeries(filenames, *node, sort, check_4d, correctTilt, callback)) { if( filenames.empty() ) { return NULL; } return node; } else { return NULL; } } bool DicomSeriesReader::LoadDicomSeries(const StringContainer &filenames, DataNode &node, bool sort, bool check_4d, bool correctTilt, UpdateCallBackMethod callback) { if( filenames.empty() ) { MITK_WARN << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; node.SetData(NULL); return true; // this is not actually an error but the result is very simple } DcmIoType::Pointer io = DcmIoType::New(); try { if (io->CanReadFile(filenames.front().c_str())) { io->SetFileName(filenames.front().c_str()); io->ReadImageInformation(); switch (io->GetComponentType()) { case DcmIoType::UCHAR: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::CHAR: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::USHORT: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::SHORT: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::UINT: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::INT: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::ULONG: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::LONG: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::FLOAT: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; case DcmIoType::DOUBLE: DicomSeriesReader::LoadDicom(filenames, node, sort, check_4d, correctTilt, callback); break; default: MITK_ERROR << "Found unsupported DICOM pixel type: (enum value) " << io->GetComponentType(); } if (node.GetData()) { return true; } } } catch(itk::MemoryAllocationError& e) { MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); } catch(std::exception& e) { MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); } catch(...) { MITK_ERROR << "Unspecified error encountered when loading DICOM series."; } return false; } bool DicomSeriesReader::IsDicom(const std::string &filename) { DcmIoType::Pointer io = DcmIoType::New(); return io->CanReadFile(filename.c_str()); } bool DicomSeriesReader::IsPhilips3DDicom(const std::string &filename) { DcmIoType::Pointer io = DcmIoType::New(); if (io->CanReadFile(filename.c_str())) { //Look at header Tag 3001,0010 if it is "Philips3D" gdcm::Reader reader; reader.SetFileName(filename.c_str()); reader.Read(); gdcm::DataSet &data_set = reader.GetFile().GetDataSet(); gdcm::StringFilter sf; sf.SetFile(reader.GetFile()); if (data_set.FindDataElement(gdcm::Tag(0x3001, 0x0010)) && (sf.ToString(gdcm::Tag(0x3001, 0x0010)) == "Philips3D ")) { return true; } } return false; } bool DicomSeriesReader::ReadPhilips3DDicom(const std::string &filename, mitk::Image::Pointer output_image) { // Now get PhilipsSpecific Tags gdcm::PixmapReader reader; reader.SetFileName(filename.c_str()); reader.Read(); gdcm::DataSet &data_set = reader.GetFile().GetDataSet(); gdcm::StringFilter sf; sf.SetFile(reader.GetFile()); gdcm::Attribute<0x0028,0x0011> dimTagX; // coloumns || sagittal gdcm::Attribute<0x3001,0x1001, gdcm::VR::UL, gdcm::VM::VM1> dimTagZ; //I have no idea what is VM1. // (Philips specific) // axial gdcm::Attribute<0x0028,0x0010> dimTagY; // rows || coronal gdcm::Attribute<0x0028,0x0008> dimTagT; // how many frames gdcm::Attribute<0x0018,0x602c> spaceTagX; // Spacing in X , unit is "physicalTagx" (usually centimeter) gdcm::Attribute<0x0018,0x602e> spaceTagY; gdcm::Attribute<0x3001,0x1003, gdcm::VR::FD, gdcm::VM::VM1> spaceTagZ; // (Philips specific) gdcm::Attribute<0x0018,0x6024> physicalTagX; // if 3, then spacing params are centimeter gdcm::Attribute<0x0018,0x6026> physicalTagY; gdcm::Attribute<0x3001,0x1002, gdcm::VR::US, gdcm::VM::VM1> physicalTagZ; // (Philips specific) dimTagX.Set(data_set); dimTagY.Set(data_set); dimTagZ.Set(data_set); dimTagT.Set(data_set); spaceTagX.Set(data_set); spaceTagY.Set(data_set); spaceTagZ.Set(data_set); physicalTagX.Set(data_set); physicalTagY.Set(data_set); physicalTagZ.Set(data_set); unsigned int dimX = dimTagX.GetValue(), dimY = dimTagY.GetValue(), dimZ = dimTagZ.GetValue(), dimT = dimTagT.GetValue(), physicalX = physicalTagX.GetValue(), physicalY = physicalTagY.GetValue(), physicalZ = physicalTagZ.GetValue(); float spaceX = spaceTagX.GetValue(), spaceY = spaceTagY.GetValue(), spaceZ = spaceTagZ.GetValue(); if (physicalX == 3) // spacing parameter in cm, have to convert it to mm. spaceX = spaceX * 10; if (physicalY == 3) // spacing parameter in cm, have to convert it to mm. spaceY = spaceY * 10; if (physicalZ == 3) // spacing parameter in cm, have to convert it to mm. spaceZ = spaceZ * 10; // Ok, got all necessary Tags! // Now read Pixeldata (7fe0,0010) X x Y x Z x T Elements const gdcm::Pixmap &pixels = reader.GetPixmap(); gdcm::RAWCodec codec; codec.SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2); codec.SetPixelFormat(pixels.GetPixelFormat()); codec.SetPlanarConfiguration(0); gdcm::DataElement out; codec.Decode(data_set.GetDataElement(gdcm::Tag(0x7fe0, 0x0010)), out); const gdcm::ByteValue *bv = out.GetByteValue(); const char *new_pixels = bv->GetPointer(); // Create MITK Image + Geometry typedef itk::Image ImageType; //Pixeltype might be different sometimes? Maybe read it out from header ImageType::RegionType myRegion; ImageType::SizeType mySize; ImageType::IndexType myIndex; ImageType::SpacingType mySpacing; ImageType::Pointer imageItk = ImageType::New(); mySpacing[0] = spaceX; mySpacing[1] = spaceY; mySpacing[2] = spaceZ; mySpacing[3] = 1; myIndex[0] = 0; myIndex[1] = 0; myIndex[2] = 0; myIndex[3] = 0; mySize[0] = dimX; mySize[1] = dimY; mySize[2] = dimZ; mySize[3] = dimT; myRegion.SetSize( mySize); myRegion.SetIndex( myIndex ); imageItk->SetSpacing(mySpacing); imageItk->SetRegions( myRegion); imageItk->Allocate(); imageItk->FillBuffer(0); itk::ImageRegionIterator iterator(imageItk, imageItk->GetLargestPossibleRegion()); iterator.GoToBegin(); unsigned long pixCount = 0; unsigned long planeSize = dimX*dimY; unsigned long planeCount = 0; unsigned long timeCount = 0; unsigned long numberOfSlices = dimZ; while (!iterator.IsAtEnd()) { unsigned long adressedPixel = pixCount + (numberOfSlices-1-planeCount)*planeSize // add offset to adress the first pixel of current plane + timeCount*numberOfSlices*planeSize; // add time offset iterator.Set( new_pixels[ adressedPixel ] ); pixCount++; ++iterator; if (pixCount == planeSize) { pixCount = 0; planeCount++; } if (planeCount == numberOfSlices) { planeCount = 0; timeCount++; } if (timeCount == dimT) { break; } } mitk::CastToMitkImage(imageItk, output_image); return true; // actually never returns false yet.. but exception possible } DicomSeriesReader::GantryTiltInformation::GantryTiltInformation() : m_ShiftUp(0.0) , m_ShiftRight(0.0) , m_ShiftNormal(0.0) , m_ITKAssumedSliceSpacing(0.0) , m_NumberOfSlicesApart(1) { } #define doublepoint(x) \ Point3Dd x; \ x[0] = x ## f[0]; \ x[1] = x ## f[1]; \ x[2] = x ## f[2]; #define doublevector(x) \ Vector3Dd x; \ x[0] = x ## f[0]; \ x[1] = x ## f[1]; \ x[2] = x ## f[2]; DicomSeriesReader::GantryTiltInformation::GantryTiltInformation( const Point3D& origin1f, const Point3D& origin2f, const Vector3D& rightf, const Vector3D& upf, unsigned int numberOfSlicesApart) : m_ShiftUp(0.0) , m_ShiftRight(0.0) , m_ShiftNormal(0.0) , m_NumberOfSlicesApart(numberOfSlicesApart) { assert(numberOfSlicesApart); doublepoint(origin1); doublepoint(origin2); doublevector(right); doublevector(up); // determine if slice 1 (imagePosition1 and imageOrientation1) and slice 2 can be in one orthogonal slice stack: // calculate a line from origin 1, directed along the normal of slice (calculated as the cross product of orientation 1) // check if this line passes through origin 2 /* Determine if line (imagePosition2 + l * normal) contains imagePosition1. Done by calculating the distance of imagePosition1 from line (imagePosition2 + l *normal) E.g. http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html squared distance = | (pointAlongNormal - origin2) x (origin2 - origin1) | ^ 2 / |pointAlongNormal - origin2| ^ 2 ( x meaning the cross product ) */ Vector3Dd normal = itk::CrossProduct(right, up); Point3Dd pointAlongNormal = origin2 + normal; double numerator = itk::CrossProduct( pointAlongNormal - origin2 , origin2 - origin1 ).GetSquaredNorm(); double denominator = (pointAlongNormal - origin2).GetSquaredNorm(); double distance = sqrt(numerator / denominator); if ( distance > 0.001 ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt { MITK_DEBUG << " Series seems to contain a tilted (or sheared) geometry"; MITK_DEBUG << " Distance of expected slice origin from actual slice origin: " << distance; MITK_DEBUG << " ==> storing this shift for later analysis:"; MITK_DEBUG << " v right: " << right; MITK_DEBUG << " v up: " << up; MITK_DEBUG << " v normal: " << normal; Point3Dd projectionRight = projectPointOnLine( origin1, origin2, right ); Point3Dd projectionNormal = projectPointOnLine( origin1, origin2, normal ); m_ShiftRight = (projectionRight - origin2).GetNorm(); m_ShiftNormal = (projectionNormal - origin2).GetNorm(); /* now also check to which side the image is shifted. Calculation e.g. from http://mathworld.wolfram.com/Point-PlaneDistance.html */ Point3Dd testPoint = origin1; Vector3Dd planeNormal = up; double signedDistance = ( planeNormal[0] * testPoint[0] + planeNormal[1] * testPoint[1] + planeNormal[2] * testPoint[2] - ( planeNormal[0] * origin2[0] + planeNormal[1] * origin2[1] + planeNormal[2] * origin2[2] ) ) / sqrt( planeNormal[0] * planeNormal[0] + planeNormal[1] * planeNormal[1] + planeNormal[2] * planeNormal[2] ); m_ShiftUp = signedDistance; m_ITKAssumedSliceSpacing = (origin2 - origin1).GetNorm(); // How do we now this is assumed? See header documentation for ITK code references //double itkAssumedSliceSpacing = sqrt( m_ShiftUp * m_ShiftUp + m_ShiftNormal * m_ShiftNormal ); MITK_DEBUG << " shift normal: " << m_ShiftNormal; MITK_DEBUG << " shift normal assumed by ITK: " << m_ITKAssumedSliceSpacing; MITK_DEBUG << " shift up: " << m_ShiftUp; MITK_DEBUG << " shift right: " << m_ShiftRight; MITK_DEBUG << " tilt angle (deg): " << atan( m_ShiftUp / m_ShiftNormal ) * 180.0 / 3.1415926535; } } Point3D DicomSeriesReader::GantryTiltInformation::projectPointOnLine( Point3Dd p, Point3Dd lineOrigin, Vector3Dd lineDirection ) { /** See illustration at http://mo.mathematik.uni-stuttgart.de/inhalt/aussage/aussage472/ vector(lineOrigin,p) = normal * ( innerproduct((p - lineOrigin),normal) / squared-length(normal) ) */ Vector3Dd lineOriginToP = p - lineOrigin; double innerProduct = lineOriginToP * lineDirection; double factor = innerProduct / lineDirection.GetSquaredNorm(); Point3Dd projection = lineOrigin + factor * lineDirection; return projection; } double DicomSeriesReader::GantryTiltInformation::GetTiltCorrectedAdditionalSize() const { return fabs(m_ShiftUp); } double DicomSeriesReader::GantryTiltInformation::GetTiltAngleInDegrees() const { return atan( fabs(m_ShiftUp) / m_ShiftNormal ) * 180.0 / 3.1415926535; } double DicomSeriesReader::GantryTiltInformation::GetMatrixCoefficientForCorrectionInWorldCoordinates() const { // so many mm need to be shifted per slice! return m_ShiftUp / static_cast(m_NumberOfSlicesApart); } double DicomSeriesReader::GantryTiltInformation::GetRealZSpacing() const { return m_ShiftNormal / static_cast(m_NumberOfSlicesApart); } bool DicomSeriesReader::GantryTiltInformation::IsSheared() const { return ( fabs(m_ShiftRight) > 0.001 || fabs(m_ShiftUp) > 0.001); } bool DicomSeriesReader::GantryTiltInformation::IsRegularGantryTilt() const { return ( fabs(m_ShiftRight) < 0.001 && fabs(m_ShiftUp) > 0.001); } std::string DicomSeriesReader::ConstCharStarToString(const char* s) { return s ? std::string(s) : std::string(); } bool DicomSeriesReader::DICOMStringToSpacing(const std::string& s, float& spacingX, float& spacingY) { bool successful = false; std::istringstream spacingReader(s); std::string spacing; if ( std::getline( spacingReader, spacing, '\\' ) ) { spacingY = atof( spacing.c_str() ); if ( std::getline( spacingReader, spacing, '\\' ) ) { spacingX = atof( spacing.c_str() ); successful = true; } } return successful; } Point3D DicomSeriesReader::DICOMStringToPoint3D(const std::string& s, bool& successful) { Point3D p; successful = true; std::istringstream originReader(s); std::string coordinate; unsigned int dim(0); while( std::getline( originReader, coordinate, '\\' ) && dim < 3) { p[dim++]= atof(coordinate.c_str()); } if (dim && dim != 3) { successful = false; MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0032). Found " << dim << " instead of 3 values."; } else if (dim == 0) { successful = false; p.Fill(0.0); // assume default (0,0,0) } return p; } void DicomSeriesReader::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful) { successful = true; std::istringstream orientationReader(s); std::string coordinate; unsigned int dim(0); while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 ) { if (dim<3) { right[dim++] = atof(coordinate.c_str()); } else { up[dim++ - 3] = atof(coordinate.c_str()); } } if (dim && dim != 6) { successful = false; MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0037). Found " << dim << " instead of 6 values."; } else if (dim == 0) { // fill with defaults right.Fill(0.0); right[0] = 1.0; up.Fill(0.0); up[1] = 1.0; successful = false; } } DicomSeriesReader::SliceGroupingAnalysisResult DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption( const StringContainer& files, bool groupImagesWithGantryTilt, const gdcm::Scanner::MappingType& tagValueMappings_) { // result.first = files that fit ITK's assumption // result.second = files that do not fit, should be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption() again SliceGroupingAnalysisResult result; // we const_cast here, because I could not use a map.at(), which would make the code much more readable gdcm::Scanner::MappingType& tagValueMappings = const_cast(tagValueMappings_); const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); // Image Position (Patient) const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0); bool fromFirstToSecondOriginInitialized(false); Point3D thisOrigin; thisOrigin.Fill(0.0f); Point3D lastOrigin; lastOrigin.Fill(0.0f); Point3D lastDifferentOrigin; lastDifferentOrigin.Fill(0.0f); bool lastOriginInitialized(false); MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "Analyzing files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")"; unsigned int fileIndex(0); for (StringContainer::const_iterator fileIter = files.begin(); fileIter != files.end(); ++fileIter, ++fileIndex) { bool fileFitsIntoPattern(false); std::string thisOriginString; // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that thisOriginString = ConstCharStarToString( tagValueMappings[fileIter->c_str()][tagImagePositionPatient] ); if (thisOriginString.empty()) { // don't let such files be in a common group. Everything without position information will be loaded as a single slice: // with standard DICOM files this can happen to: CR, DX, SC MITK_DEBUG << " ==> Sort away " << *fileIter << " for separate time step (no position information)"; // we already have one occupying this position result.AddFileToSortedBlock( *fileIter ); StringContainer remainingFiles; remainingFiles.insert( remainingFiles.end(), fileIter+1, files.end() ); result.AddFilesToUnsortedBlock( remainingFiles ); fileFitsIntoPattern = false; break; } bool ignoredConversionError(-42); // hard to get here, no graceful way to react thisOrigin = DICOMStringToPoint3D( thisOriginString, ignoredConversionError ); MITK_DEBUG << " " << fileIndex << " " << *fileIter << " at " /* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; if ( lastOriginInitialized && (thisOrigin == lastOrigin) ) { MITK_DEBUG << " ==> Sort away " << *fileIter << " for separate time step"; // we already have one occupying this position result.AddFileToUnsortedBlock( *fileIter ); fileFitsIntoPattern = false; } else { if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position { fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin; fromFirstToSecondOriginInitialized = true; // Here we calculate if this slice and the previous one are well aligned, // i.e. we test if the previous origin is on a line through the current // origin, directed into the normal direction of the current slice. // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry, // which cannot be simply loaded into a single mitk::Image at the moment. // For this case, we flag this finding in the result and DicomSeriesReader // can correct for that later. Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point DICOMStringToOrientationVectors( tagValueMappings[fileIter->c_str()][tagImageOrientation], right, up, ignoredConversionError ); GantryTiltInformation tiltInfo( lastDifferentOrigin, thisOrigin, right, up, 1 ); if ( tiltInfo.IsSheared() ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt { /* optimistic approach, accepting gantry tilt: save file for later, check all further files */ // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there is no third to verify our tilting assumption. // later with a third being available, we must check if the initial tilting vector is still valid. if yes, continue. // if NO, we need to split the already sorted part (result.first) and the currently analyzed file (*fileIter) // tell apart gantry tilt from overall skewedness // sort out irregularly sheared slices, that IS NOT tilting if ( groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt() ) { // check if this is at least roughly the same angle as recorded in DICOM tags if ( tagValueMappings[fileIter->c_str()].find(tagGantryTilt) != tagValueMappings[fileIter->c_str()].end() ) { // read value, compare to calculated angle std::string tiltStr = ConstCharStarToString( tagValueMappings[fileIter->c_str()][tagGantryTilt] ); double angle = atof(tiltStr.c_str()); MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value " << tiltInfo.GetTiltAngleInDegrees(); // TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing serious) if ( fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25) { result.AddFileToUnsortedBlock( *fileIter ); // sort away for further analysis fileFitsIntoPattern = false; } else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this is fine { result.FlagGantryTilt(); result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we are right) { result.FlagGantryTilt(); result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // caller does not want tilt compensation OR shearing is more complicated than tilt { result.AddFileToUnsortedBlock( *fileIter ); // sort away for further analysis fileFitsIntoPattern = false; } } else // not sheared { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices { Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin; Vector3D originError = assumedOrigin - thisOrigin; double norm = originError.GetNorm(); double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction if (norm > toleratedError) { MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed " << toleratedError << ")."; MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," << assumedOrigin[1] << "," << assumedOrigin[2] << "), got position (" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; MITK_DEBUG << " ==> Sort away " << *fileIter << " for later analysis"; // At this point we know we deviated from the expectation of ITK's ImageSeriesReader // We split the input file list at this point, i.e. all files up to this one (excluding it) // are returned as group 1, the remaining files (including the faulty one) are group 2 /* Optimistic approach: check if any of the remaining slices fits in */ result.AddFileToUnsortedBlock( *fileIter ); // sort away for further analysis fileFitsIntoPattern = false; } else { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // this should be the very first slice { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } // record current origin for reference in later iterations if ( !lastOriginInitialized || ( fileFitsIntoPattern && (thisOrigin != lastOrigin) ) ) { lastDifferentOrigin = thisOrigin; } lastOrigin = thisOrigin; lastOriginInitialized = true; } if ( result.ContainsGantryTilt() ) { // check here how many files were grouped. // IF it was only two files AND we assume tiltedness (e.g. save "distance") // THEN we would want to also split the two previous files (simple) because // we don't have any reason to assume they belong together if ( result.GetBlockFilenames().size() == 2 ) { result.UndoPrematureGrouping(); } } return result; } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const StringContainer& files, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { return GetSeries(files, true, groupImagesWithGantryTilt, restrictions); } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const StringContainer& files, bool sortTo3DPlust, bool groupImagesWithGantryTilt, const StringContainer& /*restrictions*/) { /** assumption about this method: returns a map of uid-like-key --> list(filename) each entry should contain filenames that have images of same - series instance uid (automatically done by GDCMSeriesFileNames - 0020,0037 image orientation (patient) - 0028,0030 pixel spacing (x,y) - 0018,0050 slice thickness */ // use GDCM directly, itk::GDCMSeriesFileNames does not work with GDCM 2 // PART I: scan files for sorting relevant DICOM tags, // separate images that differ in any of those // attributes (they cannot possibly form a 3D block) // scan for relevant tags in dicom files gdcm::Scanner scanner; const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP class UID scanner.AddTag( tagSOPClassUID ); const gdcm::Tag tagSeriesInstanceUID(0x0020,0x000e); // Series Instance UID scanner.AddTag( tagSeriesInstanceUID ); const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation scanner.AddTag( tagImageOrientation ); const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing scanner.AddTag( tagPixelSpacing ); const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing scanner.AddTag( tagImagerPixelSpacing ); const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness scanner.AddTag( tagSliceThickness ); const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows scanner.AddTag( tagNumberOfRows ); const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols scanner.AddTag( tagNumberOfColumns ); const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt scanner.AddTag( tagGantryTilt ); const gdcm::Tag tagModality(0x0008, 0x0060); // modality scanner.AddTag( tagModality ); const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames scanner.AddTag( tagNumberOfFrames ); // additional tags read in this scan to allow later analysis // THESE tag are not used for initial separating of files const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); // Image Position (Patient) scanner.AddTag( tagImagePositionPatient ); // TODO add further restrictions from arguments (when anybody asks for it) FileNamesGrouping result; // let GDCM scan files if ( !scanner.Scan( files ) ) { MITK_ERROR << "gdcm::Scanner failed when scanning " << files.size() << " input files."; return result; } // assign files IDs that will separate them for loading into image blocks for (gdcm::Scanner::ConstIterator fileIter = scanner.Begin(); fileIter != scanner.End(); ++fileIter) { if ( std::string(fileIter->first).empty() ) continue; // TODO understand why Scanner has empty string entries if ( std::string(fileIter->first) == std::string("DICOMDIR") ) continue; /* sort out multi-frame if ( scanner.GetValue( fileIter->first , tagNumberOfFrames ) ) { MITK_INFO << "Ignoring " << fileIter->first << " because we cannot handle multi-frame images."; continue; } */ // we const_cast here, because I could not use a map.at() function in CreateMoreUniqueSeriesIdentifier. // doing the same thing with find would make the code less readable. Since we forget the Scanner results // anyway after this function, we can simply tolerate empty map entries introduced by bad operator[] access std::string moreUniqueSeriesId = CreateMoreUniqueSeriesIdentifier( const_cast(fileIter->second) ); result[ moreUniqueSeriesId ].AddFile( fileIter->first ); } // PART II: sort slices spatially (or at least consistently if this is NOT possible, see method) for ( FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter ) { try { result[ groupIter->first ] = ImageBlockDescriptor( SortSeriesSlices( groupIter->second.GetFilenames() ) ); // sort each slice group spatially } catch(...) { MITK_ERROR << "Caught something."; } } // PART III: analyze pre-sorted images for valid blocks (i.e. blocks of equal z-spacing), // separate into multiple blocks if necessary. // // Analysis performs the following steps: // * imitate itk::ImageSeriesReader: use the distance between the first two images as z-spacing // * check what images actually fulfill ITK's z-spacing assumption // * separate all images that fail the test into new blocks, re-iterate analysis for these blocks // * this includes images which DO NOT PROVIDE spatial information, i.e. all images w/o ImagePositionPatient will be loaded separately FileNamesGrouping groupsOf3DPlusTBlocks; // final result of this function for ( FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter ) { FileNamesGrouping groupsOf3DBlocks; // intermediate result for only this group(!) std::map mapOf3DBlockAnalysisResults; StringContainer filesStillToAnalyze = groupIter->second.GetFilenames(); std::string groupUID = groupIter->first; unsigned int subgroup(0); MITK_DEBUG << "Analyze group " << groupUID; while (!filesStillToAnalyze.empty()) // repeat until all files are grouped somehow { SliceGroupingAnalysisResult analysisResult = AnalyzeFileForITKImageSeriesReaderSpacingAssumption( filesStillToAnalyze, groupImagesWithGantryTilt, scanner.GetMappings() ); // enhance the UID for additional groups std::stringstream newGroupUID; newGroupUID << groupUID << '.' << subgroup; ImageBlockDescriptor thisBlock( analysisResult.GetBlockFilenames() ); std::string firstFileInBlock = thisBlock.GetFilenames().front(); thisBlock.SetImageBlockUID( newGroupUID.str() ); thisBlock.SetSeriesInstanceUID( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagSeriesInstanceUID ) ) ); thisBlock.SetHasGantryTiltCorrected( analysisResult.ContainsGantryTilt() ); thisBlock.SetSOPClassUID( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagSOPClassUID ) ) ); thisBlock.SetNumberOfFrames( ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagNumberOfFrames ) ) ); thisBlock.SetModality( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagModality ) ) ); thisBlock.SetPixelSpacingInformation( DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagPixelSpacing ) ), DicomSeriesReader::ConstCharStarToString( scanner.GetValue( firstFileInBlock.c_str(), tagImagerPixelSpacing ) ) ); thisBlock.SetHasMultipleTimePoints( false ); groupsOf3DBlocks[ newGroupUID.str() ] = thisBlock; MITK_DEBUG << "Result: sorted 3D group " << newGroupUID.str() << " with " << groupsOf3DBlocks[ newGroupUID.str() ].GetFilenames().size() << " files"; ++subgroup; filesStillToAnalyze = analysisResult.GetUnsortedFilenames(); // remember what needs further analysis } // end of grouping, now post-process groups // PART IV: attempt to group blocks to 3D+t blocks if requested // inspect entries of groupsOf3DBlocks // - if number of files is identical to previous entry, collect for 3D+t block // - as soon as number of files changes from previous entry, record collected blocks as 3D+t block, start a new one, continue // decide whether or not to group 3D blocks into 3D+t blocks where possible if ( !sortTo3DPlust ) { // copy 3D blocks to output groupsOf3DPlusTBlocks.insert( groupsOf3DBlocks.begin(), groupsOf3DBlocks.end() ); } else { // sort 3D+t (as described in "PART IV") MITK_DEBUG << "================================================================================"; MITK_DEBUG << "3D+t analysis:"; unsigned int numberOfFilesInPreviousBlock(0); std::string previousBlockKey; for ( FileNamesGrouping::const_iterator block3DIter = groupsOf3DBlocks.begin(); block3DIter != groupsOf3DBlocks.end(); ++block3DIter ) { unsigned int numberOfFilesInThisBlock = block3DIter->second.GetFilenames().size(); std::string thisBlockKey = block3DIter->first; if (numberOfFilesInPreviousBlock == 0) { numberOfFilesInPreviousBlock = numberOfFilesInThisBlock; groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second; MITK_DEBUG << " 3D+t group " << thisBlockKey; previousBlockKey = thisBlockKey; } else { bool identicalOrigins; try { // check whether this and the previous block share a comon origin // TODO should be safe, but a little try/catch or other error handling wouldn't hurt const char *origin_value = scanner.GetValue( groupsOf3DBlocks[thisBlockKey].GetFilenames().front().c_str(), tagImagePositionPatient ), *previous_origin_value = scanner.GetValue( groupsOf3DBlocks[previousBlockKey].GetFilenames().front().c_str(), tagImagePositionPatient ), *destination_value = scanner.GetValue( groupsOf3DBlocks[thisBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient ), *previous_destination_value = scanner.GetValue( groupsOf3DBlocks[previousBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient ); if (!origin_value || !previous_origin_value || !destination_value || !previous_destination_value) { identicalOrigins = false; } else { std::string thisOriginString = ConstCharStarToString( origin_value ); std::string previousOriginString = ConstCharStarToString( previous_origin_value ); // also compare last origin, because this might differ if z-spacing is different std::string thisDestinationString = ConstCharStarToString( destination_value ); std::string previousDestinationString = ConstCharStarToString( previous_destination_value ); identicalOrigins = ( (thisOriginString == previousOriginString) && (thisDestinationString == previousDestinationString) ); } } catch(...) { identicalOrigins = false; } if (identicalOrigins && (numberOfFilesInPreviousBlock == numberOfFilesInThisBlock)) { // group with previous block groupsOf3DPlusTBlocks[previousBlockKey].AddFiles( block3DIter->second.GetFilenames() ); groupsOf3DPlusTBlocks[previousBlockKey].SetHasMultipleTimePoints(true); MITK_DEBUG << " --> group enhanced with another timestep"; } else { // start a new block groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second; int numberOfTimeSteps = groupsOf3DPlusTBlocks[previousBlockKey].GetFilenames().size() / numberOfFilesInPreviousBlock; MITK_DEBUG << " ==> group closed with " << numberOfTimeSteps << " time steps"; previousBlockKey = thisBlockKey; MITK_DEBUG << " 3D+t group " << thisBlockKey << " started"; } } numberOfFilesInPreviousBlock = numberOfFilesInThisBlock; } } } MITK_DEBUG << "================================================================================"; MITK_DEBUG << "Summary: "; for ( FileNamesGrouping::const_iterator groupIter = groupsOf3DPlusTBlocks.begin(); groupIter != groupsOf3DPlusTBlocks.end(); ++groupIter ) { ImageBlockDescriptor block = groupIter->second; MITK_DEBUG << " " << block.GetFilenames().size() << " '" << block.GetModality() << "' images (" << block.GetSOPClassUIDAsString() << ") in volume " << block.GetImageBlockUID(); MITK_DEBUG << " (gantry tilt : " << (block.HasGantryTiltCorrected()?"Yes":"No") << "; " "pixel spacing : " << PixelSpacingInterpretationToString( block.GetPixelSpacingType() ) << "; " "3D+t: " << (block.HasMultipleTimePoints()?"Yes":"No") << "; " "reader support: " << ReaderImplementationLevelToString( block.GetReaderImplementationLevel() ); } MITK_DEBUG << "================================================================================"; return groupsOf3DPlusTBlocks; } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const std::string &dir, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { gdcm::Directory directoryLister; directoryLister.Load( dir.c_str(), false ); // non-recursive return GetSeries(directoryLister.GetFilenames(), groupImagesWithGantryTilt, restrictions); } std::string DicomSeriesReader::CreateSeriesIdentifierPart( gdcm::Scanner::TagToValue& tagValueMap, const gdcm::Tag& tag ) { std::string result; try { result = IDifyTagValue( tagValueMap[ tag ] ? tagValueMap[ tag ] : std::string("") ); } catch (std::exception&) { // we are happy with even nothing, this will just group images of a series //MITK_WARN << "Could not access tag " << tag << ": " << e.what(); } return result; } std::string DicomSeriesReader::CreateMoreUniqueSeriesIdentifier( gdcm::Scanner::TagToValue& tagValueMap ) { const gdcm::Tag tagSeriesInstanceUID(0x0020,0x000e); // Series Instance UID const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames const char* tagSeriesInstanceUid = tagValueMap[tagSeriesInstanceUID]; if (!tagSeriesInstanceUid) { mitkThrow() << "CreateMoreUniqueSeriesIdentifier() could not access series instance UID. Something is seriously wrong with this image, so stopping here."; } std::string constructedID = tagSeriesInstanceUid; constructedID += CreateSeriesIdentifierPart( tagValueMap, tagNumberOfRows ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagNumberOfColumns ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagPixelSpacing ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagImagerPixelSpacing ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagSliceThickness ); constructedID += CreateSeriesIdentifierPart( tagValueMap, tagNumberOfFrames ); // be a bit tolerant for orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263) // NOT constructedID += CreateSeriesIdentifierPart( tagValueMap, tagImageOrientation ); if (tagValueMap.find(tagImageOrientation) != tagValueMap.end()) { bool conversionError(false); Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); DICOMStringToOrientationVectors( tagValueMap[tagImageOrientation], right, up, conversionError ); //string newstring sprintf(simplifiedOrientationString, "%.3f\\%.3f\\%.3f\\%.3f\\%.3f\\%.3f", right[0], right[1], right[2], up[0], up[1], up[2]); std::ostringstream ss; ss.setf(std::ios::fixed, std::ios::floatfield); ss.precision(5); ss << right[0] << "\\" << right[1] << "\\" << right[2] << "\\" << up[0] << "\\" << up[1] << "\\" << up[2]; std::string simplifiedOrientationString(ss.str()); constructedID += IDifyTagValue( simplifiedOrientationString ); } constructedID.resize( constructedID.length() - 1 ); // cut of trailing '.' return constructedID; } std::string DicomSeriesReader::IDifyTagValue(const std::string& value) { std::string IDifiedValue( value ); if (value.empty()) throw std::logic_error("IDifyTagValue() illegaly called with empty tag value"); // Eliminate non-alnum characters, including whitespace... // that may have been introduced by concats. for(std::size_t i=0; i= 'a' && IDifiedValue[i] <= 'z') || (IDifiedValue[i] >= '0' && IDifiedValue[i] <= '9') || (IDifiedValue[i] >= 'A' && IDifiedValue[i] <= 'Z'))) { IDifiedValue.erase(i, 1); } } IDifiedValue += "."; return IDifiedValue; } DicomSeriesReader::StringContainer DicomSeriesReader::GetSeries(const std::string &dir, const std::string &series_uid, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { FileNamesGrouping allSeries = GetSeries(dir, groupImagesWithGantryTilt, restrictions); StringContainer resultingFileList; for ( FileNamesGrouping::const_iterator idIter = allSeries.begin(); idIter != allSeries.end(); ++idIter ) { if ( idIter->first.find( series_uid ) == 0 ) // this ID starts with given series_uid { return idIter->second.GetFilenames(); } } return resultingFileList; } DicomSeriesReader::StringContainer DicomSeriesReader::SortSeriesSlices(const StringContainer &unsortedFilenames) { /* we CAN expect a group of equal - series instance uid - image orientation - pixel spacing - imager pixel spacing - slice thickness - number of rows/columns (each piece of information except the rows/columns might be missing) sorting with GdcmSortFunction tries its best by sorting by spatial position and more hints (acquisition number, acquisition time, trigger time) but will always produce a sorting by falling back to SOP Instance UID. */ gdcm::Sorter sorter; sorter.SetSortFunction(DicomSeriesReader::GdcmSortFunction); try { sorter.Sort(unsortedFilenames); return sorter.GetFilenames(); } catch(std::logic_error&) { MITK_WARN << "Sorting error. Leaving series unsorted."; return unsortedFilenames; } } bool DicomSeriesReader::GdcmSortFunction(const gdcm::DataSet &ds1, const gdcm::DataSet &ds2) { // This method MUST accept missing location and position information (and all else, too) // because we cannot rely on anything // (restriction on the sentence before: we have to provide consistent sorting, so we // rely on the minimum information all DICOM files need to provide: SOP Instance UID) /* we CAN expect a group of equal - series instance uid - image orientation - pixel spacing - imager pixel spacing - slice thickness - number of rows/columns */ const gdcm::Tag tagImagePositionPatient(0x0020,0x0032); // Image Position (Patient) const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation // see if we have Image Position and Orientation if ( ds1.FindDataElement(tagImagePositionPatient) && ds1.FindDataElement(tagImageOrientation) && ds2.FindDataElement(tagImagePositionPatient) && ds2.FindDataElement(tagImageOrientation) ) { gdcm::Attribute<0x0020,0x0032> image_pos1; // Image Position (Patient) gdcm::Attribute<0x0020,0x0037> image_orientation1; // Image Orientation (Patient) image_pos1.Set(ds1); image_orientation1.Set(ds1); gdcm::Attribute<0x0020,0x0032> image_pos2; gdcm::Attribute<0x0020,0x0037> image_orientation2; image_pos2.Set(ds2); image_orientation2.Set(ds2); /* we tolerate very small differences in image orientation, since we got to know about acquisitions where these values change across a single series (7th decimal digit) (http://bugs.mitk.org/show_bug.cgi?id=12263) still, we want to check if our assumption of 'almost equal' orientations is valid */ for (unsigned int dim = 0; dim < 6; ++dim) { if ( fabs(image_orientation2[dim] - image_orientation1[dim]) > 0.0001 ) { MITK_ERROR << "Dicom images have different orientations."; throw std::logic_error("Dicom images have different orientations. Call GetSeries() first to separate images."); } } double normal[3]; normal[0] = image_orientation1[1] * image_orientation1[5] - image_orientation1[2] * image_orientation1[4]; normal[1] = image_orientation1[2] * image_orientation1[3] - image_orientation1[0] * image_orientation1[5]; normal[2] = image_orientation1[0] * image_orientation1[4] - image_orientation1[1] * image_orientation1[3]; double dist1 = 0.0, dist2 = 0.0; // this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes for (unsigned char i = 0u; i < 3u; ++i) { dist1 += normal[i] * image_pos1[i]; dist2 += normal[i] * image_pos2[i]; } // if we can sort by just comparing the distance, we do exactly that if ( fabs(dist1 - dist2) >= mitk::eps) { // default: compare position return dist1 < dist2; } else // we need to check more properties to distinguish slices { // try to sort by Acquisition Number const gdcm::Tag tagAcquisitionNumber(0x0020, 0x0012); if (ds1.FindDataElement(tagAcquisitionNumber) && ds2.FindDataElement(tagAcquisitionNumber)) { gdcm::Attribute<0x0020,0x0012> acquisition_number1; // Acquisition number gdcm::Attribute<0x0020,0x0012> acquisition_number2; acquisition_number1.Set(ds1); acquisition_number2.Set(ds2); if (acquisition_number1 != acquisition_number2) { return acquisition_number1 < acquisition_number2; } else // neither position nor acquisition number are good for sorting, so check more { // try to sort by Acquisition Time const gdcm::Tag tagAcquisitionTime(0x0008, 0x0032); if (ds1.FindDataElement(tagAcquisitionTime) && ds2.FindDataElement(tagAcquisitionTime)) { gdcm::Attribute<0x0008,0x0032> acquisition_time1; // Acquisition time gdcm::Attribute<0x0008,0x0032> acquisition_time2; acquisition_time1.Set(ds1); acquisition_time2.Set(ds2); if (acquisition_time1 != acquisition_time2) { return acquisition_time1 < acquisition_time2; } else // we gave up on image position, acquisition number and acquisition time now { // let's try trigger time const gdcm::Tag tagTriggerTime(0x0018, 0x1060); if (ds1.FindDataElement(tagTriggerTime) && ds2.FindDataElement(tagTriggerTime)) { gdcm::Attribute<0x0018,0x1060> trigger_time1; // Trigger time gdcm::Attribute<0x0018,0x1060> trigger_time2; trigger_time1.Set(ds1); trigger_time2.Set(ds2); if (trigger_time1 != trigger_time2) { return trigger_time1 < trigger_time2; } // ELSE! // for this and many previous ifs we fall through if nothing lets us sort } // . } // . } // . } } } } // . // LAST RESORT: all valuable information for sorting is missing. // Sort by some meaningless but unique identifiers to satisfy the sort function const gdcm::Tag tagSOPInstanceUID(0x0008, 0x0018); if (ds1.FindDataElement(tagSOPInstanceUID) && ds2.FindDataElement(tagSOPInstanceUID)) { MITK_WARN << "Dicom images are missing attributes for a meaningful sorting, falling back to SOP instance UID comparison."; gdcm::Attribute<0x0008,0x0018> SOPInstanceUID1; // SOP instance UID is mandatory and unique gdcm::Attribute<0x0008,0x0018> SOPInstanceUID2; SOPInstanceUID1.Set(ds1); SOPInstanceUID2.Set(ds2); return SOPInstanceUID1 < SOPInstanceUID2; } else { // no DICOM file should really come down here, this should only be reached with unskillful and unlucky manipulation of files std::string error_message("Malformed DICOM images, which do not even contain a SOP Instance UID."); MITK_ERROR << error_message; throw std::logic_error( error_message ); } } std::string DicomSeriesReader::GetConfigurationString() { std::stringstream configuration; configuration << "MITK_USE_GDCMIO: "; configuration << "true"; configuration << "\n"; configuration << "GDCM_VERSION: "; #ifdef GDCM_MAJOR_VERSION configuration << GDCM_VERSION; #endif //configuration << "\n"; return configuration.str(); } void DicomSeriesReader::CopyMetaDataToImageProperties(StringContainer filenames, const gdcm::Scanner::MappingType &tagValueMappings_, DcmIoType *io, const ImageBlockDescriptor& blockInfo, Image *image) { std::list imageBlock; imageBlock.push_back(filenames); CopyMetaDataToImageProperties(imageBlock, tagValueMappings_, io, blockInfo, image); } void DicomSeriesReader::CopyMetaDataToImageProperties( std::list imageBlock, const gdcm::Scanner::MappingType& tagValueMappings_, DcmIoType* io, const ImageBlockDescriptor& blockInfo, Image* image) { if (!io || !image) return; StringLookupTable filesForSlices; StringLookupTable sliceLocationForSlices; StringLookupTable instanceNumberForSlices; StringLookupTable SOPInstanceNumberForSlices; gdcm::Scanner::MappingType& tagValueMappings = const_cast(tagValueMappings_); //DICOM tags which should be added to the image properties const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number unsigned int timeStep(0); std::string propertyKeySliceLocation = "dicom.image.0020.1041"; std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; // tags for each image for ( std::list::iterator i = imageBlock.begin(); i != imageBlock.end(); i++, timeStep++ ) { const StringContainer& files = (*i); unsigned int slice(0); for ( StringContainer::const_iterator fIter = files.begin(); fIter != files.end(); ++fIter, ++slice ) { filesForSlices.SetTableValue( slice, *fIter ); gdcm::Scanner::TagToValue tagValueMapForFile = tagValueMappings[fIter->c_str()]; if(tagValueMapForFile.find(tagSliceLocation) != tagValueMapForFile.end()) sliceLocationForSlices.SetTableValue(slice, tagValueMapForFile[tagSliceLocation]); if(tagValueMapForFile.find(tagInstanceNumber) != tagValueMapForFile.end()) instanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagInstanceNumber]); if(tagValueMapForFile.find(tagSOPInstanceNumber) != tagValueMapForFile.end()) SOPInstanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagSOPInstanceNumber]); } image->SetProperty( "files", StringLookupTableProperty::New( filesForSlices ) ); //If more than one time step add postfix ".t" + timestep if(timeStep != 0) { std::ostringstream postfix; postfix << ".t" << timeStep; propertyKeySliceLocation.append(postfix.str()); propertyKeyInstanceNumber.append(postfix.str()); propertyKeySOPInstanceNumber.append(postfix.str()); } image->SetProperty( propertyKeySliceLocation.c_str(), StringLookupTableProperty::New( sliceLocationForSlices ) ); image->SetProperty( propertyKeyInstanceNumber.c_str(), StringLookupTableProperty::New( instanceNumberForSlices ) ); image->SetProperty( propertyKeySOPInstanceNumber.c_str(), StringLookupTableProperty::New( SOPInstanceNumberForSlices ) ); } // Copy tags for series, study, patient level (leave interpretation to application). // These properties will be copied to the DataNode by DicomSeriesReader. // tags for the series (we just use the one that ITK copied to its dictionary (proably that of the last slice) const itk::MetaDataDictionary& dict = io->GetMetaDataDictionary(); const TagToPropertyMapType& propertyLookup = DicomSeriesReader::GetDICOMTagsToMITKPropertyMap(); itk::MetaDataDictionary::ConstIterator dictIter = dict.Begin(); while ( dictIter != dict.End() ) { //MITK_DEBUG << "Key " << dictIter->first; std::string value; if ( itk::ExposeMetaData( dict, dictIter->first, value ) ) { //MITK_DEBUG << "Value " << value; TagToPropertyMapType::const_iterator valuePosition = propertyLookup.find( dictIter->first ); if ( valuePosition != propertyLookup.end() ) { std::string propertyKey = valuePosition->second; //MITK_DEBUG << "--> " << propertyKey; image->SetProperty( propertyKey.c_str(), StringProperty::New(value) ); } } else { MITK_WARN << "Tag " << dictIter->first << " not read as string as expected. Ignoring..." ; } ++dictIter; } // copy imageblockdescriptor as properties image->SetProperty("dicomseriesreader.SOPClass", StringProperty::New(blockInfo.GetSOPClassUIDAsString())); image->SetProperty("dicomseriesreader.ReaderImplementationLevelString", StringProperty::New(ReaderImplementationLevelToString( blockInfo.GetReaderImplementationLevel() ))); image->SetProperty("dicomseriesreader.ReaderImplementationLevel", GenericProperty::New( blockInfo.GetReaderImplementationLevel() )); image->SetProperty("dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New(PixelSpacingInterpretationToString( blockInfo.GetPixelSpacingType() ))); image->SetProperty("dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New(blockInfo.GetPixelSpacingType())); image->SetProperty("dicomseriesreader.MultiFrameImage", BoolProperty::New(blockInfo.IsMultiFrameImage())); image->SetProperty("dicomseriesreader.GantyTiltCorrected", BoolProperty::New(blockInfo.HasGantryTiltCorrected())); image->SetProperty("dicomseriesreader.3D+t", BoolProperty::New(blockInfo.HasMultipleTimePoints())); } void DicomSeriesReader::FixSpacingInformation( mitk::Image* image, const ImageBlockDescriptor& imageBlockDescriptor ) { // spacing provided by ITK/GDCM Vector3D imageSpacing = image->GetGeometry()->GetSpacing(); ScalarType imageSpacingX = imageSpacing[0]; ScalarType imageSpacingY = imageSpacing[1]; // spacing as desired by MITK (preference for "in patient", else "on detector", or "1.0/1.0") ScalarType desiredSpacingX = imageSpacingX; ScalarType desiredSpacingY = imageSpacingY; imageBlockDescriptor.GetDesiredMITKImagePixelSpacing( desiredSpacingX, desiredSpacingY ); MITK_DEBUG << "Loaded spacing: " << imageSpacingX << "/" << imageSpacingY; MITK_DEBUG << "Corrected spacing: " << desiredSpacingX << "/" << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; image->GetGeometry()->SetSpacing( imageSpacing ); } } // end namespace mitk #include diff --git a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp index bb1f934c7d..f7473a7f1f 100644 --- a/Core/Code/Rendering/mitkImageVtkMapper2D.cpp +++ b/Core/Code/Rendering/mitkImageVtkMapper2D.cpp @@ -1,1085 +1,1085 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ //MITK #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include "mitkImageStatisticsHolder.h" //MITK Rendering #include "mitkImageVtkMapper2D.h" #include "vtkMitkThickSlicesFilter.h" #include "vtkMitkApplyLevelWindowToRGBFilter.h" //VTK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //ITK #include mitk::ImageVtkMapper2D::ImageVtkMapper2D() { } mitk::ImageVtkMapper2D::~ImageVtkMapper2D() { //The 3D RW Mapper (Geometry2DDataVtkMapper3D) is listening to this event, //in order to delete the images from the 3D RW. this->InvokeEvent( itk::DeleteEvent() ); } //set the two points defining the textured plane according to the dimension and spacing void mitk::ImageVtkMapper2D::GeneratePlane(mitk::BaseRenderer* renderer, vtkFloatingPointType planeBounds[6]) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float depth = this->CalculateLayerDepth(renderer); //Set the origin to (xMin; yMin; depth) of the plane. This is necessary for obtaining the correct //plane size in crosshair rotation and swivel mode. 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 * >( this->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::MitkRenderOverlay(BaseRenderer* renderer) { if ( this->IsVisible(renderer)==false ) return; if ( this->GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderOverlay(renderer->GetVtkRenderer()); } } void mitk::ImageVtkMapper2D::MitkRenderOpaqueGeometry(BaseRenderer* renderer) { if ( this->IsVisible( renderer )==false ) return; if ( this->GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderOpaqueGeometry( renderer->GetVtkRenderer() ); } } void mitk::ImageVtkMapper2D::MitkRenderTranslucentGeometry(BaseRenderer* renderer) { if ( this->IsVisible(renderer)==false ) return; if ( this->GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderTranslucentPolygonalGeometry(renderer->GetVtkRenderer()); } } void mitk::ImageVtkMapper2D::MitkRenderVolumetricGeometry(BaseRenderer* renderer) { if(IsVisible(renderer)==false) return; if ( GetVtkProp(renderer)->GetVisibility() ) { this->GetVtkProp(renderer)->RenderVolumetricGeometry(renderer->GetVtkRenderer()); } } void mitk::ImageVtkMapper2D::GenerateDataForRenderer( mitk::BaseRenderer *renderer ) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::Image *input = const_cast< mitk::Image * >( this->GetInput() ); mitk::DataNode* datanode = this->GetDataNode(); if ( input == NULL || input->IsInitialized() == false ) { return; } //check if there is a valid worldGeometry const Geometry2D *worldGeometry = renderer->GetCurrentWorldGeometry2D(); if( ( worldGeometry == NULL ) || ( !worldGeometry->IsValid() ) || ( !worldGeometry->HasReferenceGeometry() )) { return; } input->Update(); // early out if there is no intersection of the current rendering geometry // and the geometry of the image that is to be rendered. if ( !RenderingGeometryIntersectsImage( worldGeometry, input->GetSlicedGeometry() ) ) { localStorage->m_Mapper->SetInput( localStorage->m_EmptyPolyData ); return; } //set main input for ExtractSliceFilter localStorage->m_Reslicer->SetInput(input); localStorage->m_Reslicer->SetWorldGeometry(worldGeometry); localStorage->m_Reslicer->SetTimeStep( this->GetTimestep() ); //set the transformation of the image to adapt reslice axis localStorage->m_Reslicer->SetResliceTransformByGeometry( input->GetTimeSlicedGeometry()->GetGeometry3D( this->GetTimestep() ) ); //is the geometry of the slice based on the input image or the worldgeometry? bool inPlaneResampleExtentByGeometry = false; datanode->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); localStorage->m_Reslicer->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); // Initialize the interpolation mode for resampling; switch to nearest // neighbor if the input image is too small. if ( (input->GetDimension() >= 3) && (input->GetDimension(2) > 1) ) { VtkResliceInterpolationProperty *resliceInterpolationProperty; datanode->GetProperty( resliceInterpolationProperty, "reslice interpolation" ); int interpolationMode = VTK_RESLICE_NEAREST; if ( resliceInterpolationProperty != NULL ) { interpolationMode = resliceInterpolationProperty->GetInterpolation(); } switch ( interpolationMode ) { case VTK_RESLICE_NEAREST: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); break; case VTK_RESLICE_LINEAR: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_LINEAR); break; case VTK_RESLICE_CUBIC: localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_CUBIC); break; } } else { localStorage->m_Reslicer->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); } //set the vtk output property to true, makes sure that no unneeded mitk image convertion //is done. localStorage->m_Reslicer->SetVtkOutputRequest(true); //Thickslicing int thickSlicesMode = 0; int thickSlicesNum = 1; // Thick slices parameters if( input->GetPixelType().GetNumberOfComponents() == 1 ) // for now only single component are allowed { DataNode *dn=renderer->GetCurrentWorldGeometry2DNode(); if(dn) { ResliceMethodProperty *resliceMethodEnumProperty=0; if( dn->GetProperty( resliceMethodEnumProperty, "reslice.thickslices" ) && resliceMethodEnumProperty ) thickSlicesMode = resliceMethodEnumProperty->GetValueAsId(); IntProperty *intProperty=0; if( dn->GetProperty( intProperty, "reslice.thickslices.num" ) && intProperty ) { thickSlicesNum = intProperty->GetValue(); if(thickSlicesNum < 1) thickSlicesNum=1; if(thickSlicesNum > 10) thickSlicesNum=10; } } else { MITK_WARN << "no associated widget plane data tree node found"; } } if(thickSlicesMode > 0) { double dataZSpacing = 1.0; Vector3D normInIndex, normal; const PlaneGeometry *planeGeometry = dynamic_cast< const PlaneGeometry * >( worldGeometry ); if ( planeGeometry != NULL ){ normal = planeGeometry->GetNormal(); }else{ const mitk::AbstractTransformGeometry* abstractGeometry = dynamic_cast< const AbstractTransformGeometry * >(worldGeometry); if(abstractGeometry != NULL) normal = abstractGeometry->GetPlane()->GetNormal(); else return; //no fitting geometry set } normal.Normalize(); input->GetTimeSlicedGeometry()->GetGeometry3D( this->GetTimestep() )->WorldToIndex( normal, normInIndex ); dataZSpacing = 1.0 / normInIndex.GetNorm(); localStorage->m_Reslicer->SetOutputDimensionality( 3 ); localStorage->m_Reslicer->SetOutputSpacingZDirection(dataZSpacing); localStorage->m_Reslicer->SetOutputExtentZDirection( -thickSlicesNum, 0+thickSlicesNum ); // Do the reslicing. Modified() is called to make sure that the reslicer is // executed even though the input geometry information did not change; this // is necessary when the input /em data, but not the /em geometry changes. localStorage->m_TSFilter->SetThickSliceMode( thickSlicesMode-1 ); localStorage->m_TSFilter->SetInput( localStorage->m_Reslicer->GetVtkOutput() ); //vtkFilter=>mitkFilter=>vtkFilter update mechanism will fail without calling manually localStorage->m_Reslicer->Modified(); localStorage->m_Reslicer->Update(); localStorage->m_TSFilter->Modified(); localStorage->m_TSFilter->Update(); localStorage->m_ReslicedImage = localStorage->m_TSFilter->GetOutput(); } else { //this is needed when thick mode was enable bevore. These variable have to be reset to default values localStorage->m_Reslicer->SetOutputDimensionality( 2 ); localStorage->m_Reslicer->SetOutputSpacingZDirection(1.0); localStorage->m_Reslicer->SetOutputExtentZDirection( 0, 0 ); localStorage->m_Reslicer->Modified(); //start the pipeline with updating the largest possible, needed if the geometry of the input has changed localStorage->m_Reslicer->UpdateLargestPossibleRegion(); localStorage->m_ReslicedImage = localStorage->m_Reslicer->GetVtkOutput(); } // Bounds information for reslicing (only reuqired if reference geometry // is present) //this used for generating a vtkPLaneSource with the right size vtkFloatingPointType sliceBounds[6]; for ( int i = 0; i < 6; ++i ) { sliceBounds[i] = 0.0; } localStorage->m_Reslicer->GetClippedPlaneBounds(sliceBounds); //get the spacing of the slice localStorage->m_mmPerPixel = localStorage->m_Reslicer->GetOutputSpacing(); //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->ApplyLookuptable(renderer); //Interpret the values as binary values localStorage->m_Texture->MapColorScalarsThroughLookupTableOn(); } else if( numberOfComponents == 1 ) //gray images { //Interpret the values as gray values localStorage->m_Texture->MapColorScalarsThroughLookupTableOn(); this->ApplyLookuptable(renderer); } else if ( (numberOfComponents == 3) || (numberOfComponents == 4) ) //RBG(A) images { //Interpret the RGB(A) images values correctly localStorage->m_Texture->MapColorScalarsThroughLookupTableOff(); this->ApplyLookuptable(renderer); this->ApplyRBGALevelWindow(renderer); } else { MITK_ERROR << "2D Reindering Error: Unknown number of components!!! Please report to rendering task force or check your data!"; } this->ApplyColor( renderer ); this->ApplyOpacity( renderer ); this->TransformActor( renderer ); vtkActor* contourShadowActor = dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0)); if(binary && binaryOutline) //connect the mapper with the polyData which contains the lines { //We need the contour for the binary outline property as actor localStorage->m_Mapper->SetInput(localStorage->m_OutlinePolyData); localStorage->m_Actor->SetTexture(NULL); //no texture for contours bool binaryOutlineShadow( false ); datanode->GetBoolProperty( "outline binary shadow", binaryOutlineShadow, renderer ); if ( binaryOutlineShadow ) contourShadowActor->SetVisibility( true ); else contourShadowActor->SetVisibility( false ); } else { //Connect the mapper with the input texture. This is the standard case. //setup the textured plane this->GeneratePlane( renderer, sliceBounds ); //set the plane as input for the mapper localStorage->m_Mapper->SetInputConnection(localStorage->m_Plane->GetOutputPort()); //set the texture for the actor localStorage->m_Actor->SetTexture(localStorage->m_Texture); contourShadowActor->SetVisibility( false ); } // We have been modified => save this for next Update() localStorage->m_LastUpdateTime.Modified(); } void mitk::ImageVtkMapper2D::ApplyColor( mitk::BaseRenderer* renderer ) { LocalStorage *localStorage = this->GetLocalStorage( renderer ); // check for interpolation properties bool textureInterpolation = false; GetDataNode()->GetBoolProperty( "texture interpolation", textureInterpolation, renderer ); //set the interpolation modus according to the property localStorage->m_Texture->SetInterpolate(textureInterpolation); bool useColor = true; this->GetDataNode()->GetBoolProperty( "use color", useColor, renderer ); if( useColor ) { float rgb[3]= { 1.0f, 1.0f, 1.0f }; // check for color prop and use it for rendering if it exists // binary image hovering & binary image selection bool hover = false; bool selected = false; GetDataNode()->GetBoolProperty("binaryimage.ishovering", hover, renderer); GetDataNode()->GetBoolProperty("selected", selected, renderer); if(hover && !selected) { mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty ("binaryimage.hoveringcolor", renderer)); if(colorprop.IsNotNull()) { memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); } else { GetColor( rgb, renderer ); } } if(selected) { mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty ("binaryimage.selectedcolor", renderer)); if(colorprop.IsNotNull()) { memcpy(rgb, colorprop->GetColor().GetDataPointer(), 3*sizeof(float)); } else { GetColor( rgb, renderer ); } } if(!hover && !selected) { GetColor( rgb, renderer ); } double rgbConv[3] = {(double)rgb[0], (double)rgb[1], (double)rgb[2]}; //conversion to double for VTK dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0))->GetProperty()->SetColor(rgbConv); localStorage->m_Actor->GetProperty()->SetColor(rgbConv); } else { //If the user defines a lut, we dont want to use the color and take white instead. dynamic_cast (localStorage->m_Actors->GetParts()->GetItemAsObject(0))->GetProperty()->SetColor(1.0, 1.0, 1.0); localStorage->m_Actor->GetProperty()->SetColor(1.0, 1.0, 1.0); } 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 GetOpacity( opacity, renderer ); //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::ApplyLookuptable( mitk::BaseRenderer* renderer ) { bool binary = false; bool CTFcanBeApplied = false; this->GetDataNode()->GetBoolProperty( "binary", binary, renderer ); LocalStorage* localStorage = this->GetLocalStorage(renderer); //default lookuptable localStorage->m_Texture->SetLookupTable( localStorage->m_LookupTable ); if(binary) { //default lookuptable for binary images localStorage->m_Texture->GetLookupTable()->SetRange(0.0, 1.0); } else { bool useColor = true; this->GetDataNode()->GetBoolProperty( "use color", useColor, renderer ); if((!useColor)) { //BEGIN PROPERTY user-defined lut //currently we do not allow a lookuptable if it is a binary image // If lookup table use is requested... mitk::LookupTableProperty::Pointer LookupTableProp; LookupTableProp = dynamic_cast (this->GetDataNode()->GetProperty("LookupTable")); //...check if there is a lookuptable provided by the user if ( LookupTableProp.IsNotNull() ) { // If lookup table use is requested and supplied by the user: // only update the lut, when the properties have changed... if( LookupTableProp->GetLookupTable()->GetMTime() <= this->GetDataNode()->GetPropertyList()->GetMTime() ) { LookupTableProp->GetLookupTable()->ChangeOpacityForAll( LookupTableProp->GetLookupTable()->GetVtkLookupTable()->GetAlpha()*localStorage->m_Actor->GetProperty()->GetOpacity() ); LookupTableProp->GetLookupTable()->ChangeOpacity(0, 0.0); } //we use the user-defined lookuptable localStorage->m_Texture->SetLookupTable( LookupTableProp->GetLookupTable()->GetVtkLookupTable() ); } else { CTFcanBeApplied = true; } }//END PROPERTY user-defined lut LevelWindow levelWindow; this->GetLevelWindow( levelWindow, renderer ); //set up the lookuptable with the level window range localStorage->m_Texture->GetLookupTable()->SetRange( levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound() ); } //the color function can be applied if the user does not want to use color //and does not provide a lookuptable if(CTFcanBeApplied) { ApplyColorTransferFunction(renderer); } localStorage->m_Texture->SetInput( localStorage->m_ReslicedImage ); } void mitk::ImageVtkMapper2D::ApplyColorTransferFunction(mitk::BaseRenderer* renderer) { mitk::TransferFunctionProperty::Pointer transferFunctionProperty = dynamic_cast(this->GetDataNode()->GetProperty("Image Rendering.Transfer Function",renderer )); LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); if(transferFunctionProperty.IsNotNull()) { localStorage->m_Texture->SetLookupTable(transferFunctionProperty->GetValue()->GetColorTransferFunction()); } else { MITK_WARN << "Neither a lookuptable nor a transfer function is set and use color is off."; } } void mitk::ImageVtkMapper2D::ApplyRBGALevelWindow( mitk::BaseRenderer* renderer ) { LocalStorage* localStorage = this->GetLocalStorage( renderer ); //pass the LuT to the RBG filter localStorage->m_LevelWindowToRGBFilterObject->SetLookupTable(localStorage->m_Texture->GetLookupTable()); mitk::LevelWindow opacLevelWindow; if( this->GetLevelWindow( opacLevelWindow, renderer, "opaclevelwindow" ) ) {//pass the opaque level window to the filter localStorage->m_LevelWindowToRGBFilterObject->SetMinOpacity(opacLevelWindow.GetLowerWindowBound()); localStorage->m_LevelWindowToRGBFilterObject->SetMaxOpacity(opacLevelWindow.GetUpperWindowBound()); } else {//no opaque level window localStorage->m_LevelWindowToRGBFilterObject->SetMinOpacity(0.0); localStorage->m_LevelWindowToRGBFilterObject->SetMaxOpacity(255.0); } localStorage->m_LevelWindowToRGBFilterObject->SetInput(localStorage->m_ReslicedImage); //connect the texture with the output of the RGB filter localStorage->m_Texture->SetInputConnection(localStorage->m_LevelWindowToRGBFilterObject->GetOutputPort()); } void mitk::ImageVtkMapper2D::Update(mitk::BaseRenderer* renderer) { if ( !this->IsVisible( renderer ) ) { return; } mitk::Image* data = const_cast( this->GetInput() ); if ( data == NULL ) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep( renderer ); // Check if time step is valid const TimeSlicedGeometry *dataTimeGeometry = data->GetTimeSlicedGeometry(); if ( ( dataTimeGeometry == NULL ) || ( dataTimeGeometry->GetTimeSteps() == 0 ) || ( !dataTimeGeometry->IsValidTime( this->GetTimestep() ) ) ) { return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); //check if something important has changed and we need to rerender if ( (localStorage->m_LastUpdateTime < node->GetMTime()) //was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) //Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2DUpdateTime()) //was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2D()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) //was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime()) ) { this->GenerateDataForRenderer( renderer ); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } void mitk::ImageVtkMapper2D::SetDefaultProperties(mitk::DataNode* node, mitk::BaseRenderer* renderer, bool overwrite) { mitk::Image::Pointer image = dynamic_cast(node->GetData()); // Properties common for both images and segmentations node->AddProperty( "use color", mitk::BoolProperty::New( true ), renderer, overwrite ); node->AddProperty( "depthOffset", mitk::FloatProperty::New( 0.0 ), renderer, overwrite ); node->AddProperty( "outline binary", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty( "outline width", mitk::FloatProperty::New( 1.0 ), renderer, overwrite ); 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 ) ); std::string photometricInterpretation; // DICOM tag telling us how pixel values should be displayed if ( node->GetStringProperty( "dicom.pixel.PhotometricInterpretation", photometricInterpretation ) ) { // modality provided by DICOM or other reader if ( photometricInterpretation.find("MONOCHROME1") != std::string::npos ) // meaning: display MINIMUM pixels as WHITE { // generate LUT (white to black) mitk::LookupTable::Pointer mitkLut = mitk::LookupTable::New(); vtkLookupTable* bwLut = mitkLut->GetVtkLookupTable(); - bwLut->SetTableRange (0, 1); - bwLut->SetSaturationRange (0, 0); - bwLut->SetHueRange (0, 0); - bwLut->SetValueRange (1, 0); - bwLut->SetAlphaRange (1, 1); + bwLut->SetTableRange (0, 1); + bwLut->SetSaturationRange (0, 0); + bwLut->SetHueRange (0, 0); + bwLut->SetValueRange (1, 0); + bwLut->SetAlphaRange (1, 1); bwLut->SetRampToLinear(); bwLut->Build(); mitk::LookupTableProperty::Pointer mitkLutProp = mitk::LookupTableProperty::New(); mitkLutProp->SetLookupTable(mitkLut); node->SetProperty( "LookupTable", mitkLutProp ); node->SetProperty( "use color", mitk::BoolProperty::New( false ), renderer ); } else if ( photometricInterpretation.find("MONOCHROME2") != std::string::npos ) // meaning: display MINIMUM pixels as BLACK { // apply default LUT (black to white) node->SetProperty( "use color", mitk::BoolProperty::New( true ), renderer ); node->SetProperty( "color", mitk::ColorProperty::New( 1,1,1 ), renderer ); } // PALETTE interpretation should be handled ok by RGB loading } bool isBinaryImage(false); if ( ! node->GetBoolProperty("binary", isBinaryImage) ) { // ok, property is not set, use heuristic to determine if this // is a binary image mitk::Image::Pointer centralSliceImage; ScalarType minValue = 0.0; ScalarType maxValue = 0.0; ScalarType min2ndValue = 0.0; ScalarType max2ndValue = 0.0; mitk::ImageSliceSelector::Pointer sliceSelector = mitk::ImageSliceSelector::New(); sliceSelector->SetInput(image); sliceSelector->SetSliceNr(image->GetDimension(2)/2); sliceSelector->SetTimeNr(image->GetDimension(3)/2); sliceSelector->SetChannelNr(image->GetDimension(4)/2); sliceSelector->Update(); centralSliceImage = sliceSelector->GetOutput(); if ( centralSliceImage.IsNotNull() && centralSliceImage->IsInitialized() ) { minValue = centralSliceImage->GetStatistics()->GetScalarValueMin(); maxValue = centralSliceImage->GetStatistics()->GetScalarValueMax(); min2ndValue = centralSliceImage->GetStatistics()->GetScalarValue2ndMin(); max2ndValue = centralSliceImage->GetStatistics()->GetScalarValue2ndMax(); } if ((maxValue == min2ndValue && minValue == max2ndValue) || minValue == maxValue) { // centralSlice is strange, lets look at all data minValue = image->GetStatistics()->GetScalarValueMin(); maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(); min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(); max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(); } isBinaryImage = ( maxValue == min2ndValue && minValue == max2ndValue ); } // some more properties specific for a binary... if (isBinaryImage) { node->AddProperty( "opacity", mitk::FloatProperty::New(0.3f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.selectedannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binaryimage.hoveringannotationcolor", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( true ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(10), renderer, overwrite); } else //...or image type object { node->AddProperty( "opacity", mitk::FloatProperty::New(1.0f), renderer, overwrite ); node->AddProperty( "color", ColorProperty::New(1.0,1.0,1.0), renderer, overwrite ); node->AddProperty( "binary", mitk::BoolProperty::New( false ), renderer, overwrite ); node->AddProperty("layer", mitk::IntProperty::New(0), renderer, overwrite); } if(image.IsNotNull() && image->IsInitialized()) { if((overwrite) || (node->GetProperty("levelwindow", renderer)==NULL)) { /* initialize level/window from DICOM tags */ std::string sLevel; std::string sWindow; if ( image->GetPropertyList()->GetStringProperty( "dicom.voilut.WindowCenter", sLevel ) && image->GetPropertyList()->GetStringProperty( "dicom.voilut.WindowWidth", sWindow ) ) { float level = atof( sLevel.c_str() ); float window = atof( sWindow.c_str() ); mitk::LevelWindow contrast; std::string sSmallestPixelValueInSeries; std::string sLargestPixelValueInSeries; if ( image->GetPropertyList()->GetStringProperty( "dicom.series.SmallestPixelValueInSeries", sSmallestPixelValueInSeries ) && image->GetPropertyList()->GetStringProperty( "dicom.series.LargestPixelValueInSeries", sLargestPixelValueInSeries ) ) { float smallestPixelValueInSeries = atof( sSmallestPixelValueInSeries.c_str() ); float largestPixelValueInSeries = atof( sLargestPixelValueInSeries.c_str() ); contrast.SetRangeMinMax( smallestPixelValueInSeries-1, largestPixelValueInSeries+1 ); // why not a little buffer? // might remedy some l/w widget challenges } else { contrast.SetAuto( static_cast(node->GetData()), false, true ); // we need this as a fallback } contrast.SetLevelWindow( level, window, true ); node->SetProperty( "levelwindow", LevelWindowProperty::New( contrast ), renderer ); } } if(((overwrite) || (node->GetProperty("opaclevelwindow", renderer)==NULL)) && (image->GetPixelType().GetPixelTypeId() == itk::ImageIOBase::RGBA) && (image->GetPixelType().GetTypeId() == typeid( unsigned char)) ) { mitk::LevelWindow opaclevwin; opaclevwin.SetRangeMinMax(0,255); opaclevwin.SetWindowBounds(0,255); mitk::LevelWindowProperty::Pointer prop = mitk::LevelWindowProperty::New(opaclevwin); node->SetProperty( "opaclevelwindow", prop, renderer ); } if((overwrite) || (node->GetProperty("LookupTable", renderer)==NULL)) { // add a default rainbow lookup table for color mapping mitk::LookupTable::Pointer mitkLut = mitk::LookupTable::New(); vtkLookupTable* vtkLut = mitkLut->GetVtkLookupTable(); vtkLut->SetHueRange(0.6667, 0.0); vtkLut->SetTableRange(0.0, 20.0); vtkLut->Build(); mitk::LookupTableProperty::Pointer mitkLutProp = mitk::LookupTableProperty::New(); mitkLutProp->SetLookupTable(mitkLut); node->SetProperty( "LookupTable", mitkLutProp ); } } Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::ImageVtkMapper2D::LocalStorage* mitk::ImageVtkMapper2D::GetLocalStorage(mitk::BaseRenderer* renderer) { return m_LSH.GetLocalStorage(renderer); } vtkSmartPointer mitk::ImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer* renderer ){ LocalStorage* localStorage = this->GetLocalStorage(renderer); //get the min and max index values of each direction int* extent = localStorage->m_ReslicedImage->GetExtent(); int xMin = extent[0]; int xMax = extent[1]; int yMin = extent[2]; int yMax = extent[3]; int* dims = localStorage->m_ReslicedImage->GetDimensions(); //dimensions of the image int line = dims[0]; //how many pixels per line? int x = xMin; //pixel index x int y = yMin; //pixel index y char* currentPixel; //get the depth for each contour float depth = CalculateLayerDepth(renderer); vtkSmartPointer points = vtkSmartPointer::New(); //the points to draw vtkSmartPointer lines = vtkSmartPointer::New(); //the lines to connect the points // We take the pointer to the first pixel of the image currentPixel = static_cast(localStorage->m_ReslicedImage->GetScalarPointer() ); while (y <= yMax) { //if the current pixel value is set to something if ((currentPixel) && (*currentPixel != 0)) { //check in which direction a line is necessary //a line is added if the neighbor of the current pixel has the value 0 //and if the pixel is located at the edge of the image //if vvvvv not the first line vvvvv if (y > yMin && *(currentPixel-line) == 0) { //x direction - bottom edge of the pixel //add the 2 points vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); //add the line between both points lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } //if vvvvv not the last line vvvvv if (y < yMax && *(currentPixel+line) == 0) { //x direction - top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } //if vvvvv not the first pixel vvvvv if ( (x > xMin || y > yMin) && *(currentPixel-1) == 0) { //y direction - left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } //if vvvvv not the last pixel vvvvv if ( (y < yMax || (x < xMax) ) && *(currentPixel+1) == 0) { //y direction - right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } /* now consider pixels at the edge of the image */ //if vvvvv left edge of image vvvvv if (x == xMin) { //draw left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } //if vvvvv right edge of image vvvvv if (x == xMax) { //draw right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } //if vvvvv bottom edge of image vvvvv if (y == yMin) { //draw bottom edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], y*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } //if vvvvv top edge of image vvvvv if (y == yMax) { //draw top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x+1)*localStorage->m_mmPerPixel[0], (y+1)*localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } }//end if currentpixel is set x++; if (x > xMax) { //reached end of line x = xMin; y++; } // Increase the pointer-position to the next pixel. // This is safe, as the while-loop and the x-reset logic above makes // sure we do not exceed the bounds of the image currentPixel++; }//end of while // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); return polyData; } void mitk::ImageVtkMapper2D::TransformActor(mitk::BaseRenderer* renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); //get the transformation matrix of the reslicer in order to render the slice as axial, coronal or saggital vtkSmartPointer trans = vtkSmartPointer::New(); vtkSmartPointer matrix = localStorage->m_Reslicer->GetResliceAxes(); trans->SetMatrix(matrix); //transform the plane/contour (the actual actor) to the corresponding view (axial, coronal or saggital) localStorage->m_Actor->SetUserTransform(trans); //transform the origin to center based coordinates, because MITK is center based. localStorage->m_Actor->SetPosition( -0.5*localStorage->m_mmPerPixel[0], -0.5*localStorage->m_mmPerPixel[1], 0.0); if ( localStorage->m_Actors->GetNumberOfPaths() > 1 ) { vtkActor* secondaryActor = dynamic_cast( localStorage->m_Actors->GetParts()->GetItemAsObject(0) ); secondaryActor->SetUserTransform(trans); secondaryActor->SetPosition( -0.5*localStorage->m_mmPerPixel[0], -0.5*localStorage->m_mmPerPixel[1], 0.0); } } bool mitk::ImageVtkMapper2D::RenderingGeometryIntersectsImage( const Geometry2D* renderingGeometry, SlicedGeometry3D* imageGeometry ) { // if either one of the two geometries is NULL we return true // for safety reasons if ( renderingGeometry == NULL || imageGeometry == NULL ) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance( imageGeometry->GetCornerPoint( 0 ) ); for( int i=1; i<8; i++ ) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint( i ); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance( cornerPoint ); // if it has not the same signing as the distance of the first point if ( initialDistance * distance < 0 ) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } mitk::ImageVtkMapper2D::LocalStorage::LocalStorage() { //Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); m_Texture = vtkSmartPointer::New(); m_LookupTable = vtkSmartPointer::New(); m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_Actors = vtkSmartPointer::New(); m_Reslicer = mitk::ExtractSliceFilter::New(); m_TSFilter = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_ReslicedImage = vtkSmartPointer::New(); m_EmptyPolyData = vtkSmartPointer::New(); //the following actions are always the same and thus can be performed //in the constructor for each image (i.e. the image-corresponding local storage) m_TSFilter->ReleaseDataFlagOn(); //built a default lookuptable m_LookupTable->SetRampToLinear(); m_LookupTable->SetSaturationRange( 0.0, 0.0 ); m_LookupTable->SetHueRange( 0.0, 0.0 ); m_LookupTable->SetValueRange( 0.0, 1.0 ); m_LookupTable->Build(); //map all black values to transparent m_LookupTable->SetTableValue(0, 0.0, 0.0, 0.0, 0.0); //do not repeat the texture (the image) m_Texture->RepeatOff(); //set the mapper for the actor m_Actor->SetMapper( m_Mapper ); vtkSmartPointer outlineShadowActor = vtkSmartPointer::New(); outlineShadowActor->SetMapper( m_Mapper ); m_Actors->AddPart( outlineShadowActor ); m_Actors->AddPart( m_Actor ); //filter for RGB(A) images m_LevelWindowToRGBFilterObject = new vtkMitkApplyLevelWindowToRGBFilter(); }