26 #include <itksys/SystemTools.hxx> 28 #include <Poco/Glob.h> 30 #include <boost/algorithm/string.hpp> 31 #include <boost/property_tree/json_parser.hpp> 32 #include <boost/property_tree/ptree.hpp> 33 #include <boost/tokenizer.hpp> 47 std::vector<us::ServiceReference<mitk::IPropertyPersistence>> persRegisters =
49 if (!persRegisters.empty())
51 if (persRegisters.size() > 1)
53 MITK_WARN <<
"Multiple property description services found. Using just one.";
69 " \"sProtConsistencyInfo.tSystemType\" : \"SysType\",\n" 70 " \"sProtConsistencyInfo.flNominalB0\" : \"NominalB0\",\n" 71 " \"sTXSPEC.asNucleusInfo[0].lFrequency\" : \"FREQ\",\n" 72 " \"sTXSPEC.asNucleusInfo[0].flReferenceAmplitude\" : \"RefAmp\",\n" 73 " \"alTR[0]\" : \"TR\",\n" 74 " \"alTE[0]\" : \"TE\",\n" 75 " \"lAverages\" : \"averages\",\n" 76 " \"lRepetitions\" : \"repetitions\",\n" 77 " \"adFlipAngleDegree[0]\" : \"ImageFlipAngle\",\n" 78 " \"lTotalScanTimeSec\" : \"TotalScanTime\",";
81 " \"default mapping, corresponds to revision 1416\" : \"revision_json\",\n" 82 " \"sWiPMemBlock.alFree[1]\" : \"AdvancedMode\",\n" 83 " \"sWiPMemBlock.alFree[2]\" : \"RecoveryMode\",\n" 84 " \"sWiPMemBlock.alFree[3]\" : \"DoubleIrrMode\",\n" 85 " \"sWiPMemBlock.alFree[4]\" : \"BinomMode\",\n" 86 " \"sWiPMemBlock.alFree[5]\" : \"MtMode\",\n" 87 " \"sWiPMemBlock.alFree[6]\" : \"PreparationType\",\n" 88 " \"sWiPMemBlock.alFree[7]\" : \"PulseType\",\n" 89 " \"sWiPMemBlock.alFree[8]\" : \"SamplingType\",\n" 90 " \"sWiPMemBlock.alFree[9]\" : \"SpoilingType\",\n" 91 " \"sWiPMemBlock.alFree[10]\" : \"measurements\",\n" 92 " \"sWiPMemBlock.alFree[11]\" : \"NumberOfPulses\",\n" 93 " \"sWiPMemBlock.alFree[12]\" : \"NumberOfLockingPulses\",\n" 94 " \"sWiPMemBlock.alFree[13]\" : \"PulseDuration\",\n" 95 " \"sWiPMemBlock.alFree[14]\" : \"DutyCycle\",\n" 96 " \"sWiPMemBlock.alFree[15]\" : \"RecoveryTime\",\n" 97 " \"sWiPMemBlock.alFree[16]\" : \"RecoveryTimeM0\",\n" 98 " \"sWiPMemBlock.alFree[17]\" : \"ReadoutDelay\",\n" 99 " \"sWiPMemBlock.alFree[18]\" : \"BinomDuration\",\n" 100 " \"sWiPMemBlock.alFree[19]\" : \"BinomDistance\",\n" 101 " \"sWiPMemBlock.alFree[20]\" : \"BinomNumberofPulses\",\n" 102 " \"sWiPMemBlock.alFree[21]\" : \"BinomPreRepetions\",\n" 103 " \"sWiPMemBlock.alFree[22]\" : \"BinomType\",\n" 104 " \"sWiPMemBlock.adFree[1]\" : \"Offset\",\n" 105 " \"sWiPMemBlock.adFree[2]\" : \"B1Amplitude\",\n" 106 " \"sWiPMemBlock.adFree[3]\" : \"AdiabaticPulseMu\",\n" 107 " \"sWiPMemBlock.adFree[4]\" : \"AdiabaticPulseBW\",\n" 108 " \"sWiPMemBlock.adFree[5]\" : \"AdiabaticPulseLength\",\n" 109 " \"sWiPMemBlock.adFree[6]\" : \"AdiabaticPulseAmp\",\n" 110 " \"sWiPMemBlock.adFree[7]\" : \"FermiSlope\",\n" 111 " \"sWiPMemBlock.adFree[8]\" : \"FermiFWHM\",\n" 112 " \"sWiPMemBlock.adFree[9]\" : \"DoubleIrrDuration\",\n" 113 " \"sWiPMemBlock.adFree[10]\" : \"DoubleIrrAmplitude\",\n" 114 " \"sWiPMemBlock.adFree[11]\" : \"DoubleIrrRepetitions\",\n" 115 " \"sWiPMemBlock.adFree[12]\" : \"DoubleIrrPreRepetitions\"\n" 120 std::string pathToDirectory;
121 std::string fileName;
122 itksys::SystemTools::SplitProgramPath(relevantFile, pathToDirectory, fileName);
132 std::string cestPrefix =
"cest";
133 std::string cestPrefix2 =
"_cest";
134 std::string cestPrefix3 =
"\\cest";
136 std::string revisionPrefix =
"_rev";
137 std::transform(sequenceFileName.begin(), sequenceFileName.end(), sequenceFileName.begin(), ::tolower);
139 bool isCEST = sequenceFileName.compare(0, cestPrefix.length(), cestPrefix) == 0;
140 std::size_t foundPosition = 0;
144 foundPosition = sequenceFileName.find(cestPrefix2);
145 isCEST = foundPosition != std::string::npos;
150 foundPosition = sequenceFileName.find(cestPrefix3);
151 isCEST = foundPosition != std::string::npos;
156 mitkThrow() <<
"Invalid CEST sequence file name. No CEST prefix found. Could not extract revision.";
159 foundPosition = sequenceFileName.find(revisionPrefix, foundPosition);
160 if (foundPosition == std::string::npos)
162 mitkThrow() <<
"Invalid CEST sequence file name. No revision prefix was found in CEST sequence file name. Could not extract revision.";
165 std::string revisionString = sequenceFileName.substr(foundPosition + revisionPrefix.length(), std::string::npos);
166 std::size_t firstNoneNumber = revisionString.find_first_not_of(
"0123456789");
167 if (firstNoneNumber != std::string::npos)
169 revisionString.erase(firstNoneNumber, std::string::npos);
172 return revisionString;
176 std::string recoveryMode,
177 std::string spoilingType,
178 std::string revisionString)
192 if ((
"T1Recovery" == preparationType) || (
"T1Inversion" == preparationType))
205 std::string thisIsTrue =
"1";
206 std::string thisIsFalse =
"0";
207 if (
"0" == spoilingType)
212 else if (
"1" == spoilingType)
219 int revisionNrWeAssumeToBeDifferenciating = 1485;
220 if (std::stoi(revisionString) - revisionNrWeAssumeToBeDifferenciating < 0)
232 if (thisIsFalse == recoveryMode)
236 else if (thisIsTrue == recoveryMode)
249 if (
"" == dicomPropertyString)
255 std::map<std::string, std::string> privateParameters;
262 const std::size_t SUBSTR_LENGTH = 2;
263 const std::size_t INPUT_LENGTH = dicomPropertyString.length();
265 if (INPUT_LENGTH < SUBSTR_LENGTH)
268 const std::size_t MAX_INPUT_OFFSET = INPUT_LENGTH - SUBSTR_LENGTH;
269 bytes.reserve(INPUT_LENGTH / 3 + 1);
273 for (std::size_t i = 0; i <= MAX_INPUT_OFFSET; i += 3)
275 std::string byte_string = dicomPropertyString.substr(i, SUBSTR_LENGTH);
276 int byte =
static_cast<std::string::value_type
>(std::stoi(byte_string.c_str(),
nullptr, 16));
277 bytes.push_back(byte);
280 catch (
const std::invalid_argument&)
287 std::string parameterListString;
290 const std::string ASCCONV_BEGIN =
"### ASCCONV BEGIN ###";
291 const std::string ASCCONV_END =
"### ASCCONV END ###";
293 auto offset = bytes.find(ASCCONV_BEGIN);
295 if (std::string::npos ==
offset)
298 offset += ASCCONV_BEGIN.length();
300 auto count = bytes.find(ASCCONV_END,
offset);
302 if (std::string::npos == count)
307 parameterListString = bytes.substr(
offset, count);
310 boost::replace_all(parameterListString,
"\r\n",
"\n");
311 boost::char_separator<char> newlineSeparator(
"\n");
312 boost::tokenizer<boost::char_separator<char>> parameters(parameterListString, newlineSeparator);
313 for (
const auto ¶meter : parameters)
315 std::vector<std::string> parts;
316 boost::split(parts, parameter, boost::is_any_of(
"="));
318 if (parts.size() == 2)
320 parts[0].erase(std::remove(parts[0].begin(), parts[0].end(),
' '), parts[0].end());
321 parts[1].erase(parts[1].begin(), parts[1].begin() + 1);
322 privateParameters[parts[0]] = parts[1];
326 std::string revisionString =
"";
330 revisionString =
ExtractRevision(privateParameters[
"tSequenceFileName"]);
332 catch (
const std::exception &e)
334 MITK_ERROR <<
"Cannot deduce revision information. Reason: "<< e.what();
342 boost::property_tree::ptree root;
343 std::istringstream jsonStream(jsonString);
346 boost::property_tree::read_json(jsonStream, root);
348 catch (
const boost::property_tree::json_parser_error &e)
350 mitkThrow() <<
"Could not parse json file. Error was:\n" << e.what();
355 if (it.second.empty())
369 MITK_ERROR <<
"Currently no support for nested dicom tag descriptors in json file.";
374 std::string measurements =
"";
375 results->GetStringProperty(
"CEST.Offset", offset);
376 results->GetStringProperty(
"CEST.measurements", measurements);
378 if (measurements.empty())
380 std::string stringRepetitions =
"";
381 results->GetStringProperty(
"CEST.repetitions", stringRepetitions);
383 std::string stringAverages =
"";
384 results->GetStringProperty(
"CEST.averages", stringAverages);
386 const auto ERROR_STRING =
"Could not find measurements, fallback assumption of repetitions + averages could not be determined either.";
388 if (!stringRepetitions.empty() && !stringAverages.empty())
390 std::stringstream measurementStream;
394 measurementStream << std::stoi(stringRepetitions) + std::stoi(stringAverages);
395 measurements = measurementStream.str();
396 MITK_INFO <<
"Could not find measurements, assuming repetitions + averages. That is: " << measurements;
398 catch (
const std::invalid_argument&)
409 std::string preparationType =
"";
410 std::string recoveryMode =
"";
411 std::string spoilingType =
"";
416 if (this->
IsT1Sequence(preparationType, recoveryMode, spoilingType, revisionString))
422 std::stringstream trecStream;
425 std::ifstream list(trecPath.c_str());
429 std::string currentTime;
430 while (std::getline(list, currentTime))
432 trecStream << currentTime <<
" ";
437 MITK_WARN <<
"Assumed T1, but could not load TREC at " << trecPath;
444 MITK_INFO <<
"Parsed as CEST or WASABI image";
445 std::string sampling =
"";
446 bool hasSamplingInformation = results->GetStringProperty(
"CEST.SamplingType", sampling);
447 if (hasSamplingInformation)
454 MITK_WARN <<
"Could not determine sampling type.";
463 auto propertyMap = results->GetMap();
464 for (
auto const &prop : *propertyMap)
467 std::string key = prop.first;
469 info->SetNameAndKey(prop.first, key);
497 const std::vector<us::ModuleResource> configs =
500 std::vector<int> availableRevisionsVector;
502 for (
auto const resource : configs)
504 availableRevisionsVector.push_back(std::stoi(resource.GetBaseName()));
507 return availableRevisionsVector;
514 std::string prospectiveJsonsPath = stringToJSONDirectory +
"/*.json";
516 std::set<std::string> JsonFiles;
517 Poco::Glob::glob(prospectiveJsonsPath, JsonFiles, Poco::Glob::GLOB_CASELESS);
519 std::vector<int> availableRevisionsVector;
521 for (
auto const jsonpath : JsonFiles)
524 std::string jsonName;
525 itksys::SystemTools::SplitProgramPath(jsonpath, jsonDir, jsonName);
526 std::string revision = itksys::SystemTools::GetFilenameWithoutExtension(jsonName);
529 bool onlyNumbers = (revision.find_first_not_of(
"0123456789") == std::string::npos);
533 availableRevisionsVector.push_back(std::stoi(revision));
537 return availableRevisionsVector;
544 std::sort(availableRevisionsVector.begin(), availableRevisionsVector.end(), std::greater<>());
546 int revision = std::stoi(revisionString);
549 int numberOfRevisions = availableRevisionsVector.size();
551 while (index < numberOfRevisions)
554 if ((availableRevisionsVector[index] - revision) > 0)
564 if (index < numberOfRevisions)
566 std::stringstream foundRevisionStream;
567 foundRevisionStream << availableRevisionsVector[index];
569 return foundRevisionStream.str();
583 std::stringstream errorMessageStream;
584 errorMessageStream <<
"\nCould not parse dicom data in strict mode, data revision " << revisionString
585 <<
" has no known matching parameter mapping. To use the closest known older parameter mapping select the " 586 <<
"\"Fuzzy\" revision mapping option when loading the data.\n" 587 <<
"\nCurrently known revision mappings are:\n Precompiled:";
590 errorMessageStream <<
" " << revision;
592 errorMessageStream <<
"\n External:";
595 errorMessageStream <<
" " << revision;
597 errorMessageStream <<
"\n\nExternal revision mapping descriptions should be located at\n\n";
599 errorMessageStream << stringToJSONDirectory;
601 errorMessageStream <<
"\n\nTo provide an external mapping for this revision create a " << revisionString
602 <<
".json there. You might need to create the directory first.";
610 std::string returnValue =
"";
612 if (
"" == revisionString)
614 MITK_WARN <<
"Could not extract revision";
620 bool useExternal =
false;
621 bool useInternal =
false;
632 if (useExternal && useInternal)
646 std::ifstream externalJSON(prospectiveJsonPath.c_str());
648 if (externalJSON.good())
650 MITK_INFO <<
"Found external json for CEST parameters at " << prospectiveJsonPath;
652 std::stringstream buffer;
653 buffer << externalJSON.rdbuf();
655 returnValue = buffer.str();
668 MITK_INFO <<
"Found no external json for CEST parameters. Closest internal mapping is for revision " 671 std::stringstream buffer;
672 buffer << jsonStream.rdbuf();
673 returnValue = buffer.str();
678 if (
"" == returnValue)
680 MITK_WARN <<
"Could not identify parameter mapping for the given revision " << revisionString
681 <<
", using default mapping.";
696 std::stringstream results;
698 std::string normalizationIndicatingOffset =
"-300";
700 double offsetDouble = 0.0;
701 int measurementsInt = 0;
703 bool validOffset =
false;
704 bool validMeasurements =
false;
709 offsetDouble = std::stod(offset);
711 if (
"" != measurements)
713 validMeasurements =
true;
714 measurementsInt = std::stoi(measurements);
717 std::vector<double> offsetVector;
719 if (validOffset && validMeasurements)
721 for (
int step = 0; step < measurementsInt -1; ++step)
723 double currentOffset = -offsetDouble + 2 * step * offsetDouble / (measurementsInt - 2.0);
724 offsetVector.push_back(currentOffset);
729 MITK_WARN <<
"Invalid offset or measurements, offset calculation will only work for list sampling type.";
732 if (samplingType ==
"1" || samplingType ==
"Regular")
734 if (validOffset && validMeasurements)
736 results << normalizationIndicatingOffset <<
" ";
737 for (
const auto& entry : offsetVector)
739 results << entry <<
" ";
743 else if (samplingType ==
"2" || samplingType ==
"Alternating")
745 if (validOffset && validMeasurements)
747 results << normalizationIndicatingOffset <<
" ";
748 for (
auto& entry : offsetVector)
750 entry = std::abs(entry);
753 std::sort(offsetVector.begin(), offsetVector.end(), std::greater<>());
755 for (
unsigned int index = 0; index < offsetVector.size(); ++index)
757 offsetVector[index] = std::pow(-1, index) * offsetVector[index];
760 for (
auto& entry : offsetVector)
762 results << entry <<
" ";
766 else if (samplingType ==
"3" || samplingType ==
"List")
769 std::ifstream list(listPath.c_str());
773 std::string currentOffset;
774 while (std::getline(list, currentOffset))
776 results << currentOffset <<
" ";
781 MITK_ERROR <<
"Could not load list at " << listPath;
784 else if (samplingType ==
"4" || samplingType ==
"SingleOffset")
786 if (validOffset && validMeasurements)
788 results << normalizationIndicatingOffset <<
" ";
789 for (
int step = 0; step < measurementsInt - 1; ++step)
791 results << offsetDouble <<
" ";
797 MITK_WARN <<
"Encountered unknown sampling type.";
800 std::string resultString = results.str();
802 std::string::iterator newEnditerator =
803 std::unique(resultString.begin(), resultString.end(),
804 [=](
char lhs,
char rhs) {
return (lhs == rhs) && (lhs ==
' '); }
806 resultString.erase(newEnditerator, resultString.end());
808 if ((resultString.length() > 0) && (resultString.at(resultString.length() - 1) ==
' '))
810 resultString.erase(resultString.end() - 1, resultString.end());
813 if ((resultString.length() > 0) && (resultString.at(0) ==
' '))
815 resultString.erase(resultString.begin(), ++(resultString.begin()));
834 std::string stringToModule;
835 std::string libraryName;
836 itksys::SystemTools::SplitProgramPath(moduleLocation, stringToModule, libraryName);
838 std::stringstream jsonDirectory;
839 jsonDirectory << stringToModule <<
"/CESTRevisionMapping";
841 return jsonDirectory.str();
846 return "CEST.TotalScanTime";
851 return "CEST.PreparationType";
856 return "CEST.RecoveryMode";
861 return "CEST.SpoilingType";
866 return "CEST.Offsets";
871 return std::string(
"CEST.TREC");
static std::string ExtractRevision(std::string sequenceFileName)
const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_PREPERATIONTYPE()
mitk::PropertyList::Pointer ParseDicomProperty(mitk::TemporoSpatialStringProperty *dicomProperty)
parse the provided dicom property and return a property list based on the closest revision parameter ...
bool IsT1Sequence(std::string preparationType, std::string recoveryMode, std::string spoilingType, std::string revisionString)
Decides whether or not the image is likely to be a T1Map, if not it is assumed to be a CEST sequence...
void GetClosestLowerRevision(std::string revisionString)
const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_OFFSETS()
std::vector< int > GetExternalRevisions()
returns a vector revision numbers of all REVISIONNUMBER.json found beside the MitkCEST library ...
static const std::string m_CESTPropertyPrefix
prefix for all CEST related property names
const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_TOTALSCANTIME()
mitk::PropertyList::Pointer ParseDicomPropertyString(std::string dicomPropertyString)
parse the provided string and return a property list based on the closest revision parameter mapping ...
static const std::string m_RevisionPropertyName
name of the property for the data acquisition revision
void * GetService(const ServiceReferenceBase &reference)
static void info(const char *fmt,...)
std::string GetRevisionAppropriateJSONString(std::string revisionString)
Convenience class to temporarily change the current locale.
std::string GetExternalJSONDirectory()
returns the path where external jsons are expected to be located
void SetParseStrategy(std::string parseStrategy)
Module * GetModule() const
const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_TREC()
std::vector< int > GetInternalRevisions()
returns a vector revision numbers of all REVISIONNUMBER.json provided as resources during the compile...
std::string m_RevisionMappingStrategy
How to handle parameter mapping based on absent revision jsons.
std::vector< ServiceReferenceU > GetServiceReferences(const std::string &clazz, const std::string &filter=std::string())
std::string GetOffsetString(std::string samplingType, std::string offset, std::string measurements)
Get a string filled with the properly formated offsets based on the sampling type and offset...
ValueType GetValue() const
std::string m_DicomDataPath
path to the dicom data
const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_RECOVERYMODE()
std::string m_ClosestExternalRevision
the closest lower revision provided as a json beside the library, empty if none found ...
Interface of property persistence service.
static const std::string m_DefaultJsonString
default revision dependent json string if none is found
CustomTagParser(std::string relevantFile)
the constructor expects a path to one of the files to be loaded or the directory of the dicom files ...
std::string m_ClosestInternalRevision
the closest lower revision provided as resource, empty if none found
std::string m_ParseStrategy
Should the kind of data be automatically determined or should it be parsed as a specific one...
static const char * replace[]
This is a dictionary to replace long names of classes, modules, etc. to shorter versions in the conso...
static const std::string m_RevisionIndependentMapping
revision independent mapping to inject into the revision dependent json string
virtual bool AddInfo(const PropertyPersistenceInfo *info, bool overwrite=false)=0
Add persistence info for a specific base data property. If there is already a property info instance ...
const std::string MITKCEST_EXPORT CEST_PROPERTY_NAME_SPOILINGTYPE()
static const std::string m_JSONRevisionPropertyName
name of the property for the json parameter mapping revision
static ModuleContext * GetModuleContext()
Returns the module context of the calling module.
static const std::string m_OffsetsPropertyName
name of the property for the offsets, including normalization offsets
Property for time and space resolved string values.
void SetRevisionMappingStrategy(std::string revisionMappingStrategy)