diff --git a/Modules/CEST/autoload/IO/files.cmake b/Modules/CEST/autoload/IO/files.cmake index 8864bec575..d6dda67b3b 100644 --- a/Modules/CEST/autoload/IO/files.cmake +++ b/Modules/CEST/autoload/IO/files.cmake @@ -1,9 +1,10 @@ set(CPP_FILES mitkCESTDICOMReaderService.cpp + mitkCESTGenericDICOMReaderService.cpp mitkCESTIOMimeTypes.cpp mitkCESTIOActivator.cpp ) set(RESOURCE_FILES cest_DKFZ.xml ) diff --git a/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp new file mode 100644 index 0000000000..bd6f8fa1a4 --- /dev/null +++ b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.cpp @@ -0,0 +1,261 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkCESTGenericDICOMReaderService.h" + +#include "mitkIOMimeTypes.h" +#include +#include +#include +#include "mitkCESTImageNormalizationFilter.h" + +#include "itksys/SystemTools.hxx" + +#include +#include +#include + +#include +#include + +namespace mitk +{ + CESTDICOMManualReaderService::CESTDICOMManualReaderService(const CustomMimeType& mimeType, const std::string& description) + : BaseDICOMReaderService(mimeType, description) + { + IFileIO::Options options; + options["B1 amplitude"] = 0.0; + options["CEST frequency [Hz]"] = 0.0; + options["Pulse duration [us]"] = 0.0; + options["Duty cycle [%]"] = 0.0; + std::vector normalizationStrategy; + normalizationStrategy.push_back("Automatic"); + normalizationStrategy.push_back("No"); + options["Normalize data"] = normalizationStrategy; + + this->SetDefaultOptions(options); + + this->RegisterService(); + } + + CESTDICOMManualReaderService::CESTDICOMManualReaderService(const mitk::CESTDICOMManualReaderService& other) + : BaseDICOMReaderService(other) + { + } + + void ExtractOptionFromPropertyTree(const std::string& key, boost::property_tree::ptree& root, std::map& options) + { + auto finding = root.find(key); + if (finding != root.not_found()) + { + options[key] = finding->second.get_value(); + } + } + + IFileIO::Options ExtractOptionsFromFile(const std::string& file) + { + boost::property_tree::ptree root; + + try + { + boost::property_tree::read_json(file, root, std::locale("C")); + } + catch (const boost::property_tree::json_parser_error & e) + { + MITK_WARN << "Could not parse CEST meta file. Fall back to default values. Error was:\n" << e.what(); + } + + IFileIO::Options options; + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_FREQ(),root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_B1Amplitude(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_PULSEDURATION(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_DutyCycle(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_OFFSETS(), root, options); + ExtractOptionFromPropertyTree(CEST_PROPERTY_NAME_TREC(), root, options); + + return options; + } + + void TransferOption(const mitk::IFileIO::Options& sourceOptions, const std::string& sourceName, mitk::IFileIO::Options& options, const std::string& newName) + { + auto sourceFinding = sourceOptions.find(sourceName); + auto finding = options.find(newName); + + bool replaceValue = finding == options.end(); + if (!replaceValue) + { + replaceValue = us::any_cast(finding->second) == 0.; + } + + if (sourceFinding != sourceOptions.end() && us::any_cast(sourceFinding->second) != 0. && replaceValue) + { + options[newName] = sourceFinding->second; + } + } + + std::string CESTDICOMManualReaderService::GetCESTMetaFilePath() const + { + auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); + std::string metafile = dir + "/" + "CEST_META.json"; + return metafile; + } + + std::string CESTDICOMManualReaderService::GetTRECFilePath() const + { + auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); + std::string metafile = dir + "/" + "TREC.txt"; + return metafile; + } + + std::string CESTDICOMManualReaderService::GetLISTFilePath() const + { + auto dir = itksys::SystemTools::GetFilenamePath(this->GetInputLocation()); + std::string metafile = dir + "/" + "LIST.txt"; + return metafile; + } + + + IFileIO::Options CESTDICOMManualReaderService::GetOptions() const + { + auto options = AbstractFileReader::GetOptions(); + if (!this->GetInputLocation().empty()) + { + auto fileOptions = ExtractOptionsFromFile(this->GetCESTMetaFilePath()); + + TransferOption(fileOptions, CEST_PROPERTY_NAME_FREQ(), options, "CEST frequency [Hz]"); + TransferOption(fileOptions, CEST_PROPERTY_NAME_B1Amplitude(), options, "B1 amplitude"); + TransferOption(fileOptions, CEST_PROPERTY_NAME_PULSEDURATION(), options, "Pulse duration [us]"); + TransferOption(fileOptions, CEST_PROPERTY_NAME_DutyCycle(), options, "Duty cycle [%]"); + } + return options; + } + + us::Any CESTDICOMManualReaderService::GetOption(const std::string& name) const + { + this->GetOptions(); //ensure (default) options are set. + return AbstractFileReader::GetOption(name); + } + + DICOMFileReader::Pointer CESTDICOMManualReaderService::GetReader(const mitk::StringList& relevantFiles) const + { + mitk::DICOMFileReaderSelector::Pointer selector = mitk::DICOMFileReaderSelector::New(); + + selector->LoadBuiltIn3DnTConfigs(); + selector->SetInputFiles(relevantFiles); + + mitk::DICOMFileReader::Pointer reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); + if (reader.IsNotNull()) + { + //reset tag cache to ensure that additional tags of interest + //will be regarded by the reader if set later on. + reader->SetTagCache(nullptr); + } + + return reader; + }; + + std::vector> CESTDICOMManualReaderService::Read() + { + std::vector result; + std::vector dicomResult = BaseDICOMReaderService::Read(); + + const Options userOptions = this->GetOptions(); + + const std::string normalizationStrategy = userOptions.find("Normalize data")->second.ToString(); + + for (auto &item : dicomResult) + { + auto fileOptions = ExtractOptionsFromFile(this->GetCESTMetaFilePath()); + IFileIO::Options options; + TransferOption(userOptions, "CEST frequency [Hz]", options, CEST_PROPERTY_NAME_FREQ()); + TransferOption(userOptions, "B1 amplitude", options, CEST_PROPERTY_NAME_B1Amplitude()); + TransferOption(userOptions, "Pulse duration [us]", options, CEST_PROPERTY_NAME_PULSEDURATION()); + TransferOption(userOptions, "Duty cycle [%]", options, CEST_PROPERTY_NAME_DutyCycle()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_FREQ(), options, CEST_PROPERTY_NAME_FREQ()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_B1Amplitude(), options, CEST_PROPERTY_NAME_B1Amplitude()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_PULSEDURATION(), options, CEST_PROPERTY_NAME_PULSEDURATION()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_DutyCycle(), options, CEST_PROPERTY_NAME_DutyCycle()); + + TransferOption(fileOptions, CEST_PROPERTY_NAME_OFFSETS(), options, CEST_PROPERTY_NAME_OFFSETS()); + TransferOption(fileOptions, CEST_PROPERTY_NAME_TREC(), options, CEST_PROPERTY_NAME_TREC()); + + auto trecValues = CustomTagParser::ReadListFromFile(this->GetTRECFilePath()); + auto offsetValues = CustomTagParser::ReadListFromFile(this->GetLISTFilePath()); + + bool isCEST = !offsetValues.empty(); + bool isT1 = !trecValues.empty(); + + if (!isCEST && !isT1) + {//check if there are settings in the metafile + auto finding = fileOptions.find(CEST_PROPERTY_NAME_OFFSETS()); + if (finding != fileOptions.end()) + { + isCEST = true; + offsetValues = finding->second.ToString(); + }; + + finding = fileOptions.find(CEST_PROPERTY_NAME_TREC()); + if (finding != fileOptions.end()) + { + isT1 = true; + trecValues = finding->second.ToString(); + }; + } + + if (isCEST) + { + MITK_INFO << "CEST image detected due to LIST.txt or offset property in CEST_META.json"; + options[CEST_PROPERTY_NAME_OFFSETS()] = offsetValues; + } + else if (isT1) + { + MITK_INFO << "T1 image detected due to TREC.txt or trec property in CEST_META.json"; + options[CEST_PROPERTY_NAME_TREC()] = trecValues; + } + else + { + mitkThrow() << "Cannot load CEST/T1 file. No CEST offsets or T1 trec values specified. LIST.txt/TREC.txt or information in CEST_META.json is missing."; + } + + for (const auto& option : options) + { + item->GetPropertyList()->SetStringProperty(option.first.c_str(), option.second.ToString().c_str()); + } + + auto image = dynamic_cast(item.GetPointer()); + if (normalizationStrategy == "Automatic" && mitk::IsNotNormalizedCESTImage(image)) + { + MITK_INFO << "Unnormalized CEST image was loaded and will be normalized automatically."; + auto normalizationFilter = mitk::CESTImageNormalizationFilter::New(); + normalizationFilter->SetInput(image); + normalizationFilter->Update(); + auto normalizedImage = normalizationFilter->GetOutput(); + + auto nameProp = item->GetProperty("name"); + if (!nameProp) + { + mitkThrow() << "Cannot load CEST file. Property \"name\" is missing after BaseDICOMReaderService::Read()."; + } + normalizedImage->SetProperty("name", mitk::StringProperty::New(nameProp->GetValueAsString() + "_normalized")); + result.push_back(normalizedImage); + } + else + { + result.push_back(item); + } + } + + return result; + } + + CESTDICOMManualReaderService *CESTDICOMManualReaderService::Clone() const { return new CESTDICOMManualReaderService(*this); } +} diff --git a/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.h b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.h new file mode 100644 index 0000000000..46b2a6321f --- /dev/null +++ b/Modules/CEST/autoload/IO/mitkCESTGenericDICOMReaderService.h @@ -0,0 +1,51 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef MITKCESTGenericDICOMReaderService_H +#define MITKCESTGenericDICOMReaderService_H + +#include + +namespace mitk { + + /** + Service wrapper that auto selects (using the mitk::DICOMFileReaderSelector) the best DICOMFileReader from + the DICOMReader module and loads the CEST relevant meta data from a provided cest_meta.json file or + provided from the user as reader options. + */ + class CESTDICOMManualReaderService : public BaseDICOMReaderService + { + public: + CESTDICOMManualReaderService(const CustomMimeType& mimeType, const std::string& description); + CESTDICOMManualReaderService(const mitk::CESTDICOMManualReaderService& other); + + /** Uses the AbstractFileReader Read function and add extra steps for CEST meta data */ + using AbstractFileReader::Read; + std::vector > Read() override; + + Options GetOptions() const override; + us::Any GetOption(const std::string& name) const override; + + protected: + std::string GetCESTMetaFilePath() const; + std::string GetTRECFilePath() const; + std::string GetLISTFilePath() const; + + mitk::DICOMFileReader::Pointer GetReader(const mitk::StringList& relevantFiles) const override; + private: + + CESTDICOMManualReaderService* Clone() const override; + }; + +} + +#endif // MITKCESTGenericDICOMReaderService_H diff --git a/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp b/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp index 008c80bf88..7da1044d6c 100644 --- a/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp +++ b/Modules/CEST/autoload/IO/mitkCESTIOActivator.cpp @@ -1,43 +1,46 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCESTIOActivator.h" #include "mitkCESTDICOMReaderService.h" +#include "mitkCESTGenericDICOMReaderService.h" #include #include "mitkCESTIOMimeTypes.h" +#include "mitkIOMimeTypes.h" namespace mitk { void CESTIOActivator::Load(us::ModuleContext *context) { us::ServiceProperties props; props[us::ServiceConstants::SERVICE_RANKING()] = 10; m_MimeTypes = mitk::MitkCESTIOMimeTypes::Get(); for (std::vector::const_iterator mimeTypeIter = m_MimeTypes.begin(), iterEnd = m_MimeTypes.end(); mimeTypeIter != iterEnd; ++mimeTypeIter) { context->RegisterService(*mimeTypeIter, props); } m_CESTDICOMReader.reset(new CESTDICOMReaderService()); + m_CESTDICOMManualReader.reset(new CESTDICOMManualReaderService(CustomMimeType(IOMimeTypes::DICOM_MIMETYPE()), "CEST DICOM Manual Reader")); } void CESTIOActivator::Unload(us::ModuleContext *) {} } US_EXPORT_MODULE_ACTIVATOR(mitk::CESTIOActivator) diff --git a/Modules/CEST/autoload/IO/mitkCESTIOActivator.h b/Modules/CEST/autoload/IO/mitkCESTIOActivator.h index 87f2aca5ff..bbfd824d1d 100644 --- a/Modules/CEST/autoload/IO/mitkCESTIOActivator.h +++ b/Modules/CEST/autoload/IO/mitkCESTIOActivator.h @@ -1,40 +1,41 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKCESTIOActivator_H #define MITKCESTIOActivator_H #include #include #include #include namespace mitk { struct IFileReader; class IDICOMTagsOfInterest; class CESTIOActivator : public us::ModuleActivator { public: void Load(us::ModuleContext *context) override; void Unload(us::ModuleContext *context) override; private: std::unique_ptr m_CESTDICOMReader; + std::unique_ptr m_CESTDICOMManualReader; std::vector m_MimeTypes; }; } #endif // MITKCESTIOActivator_H diff --git a/Modules/CEST/include/mitkCustomTagParser.h b/Modules/CEST/include/mitkCustomTagParser.h index 9798b975d3..be602245ef 100644 --- a/Modules/CEST/include/mitkCustomTagParser.h +++ b/Modules/CEST/include/mitkCustomTagParser.h @@ -1,147 +1,150 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MITKCUSTOMTAGPARSER_H #define MITKCUSTOMTAGPARSER_H #include #include #include namespace mitk { /** The custom tag parser can be used to parse the custom dicom tag of the siemens private tag (0x0029, 0x1020) to extract relevant CEST data. An initial parsing determines whether the provided string belongs to CEST data at all. To make the check and extract the revision number the following rules are aplied: \n
  1. Sequence name (tSequenceFileName) must either
    1. start with the substring "CEST" (case insensitiv), or
    2. contain the substring "_CEST" (case insensitiv).
  2. Sequence name (tSequenceFileName) must contain the substring "_Rev" (case insensitiv).
  3. All numbers after "_Rev" represent the revision number; until either
    1. the next _, or
    2. end of sequence name.
