diff --git a/Modules/ModelFit/src/Common/mitkFormulaParser.cpp b/Modules/ModelFit/src/Common/mitkFormulaParser.cpp index ab6f4e85e8..62cf9f373b 100644 --- a/Modules/ModelFit/src/Common/mitkFormulaParser.cpp +++ b/Modules/ModelFit/src/Common/mitkFormulaParser.cpp @@ -1,288 +1,287 @@ /*============================================================================ 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 #include #include #include #include "mitkFormulaParser.h" #include "mitkFresnel.h" namespace qi = boost::spirit::qi; namespace ascii = boost::spirit::ascii; namespace phx = boost::phoenix; typedef std::string::const_iterator Iter; typedef ascii::space_type Skipper; namespace qi = boost::spirit::qi; namespace mitk { /*! * @brief Transforms the given number from degrees to radians and returns it. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] deg A scalar value in degrees. * @return The given value in radians. */ template inline T deg2rad(const T deg) { return deg * boost::math::constants::pi() / static_cast(180); } /*! * @brief Returns the cosine of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose cosine should be returned. * @return The cosine of the given degree scalar. */ template inline T cosd(const T t) { return std::cos(deg2rad(t)); } /*! * @brief Returns the sine of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose sine should be returned. * @return The sine of the given degree scalar. */ template inline T sind(const T t) { return std::sin(deg2rad(t)); } /*! * @brief Returns the tangent of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose tangent should be returned. * @return The tangent of the given degree scalar. */ template inline T tand(const T t) { return std::tan(deg2rad(t)); } /*! * @brief Returns the fresnel integral sine at the given x-coordinate. * @details Code for "fresnel_s()" (fresnel.cpp and fresnel.h) taken as-is from the GNU * Scientific Library (https://www.gnu.org/software/gsl/). * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t The x-coordinate at which the fresnel integral sine should be returned. * @return The fresnel integral sine at the given x-coordinate. */ template T fresnelS(const T t) { T x = t / boost::math::constants::root_half_pi(); return static_cast(fresnel_s(x) / boost::math::constants::root_two_div_pi()); } /*! * @brief Returns the fresnel integral cosine at the given x-coordinate. * @details Code for "fresnel_c()" (fresnel.cpp and fresnel.h) taken as-is from the GNU * Scientific Library (https://www.gnu.org/software/gsl/). * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t The x-coordinate at which the fresnel integral cosine should be returned. * @return The fresnel integral cosine at the given x-coordinate. */ template T fresnelC(const T t) { T x = t / boost::math::constants::root_half_pi(); return static_cast(fresnel_c(x) / boost::math::constants::root_two_div_pi()); } /*! * @brief The grammar that defines the language (i.e. what is allowed) for the parser. */ class Grammar : public qi::grammar { /*! * @brief Helper structure that makes it easier to dynamically call any * one-parameter-function by overloading the @c () operator. */ struct func1_ { // Required for Phoenix 3+ template struct result; /*! * @brief Helper structure that is needed for compatibility with * @c boost::phoenix. * @tparam Functor Type of the functor (this struct). * @tparam Function Type of the function that should be called. * @tparam Arg1 Type of the argument the function should be called with. * */ template struct result { /*! @brief The result structure always needs this typedef */ typedef Arg1 type; }; /*! * @brief Calls the function @b f with the argument @b a1 and returns the * result. * The result always has the same type as the argument. * @tparam Function Type of the function that should be called. * @tparam Arg1 Type of the argument the function should be called with. * @param[in] f The function that should be called. * @param[in] a1 The argument the function should be called with. * @return The result of the called function. */ template Arg1 operator()(const Function f, const Arg1 a1) const { return f(a1); } }; /*! * @brief Helper structure that maps strings to function calls so that parsing e.g. * @c "cos(0)" actually calls the @c std::cos function with parameter @c 1 so it * returns @c 0. */ class unaryFunction_ : public qi::symbols::value_type, FormulaParser::ValueType(*)(FormulaParser::ValueType)> { public: /*! * @brief Constructs the structure, this is where the mapping takes place. */ unaryFunction_() { this->add ("abs", static_cast(&std::abs)) ("exp", static_cast(&std::exp)) // @TODO: exp ignores division by zero ("sin", static_cast(&std::sin)) ("cos", static_cast(&std::cos)) ("tan", static_cast(&std::tan)) ("sind", &sind) ("cosd", &cosd) ("tand", &tand) ("fresnelS", &fresnelS) ("fresnelC", &fresnelC); } } unaryFunction; public: /*! * @brief Constructs the grammar with the given formula parser. * @param[in, out] formulaParser The formula parser this grammar is for - so it can * access its variable map. */ Grammar(FormulaParser& formulaParser) : Grammar::base_type(start) { using qi::_val; using qi::_1; using qi::_2; using qi::char_; using qi::alpha; using qi::alnum; using qi::double_; using qi::as_string; phx::function func1; start = expression > qi::eoi; expression = term[_val = _1] >> *(('+' >> term[_val += _1]) | ('-' >> term[_val -= _1])); term = factor[_val = _1] >> *(('*' >> factor[_val *= _1]) | ('/' >> factor[_val /= _1])); - factor = primary[_val = _1]; - /*! @TODO: Repair exponentiation */ - //>> *('^' >> factor[phx::bind(std::pow, _val, _1)]); + factor = primary[_val = _1] + >> *('^' >> primary[_val = phx::bind(std::pow, _val, _1)]); variable = as_string[alpha >> *(alnum | char_('_'))] [_val = phx::bind(&FormulaParser::lookupVariable, &formulaParser, _1)]; primary = double_[_val = _1] | '(' >> expression[_val = _1] >> ')' | ('-' >> primary[_val = -_1]) | ('+' >> primary[_val = _1]) | (unaryFunction >> '(' >> expression >> ')')[_val = func1(_1, _2)] | variable[_val = _1]; } /*! the rules of the grammar. */ qi::rule start; qi::rule expression; qi::rule term; qi::rule factor; qi::rule variable; qi::rule primary; }; FormulaParser::FormulaParser(const VariableMapType* variables) : m_Variables(variables) {} FormulaParser::ValueType FormulaParser::parse(const std::string& input) { std::string::const_iterator iter = input.begin(); std::string::const_iterator end = input.end(); FormulaParser::ValueType result = static_cast(0); try { if (!qi::phrase_parse(iter, end, Grammar(*this), ascii::space, result)) { mitkThrowException(FormulaParserException) << "Could not parse '" << input << "': Grammar could not be applied to the input " << "at all."; } } catch (qi::expectation_failure& e) { std::string parsed = ""; for (Iter i = input.begin(); i != e.first; i++) { parsed += *i; } mitkThrowException(FormulaParserException) << "Error while parsing '" << input << "': Unexpected character '" << *e.first << "' after '" << parsed << "'"; } return result; }; FormulaParser::ValueType FormulaParser::lookupVariable(const std::string var) { if (m_Variables == nullptr) { mitkThrowException(FormulaParserException) << "Map of variables is empty"; } try { return m_Variables->at(var); } catch (std::out_of_range&) { mitkThrowException(FormulaParserException) << "No variable '" << var << "' defined in lookup"; } }; } diff --git a/Modules/ModelFit/test/mitkFormulaParserTest.cpp b/Modules/ModelFit/test/mitkFormulaParserTest.cpp index 1b91a8a70f..f372b79303 100644 --- a/Modules/ModelFit/test/mitkFormulaParserTest.cpp +++ b/Modules/ModelFit/test/mitkFormulaParserTest.cpp @@ -1,225 +1,224 @@ /*============================================================================ 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 "mitkTestingMacros.h" #include "mitkFormulaParser.h" using namespace mitk; #define TEST_NOTHROW(expression, MSG) \ do \ { \ MITK_TEST_OUTPUT_NO_ENDL(<< MSG) \ bool test_caught = false; \ try \ { \ expression; \ } \ catch(...) \ { \ test_caught = true; \ MITK_TEST_FAILED_MSG(<< "An unwanted exception was thrown"); \ } \ if(!test_caught) \ { \ MITK_TEST_OUTPUT(<< " [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } \ } while(0) /*! * @author Sascha Diatschuk */ class FormulaParserTests { public: static void TestConstructor() { std::map varMap; FormulaParser *nullParser = nullptr, *parser = nullptr; TEST_NOTHROW(nullParser = new FormulaParser(nullptr), "Testing constructor with NULL argument"); TEST_NOTHROW(parser = new FormulaParser(&varMap), "Testing constructor with valid argument"); delete nullParser; delete parser; } static void TestLookupVariable() { // variable map is NULL FormulaParser *nullParser = new FormulaParser(nullptr); MITK_TEST_FOR_EXCEPTION(FormulaParserException, nullParser->lookupVariable("test")); delete nullParser; // variable map is empty std::map varMap; FormulaParser *parser = new FormulaParser(&varMap); MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->lookupVariable("test")); // lookup should succeed double var; varMap["test"] = 17; TEST_NOTHROW(var = parser->lookupVariable("test"), "Testing if lookupVariable throws unwanted exceptions"); MITK_TEST_CONDITION_REQUIRED(var == 17, "Testing if lookupVariable returns the correct value"); delete parser; } static void TestParse() { std::map varMap; varMap["test"] = 17; FormulaParser *parser = new FormulaParser(&varMap); // empty string MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("")); // grammar can't process string MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("_")); // unexpected character MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("5=")); // unknown variable MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("a")); double d; // addition TEST_NOTHROW(d = parser->parse("1+2"), "Testing if addition throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 3, "Testing if addition produces the correct result"); // subtraction TEST_NOTHROW(d = parser->parse("5-1"), "Testing if subtraction throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 4, "Testing if subtraction produces the correct result"); // multiplication TEST_NOTHROW(d = parser->parse("3*4"), "Testing if multiplication throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 12, "Testing if multiplication produces the correct result"); // division TEST_NOTHROW(d = parser->parse("28/4"), "Testing if division throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 7, "Testing if division produces the correct result"); - /*! @TODO: Reactivate as soon as exponentiation works again */ // exponentiation - //TEST_NOTHROW(d = parser->parse("2^3"), - // "Testing if exponentiation throws an unwanted exception"); - //MITK_TEST_CONDITION_REQUIRED(d == 8, - // "Testing if exponentiation produces the correct result"); + TEST_NOTHROW(d = parser->parse("2^3"), + "Testing if exponentiation throws an unwanted exception"); + MITK_TEST_CONDITION_REQUIRED(d == 8, + "Testing if exponentiation produces the correct result"); // algebraic signs TEST_NOTHROW(d = parser->parse("-7 + +1 - -1"), "Testing if algebraic signs throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == -5, "Testing if algebraic signs produce the correct result"); // parentheses TEST_NOTHROW(d = parser->parse("(1+2)*(4-2)"), "Testing if parentheses throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 6, "Testing if parentheses produce the correct result"); // variables TEST_NOTHROW(d = parser->parse("2*test-test"), "Testing if variables throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 17, "Testing if variables produce the correct result"); // abs TEST_NOTHROW(d = parser->parse("abs(-5)"), "Testing if abs throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 5, "Testing if abs produces the correct result"); const double eps = 0.0001; // exp TEST_NOTHROW(d = parser->parse("exp(1)"), "Testing if exp throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 2.71828182846) < eps, "Testing if exp produces the correct result"); // sin TEST_NOTHROW(d = parser->parse("sin(1.57079632679)"), "Testing if sin throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 1) < eps, "Testing if sin produces the correct result"); // cos TEST_NOTHROW(d = parser->parse("cos(3.14159265359)"), "Testing if cos throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d + 1) < eps, "Testing if cos produces the correct result"); // tan TEST_NOTHROW(d = parser->parse("tan(0.46364760899)"), "Testing if tan throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.5) < eps, "Testing if tan produces the correct result"); // sind TEST_NOTHROW(d = parser->parse("sind(145)"), "Testing if sind throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.57357643635) < eps, "Testing if sind produces the correct result"); // cosd TEST_NOTHROW(d = parser->parse("cosd(90)"), "Testing if cosd throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d < eps, "Testing if cosd produces the correct result"); // tand TEST_NOTHROW(d = parser->parse("tand(15)"), "Testing if tand throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.26794919243) < eps, "Testing if tand produces the correct result"); // fresnelS TEST_NOTHROW(d = parser->parse("fresnelS(1)"), "Testing if fresnelS throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.310268) < eps, "Testing if fresnelS produces the correct result"); TEST_NOTHROW(d = parser->parse("fresnelC(1)"), "Testing if fresnelC throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.904524) < eps, "Testing if fresnelC produces the correct result"); delete parser; } }; int mitkFormulaParserTest(int, char *[]) { MITK_TEST_BEGIN("FormulaParser Test"); FormulaParserTests::TestConstructor(); FormulaParserTests::TestLookupVariable(); FormulaParserTests::TestParse(); MITK_TEST_END(); }