diff --git a/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml b/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml index c8e22f51d0..8151f5a463 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/classicreader.xml @@ -1,6 +1,7 @@ + class="ClassicDICOMSeriesReader" + version="1"> diff --git a/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml b/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml index 81ce92b3d4..5307c094f5 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/imageposition.xml @@ -1,24 +1,25 @@ diff --git a/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml b/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml index 0aea5c2037..02309cdfb8 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/imageposition_byacquisition.xml @@ -1,26 +1,26 @@ diff --git a/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml b/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml index 67cd452768..4de03a0d7a 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/imagetime.xml @@ -1,23 +1,24 @@ diff --git a/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml b/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml index 556b668389..e8813cbb00 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/instancenumber.xml @@ -1,23 +1,24 @@ diff --git a/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml b/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml index b30ab99907..4023c92764 100644 --- a/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml +++ b/Modules/DICOMReader/Resources/configurations/3D/slicelocation.xml @@ -1,23 +1,24 @@ diff --git a/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml b/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml index c5df7b2951..0f382e3a0d 100644 --- a/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml +++ b/Modules/DICOMReader/Resources/configurations/3DnT/classicreader.xml @@ -1,20 +1,21 @@ diff --git a/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp b/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp index 3815c07bc4..f0b6da45ed 100644 --- a/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp +++ b/Modules/DICOMReader/mitkDICOMReaderConfigurator.cpp @@ -1,620 +1,637 @@ /*=================================================================== 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 "mitkDICOMReaderConfigurator.h" #include "mitkDICOMSortByTag.h" #include "mitkSortByImagePositionPatient.h" mitk::DICOMReaderConfigurator ::DICOMReaderConfigurator() { } mitk::DICOMReaderConfigurator ::~DICOMReaderConfigurator() { } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromConfigFile(const std::string& filename) const { TiXmlDocument doc (filename); if (doc.LoadFile()) { return this->CreateFromTiXmlDocument( doc ); } else { MITK_ERROR << "Unable to load file at '" << filename <<"'"; return DICOMFileReader::Pointer(); } } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromUTF8ConfigString(const std::string& xmlContents) const { TiXmlDocument doc; doc.Parse(xmlContents.c_str(), 0, TIXML_ENCODING_UTF8); return this->CreateFromTiXmlDocument( doc ); } mitk::DICOMFileReader::Pointer mitk::DICOMReaderConfigurator ::CreateFromTiXmlDocument(TiXmlDocument& doc) const { TiXmlHandle root(doc.RootElement()); if (TiXmlElement* rootElement = root.ToElement()) { if (strcmp(rootElement->Value(), "DICOMFileReader")) // :-( no std::string methods { MITK_ERROR << "File should contain a tag at top-level! Found '" << (rootElement->Value() ? std::string(rootElement->Value()) : std::string("!nothing!")) << "' instead"; return NULL; } const char* classnameC = rootElement->Attribute("class"); if (!classnameC) { MITK_ERROR << "File should name a reader class in the class attribute: . Found nothing instead"; return NULL; } + int version(1); + if ( rootElement->QueryIntAttribute("version", &version) == TIXML_SUCCESS) + { + if (version != 1) + { + MITK_WARN << "This reader is only capable of creating DICOMFileReaders of version 1. " + << "Will not continue, because given configuration is meant for version " << version << "."; + return NULL; + } + } + else + { + MITK_ERROR << "File should name the version of the reader class in the version attribute: ." + << " Found nothing instead, assuming version 1!"; + version = 1; + } + std::string classname(classnameC); double decimalPlacesForOrientation(5); bool useDecimalPlacesForOrientation(false); useDecimalPlacesForOrientation = rootElement->QueryDoubleAttribute("decimalPlacesForOrientation", &decimalPlacesForOrientation) == TIXML_SUCCESS; // attribute present and a double value if (classname == "ClassicDICOMSeriesReader") { mitk::ClassicDICOMSeriesReader::Pointer reader = mitk::ClassicDICOMSeriesReader::New(); return reader.GetPointer(); } if (classname == "ThreeDnTDICOMSeriesReader") { mitk::ThreeDnTDICOMSeriesReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::ThreeDnTDICOMSeriesReader::New(decimalPlacesForOrientation); else reader = mitk::ThreeDnTDICOMSeriesReader::New(); return ConfigureThreeDnTDICOMSeriesReader(reader, rootElement).GetPointer(); } else if (classname == "DICOMITKSeriesGDCMReader") { mitk::DICOMITKSeriesGDCMReader::Pointer reader; if (useDecimalPlacesForOrientation) reader = mitk::DICOMITKSeriesGDCMReader::New(decimalPlacesForOrientation); else reader = mitk::DICOMITKSeriesGDCMReader::New(); return ConfigureDICOMITKSeriesGDCMReader(reader, rootElement).GetPointer(); } else { MITK_ERROR << "DICOMFileReader tag names unknown class '" << classname << "'"; return NULL; } } else { MITK_ERROR << "Great confusion: no root element in XML document. Expecting a DICOMFileReader tag at top-level."; return NULL; } } #define boolStringTrue(s) \ ( s == "true" || s == "on" || s == "1" \ || s == "TRUE" || s == "ON") mitk::ThreeDnTDICOMSeriesReader::Pointer mitk::DICOMReaderConfigurator ::ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement* element) const { assert(element); // use all the base class configuration if (this->ConfigureDICOMITKSeriesGDCMReader( reader.GetPointer(), element ).IsNull()) { return NULL; } // add the "group3DnT" flag bool group3DnT(true); const char* group3DnTC = element->Attribute("group3DnT"); if (group3DnTC) { std::string group3DnTS(group3DnTC); group3DnT = boolStringTrue(group3DnTS); } reader->SetGroup3DandT( group3DnT ); return reader; } mitk::DICOMITKSeriesGDCMReader::Pointer mitk::DICOMReaderConfigurator ::ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement* element) const { assert(element); const char* configLabelC = element->Attribute("label"); if (configLabelC) { std::string configLabel(configLabelC); reader->SetConfigurationLabel(configLabel); } const char* configDescriptionC = element->Attribute("description"); if (configDescriptionC) { std::string configDescription(configDescriptionC); reader->SetConfigurationDescription(configDescriptionC); } // "fixTiltByShearing" flag bool fixTiltByShearing(false); const char* fixTiltByShearingC = element->Attribute("fixTiltByShearing"); if (fixTiltByShearingC) { std::string fixTiltByShearingS(fixTiltByShearingC); fixTiltByShearing = boolStringTrue(fixTiltByShearingS); } reader->SetFixTiltByShearing( fixTiltByShearing ); // "toleratedOriginError" attribute (double) bool toleratedOriginErrorIsAbsolute(false); const char* toleratedOriginErrorIsAbsoluteC = element->Attribute("toleratedOriginErrorIsAbsolute"); if (toleratedOriginErrorIsAbsoluteC) { std::string toleratedOriginErrorIsAbsoluteS(toleratedOriginErrorIsAbsoluteC); toleratedOriginErrorIsAbsolute = boolStringTrue(toleratedOriginErrorIsAbsoluteS); } double toleratedOriginError(0.3); if (element->QueryDoubleAttribute("toleratedOriginError", &toleratedOriginError) == TIXML_SUCCESS) // attribute present and a double value { if (toleratedOriginErrorIsAbsolute) { reader->SetToleratedOriginOffset( toleratedOriginError ); } else { reader->SetToleratedOriginOffsetToAdaptive( toleratedOriginError ); } } // DICOMTagBasedSorters are the only thing we create at this point // TODO for-loop over all child elements of type DICOMTagBasedSorter, BUT actually a single sorter of this type is enough. TiXmlElement* dElement = element->FirstChildElement("DICOMDatasetSorter"); if (dElement) { const char* classnameC = dElement->Attribute("class"); if (!classnameC) { MITK_ERROR << "File should name a DICOMDatasetSorter class in the class attribute of . Found nothing instead"; return NULL; } std::string classname(classnameC); if (classname == "DICOMTagBasedSorter") { DICOMTagBasedSorter::Pointer tagSorter = CreateDICOMTagBasedSorter(dElement); if (tagSorter.IsNotNull()) { reader->AddSortingElement( tagSorter ); } } else { MITK_ERROR << "DICOMDatasetSorter tag names unknown class '" << classname << "'"; return NULL; } } return reader; } mitk::DICOMTagBasedSorter::Pointer mitk::DICOMReaderConfigurator ::CreateDICOMTagBasedSorter(TiXmlElement* element) const { mitk::DICOMTagBasedSorter::Pointer tagSorter = mitk::DICOMTagBasedSorter::New(); TiXmlElement* dElement = element->FirstChildElement("Distinguishing"); if (dElement) { for ( TiXmlElement* tChild = dElement->FirstChildElement(); tChild != NULL; tChild = tChild->NextSiblingElement() ) { try { mitk::DICOMTag tag = tagFromXMLElement(tChild); int i(5); if (tChild->QueryIntAttribute("cutDecimalPlaces", &i) == TIXML_SUCCESS) { tagSorter->AddDistinguishingTag( tag, new mitk::DICOMTagBasedSorter::CutDecimalPlaces(i) ); } else { tagSorter->AddDistinguishingTag( tag ); } } catch(...) { return NULL; } } } // "sorting tags" TiXmlElement* sElement = element->FirstChildElement("Sorting"); if (sElement) { DICOMSortCriterion::Pointer previousCriterion; DICOMSortCriterion::Pointer currentCriterion; for ( TiXmlNode* tChildNode = sElement->LastChild(); tChildNode != NULL; tChildNode = tChildNode->PreviousSibling() ) { TiXmlElement* tChild = tChildNode->ToElement(); if (!tChild) continue; if (!strcmp(tChild->Value(), "Tag")) { try { currentCriterion = this->CreateDICOMSortByTag(tChild, previousCriterion); } catch(...) { std::stringstream ss; ss << "Could not parse element at (input line " << tChild->Row() << ", col. " << tChild->Column() << ")!"; MITK_ERROR << ss.str(); return NULL; } } else if (!strcmp(tChild->Value(), "ImagePositionPatient")) { try { currentCriterion = this->CreateSortByImagePositionPatient(tChild, previousCriterion); } catch(...) { std::stringstream ss; ss << "Could not parse element at (input line " << tChild->Row() << ", col. " << tChild->Column() << ")!"; MITK_ERROR << ss.str(); return NULL; } } else { MITK_ERROR << "File contain unknown tag <" << tChild->Value() << "> tag as child to ! Cannot interpret..."; } previousCriterion = currentCriterion; } tagSorter->SetSortCriterion( currentCriterion.GetPointer() ); } return tagSorter; } std::string mitk::DICOMReaderConfigurator ::requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) const { assert(xmlElement); const char* gC = xmlElement->Attribute(key.c_str()); if (gC) { std::string gS(gC); return gS; } else { std::stringstream ss; ss << "Expected an attribute '" << key << "' at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } unsigned int mitk::DICOMReaderConfigurator ::hexStringToUInt(const std::string& s) const { std::stringstream converter(s); unsigned int ui; converter >> std::hex >> ui; MITK_DEBUG << "Converted string '" << s << "' to unsigned int " << ui; return ui; } mitk::DICOMTag mitk::DICOMReaderConfigurator ::tagFromXMLElement(TiXmlElement* xmlElement) const { assert(xmlElement); if (strcmp(xmlElement->Value(), "Tag")) // :-( no std::string methods { std::stringstream ss; ss << "Expected a tag at this position " "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } std::string name = requiredStringAttribute(xmlElement, "name"); std::string groupS = requiredStringAttribute(xmlElement, "group"); std::string elementS = requiredStringAttribute(xmlElement, "element"); try { // convert string to int (assuming string is in hex format with leading "0x" like "0x0020") unsigned int group = hexStringToUInt(groupS); unsigned int element = hexStringToUInt(elementS); return DICOMTag(group, element); } catch(...) { std::stringstream ss; ss << "Expected group and element values in to be hexadecimal with leading 0x, e.g. '0x0020'" "(input line " << xmlElement->Row() << ", col. " << xmlElement->Column() << ")!"; MITK_ERROR << ss.str(); throw std::invalid_argument( ss.str() ); } } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator ::CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const { mitk::DICOMTag tag = tagFromXMLElement(xmlElement); return DICOMSortByTag::New(tag, secondaryCriterion).GetPointer(); } mitk::DICOMSortCriterion::Pointer mitk::DICOMReaderConfigurator ::CreateSortByImagePositionPatient(TiXmlElement*, DICOMSortCriterion::Pointer secondaryCriterion) const { return SortByImagePositionPatient::New(secondaryCriterion).GetPointer(); } std::string mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(DICOMFileReader::ConstPointer reader) const { // check possible sub-classes from the most-specific one up to the most generic one const DICOMFileReader* cPointer = reader; TiXmlElement* root; if (const ClassicDICOMSeriesReader* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else if (const ThreeDnTDICOMSeriesReader* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else if (const DICOMITKSeriesGDCMReader* specificReader = dynamic_cast(cPointer)) { root = this->CreateConfigStringFromReader(specificReader); } else { MITK_WARN << "Unknown reader class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; return ""; // no serialization, what a pity } if (root) { TiXmlDocument document; document.LinkEndChild( root ); TiXmlPrinter printer; printer.SetIndent( " " ); document.Accept( &printer ); std::string xmltext = printer.CStr(); return xmltext; } else { MITK_WARN << "DICOMReaderConfigurator::CreateConfigStringFromReader() created empty serialization. Problem?"; return ""; } } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const DICOMITKSeriesGDCMReader* reader) const { TiXmlElement* root = this->CreateDICOMFileReaderTag(reader); assert(root); root->SetAttribute("fixTiltByShearing", toString(reader->GetFixTiltByShearing())); root->SetDoubleAttribute("toleratedOriginError", reader->GetToleratedOriginError()); root->SetAttribute("toleratedOriginErrorIsAbsolute", toString(reader->IsToleratedOriginOffsetAbsolute())); root->SetDoubleAttribute("decimalPlacesForOrientation", reader->GetDecimalPlacesForOrientation()); // iterate DICOMDatasetSorter objects DICOMITKSeriesGDCMReader::ConstSorterList sorterList = reader->GetFreelyConfiguredSortingElements(); for(DICOMITKSeriesGDCMReader::ConstSorterList::const_iterator sorterIter = sorterList.begin(); sorterIter != sorterList.end(); ++sorterIter) { const DICOMDatasetSorter* sorter = *sorterIter; if (const DICOMTagBasedSorter* specificSorter = dynamic_cast(sorter)) { TiXmlElement* sorterTag = this->CreateConfigStringFromDICOMDatasetSorter(specificSorter); root->LinkEndChild(sorterTag); } else { MITK_WARN << "Unknown DICOMDatasetSorter class passed to DICOMReaderConfigurator::CreateConfigStringFromReader(). Cannot serialize."; return NULL; } } return root; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromDICOMDatasetSorter(const DICOMTagBasedSorter* sorter) const { assert(sorter); TiXmlElement* sorterTag = new TiXmlElement("DICOMDatasetSorter"); sorterTag->SetAttribute("class", sorter->GetNameOfClass()); TiXmlElement* distinguishingTagsElement = new TiXmlElement("Distinguishing"); sorterTag->LinkEndChild(distinguishingTagsElement); mitk::DICOMTagList distinguishingTags = sorter->GetDistinguishingTags(); for (DICOMTagList::iterator tagIter = distinguishingTags.begin(); tagIter != distinguishingTags.end(); ++tagIter) { TiXmlElement* tag = this->CreateConfigStringFromDICOMTag(*tagIter); distinguishingTagsElement->LinkEndChild(tag); const DICOMTagBasedSorter::TagValueProcessor* processor = sorter->GetTagValueProcessorForDistinguishingTag(*tagIter); if (const DICOMTagBasedSorter::CutDecimalPlaces* specificProcessor = dynamic_cast(processor)) { tag->SetDoubleAttribute("cutDecimalPlaces", specificProcessor->GetPrecision()); } } TiXmlElement* sortingElement = new TiXmlElement("Sorting"); sorterTag->LinkEndChild(sortingElement); mitk::DICOMSortCriterion::ConstPointer sortCriterion = sorter->GetSortCriterion(); while (sortCriterion.IsNotNull()) { std::string classname = sortCriterion->GetNameOfClass(); if (classname == "SortByImagePositionPatient") { sortingElement->LinkEndChild( new TiXmlElement("ImagePositionPatient") ); // no parameters } else if (classname == "DICOMSortByTag") { DICOMTagList pseudoTagList = sortCriterion->GetTagsOfInterest(); if (pseudoTagList.size() == 1) { DICOMTag firstTag = pseudoTagList.front(); TiXmlElement* tagElement = this->CreateConfigStringFromDICOMTag(firstTag); sortingElement->LinkEndChild( tagElement ); } else { MITK_ERROR << "Encountered SortByTag class with MULTIPLE tag in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; return NULL; } } else { MITK_ERROR << "Encountered unknown class '" << classname << "' in CreateConfigStringFromDICOMDatasetSorter. Cannot serialize."; return NULL; } sortCriterion = sortCriterion->GetSecondaryCriterion(); } return sorterTag; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromDICOMTag(const DICOMTag& tag) const { TiXmlElement* tagElement = new TiXmlElement("Tag"); // name group element tagElement->SetAttribute("name", tag.GetName().c_str()); tagElement->SetAttribute("group", toHexString(tag.GetGroup())); tagElement->SetAttribute("element", toHexString(tag.GetElement())); return tagElement; } std::string mitk::DICOMReaderConfigurator ::toHexString(unsigned int i) const { std::stringstream ss; ss << "0x" << std::setfill ('0') << std::setw(4) << std::hex << i; return ss.str(); } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const ThreeDnTDICOMSeriesReader* reader) const { TiXmlElement* root = this->CreateConfigStringFromReader(static_cast(reader)); assert(root); root->SetAttribute("group3DnT", toString(reader->GetGroup3DandT())); return root; } const char* mitk::DICOMReaderConfigurator ::toString(bool b) const { return b ? "true" : "false"; } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateConfigStringFromReader(const ClassicDICOMSeriesReader* reader) const { return this->CreateDICOMFileReaderTag(reader); } TiXmlElement* mitk::DICOMReaderConfigurator ::CreateDICOMFileReaderTag(const DICOMFileReader* reader) const { TiXmlElement* readerTag = new TiXmlElement("DICOMFileReader"); readerTag->SetAttribute("class", reader->GetNameOfClass()); readerTag->SetAttribute("label", reader->GetConfigurationLabel().c_str()); readerTag->SetAttribute("description", reader->GetConfigurationDescription().c_str()); readerTag->SetAttribute("version", "1"); return readerTag; }