Which custom parameters to save and to which property name can be controlled by a json file. This file can be either provided as a resource for the MitkCEST module during compilation or placed next to the MitkCEST library in your binary folder. The expected format for the file "REVISIONNUMBER.json":
{
"REVISIONNUMBER" : "revision_json",
"sWiPMemBlock.alFree[1]" : "AdvancedMode",
"sWiPMemBlock.alFree[2]" : "RetreatMode"
}
where :
  • REVISIONNUMBER is the revision number of this json parameter mapping (files with non digit characters in their name will be ignored)
  • sWiPMemBlock.alFree[1] is the name of one parameter in the private dicom tag
  • AdvancedMode is the name of the property the content of sWiPMemBlock.alFree[1] should be saved to
\note It is assumed that the entire content of tag (0x0029, 0x1020) is provided and that it es hex encoded (12\23\04...). If the sampling type is list it will try to access LIST.txt at the location provided in the constructor to read the offsets. */ class MITKCEST_EXPORT CustomTagParser { public: /// the constructor expects a path to one of the files to be loaded or the directory of the dicom files CustomTagParser(std::string relevantFile); /// parse the provided dicom property and return a property list based on the closest revision parameter mapping mitk::PropertyList::Pointer ParseDicomProperty(mitk::TemporoSpatialStringProperty *dicomProperty); /// parse the provided string and return a property list based on the closest revision parameter mapping mitk::PropertyList::Pointer ParseDicomPropertyString(std::string dicomPropertyString); + static std::string ReadListFromFile(const std::string& filePath); + /** Extract the revision out of the passed sequenceFileName. If the file name is not a valid CEST file name (see rules in the class documentation) exceptions will be thrown. If the file name is valid but contains no revision number an empty string will be returned. */ static std::string ExtractRevision(std::string sequenceFileName); void SetParseStrategy(std::string parseStrategy); void SetRevisionMappingStrategy(std::string revisionMappingStrategy); - /// name of the property for the offsets, including normalization offsets - static const std::string m_OffsetsPropertyName; - /// name of the property for the data acquisition revision static const std::string m_RevisionPropertyName; /// name of the property for the json parameter mapping revision static const std::string m_JSONRevisionPropertyName; /// prefix for all CEST related property names static const std::string m_CESTPropertyPrefix; protected: std::string GetRevisionAppropriateJSONString(std::string revisionString); void GetClosestLowerRevision(std::string revisionString); std::string GetClosestLowerRevision(std::string revisionString, std::vector availableRevisionsVector); /// Decides whether or not the image is likely to be a T1Map, if not it is assumed to be a CEST sequence bool IsT1Sequence(std::string preparationType, std::string recoveryMode, std::string spoilingType, std::string revisionString); /// Get a string filled with the properly formated offsets based on the sampling type and offset std::string GetOffsetString(std::string samplingType, std::string offset, std::string measurements); /// returns a vector revision numbers of all REVISIONNUMBER.json found beside the MitkCEST library std::vector GetExternalRevisions(); /// returns a vector revision numbers of all REVISIONNUMBER.json provided as resources during the compile std::vector GetInternalRevisions(); /// returns the path where external jsons are expected to be located std::string GetExternalJSONDirectory(); /// the closest lower revision provided as resource, empty if none found std::string m_ClosestInternalRevision; /// the closest lower revision provided as a json beside the library, empty if none found std::string m_ClosestExternalRevision; /// revision independent mapping to inject into the revision dependent json string static const std::string m_RevisionIndependentMapping; /// default revision dependent json string if none is found static const std::string m_DefaultJsonString; /// path to the dicom data std::string m_DicomDataPath; /// Should the kind of data be automatically determined or should it be parsed as a specific one std::string m_ParseStrategy; /// How to handle parameter mapping based on absent revision jsons std::string m_RevisionMappingStrategy; }; const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_TOTALSCANTIME(); const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_PREPERATIONTYPE(); const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_RECOVERYMODE(); const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_SPOILINGTYPE(); const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_OFFSETS(); const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_TREC(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_FREQ(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_PULSEDURATION(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_B1Amplitude(); + const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_DutyCycle(); } #endif // MITKCUSTOMTAGPARSER_H diff --git a/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp b/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp index ac4f25cb88..8648ae9bc3 100644 --- a/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp +++ b/Modules/CEST/src/mitkCESTImageNormalizationFilter.cpp @@ -1,229 +1,229 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCESTImageNormalizationFilter.h" #include #include #include #include #include mitk::CESTImageNormalizationFilter::CESTImageNormalizationFilter() { } mitk::CESTImageNormalizationFilter::~CESTImageNormalizationFilter() { } void mitk::CESTImageNormalizationFilter::GenerateData() { mitk::Image::ConstPointer inputImage = this->GetInput(0); if ((inputImage->GetDimension() != 4)) { mitkThrow() << "mitk::CESTImageNormalizationFilter:GenerateData works only with 4D images, sorry."; return; } auto resultMitkImage = this->GetOutput(); AccessFixedDimensionByItk(inputImage, NormalizeTimeSteps, 4); auto originalTimeGeometry = this->GetInput()->GetTimeGeometry(); auto resultTimeGeometry = mitk::ProportionalTimeGeometry::New(); unsigned int numberOfNonM0s = m_NonM0Indices.size(); resultTimeGeometry->Expand(numberOfNonM0s); for (unsigned int index = 0; index < numberOfNonM0s; ++index) { resultTimeGeometry->SetTimeStepGeometry(originalTimeGeometry->GetGeometryCloneForTimeStep(m_NonM0Indices.at(index)), index); } resultMitkImage->SetTimeGeometry(resultTimeGeometry); resultMitkImage->SetPropertyList(this->GetInput()->GetPropertyList()->Clone()); - resultMitkImage->GetPropertyList()->SetStringProperty(mitk::CustomTagParser::m_OffsetsPropertyName.c_str(), m_RealOffsets.c_str()); + resultMitkImage->GetPropertyList()->SetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), m_RealOffsets.c_str()); // remove uids resultMitkImage->GetPropertyList()->DeleteProperty("DICOM.0008.0018"); resultMitkImage->GetPropertyList()->DeleteProperty("DICOM.0020.000D"); resultMitkImage->GetPropertyList()->DeleteProperty("DICOM.0020.000E"); } std::vector ExtractOffsets(const mitk::Image* image) { std::vector result; if (image) { std::string offsets = ""; std::vector parts; - if (image->GetPropertyList()->GetStringProperty(mitk::CustomTagParser::m_OffsetsPropertyName.c_str(), offsets) && !offsets.empty()) + if (image->GetPropertyList()->GetStringProperty(mitk::CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets) && !offsets.empty()) { boost::algorithm::trim(offsets); boost::split(parts, offsets, boost::is_any_of(" ")); for (auto part : parts) { std::istringstream iss(part); iss.imbue(std::locale("C")); double d; iss >> d; result.push_back(d); } } } return result; } template void mitk::CESTImageNormalizationFilter::NormalizeTimeSteps(const itk::Image* image) { typedef itk::Image ImageType; typedef itk::Image OutputImageType; auto offsets = ExtractOffsets(this->GetInput()); // determine normalization images std::vector mZeroIndices; std::stringstream offsetsWithoutM0; offsetsWithoutM0.imbue(std::locale("C")); m_NonM0Indices.clear(); for (unsigned int index = 0; index < offsets.size(); ++index) { if ((offsets.at(index) < -299) || (offsets.at(index) > 299)) { mZeroIndices.push_back(index); } else { offsetsWithoutM0 << offsets.at(index) << " "; m_NonM0Indices.push_back(index); } } auto resultImage = OutputImageType::New(); typename ImageType::RegionType targetEntireRegion = image->GetLargestPossibleRegion(); targetEntireRegion.SetSize(3, m_NonM0Indices.size()); resultImage->SetRegions(targetEntireRegion); resultImage->Allocate(); resultImage->FillBuffer(0); unsigned int numberOfTimesteps = image->GetLargestPossibleRegion().GetSize(3); typename ImageType::RegionType lowerMZeroRegion = image->GetLargestPossibleRegion(); lowerMZeroRegion.SetSize(3, 1); typename ImageType::RegionType upperMZeroRegion = image->GetLargestPossibleRegion(); upperMZeroRegion.SetSize(3, 1); typename ImageType::RegionType sourceRegion = image->GetLargestPossibleRegion(); sourceRegion.SetSize(3, 1); typename OutputImageType::RegionType targetRegion = resultImage->GetLargestPossibleRegion(); targetRegion.SetSize(3, 1); unsigned int targetTimestep = 0; for (unsigned int sourceTimestep = 0; sourceTimestep < numberOfTimesteps; ++sourceTimestep) { unsigned int lowerMZeroIndex = mZeroIndices[0]; unsigned int upperMZeroIndex = mZeroIndices[0]; for (unsigned int loop = 0; loop < mZeroIndices.size(); ++loop) { if (mZeroIndices[loop] <= sourceTimestep) { lowerMZeroIndex = mZeroIndices[loop]; } if (mZeroIndices[loop] > sourceTimestep) { upperMZeroIndex = mZeroIndices[loop]; break; } } bool isMZero = (lowerMZeroIndex == sourceTimestep); double weight = 0.0; if (lowerMZeroIndex == upperMZeroIndex) { weight = 1.0; } else { weight = 1.0 - double(sourceTimestep - lowerMZeroIndex) / double(upperMZeroIndex - lowerMZeroIndex); } if (isMZero) { //do nothing } else { lowerMZeroRegion.SetIndex(3, lowerMZeroIndex); upperMZeroRegion.SetIndex(3, upperMZeroIndex); sourceRegion.SetIndex(3, sourceTimestep); targetRegion.SetIndex(3, targetTimestep); itk::ImageRegionConstIterator lowerMZeroIterator(image, lowerMZeroRegion); itk::ImageRegionConstIterator upperMZeroIterator(image, upperMZeroRegion); itk::ImageRegionConstIterator sourceIterator(image, sourceRegion); itk::ImageRegionIterator targetIterator(resultImage.GetPointer(), targetRegion); while (!sourceIterator.IsAtEnd()) { double normalizationFactor = weight * lowerMZeroIterator.Get() + (1.0 - weight) * upperMZeroIterator.Get(); if (mitk::Equal(normalizationFactor, 0)) { targetIterator.Set(0); } else { targetIterator.Set(double(sourceIterator.Get()) / normalizationFactor); } ++lowerMZeroIterator; ++upperMZeroIterator; ++sourceIterator; ++targetIterator; } ++targetTimestep; } } // get Pointer to output image mitk::Image::Pointer resultMitkImage = this->GetOutput(); // write into output image mitk::CastToMitkImage(resultImage, resultMitkImage); m_RealOffsets = offsetsWithoutM0.str(); } void mitk::CESTImageNormalizationFilter::GenerateOutputInformation() { mitk::Image::ConstPointer input = this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); } bool mitk::IsNotNormalizedCESTImage(const Image* cestImage) { auto offsets = ExtractOffsets(cestImage); for (auto offset : offsets) { if (offset < -299 || offset > 299) { return true; } } return false; }; diff --git a/Modules/CEST/src/mitkCustomTagParser.cpp b/Modules/CEST/src/mitkCustomTagParser.cpp index 4f0f0f3d9d..aa592a9878 100644 --- a/Modules/CEST/src/mitkCustomTagParser.cpp +++ b/Modules/CEST/src/mitkCustomTagParser.cpp @@ -1,872 +1,894 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCustomTagParser.h" #include #include -#include #include "mitkIPropertyPersistence.h" #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleContext.h" #include "usModuleResource.h" #include "usModuleResourceStream.h" #include #include #include #include #include #include #include #include #include #include #include namespace { mitk::IPropertyPersistence *GetPersistenceService() { mitk::IPropertyPersistence *result = nullptr; std::vector> persRegisters = us::GetModuleContext()->GetServiceReferences(); if (!persRegisters.empty()) { if (persRegisters.size() > 1) { MITK_WARN << "Multiple property description services found. Using just one."; } result = us::GetModuleContext()->GetService(persRegisters.front()); } return result; }; } const std::string mitk::CustomTagParser::m_CESTPropertyPrefix = "CEST."; -const std::string mitk::CustomTagParser::m_OffsetsPropertyName = m_CESTPropertyPrefix + "Offsets"; const std::string mitk::CustomTagParser::m_RevisionPropertyName = m_CESTPropertyPrefix + "Revision"; const std::string mitk::CustomTagParser::m_JSONRevisionPropertyName = m_CESTPropertyPrefix + "revision_json"; const std::string mitk::CustomTagParser::m_RevisionIndependentMapping = "\n" " \"sProtConsistencyInfo.tSystemType\" : \"SysType\",\n" " \"sProtConsistencyInfo.flNominalB0\" : \"NominalB0\",\n" " \"sTXSPEC.asNucleusInfo[0].lFrequency\" : \"FREQ\",\n" " \"sTXSPEC.asNucleusInfo[0].flReferenceAmplitude\" : \"RefAmp\",\n" " \"alTR[0]\" : \"TR\",\n" " \"alTE[0]\" : \"TE\",\n" " \"lAverages\" : \"averages\",\n" " \"lRepetitions\" : \"repetitions\",\n" " \"adFlipAngleDegree[0]\" : \"ImageFlipAngle\",\n" " \"lTotalScanTimeSec\" : \"TotalScanTime\","; const std::string mitk::CustomTagParser::m_DefaultJsonString = "{\n" " \"default mapping, corresponds to revision 1416\" : \"revision_json\",\n" " \"sWiPMemBlock.alFree[1]\" : \"AdvancedMode\",\n" " \"sWiPMemBlock.alFree[2]\" : \"RecoveryMode\",\n" " \"sWiPMemBlock.alFree[3]\" : \"DoubleIrrMode\",\n" " \"sWiPMemBlock.alFree[4]\" : \"BinomMode\",\n" " \"sWiPMemBlock.alFree[5]\" : \"MtMode\",\n" " \"sWiPMemBlock.alFree[6]\" : \"PreparationType\",\n" " \"sWiPMemBlock.alFree[7]\" : \"PulseType\",\n" " \"sWiPMemBlock.alFree[8]\" : \"SamplingType\",\n" " \"sWiPMemBlock.alFree[9]\" : \"SpoilingType\",\n" " \"sWiPMemBlock.alFree[10]\" : \"measurements\",\n" " \"sWiPMemBlock.alFree[11]\" : \"NumberOfPulses\",\n" " \"sWiPMemBlock.alFree[12]\" : \"NumberOfLockingPulses\",\n" " \"sWiPMemBlock.alFree[13]\" : \"PulseDuration\",\n" " \"sWiPMemBlock.alFree[14]\" : \"DutyCycle\",\n" " \"sWiPMemBlock.alFree[15]\" : \"RecoveryTime\",\n" " \"sWiPMemBlock.alFree[16]\" : \"RecoveryTimeM0\",\n" " \"sWiPMemBlock.alFree[17]\" : \"ReadoutDelay\",\n" " \"sWiPMemBlock.alFree[18]\" : \"BinomDuration\",\n" " \"sWiPMemBlock.alFree[19]\" : \"BinomDistance\",\n" " \"sWiPMemBlock.alFree[20]\" : \"BinomNumberofPulses\",\n" " \"sWiPMemBlock.alFree[21]\" : \"BinomPreRepetions\",\n" " \"sWiPMemBlock.alFree[22]\" : \"BinomType\",\n" " \"sWiPMemBlock.adFree[1]\" : \"Offset\",\n" " \"sWiPMemBlock.adFree[2]\" : \"B1Amplitude\",\n" " \"sWiPMemBlock.adFree[3]\" : \"AdiabaticPulseMu\",\n" " \"sWiPMemBlock.adFree[4]\" : \"AdiabaticPulseBW\",\n" " \"sWiPMemBlock.adFree[5]\" : \"AdiabaticPulseLength\",\n" " \"sWiPMemBlock.adFree[6]\" : \"AdiabaticPulseAmp\",\n" " \"sWiPMemBlock.adFree[7]\" : \"FermiSlope\",\n" " \"sWiPMemBlock.adFree[8]\" : \"FermiFWHM\",\n" " \"sWiPMemBlock.adFree[9]\" : \"DoubleIrrDuration\",\n" " \"sWiPMemBlock.adFree[10]\" : \"DoubleIrrAmplitude\",\n" " \"sWiPMemBlock.adFree[11]\" : \"DoubleIrrRepetitions\",\n" " \"sWiPMemBlock.adFree[12]\" : \"DoubleIrrPreRepetitions\"\n" "}"; mitk::CustomTagParser::CustomTagParser(std::string relevantFile) : m_ClosestInternalRevision(""), m_ClosestExternalRevision("") { std::string pathToDirectory; std::string fileName; itksys::SystemTools::SplitProgramPath(relevantFile, pathToDirectory, fileName); m_DicomDataPath = pathToDirectory; m_ParseStrategy = "Automatic"; m_RevisionMappingStrategy = "Fuzzy"; } std::string mitk::CustomTagParser::ExtractRevision(std::string sequenceFileName) { //all rules are case insesitive. Thus we convert everything to lower case //in order to check everything only once. std::string cestPrefix = "cest"; std::string cestPrefix2 = "_cest"; std::string cestPrefix3 = "\\cest"; //this version covers the fact that the strings extracted //from the SIEMENS tag has an additional prefix that is seperated by backslash. std::string revisionPrefix = "_rev"; std::transform(sequenceFileName.begin(), sequenceFileName.end(), sequenceFileName.begin(), ::tolower); bool isCEST = sequenceFileName.compare(0, cestPrefix.length(), cestPrefix) == 0; std::size_t foundPosition = 0; if (!isCEST) { foundPosition = sequenceFileName.find(cestPrefix2); isCEST = foundPosition != std::string::npos; } if (!isCEST) { foundPosition = sequenceFileName.find(cestPrefix3); isCEST = foundPosition != std::string::npos; } if (!isCEST) { mitkThrow() << "Invalid CEST sequence file name. No CEST prefix found. Could not extract revision."; } foundPosition = sequenceFileName.find(revisionPrefix, foundPosition); if (foundPosition == std::string::npos) { mitkThrow() << "Invalid CEST sequence file name. No revision prefix was found in CEST sequence file name. Could not extract revision."; } std::string revisionString = sequenceFileName.substr(foundPosition + revisionPrefix.length(), std::string::npos); std::size_t firstNoneNumber = revisionString.find_first_not_of("0123456789"); if (firstNoneNumber != std::string::npos) { revisionString.erase(firstNoneNumber, std::string::npos); } return revisionString; } bool mitk::CustomTagParser::IsT1Sequence(std::string preparationType, std::string recoveryMode, std::string spoilingType, std::string revisionString) { bool isT1 = false; // if a forced parse strategy is set, use that one if ("T1" == m_ParseStrategy) { return true; } if ("CEST/WASABI" == m_ParseStrategy) { return false; } if (("T1Recovery" == preparationType) || ("T1Inversion" == preparationType)) { isT1 = true; } // How to interpret the recoveryMode depends on the age of the sequence // older sequences use 0 = false and 1 = true, newer ones 1 = false and 2 = true. // A rough rule of thumb is to assume that if the SpoilingType is 0, then the first // convention is chosen, if it is 1, then the second applies. Otherwise // we assume revision 1485 and newer to follow the new convention. // This unfortunate heuristic is due to somewhat arbitrary CEST sequence implementations. if (!isT1) { std::string thisIsTrue = "1"; std::string thisIsFalse = "0"; if ("0" == spoilingType) { thisIsFalse = "0"; thisIsTrue = "1"; } else if ("1" == spoilingType) { thisIsFalse = "1"; thisIsTrue = "2"; } else { int revisionNrWeAssumeToBeDifferenciating = 1485; if (std::stoi(revisionString) - revisionNrWeAssumeToBeDifferenciating < 0) { thisIsFalse = "0"; thisIsTrue = "1"; } else { thisIsFalse = "1"; thisIsTrue = "2"; } } if (thisIsFalse == recoveryMode) { isT1 = false; } else if (thisIsTrue == recoveryMode) { isT1 = true; } } return isT1; } mitk::PropertyList::Pointer mitk::CustomTagParser::ParseDicomPropertyString(std::string dicomPropertyString) { auto results = mitk::PropertyList::New(); if ("" == dicomPropertyString) { //MITK_ERROR << "Could not parse empty custom dicom string"; return results; } std::map privateParameters; // The Siemens private tag contains information like "43\52\23\34". // We jump over each "\" and convert the number; std::string bytes; { const std::size_t SUBSTR_LENGTH = 2; const std::size_t INPUT_LENGTH = dicomPropertyString.length(); if (INPUT_LENGTH < SUBSTR_LENGTH) return results; const std::size_t MAX_INPUT_OFFSET = INPUT_LENGTH - SUBSTR_LENGTH; bytes.reserve(INPUT_LENGTH / 3 + 1); try { for (std::size_t i = 0; i <= MAX_INPUT_OFFSET; i += 3) { std::string byte_string = dicomPropertyString.substr(i, SUBSTR_LENGTH); int byte = static_cast(std::stoi(byte_string.c_str(), nullptr, 16)); bytes.push_back(byte); } } catch (const std::invalid_argument&) // std::stoi() could not perform conversion { return results; } } // extract parameter list std::string parameterListString; { const std::string ASCCONV_BEGIN = "### ASCCONV BEGIN ###"; const std::string ASCCONV_END = "### ASCCONV END ###"; auto offset = bytes.find(ASCCONV_BEGIN); if (std::string::npos == offset) return results; offset += ASCCONV_BEGIN.length(); auto count = bytes.find(ASCCONV_END, offset); if (std::string::npos == count) return results; count -= offset; parameterListString = bytes.substr(offset, count); } boost::replace_all(parameterListString, "\r\n", "\n"); boost::char_separator newlineSeparator("\n"); boost::tokenizer> parameters(parameterListString, newlineSeparator); for (const auto ¶meter : parameters) { std::vector parts; boost::split(parts, parameter, boost::is_any_of("=")); if (parts.size() == 2) { parts[0].erase(std::remove(parts[0].begin(), parts[0].end(), ' '), parts[0].end()); parts[1].erase(parts[1].begin(), parts[1].begin() + 1); // first character is a space privateParameters[parts[0]] = parts[1]; } } std::string revisionString = ""; try { revisionString = ExtractRevision(privateParameters["tSequenceFileName"]); } catch (const std::exception &e) { MITK_ERROR << "Cannot deduce revision information. Reason: "<< e.what(); return results; } results->SetProperty(m_RevisionPropertyName, mitk::StringProperty::New(revisionString)); std::string jsonString = GetRevisionAppropriateJSONString(revisionString); boost::property_tree::ptree root; std::istringstream jsonStream(jsonString); try { boost::property_tree::read_json(jsonStream, root); } catch (const boost::property_tree::json_parser_error &e) { mitkThrow() << "Could not parse json file. Error was:\n" << e.what(); } for (auto it : root) { if (it.second.empty()) { std::string propertyName = m_CESTPropertyPrefix + it.second.data(); if (m_JSONRevisionPropertyName == propertyName) { results->SetProperty(propertyName, mitk::StringProperty::New(it.first)); } else { results->SetProperty(propertyName, mitk::StringProperty::New(privateParameters[it.first])); } } else { MITK_ERROR << "Currently no support for nested dicom tag descriptors in json file."; } } std::string offset = ""; std::string measurements = ""; results->GetStringProperty("CEST.Offset", offset); results->GetStringProperty("CEST.measurements", measurements); if (measurements.empty()) { std::string stringRepetitions = ""; results->GetStringProperty("CEST.repetitions", stringRepetitions); std::string stringAverages = ""; results->GetStringProperty("CEST.averages", stringAverages); const auto ERROR_STRING = "Could not find measurements, fallback assumption of repetitions + averages could not be determined either."; if (!stringRepetitions.empty() && !stringAverages.empty()) { std::stringstream measurementStream; try { measurementStream << std::stoi(stringRepetitions) + std::stoi(stringAverages); measurements = measurementStream.str(); MITK_INFO << "Could not find measurements, assuming repetitions + averages. That is: " << measurements; } catch (const std::invalid_argument&) { MITK_ERROR << ERROR_STRING; } } else { MITK_WARN << ERROR_STRING; } } std::string preparationType = ""; std::string recoveryMode = ""; std::string spoilingType = ""; results->GetStringProperty(CEST_PROPERTY_NAME_PREPERATIONTYPE().c_str(), preparationType); results->GetStringProperty(CEST_PROPERTY_NAME_RECOVERYMODE().c_str(), recoveryMode); results->GetStringProperty(CEST_PROPERTY_NAME_SPOILINGTYPE().c_str(), spoilingType); if (this->IsT1Sequence(preparationType, recoveryMode, spoilingType, revisionString)) { MITK_INFO << "Parsed as T1 image"; - mitk::LocaleSwitch localeSwitch("C"); - std::stringstream trecStream; std::string trecPath = m_DicomDataPath + "/TREC.txt"; - std::ifstream list(trecPath.c_str()); + auto trec = ReadListFromFile(trecPath); - if (list.good()) - { - std::string currentTime; - while (std::getline(list, currentTime)) - { - trecStream << currentTime << " "; - } - } - else + if(trec.empty()) { MITK_WARN << "Assumed T1, but could not load TREC at " << trecPath; } - results->SetStringProperty(CEST_PROPERTY_NAME_TREC().c_str(), trecStream.str().c_str()); + results->SetStringProperty(CEST_PROPERTY_NAME_TREC().c_str(), trec.c_str()); } else { MITK_INFO << "Parsed as CEST or WASABI image"; std::string sampling = ""; bool hasSamplingInformation = results->GetStringProperty("CEST.SamplingType", sampling); if (hasSamplingInformation) { std::string offsets = GetOffsetString(sampling, offset, measurements); - results->SetStringProperty(m_OffsetsPropertyName.c_str(), offsets.c_str()); + results->SetStringProperty(CEST_PROPERTY_NAME_OFFSETS().c_str(), offsets.c_str()); } else { MITK_WARN << "Could not determine sampling type."; } } //persist all properties mitk::IPropertyPersistence *persSrv = GetPersistenceService(); if (persSrv) { auto propertyMap = results->GetMap(); for (auto const &prop : *propertyMap) { PropertyPersistenceInfo::Pointer info = PropertyPersistenceInfo::New(); std::string key = prop.first; std::replace(key.begin(), key.end(), '.', '_'); info->SetNameAndKey(prop.first, key); persSrv->AddInfo(info); } } return results; } +std::string mitk::CustomTagParser::ReadListFromFile(const std::string& filePath) +{ + std::stringstream listStream; + std::ifstream list(filePath.c_str()); + list.imbue(std::locale("C")); + + if (list.good()) + { + std::string currentValue; + while (std::getline(list, currentValue)) + { + listStream << currentValue << " "; + } + } + return listStream.str(); +} + mitk::PropertyList::Pointer mitk::CustomTagParser::ParseDicomProperty(mitk::TemporoSpatialStringProperty *dicomProperty) { if (!dicomProperty) { MITK_ERROR << "DICOM property empty"; } auto results = mitk::PropertyList::New(); if (dicomProperty) { results = ParseDicomPropertyString(dicomProperty->GetValue()); } return results; } std::vector mitk::CustomTagParser::GetInternalRevisions() { const std::vector configs = us::GetModuleContext()->GetModule()->FindResources("/", "*.json", false); std::vector availableRevisionsVector; for (auto const resource : configs) { availableRevisionsVector.push_back(std::stoi(resource.GetBaseName())); } return availableRevisionsVector; } std::vector mitk::CustomTagParser::GetExternalRevisions() { std::string stringToJSONDirectory = GetExternalJSONDirectory(); std::string prospectiveJsonsPath = stringToJSONDirectory + "/*.json"; std::set JsonFiles; Poco::Glob::glob(prospectiveJsonsPath, JsonFiles, Poco::Glob::GLOB_CASELESS); std::vector availableRevisionsVector; for (auto const jsonpath : JsonFiles) { std::string jsonDir; std::string jsonName; itksys::SystemTools::SplitProgramPath(jsonpath, jsonDir, jsonName); std::string revision = itksys::SystemTools::GetFilenameWithoutExtension(jsonName); // disregard jsons which contain letters in their name bool onlyNumbers = (revision.find_first_not_of("0123456789") == std::string::npos); if(onlyNumbers) { availableRevisionsVector.push_back(std::stoi(revision)); } } return availableRevisionsVector; } std::string mitk::CustomTagParser::GetClosestLowerRevision(std::string revisionString, std::vector availableRevisionsVector) { // descending order std::sort(availableRevisionsVector.begin(), availableRevisionsVector.end(), std::greater<>()); int revision = std::stoi(revisionString); int index = 0; int numberOfRevisions = availableRevisionsVector.size(); while (index < numberOfRevisions) { // current mapping still has a higher revision number if ((availableRevisionsVector[index] - revision) > 0) { ++index; } else { break; } } if (index < numberOfRevisions) { std::stringstream foundRevisionStream; foundRevisionStream << availableRevisionsVector[index]; return foundRevisionStream.str(); } return ""; } void mitk::CustomTagParser::GetClosestLowerRevision(std::string revisionString) { m_ClosestInternalRevision = GetClosestLowerRevision(revisionString, GetInternalRevisions()); m_ClosestExternalRevision = GetClosestLowerRevision(revisionString, GetExternalRevisions()); if ("Strict" == m_RevisionMappingStrategy && !((0 == m_ClosestInternalRevision.compare(revisionString)) || (0 == m_ClosestExternalRevision.compare(revisionString)))) { // strict revision mapping and neither revision does match the dicom meta data std::stringstream errorMessageStream; errorMessageStream << "\nCould not parse dicom data in strict mode, data revision " << revisionString << " has no known matching parameter mapping. To use the closest known older parameter mapping select the " << "\"Fuzzy\" revision mapping option when loading the data.\n" << "\nCurrently known revision mappings are:\n Precompiled:"; for (const auto revision : GetInternalRevisions()) { errorMessageStream << " " << revision; } errorMessageStream << "\n External:"; for (const auto revision : GetExternalRevisions()) { errorMessageStream << " " << revision; } errorMessageStream << "\n\nExternal revision mapping descriptions should be located at\n\n"; std::string stringToJSONDirectory = GetExternalJSONDirectory(); errorMessageStream << stringToJSONDirectory; errorMessageStream << "\n\nTo provide an external mapping for this revision create a " << revisionString << ".json there. You might need to create the directory first."; mitkThrow() << errorMessageStream.str(); } } std::string mitk::CustomTagParser::GetRevisionAppropriateJSONString(std::string revisionString) { std::string returnValue = ""; if ("" == revisionString) { MITK_WARN << "Could not extract revision"; } else { GetClosestLowerRevision(revisionString); bool useExternal = false; bool useInternal = false; if ("" != m_ClosestExternalRevision) { useExternal = true; } if ("" != m_ClosestInternalRevision) { useInternal = true; } if (useExternal && useInternal) { if (std::stoi(m_ClosestInternalRevision) > std::stoi(m_ClosestExternalRevision)) { useExternal = false; } } if (useExternal) { std::string stringToJSONDirectory = GetExternalJSONDirectory(); std::string prospectiveJsonPath = stringToJSONDirectory + "/" + m_ClosestExternalRevision + ".json"; std::ifstream externalJSON(prospectiveJsonPath.c_str()); if (externalJSON.good()) { MITK_INFO << "Found external json for CEST parameters at " << prospectiveJsonPath; std::stringstream buffer; buffer << externalJSON.rdbuf(); returnValue = buffer.str(); useInternal = false; } } if (useInternal) { std::string filename = m_ClosestInternalRevision + ".json"; us::ModuleResource jsonResource = us::GetModuleContext()->GetModule()->GetResource(filename); if (jsonResource.IsValid() && jsonResource.IsFile()) { MITK_INFO << "Found no external json for CEST parameters. Closest internal mapping is for revision " << m_ClosestInternalRevision; us::ModuleResourceStream jsonStream(jsonResource); std::stringstream buffer; buffer << jsonStream.rdbuf(); returnValue = buffer.str(); } } } if ("" == returnValue) { MITK_WARN << "Could not identify parameter mapping for the given revision " << revisionString << ", using default mapping."; returnValue = m_DefaultJsonString; } // inject the revision independent mapping before the first newline { returnValue.insert(returnValue.find("\n"), m_RevisionIndependentMapping); } return returnValue; } std::string mitk::CustomTagParser::GetOffsetString(std::string samplingType, std::string offset, std::string measurements) { - mitk::LocaleSwitch localeSwitch("C"); std::stringstream results; + results.imbue(std::locale("C")); std::string normalizationIndicatingOffset = "-300"; double offsetDouble = 0.0; int measurementsInt = 0; bool validOffset = false; bool validMeasurements = false; if ("" != offset) { validOffset = true; offsetDouble = std::stod(offset); } if ("" != measurements) { validMeasurements = true; measurementsInt = std::stoi(measurements); } std::vector offsetVector; if (validOffset && validMeasurements) { for (int step = 0; step < measurementsInt -1; ++step) { double currentOffset = -offsetDouble + 2 * step * offsetDouble / (measurementsInt - 2.0); offsetVector.push_back(currentOffset); } } else { MITK_WARN << "Invalid offset or measurements, offset calculation will only work for list sampling type."; } if (samplingType == "1" || samplingType == "Regular") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (const auto& entry : offsetVector) { results << entry << " "; } } } else if (samplingType == "2" || samplingType == "Alternating") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (auto& entry : offsetVector) { entry = std::abs(entry); } std::sort(offsetVector.begin(), offsetVector.end(), std::greater<>()); for (unsigned int index = 0; index < offsetVector.size(); ++index) { offsetVector[index] = std::pow(-1, index) * offsetVector[index]; } for (auto& entry : offsetVector) { results << entry << " "; } } } else if (samplingType == "3" || samplingType == "List") { std::string listPath = m_DicomDataPath + "/LIST.txt"; - std::ifstream list(listPath.c_str()); - if (list.good()) + auto values = ReadListFromFile(listPath); + + if (!values.empty()) { - std::string currentOffset; - while (std::getline(list, currentOffset)) - { - results << currentOffset << " "; - } + results << values; } else { MITK_ERROR << "Could not load list at " << listPath; } } else if (samplingType == "4" || samplingType == "SingleOffset") { if (validOffset && validMeasurements) { results << normalizationIndicatingOffset << " "; for (int step = 0; step < measurementsInt - 1; ++step) { results << offsetDouble << " "; } } } else { MITK_WARN << "Encountered unknown sampling type."; } std::string resultString = results.str(); // replace multiple spaces by a single space std::string::iterator newEnditerator = std::unique(resultString.begin(), resultString.end(), [=](char lhs, char rhs) { return (lhs == rhs) && (lhs == ' '); } ); resultString.erase(newEnditerator, resultString.end()); if ((resultString.length() > 0) && (resultString.at(resultString.length() - 1) == ' ')) { resultString.erase(resultString.end() - 1, resultString.end()); } if ((resultString.length() > 0) && (resultString.at(0) == ' ')) { resultString.erase(resultString.begin(), ++(resultString.begin())); } return resultString; } void mitk::CustomTagParser::SetParseStrategy(std::string parseStrategy) { m_ParseStrategy = parseStrategy; } void mitk::CustomTagParser::SetRevisionMappingStrategy(std::string revisionMappingStrategy) { m_RevisionMappingStrategy = revisionMappingStrategy; } std::string mitk::CustomTagParser::GetExternalJSONDirectory() { std::string moduleLocation = us::GetModuleContext()->GetModule()->GetLocation(); std::string stringToModule; std::string libraryName; itksys::SystemTools::SplitProgramPath(moduleLocation, stringToModule, libraryName); std::stringstream jsonDirectory; jsonDirectory << stringToModule << "/CESTRevisionMapping"; return jsonDirectory.str(); } const std::string mitk::CEST_PROPERTY_NAME_TOTALSCANTIME() { return "CEST.TotalScanTime"; }; const std::string mitk::CEST_PROPERTY_NAME_PREPERATIONTYPE() { return "CEST.PreparationType"; }; const std::string mitk::CEST_PROPERTY_NAME_RECOVERYMODE() { return "CEST.RecoveryMode"; }; const std::string mitk::CEST_PROPERTY_NAME_SPOILINGTYPE() { return "CEST.SpoilingType"; }; const std::string mitk::CEST_PROPERTY_NAME_OFFSETS() { return "CEST.Offsets"; }; const std::string mitk::CEST_PROPERTY_NAME_TREC() { return std::string("CEST.TREC"); } + +const std::string mitk::CEST_PROPERTY_NAME_FREQ() +{ + return std::string("CEST.FREQ"); +} + +const std::string mitk::CEST_PROPERTY_NAME_PULSEDURATION() +{ + return std::string("CEST.PulseDuration"); +} + +const std::string mitk::CEST_PROPERTY_NAME_B1Amplitude() +{ + return std::string("CEST.B1Amplitude"); +} + +const std::string mitk::CEST_PROPERTY_NAME_DutyCycle() +{ + return std::string("CEST.DutyCycle"); +}