diff --git a/Modules/FitMIData/CMakeLists.txt b/Modules/FitMIData/CMakeLists.txt index 520ddbd086..c9f3089296 100644 --- a/Modules/FitMIData/CMakeLists.txt +++ b/Modules/FitMIData/CMakeLists.txt @@ -1,19 +1,19 @@ MITK_CREATE_MODULE(FitMIData INCLUDE_DIRS PUBLIC ${MITK_BINARY_DIR} PRIVATE src/Common src/Functors src/Models src/TestingHelper DEPENDS PUBLIC MitkCore MitkSceneSerializationBase - PRIVATE MitkFormulaParser MitkMultilabel + PRIVATE MitkMultilabel PACKAGE_DEPENDS PUBLIC ITK|ITKOptimizers PRIVATE Boost ) if(BUILD_TESTING) ADD_SUBDIRECTORY(test) endif(BUILD_TESTING) ADD_SUBDIRECTORY(autoload/IO) ADD_SUBDIRECTORY(autoload/Models) ADD_SUBDIRECTORY(cmdapps) diff --git a/Modules/FitMIData/files.cmake b/Modules/FitMIData/files.cmake index 99bbfb762e..4db6c02121 100644 --- a/Modules/FitMIData/files.cmake +++ b/Modules/FitMIData/files.cmake @@ -1,73 +1,75 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES Common/mitkExtractTimeGrid.cpp Common/mitkTimeGridHelper.cpp Common/mitkMaskedDynamicImageStatisticsGenerator.cpp Common/mitkModelFitConstants.cpp Common/mitkModelFitParameter.cpp Common/mitkModelFitCmdAppsHelper.cpp Common/mitkParameterFitImageGeneratorBase.cpp Common/mitkPixelBasedParameterFitImageGenerator.cpp Common/mitkROIBasedParameterFitImageGenerator.cpp Common/mitkModelFitInfo.cpp Common/mitkModelFitStaticParameterMap.cpp Common/mitkModelGenerator.cpp Common/mitkModelFitUIDHelper.cpp Common/mitkModelFitResultHelper.cpp Common/mitkScalarListLookupTable.cpp Common/mitkScalarListLookupTableProperty.cpp Common/mitkScalarListLookupTablePropertySerializer.cpp Common/mitkIModelFitProvider.cpp Common/mitkModelFitParameterValueExtraction.cpp Common/mitkBinaryImageToLabelSetImageFilter.cpp + Common/mitkFormulaParser.cpp + Common/mitkFresnel.cpp Functors/mitkSimpleFunctorBase.cpp Functors/mitkSimpleFunctorPolicy.cpp Functors/mitkChiSquareFitCostFunction.cpp Functors/mitkReducedChiSquareFitCostFunction.cpp Functors/mitkConstraintCheckerBase.cpp Functors/mitkSimpleBarrierConstraintChecker.cpp Functors/mitkSquaredDifferencesFitCostFunction.cpp Functors/mitkSumOfSquaredDifferencesFitCostFunction.cpp Functors/mitkMVConstrainedCostFunctionDecorator.cpp Functors/mitkMVModelFitCostFunction.cpp Functors/mitkNormalizedSumOfSquaredDifferencesFitCostFunction.cpp Functors/mitkSVModelFitCostFunction.cpp Functors/mitkModelFitFunctorBase.cpp Functors/mitkLevenbergMarquardtModelFitFunctor.cpp Functors/mitkDummyModelFitFunctor.cpp Functors/mitkModelFitInfoSignalGenerationFunctor.cpp Functors/mitkIndexedValueFunctorPolicy.cpp Functors/mitkModelDataGenerationFunctor.cpp Models/mitkModelBase.cpp Models/mitkModelFactoryBase.cpp Models/mitkModelParameterizerBase.cpp Models/mitkLinearModel.cpp Models/mitkLinearModelFactory.cpp Models/mitkInitialParameterizationDelegateBase.cpp Models/mitkImageBasedParameterizationDelegate.cpp Models/mitkGenericParamModel.cpp Models/mitkGenericParamModelFactory.cpp Models/mitkGenericParamModelParameterizer.cpp Models/mitkValueBasedParameterizationDelegate.cpp Models/mitkT2DecayModel.cpp Models/mitkT2DecayModelFactory.cpp Models/mitkT2DecayModelParameterizer.cpp TestingHelper/mitkTestArtifactGenerator.cpp TestingHelper/mitkTestModel.cpp TestingHelper/mitkTestModelFactory.cpp ) set(TPP_FILES include/itkMultiOutputNaryFunctorImageFilter.tpp include/itkMaskedStatisticsImageFilter.hxx include/itkMaskedNaryStatisticsImageFilter.hxx include/mitkModelFitProviderBase.tpp ) set(HXX_FILES ) set(MOC_H_FILES ) diff --git a/Modules/FitMIData/include/mitkFormulaParser.h b/Modules/FitMIData/include/mitkFormulaParser.h new file mode 100644 index 0000000000..feba5c6fc4 --- /dev/null +++ b/Modules/FitMIData/include/mitkFormulaParser.h @@ -0,0 +1,111 @@ +/*=================================================================== + +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. + +===================================================================*/ + +#ifndef __MITKFORMULAPARSER_H__ +#define __MITKFORMULAPARSER_H__ + +#include +#include + +#include "mitkExceptionMacro.h" + +#include "MitkFitMIDataExports.h" + +namespace mitk +{ + /*! + * @brief Exception class for all exceptions that are generated in the FormulaParser module. + */ + class MITKFITMIDATA_EXPORT FormulaParserException : public mitk::Exception + { + public: + mitkExceptionClassMacro(FormulaParserException, mitk::Exception); + }; + + + /*! + * @brief This class offers the functionality to evaluate simple mathematical formula + * strings (e.g. "3.5 + 4 * x * sin(x) - 1 / 2"). + * @details Internally it utilizes the @c boost::spirit framework with the @ref Grammar + * structure to parse the input string into a valid result number. + * Function strings (e.g. @c "sin", @c "tan" or @c "abs") are translated to + * actual function calls and variables (e.g. @c "x", @c "myVariable", "amount_") + * are replaced by their currently assigned value via a look-up table. + * + * The parser is able to recognize: + * @li sums, differences, products and divisions (a + b, + * 4 - 3, 2 * x, 9 / 3) + * @li algebraic signs (@c +5, @c -5) + * @li exponentiation (2 ^ 4) + * @li parentheses (3 * (4 + 2)) + * @li variables (@c x, @c myVar, @c test2_var_) + * @li the following unary functions: @c abs, @c exp, @c sin, @c cos, @c tan, + * @c sind (sine in degrees), @c cosd (cosine in degrees), @c tand (tangent in + * degrees) + * + * In order to use the FormulaParser you just have to initialize it with a map of + * variables (i.e. a look-up table where @c "x" is assigned to @c 5 for example and + * @c "y" is assigned to @c 13 and so on) and then call the + * @ref FormulaParser::parse function with the string that should be evaluated. Be + * sure to update the look-up table everytime a variable's value changes since that + * is not done automatically. + * + * @author Sascha Diatschuk + */ + class MITKFITMIDATA_EXPORT FormulaParser + { + public: + using ValueType = double; + using VariableMapType = std::map; + /*! + * @brief Construct the FormulaParser and initialized the variables with + * @b variables. + * @param[in] variables A map of variables with values that will be assigned to the + * corresponding member variable. The map is delivered as a + * pointer so you can still change it from outside this class. + */ + FormulaParser(const VariableMapType* variables); + + /*! + * @brief Evaluates the @b input string and returns the resulting + * value. + * @param[in] input The string to be evaluated. + * @return The number that results from the evaluated string. + * @throw FormulaParserException If + * @li the parser comes across an unexpected character, + * @li a variable in the input string could not be found in the look-up + * table or + * @li the parser cannot apply the grammar to the string at all. + */ + ValueType parse(const std::string& input); + + /*! + * @brief Looks up the associated value of the given string @b var in the + * variables map. + * @param[in] var The name of the variable whose value is to be returned. + * @return The associated value of the given variable name. + * @throw FormulaParserException If the variable map is empty or the given variable name + * cannot be found. + */ + ValueType lookupVariable(const std::string var); + + private: + /*! @brief Map that holds the values that will replace the variables during evaluation. */ + const VariableMapType* m_Variables; + }; +} + +#endif diff --git a/Modules/FitMIData/include/mitkFresnel.h b/Modules/FitMIData/include/mitkFresnel.h new file mode 100644 index 0000000000..328de73873 --- /dev/null +++ b/Modules/FitMIData/include/mitkFresnel.h @@ -0,0 +1,57 @@ +/*=================================================================== + +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. + +===================================================================*/ + +/**************************************************************************** + * fresnel.h - + * Calculation of Fresnel integrals by expansion to Chebyshev series + * Expansions are taken from the book + * Y.L. Luke. Mathematical functions and their approximations. + * Ìoscow, "Mir", 1980. PP. 145-149 (Russian edition) + **************************************************************************** + */ + +#ifndef MITKFRESNEL_H_ +#define MITKFRESNEL_H_ + +namespace mitk +{ + + /* fresnel_c(x) - Fresnel Cosine Integral + * C(x)=fresnel_c(x)=\dint\limits_{0}^{x}\cos (\frac{\pi}{2}t^{2})dt + */ + double fresnel_c(double x); + + /* fresnel_s(x) - Fresnel Sine Integral + * S(x)=fresnel_s(x)=\dint\limits_{0}^{x}\sin (\frac{\pi}{2}t^{2})dt + */ + double fresnel_s(double x); + + /* Additional functions*/ + /* fresnel_c2(x) + * fresnel_c2(x)=fresnel_c(x*sqrt(2/pi))= + * = \sqrt{\frac{2}{\pi }}\dint\limits_{0}^{x}\cos (t^{2})dt + */ + double fresnel_c2(double x); + + /* fresnel_s2(x) + * fresnel_s2(x)=fresnel_s(x*sqrt(2/pi))= + * = \sqrt{\frac{2}{\pi }}\dint\limits_{0}^{x}\sin (t^{2})dt + */ + double fresnel_s2(double x); +} + +#endif /* FRESNEL_H_ */ + diff --git a/Modules/FitMIData/src/Common/mitkFormulaParser.cpp b/Modules/FitMIData/src/Common/mitkFormulaParser.cpp new file mode 100644 index 0000000000..614209d43c --- /dev/null +++ b/Modules/FitMIData/src/Common/mitkFormulaParser.cpp @@ -0,0 +1,294 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include +#include +#include +#include + +#include "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 (http://www.gnu.org/software/gsl/), specifically from + * http://www.network-theory.co.uk/download/gslextras/Fresnel/. + * @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 (http://www.gnu.org/software/gsl/), specifically from + * http://www.network-theory.co.uk/download/gslextras/Fresnel/. + * @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)]); + + 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 == NULL) + { + 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"; + } + }; + +} \ No newline at end of file diff --git a/Modules/FitMIData/src/Common/mitkFresnel.cpp b/Modules/FitMIData/src/Common/mitkFresnel.cpp new file mode 100644 index 0000000000..4a533b10f4 --- /dev/null +++ b/Modules/FitMIData/src/Common/mitkFresnel.cpp @@ -0,0 +1,287 @@ +/*=================================================================== + +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. + +===================================================================*/ + +/**************************************************************************** + * fresnel.c - + * Calculation of Fresnel integrals by expansion to Chebyshev series + * Expansions are taken from the book + * Y.L. Luke. Mathematical functions and their approximations. + * Moscow, "Mir", 1980. PP. 145-149 (Russian edition) + **************************************************************************** + */ + +#include + +static const double sqrt_pi_2 = 1.2533141373155002512078826424; /* sqrt(pi/2) */ +static const double sqrt_2_pi = 0.7978845608028653558798921199; /* sqrt(2/pi) */ +static const double _1_sqrt_2pi = 0.3989422804014326779399460599; /* 1/sqrt(2*pi) */ +static const double pi_2 = 1.5707963267948966192313216916; /* pi/2 */ + +static double f_data_a[18] = +{ + 0.76435138664186000189, + -0.43135547547660179313, + 0.43288199979726653054, + -0.26973310338387111029, + 0.08416045320876935378, + -0.01546524484461381958, + 0.00187855423439822018, + -0.00016264977618887547, + 0.00001057397656383260, + -0.00000053609339889243, + 0.00000002181658454933, + -0.00000000072901621186, + 0.00000000002037332546, + -0.00000000000048344033, + 0.00000000000000986533, + -0.00000000000000017502, + 0.00000000000000000272, + -0.00000000000000000004 +}; + +static double f_data_b[17] = +{ + 0.63041404314570539241, + -0.42344511405705333544, + 0.37617172643343656625, + -0.16249489154509567415, + 0.03822255778633008694, + -0.00564563477132190899, + 0.00057454951976897367, + -0.00004287071532102004, + 0.00000245120749923299, + -0.00000011098841840868, + 0.00000000408249731696, + -0.00000000012449830219, + 0.00000000000320048425, + -0.00000000000007032416, + 0.00000000000000133638, + -0.00000000000000002219, + 0.00000000000000000032 +}; + +static double fresnel_cos_0_8(double x) +{ + double x_8 = x / 8.0; + double xx = 2.0*x_8*x_8 - 1.0; + + double t0 = 1.0; + double t1 = xx; + double sumC = f_data_a[0] + f_data_a[1] * t1; + double t2; + int n; + for (n = 2; n < 18; n++) + { + t2 = 2.0*xx*t1 - t0; + sumC += f_data_a[n] * t2; + t0 = t1; t1 = t2; + } + return _1_sqrt_2pi * sqrt(x)*sumC; +} + +static double fresnel_sin_0_8(double x) +{ + double x_8 = x / 8.0; + double xx = 2.0*x_8*x_8 - 1.0; + double t0 = 1.; + double t1 = xx; + double ot1 = x_8; + double ot2 = 2.0*x_8*t1 - ot1; + double sumS = f_data_b[0] * ot1 + f_data_b[1] * ot2; + int n; + double t2; + for (n = 2; n < 17; n++) + { + t2 = 2.0*xx*t1 - t0; + ot1 = ot2; + ot2 = 2.0*x_8*t2 - ot1; + sumS += f_data_b[n] * ot2; + t0 = t1; t1 = t2; + } + return _1_sqrt_2pi * sqrt(x)*sumS; +} + +static double f_data_e[41] = +{ + 0.97462779093296822410, + -0.02424701873969321371, + 0.00103400906842977317, + -0.00008052450246908016, + 0.00000905962481966582, + -0.00000131016996757743, + 0.00000022770820391497, + -0.00000004558623552026, + 0.00000001021567537083, + -0.00000000251114508133, + 0.00000000066704761275, + -0.00000000018931512852, + 0.00000000005689898935, + -0.00000000001798219359, + 0.00000000000594162963, + -0.00000000000204285065, + 0.00000000000072797580, + -0.00000000000026797428, + 0.00000000000010160694, + -0.00000000000003958559, + 0.00000000000001581262, + -0.00000000000000646411, + 0.00000000000000269981, + -0.00000000000000115038, + 0.00000000000000049942, + -0.00000000000000022064, + 0.00000000000000009910, + -0.00000000000000004520, + 0.00000000000000002092, + -0.00000000000000000982, + 0.00000000000000000467, + -0.00000000000000000225, + 0.00000000000000000110, + -0.00000000000000000054, + 0.00000000000000000027, + -0.00000000000000000014, + 0.00000000000000000007, + -0.00000000000000000004, + 0.00000000000000000002, + -0.00000000000000000001, + 0.00000000000000000001 +}; + +static double f_data_f[35] = +{ + 0.99461545179407928910, + -0.00524276766084297210, + 0.00013325864229883909, + -0.00000770856452642713, + 0.00000070848077032045, + -0.00000008812517411602, + 0.00000001359784717148, + -0.00000000246858295747, + 0.00000000050925789921, + -0.00000000011653400634, + 0.00000000002906578309, + -0.00000000000779847361, + 0.00000000000222802542, + -0.00000000000067239338, + 0.00000000000021296411, + -0.00000000000007041482, + 0.00000000000002419805, + -0.00000000000000861080, + 0.00000000000000316287, + -0.00000000000000119596, + 0.00000000000000046444, + -0.00000000000000018485, + 0.00000000000000007527, + -0.00000000000000003131, + 0.00000000000000001328, + -0.00000000000000000574, + 0.00000000000000000252, + -0.00000000000000000113, + 0.00000000000000000051, + -0.00000000000000000024, + 0.00000000000000000011, + -0.00000000000000000005, + 0.00000000000000000002, + -0.00000000000000000001, + 0.00000000000000000001 +}; + +static double fresnel_cos_8_inf(double x) +{ + double xx = 128.0 / (x*x) - 1.0; /* 2.0*(8/x)^2 - 1 */ + double t0 = 1.0; + double t1 = xx; + double sumP = f_data_e[0] + f_data_e[1] * t1; + double sumQ = f_data_f[0] + f_data_f[1] * t1; + double t2; + int n; + for (n = 2; n < 35; n++) + { + t2 = 2.0*xx*t1 - t0; + sumP += f_data_e[n] * t2; /* sumP += f_data_e[n]*ChebyshevT(n,xx) */ + sumQ += f_data_f[n] * t2; /* sumQ += f_data_f[n]*ChebyshevT(n,xx) */ + t0 = t1; t1 = t2; + } + for (n = 35; n < 41; n++) + { + t2 = 2.0*xx*t1 - t0; + sumP += f_data_e[n] * t2; /* sumP += f_data_e[n]*ChebyshevT(n,xx) */ + t0 = t1; t1 = t2; + } + return 0.5 - _1_sqrt_2pi * (0.5*sumP*cos(x) / x - sumQ * sin(x)) / sqrt(x); +} + +static double fresnel_sin_8_inf(double x) +{ + double xx = 128.0 / (x*x) - 1.0; /* 2.0*(8/x)^2 - 1 */ + double t0 = 1.0; + double t1 = xx; + double sumP = f_data_e[0] + f_data_e[1] * t1; + double sumQ = f_data_f[0] + f_data_f[1] * t1; + double t2; + int n; + for (n = 2; n < 35; n++) + { + t2 = 2.0*xx*t1 - t0; + sumP += f_data_e[n] * t2; /* sumP += f_data_e[n]*ChebyshevT(n,xx) */ + sumQ += f_data_f[n] * t2; /* sumQ += f_data_f[n]*ChebyshevT(n,xx) */ + t0 = t1; t1 = t2; + } + for (n = 35; n < 41; n++) + { + t2 = 2.0*xx*t1 - t0; + sumP += f_data_e[n] * t2; /* sumQ += f_data_f[n]*ChebyshevT(n,xx) */ + t0 = t1; t1 = t2; + } + return 0.5 - _1_sqrt_2pi * (0.5*sumP*sin(x) / x + sumQ * cos(x)) / sqrt(x); +} + + +namespace mitk +{ + + double fresnel_c(double x) + { + double xx = x * x*pi_2; + double ret_val; + if (xx <= 8.0) + ret_val = fresnel_cos_0_8(xx); + else + ret_val = fresnel_cos_8_inf(xx); + return (x < 0.0) ? -ret_val : ret_val; + } + + double fresnel_s(double x) + { + double xx = x * x*pi_2; + double ret_val; + if (xx <= 8.0) + ret_val = fresnel_sin_0_8(xx); + else + ret_val = fresnel_sin_8_inf(xx); + return (x < 0.0) ? -ret_val : ret_val; + } + + double fresnel_c2(double x) + { + return fresnel_c(x*sqrt_2_pi); + } + + double fresnel_s2(double x) + { + return fresnel_s(x*sqrt_2_pi); + } + +} diff --git a/Modules/FitMIData/src/Models/mitkGenericParamModel.cpp b/Modules/FitMIData/src/Models/mitkGenericParamModel.cpp index 00b0ef014c..a41d98c78e 100644 --- a/Modules/FitMIData/src/Models/mitkGenericParamModel.cpp +++ b/Modules/FitMIData/src/Models/mitkGenericParamModel.cpp @@ -1,173 +1,173 @@ /*=================================================================== 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 "mitkGenericParamModel.h" -#include "FormulaParser.h" +#include "mitkFormulaParser.h" const std::string mitk::GenericParamModel::NAME_STATIC_PARAMETER_number = "number_of_parameters"; std::string mitk::GenericParamModel::GetModelDisplayName() const { return "Generic Parameter Model"; }; std::string mitk::GenericParamModel::GetModelType() const { return "Generic"; }; mitk::GenericParamModel::FunctionStringType mitk::GenericParamModel::GetFunctionString() const { return m_FunctionString; }; std::string mitk::GenericParamModel::GetXName() const { return "x"; }; mitk::GenericParamModel::GenericParamModel(): m_FunctionString(""), m_NumberOfParameters(1) { }; mitk::GenericParamModel::ParameterNamesType mitk::GenericParamModel::GetParameterNames() const { ParameterNamesType result; result.push_back("a"); if (m_NumberOfParameters > 1) { result.push_back("b"); } if (m_NumberOfParameters > 2) { result.push_back("c"); } if (m_NumberOfParameters > 3) { result.push_back("d"); } if (m_NumberOfParameters > 4) { result.push_back("e"); } if (m_NumberOfParameters > 5) { result.push_back("f"); } if (m_NumberOfParameters > 6) { result.push_back("g"); } if (m_NumberOfParameters > 7) { result.push_back("h"); } if (m_NumberOfParameters > 8) { result.push_back("i"); } if (m_NumberOfParameters > 9) { result.push_back("j"); } return result; }; mitk::GenericParamModel::ParametersSizeType mitk::GenericParamModel::GetNumberOfParameters() const { return m_NumberOfParameters; }; mitk::GenericParamModel::ModelResultType mitk::GenericParamModel::ComputeModelfunction(const ParametersType& parameters) const { unsigned int timeSteps = m_TimeGrid.GetSize(); ModelResultType signal(timeSteps); std::map parameterMap; parameterMap.insert(std::make_pair(GetXName(), 0.0)); auto paramNames = this->GetParameterNames(); for (ParametersType::size_type i = 0; i < parameters.size(); ++i) { parameterMap.insert(std::make_pair(paramNames[i], parameters[i])); } - FormulaParsing::FormulaParser formulaParser(¶meterMap); + FormulaParser formulaParser(¶meterMap); TimeGridType::const_iterator timeGridEnd = m_TimeGrid.end(); ModelResultType::iterator signalPos = signal.begin(); for (TimeGridType::const_iterator gridPos = m_TimeGrid.begin(); gridPos != timeGridEnd; ++gridPos, ++signalPos) { parameterMap[GetXName()] = *gridPos; *signalPos = formulaParser.parse(m_FunctionString); } return signal; }; mitk::GenericParamModel::ParameterNamesType mitk::GenericParamModel::GetStaticParameterNames() const { ParameterNamesType result; result.push_back(NAME_STATIC_PARAMETER_number); return result; } mitk::GenericParamModel::ParametersSizeType mitk::GenericParamModel::GetNumberOfStaticParameters() const { return 1; } void mitk::GenericParamModel::SetStaticParameter(const ParameterNameType& name, const StaticParameterValuesType& values) { if (name == NAME_STATIC_PARAMETER_number) { SetNumberOfParameters(values[0]); } }; mitk::GenericParamModel::StaticParameterValuesType mitk::GenericParamModel::GetStaticParameterValue( const ParameterNameType& name) const { StaticParameterValuesType result; if (name == NAME_STATIC_PARAMETER_number) { result.push_back(m_NumberOfParameters); } return result; }; itk::LightObject::Pointer mitk::GenericParamModel::InternalClone() const { GenericParamModel::Pointer newClone = GenericParamModel::New(); newClone->SetTimeGrid(this->m_TimeGrid); newClone->SetNumberOfParameters(this->m_NumberOfParameters); return newClone.GetPointer(); }; \ No newline at end of file diff --git a/Modules/FitMIData/test/files.cmake b/Modules/FitMIData/test/files.cmake index 65e3db4380..8e408bd49c 100644 --- a/Modules/FitMIData/test/files.cmake +++ b/Modules/FitMIData/test/files.cmake @@ -1,15 +1,16 @@ SET(MODULE_TESTS itkMultiOutputNaryFunctorImageFilterTest.cpp itkMaskedStatisticsImageFilterTest.cpp itkMaskedNaryStatisticsImageFilterTest.cpp mitkLevenbergMarquardtModelFitFunctorTest.cpp mitkPixelBasedParameterFitImageGeneratorTest.cpp mitkROIBasedParameterFitImageGeneratorTest.cpp mitkMaskedDynamicImageStatisticsGeneratorTest.cpp mitkModelFitInfoTest.cpp mitkModelFitUIDHelperTest.cpp mitkModelFitStaticParameterMapTest.cpp mitkSimpleBarrierConstraintCheckerTest.cpp mitkMVConstrainedCostFunctionDecoratorTest.cpp mitkConcreteModelFactoryBaseTest.cpp + mitkFormulaParserTest.cpp ) diff --git a/Modules/FitMIData/test/mitkFormulaParserTest.cpp b/Modules/FitMIData/test/mitkFormulaParserTest.cpp new file mode 100644 index 0000000000..395524b354 --- /dev/null +++ b/Modules/FitMIData/test/mitkFormulaParserTest.cpp @@ -0,0 +1,229 @@ +/*=================================================================== + +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 "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 = NULL, *parser = NULL; + + TEST_NOTHROW(nullParser = new FormulaParser(NULL), + "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(NULL); + 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"); + + // 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(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(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(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(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(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(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(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(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(); +} diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/CMakeLists.txt b/Plugins/org.mitk.gui.qt.fit.inspector/CMakeLists.txt index 2a00b46c8a..51e2691229 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/CMakeLists.txt +++ b/Plugins/org.mitk.gui.qt.fit.inspector/CMakeLists.txt @@ -1,7 +1,7 @@ project(org_mitk_gui_qt_fit_inspector) mitk_create_plugin( EXPORT_DIRECTIVE MODELFIT_INSPECTOR_EXPORT EXPORTED_INCLUDE_SUFFIXES src - MODULE_DEPENDS MitkQtWidgetsExt MitkFormulaParser MitkFitMIData MitkFitMIDataUI + MODULE_DEPENDS MitkQtWidgetsExt MitkFitMIData MitkFitMIDataUI ) diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp index 60c4685d48..7a5c2abbab 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp +++ b/Plugins/org.mitk.gui.qt.fit.inspector/src/internal/ModelFitInspectorView.cpp @@ -1,1342 +1,1342 @@ /*=================================================================== 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. ===================================================================*/ // Blueberry #include #include #include // mitk #include // Qt #include #include #include #include -#include "FormulaParser.h" +#include "mitkFormulaParser.h" #include "mitkScalarListLookupTableProperty.h" #include "mitkModelFitConstants.h" #include "mitkExtractTimeGrid.h" #include "mitkModelGenerator.h" #include "mitkModelFitException.h" #include "mitkModelFitParameterValueExtraction.h" #include "ModelFitInspectorView.h" #include const std::string ModelFitInspectorView::VIEW_ID = "org.mitk.gui.gt.fit.inspector"; const unsigned int ModelFitInspectorView::INTERPOLATION_STEPS = 100; ModelFitInspectorView::ObserverInfo::ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part) : controller(controller), observerTag(observerTag), renderWindowName(renderWindowName), renderWindowPart(part) { } ModelFitInspectorView::ModelFitInspectorView() : m_renderWindowPart(nullptr), m_PendingSliceChangedEvent(false), m_internalUpdateFlag(false), m_currentFit(nullptr), m_currentModelParameterizer(nullptr), m_currentModelProviderService(nullptr), m_currentSelectedTimeStep(0), m_currentSelectedNode(nullptr) { m_currentSelectedPosition.Fill(0.0); m_modelfitList.clear(); } ModelFitInspectorView::~ModelFitInspectorView() { this->RemoveAllObservers(); } bool ModelFitInspectorView::InitObservers() { bool result = true; typedef QHash WindowMapType; WindowMapType windowMap = m_renderWindowPart->GetQmitkRenderWindows(); auto i = windowMap.begin(); while (i != windowMap.end()) { mitk::SliceNavigationController* sliceNavController = i.value()->GetSliceNavigationController(); if (sliceNavController) { itk::ReceptorMemberCommand::Pointer cmdSliceEvent = itk::ReceptorMemberCommand::New(); cmdSliceEvent->SetCallbackFunction(this, &ModelFitInspectorView::OnSliceChanged); int tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), cmdSliceEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &ModelFitInspectorView::OnSliceChanged); tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); itk::MemberCommand::Pointer cmdDelEvent = itk::MemberCommand::New(); cmdDelEvent->SetCallbackFunction(this, &ModelFitInspectorView::OnSliceNavigationControllerDeleted); tag = sliceNavController->AddObserver( itk::DeleteEvent(), cmdDelEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); } ++i; result = result && sliceNavController; } return result; } void ModelFitInspectorView::RemoveObservers(const mitk::SliceNavigationController* deletedSlicer) { std::pair < ObserverMapType::const_iterator, ObserverMapType::const_iterator> obsRange = m_ObserverMap.equal_range(deletedSlicer); for (ObserverMapType::const_iterator pos = obsRange.first; pos != obsRange.second; ++pos) { pos->second.controller->RemoveObserver(pos->second.observerTag); } m_ObserverMap.erase(deletedSlicer); } void ModelFitInspectorView::RemoveAllObservers(mitk::IRenderWindowPart* deletedPart) { for (ObserverMapType::const_iterator pos = m_ObserverMap.begin(); pos != m_ObserverMap.end(); ) { ObserverMapType::const_iterator delPos = pos++; if (deletedPart == nullptr || deletedPart == delPos->second.renderWindowPart) { delPos->second.controller->RemoveObserver(delPos->second.observerTag); m_ObserverMap.erase(delPos); } } } void ModelFitInspectorView::OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/) { const mitk::SliceNavigationController* sendingSlicer = dynamic_cast(sender); this->RemoveObservers(sendingSlicer); } void ModelFitInspectorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_renderWindowPart != renderWindowPart) { m_renderWindowPart = renderWindowPart; if (!InitObservers()) { QMessageBox::information(nullptr, "Error", "Unable to set up the event observers. The " \ "plot will not be triggered on changing the crosshair, " \ "position or time step."); } } } void ModelFitInspectorView::RenderWindowPartDeactivated( mitk::IRenderWindowPart* renderWindowPart) { m_renderWindowPart = nullptr; this->RemoveAllObservers(renderWindowPart); } void ModelFitInspectorView::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect(m_Controls.cmbFit, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFitSelectionChanged(int))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.sbFixMin, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.sbFixMax, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.labelFixMin, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.labelFixMax, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), m_Controls.btnScaleToData, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed, SIGNAL(toggled(bool)), this, SLOT(OnScaleFixedYChecked(bool))); connect(m_Controls.btnScaleToData, SIGNAL(clicked()), this, SLOT(OnScaleToDataYClicked())); connect(m_Controls.sbFixMax, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingYChanged(double))); connect(m_Controls.sbFixMin, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingYChanged(double))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.sbFixMin_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.sbFixMax_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.labelFixMin_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.labelFixMax_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), m_Controls.btnScaleToData_x, SLOT(setEnabled(bool))); connect(m_Controls.radioScaleFixed_x, SIGNAL(toggled(bool)), this, SLOT(OnScaleFixedXChecked(bool))); connect(m_Controls.btnScaleToData_x, SIGNAL(clicked()), this, SLOT(OnScaleToDataXClicked())); connect(m_Controls.sbFixMax_x, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingXChanged(double))); connect(m_Controls.sbFixMin_x, SIGNAL(valueChanged(double)), this, SLOT(OnFixedScalingXChanged(double))); connect(m_Controls.btnExport, SIGNAL(clicked()), this, SLOT(OnExportClicked())); // Add SIGNAL and SLOT for "Copy to clipboard" functionality connect(m_Controls.btnCopyResultsToClipboard, SIGNAL(clicked()), this, SLOT(OnClipboardResultsButtonClicked())); // For some reason this needs to be called to set the plot widget's minimum width to an // acceptable level (since Qwt 6). // Otherwise it tries to keep both axes equal in length, resulting in a minimum width of // 400-500px which is way too much. m_Controls.widgetPlot->GetPlot()->updateAxes(); m_Controls.cmbFit->clear(); mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); RenderWindowPartActivated(renderWindowPart); } void ModelFitInspectorView::SetFocus() { } void ModelFitInspectorView::OnScaleFixedYChecked(bool checked) { m_Controls.widgetPlot->GetPlot()->setAxisAutoScale(QwtPlot::yLeft, !checked); if (checked) { OnScaleToDataYClicked(); } m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnScaleFixedXChecked(bool checked) { m_Controls.widgetPlot->GetPlot()->setAxisAutoScale(QwtPlot::xBottom, !checked); if (checked) { OnScaleToDataXClicked(); } m_Controls.widgetPlot->GetPlot()->replot(); }; void CheckYMinMaxFromXYData(const QmitkPlotWidget::XYDataVector& data, double& min, double& max) { for (const auto & pos : data) { if (max < pos.second) { max = pos.second; } if (min > pos.second) { min = pos.second; } } } void CheckXMinMaxFromXYData(const QmitkPlotWidget::XYDataVector& data, double& min, double& max) { for (const auto & pos : data) { if (max < pos.first) { max = pos.first; } if (min > pos.first) { min = pos.first; } } } void ModelFitInspectorView::OnScaleToDataYClicked() { double max = itk::NumericTraits::NonpositiveMin(); double min = itk::NumericTraits::max(); CheckYMinMaxFromXYData(this->m_ImagePlotCurve, min, max); CheckYMinMaxFromXYData(this->m_ModelPlotCurve, min, max); for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos) { CheckYMinMaxFromXYData(pos->second, min, max); } min -= abs(min) * 0.01; max += abs(max) * 0.01; m_Controls.sbFixMax->setValue(max); m_Controls.sbFixMin->setValue(min); }; void ModelFitInspectorView::OnScaleToDataXClicked() { double max = itk::NumericTraits::NonpositiveMin(); double min = itk::NumericTraits::max(); CheckXMinMaxFromXYData(this->m_ImagePlotCurve, min, max); CheckXMinMaxFromXYData(this->m_ModelPlotCurve, min, max); for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos) { CheckXMinMaxFromXYData(pos->second, min, max); } min -= abs(min) * 0.01; max += abs(max) * 0.01; m_Controls.sbFixMax_x->setValue(max); m_Controls.sbFixMin_x->setValue(min); }; void ModelFitInspectorView::OnFixedScalingYChanged(double /*value*/) { m_Controls.widgetPlot->GetPlot()->setAxisScale(QwtPlot::yLeft, m_Controls.sbFixMin->value(), m_Controls.sbFixMax->value()); m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnFixedScalingXChanged(double /*value*/) { m_Controls.widgetPlot->GetPlot()->setAxisScale(QwtPlot::xBottom, m_Controls.sbFixMin_x->value(), m_Controls.sbFixMax_x->value()); m_Controls.widgetPlot->GetPlot()->replot(); }; void ModelFitInspectorView::OnExportClicked() { QString fileName = QFileDialog::getSaveFileName(nullptr, tr("Save File")); if (fileName.isEmpty()) { QMessageBox::critical(nullptr, tr("No file selected!"), tr("Cannot export pixel dump. Please selected a file.")); } else { std::ofstream file; std::ios_base::openmode iOpenFlag = std::ios_base::out | std::ios_base::trunc; file.open(fileName.toStdString().c_str(), iOpenFlag); if (!file.is_open()) { QMessageBox::critical(nullptr, tr("Cannot create/open selected file!"), tr("Cannot open or create the selected file. Export will be aborted. Selected file name: ") + fileName); return; } for (int i = 0; i < m_Controls.tableData->columnCount(); ++i) { file << m_Controls.tableData->horizontalHeaderItem(i)->text().toStdString(); if (i < m_Controls.tableData->columnCount() - 1) { file << ","; } } file << std::endl; for (int row = 0; row < m_Controls.tableData->rowCount(); ++row) { for (int i = 0; i < m_Controls.tableData->columnCount(); ++i) { file << m_Controls.tableData->item(row, i)->text().toStdString(); if (i < m_Controls.tableData->columnCount() - 1) { file << ","; } } file << std::endl; } file.close(); } } void ModelFitInspectorView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& nodes) { if (nodes.size() > 0) { if (nodes.front() != this->m_currentSelectedNode) { m_internalUpdateFlag = true; this->m_currentSelectedNode = nodes.front(); mitk::DataStorage::Pointer storage = this->GetDataStorage(); mitk::NodeUIDType selectedFitUD = ""; bool isModelFitNode = this->m_currentSelectedNode->GetData()->GetPropertyList()->GetStringProperty( mitk::ModelFitConstants::FIT_UID_PROPERTY_NAME().c_str(), selectedFitUD); if (isModelFitNode) { this->m_currentSelectedNode = this->GetParentNode(this->m_currentSelectedNode); } mitk::modelFit::NodeUIDSetType fitUIDs = mitk::modelFit::GetFitUIDsOfNode( this->m_currentSelectedNode, storage); this->m_modelfitList.clear(); this->m_Controls.cmbFit->clear(); for (const auto & fitUID : fitUIDs) { mitk::modelFit::ModelFitInfo::ConstPointer info = mitk::modelFit::CreateFitInfoFromNode(fitUID, storage).GetPointer(); if (info.IsNotNull()) { this->m_modelfitList.insert(std::make_pair(info->uid, info)); QVariant data(info->uid.c_str()); m_Controls.cmbFit->addItem(QString::fromStdString(info->modelName), data); } else { MITK_ERROR << "Was not able to extract model fit information from storage. Node properties in storage may be invalid. Failed fit UID:" << fitUID; } } int cmbIndex = 0; if (m_modelfitList.empty()) { cmbIndex = -1; }; if (isModelFitNode) { //model was selected, thus select this one in combobox QVariant data(selectedFitUD.c_str()); cmbIndex = m_Controls.cmbFit->findData(data); if (cmbIndex == -1) { MITK_WARN << "Model fit Inspector in invalid state. Selected fit seems to be not avaible in plugin selection. Failed fit UID:" << selectedFitUD; } }; m_Controls.cmbFit->setCurrentIndex(cmbIndex); m_internalUpdateFlag = false; m_selectedNodeTime.Modified(); if (cmbIndex == -1) { //only raw 4D data selected. Just update plots for current position m_currentFit = nullptr; m_currentFitTime.Modified(); OnSliceChangedDelayed(); } else { //refresh fit selection (and implicitly update plots) OnFitSelectionChanged(cmbIndex); } } } else { if (this->m_currentSelectedNode.IsNotNull()) { m_internalUpdateFlag = true; this->m_currentSelectedNode = nullptr; this->m_currentFit = nullptr; this->m_modelfitList.clear(); this->m_Controls.cmbFit->clear(); m_internalUpdateFlag = false; m_selectedNodeTime.Modified(); OnFitSelectionChanged(0); } } } mitk::DataNode::ConstPointer ModelFitInspectorView::GetParentNode(mitk::DataNode::ConstPointer node) { if (node.IsNotNull()) { mitk::DataStorage::SetOfObjects::ConstPointer parentNodeList = GetDataStorage()->GetSources(node); if (parentNodeList->size() > 0) { return parentNodeList->front().GetPointer(); } } return mitk::DataNode::ConstPointer(); } void ModelFitInspectorView::ValidateAndSetCurrentPosition() { mitk::Point3D currentSelectedPosition = GetRenderWindowPart()->GetSelectedPosition(nullptr); unsigned int currentSelectedTimestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()-> GetPos(); if (m_currentSelectedPosition != currentSelectedPosition || m_currentSelectedTimeStep != currentSelectedTimestep || m_selectedNodeTime > m_currentPositionTime) { //the current position has been changed or the selected node has been changed since the last position validation -> check position m_currentSelectedPosition = currentSelectedPosition; m_currentSelectedTimeStep = currentSelectedTimestep; m_currentPositionTime.Modified(); m_validSelectedPosition = false; mitk::Image::Pointer inputImage = this->GetCurrentInputImage(); if (inputImage.IsNull()) { return; } mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep( m_currentSelectedTimeStep); // check for invalid time step if (geometry.IsNull()) { geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); } if (geometry.IsNull()) { return; } m_validSelectedPosition = geometry->IsInside(m_currentSelectedPosition); } } mitk::Image::Pointer ModelFitInspectorView::GetCurrentInputImage() const { mitk::Image::Pointer result = nullptr; if (this->m_currentFit.IsNotNull()) { result = m_currentFit->inputImage; } else if (this->m_currentSelectedNode.IsNotNull()) { result = dynamic_cast(this->m_currentSelectedNode->GetData()); if (result.IsNotNull() && result->GetTimeSteps() <= 1) { //if the image is not dynamic, we can't use it. result = nullptr; } } return result; }; const mitk::ModelBase::TimeGridType ModelFitInspectorView::GetCurrentTimeGrid() const { if (m_currentModelProviderService && m_currentFit.IsNotNull()) { return m_currentModelProviderService->GetVariableGrid(m_currentFit); } else { //fall back if there is no model provider we assume to use the normal time grid. return ExtractTimeGrid(GetCurrentInputImage()); } }; void ModelFitInspectorView::OnSliceChanged(const itk::EventObject&) { // Taken from QmitkStdMultiWidget::HandleCrosshairPositionEvent(). // Since there are always 3 events arriving (one for each render window) every time the slice // or time changes, the slot OnSliceChangedDelayed is triggered - and only if it hasn't been // triggered yet - so it is only executed once for every slice/time change. if (!m_PendingSliceChangedEvent) { m_PendingSliceChangedEvent = true; QTimer::singleShot(0, this, SLOT(OnSliceChangedDelayed())); } } void ModelFitInspectorView::OnSliceChangedDelayed() { m_PendingSliceChangedEvent = false; ValidateAndSetCurrentPosition(); m_Controls.widgetPlot->setEnabled(m_validSelectedPosition); if (m_currentSelectedNode.IsNotNull()) { if (RefreshPlotData()) { RenderPlot(); RenderFitInfo(); } } } mitk::ModelBase::TimeGridType ModelFitInspectorView::GenerateInterpolatedTimeGrid(const mitk::ModelBase::TimeGridType& grid) const { unsigned int origGridSize = grid.size(); mitk::ModelBase::TimeGridType interpolatedTimeGrid(((origGridSize - 1) * INTERPOLATION_STEPS) + 1); for (unsigned int t = 0; t < origGridSize - 1; ++t) { double delta = (grid[t + 1] - grid[t]) / INTERPOLATION_STEPS; for (unsigned int i = 0; i < INTERPOLATION_STEPS; ++i) { interpolatedTimeGrid[(t * INTERPOLATION_STEPS) + i] = grid[t] + i * delta; } } interpolatedTimeGrid[interpolatedTimeGrid.size() - 1] = grid[grid.size() - 1]; return interpolatedTimeGrid; }; QmitkPlotWidget::XYDataVector ModelFitInspectorView::CalcCurveFromModel(const mitk::Point3D& position) { assert(m_currentModelParameterizer.IsNotNull()); assert(m_currentFit.IsNotNull()); mitk::Image::Pointer inputImage = this->GetCurrentInputImage(); assert(inputImage.IsNotNull()); // Calculate index ::itk::Index<3> index; mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); geometry->WorldToIndex(position, index); //model generation mitk::ModelBase::Pointer model = m_currentModelParameterizer->GenerateParameterizedModel(index); mitk::ParameterValueMapType parameterMap = mitk::ExtractParameterValueMapFromModelFit(m_currentFit, position); mitk::ModelBase::ParametersType paramArray = mitk::ConvertParameterMapToParameterVector(parameterMap, model); mitk::ModelBase::ModelResultType curveDataY = model->GetSignal(paramArray); QmitkPlotWidget::XYDataVector result; mitk::ModelBase::TimeGridType timeGrid = model->GetTimeGrid(); for (unsigned int t = 0; t < timeGrid.size(); ++t) { double x = timeGrid[t]; double y = curveDataY[t]; result.push_back(std::make_pair(x, y)); } return result; } QmitkPlotWidget::XYDataVector ModelFitInspectorView::CalcCurveFromFunction(const mitk::Point3D& position, const mitk::ModelBase::TimeGridType& timeGrid) { assert(m_currentFit.IsNotNull()); assert(m_renderWindowPart != nullptr); mitk::Image::Pointer inputImage = this->GetCurrentInputImage(); assert(inputImage.IsNotNull()); QmitkPlotWidget::XYDataVector result; // Calculate index ::itk::Index<3> index;; mitk::BaseGeometry::Pointer geometry = inputImage->GetTimeGeometry()->GetGeometryForTimeStep(0); geometry->WorldToIndex(position, index); mitk::ParameterValueMapType parameterMap = mitk::ExtractParameterValueMapFromModelFit(m_currentFit, position); - FormulaParsing::FormulaParser parser(¶meterMap); + mitk::FormulaParser parser(¶meterMap); unsigned int timestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()-> GetPos(); for (unsigned int t = 0; t < timeGrid.size(); ++t) { // Set up static parameters foreach (const mitk::modelFit::StaticParameterMap::StaticParameterType& var, m_currentFit->staticParamMap) { const std::string& name = var.first; const mitk::modelFit::StaticParameterMap::ValueType& list = var.second; if (list.size() == 1) { parameterMap[name] = list.front(); } else { parameterMap[name] = list.at(timestep); } } // Calculate curve data double x = timeGrid[t]; parameterMap[m_currentFit->x] = x; double y = parser.parse(m_currentFit->function); result.push_back(std::make_pair(x, y)); } return result; } void ModelFitInspectorView::OnFitSelectionChanged(int index) { if (!m_internalUpdateFlag) { MITK_DEBUG << "selected fit index: " << index; std::string uid = ""; if (m_Controls.cmbFit->count() > index) { uid = m_Controls.cmbFit->itemData(index).toString().toStdString(); } mitk::modelFit::ModelFitInfo::ConstPointer newFit = nullptr; ModelFitInfoListType::iterator finding = m_modelfitList.find(uid); if (finding != m_modelfitList.end()) { newFit = finding->second; } if (m_currentFit != newFit) { m_currentModelParameterizer = nullptr; m_currentModelProviderService = nullptr; if (newFit.IsNotNull()) { m_currentModelParameterizer = mitk::ModelGenerator::GenerateModelParameterizer(*newFit); m_currentModelProviderService = mitk::ModelGenerator::GetProviderService(newFit->functionClassID); } m_currentFit = newFit; m_currentFitTime.Modified(); OnSliceChangedDelayed(); } } } bool ModelFitInspectorView::RefreshPlotData() { bool changed = false; if (m_currentSelectedNode.IsNull()) { this->m_ImagePlotCurve.clear(); this->m_ModelPlotCurve.clear(); this->m_InputDataPlotCurves.clear(); changed = m_selectedNodeTime > m_lastRefreshTime; m_lastRefreshTime.Modified(); } else { assert(GetRenderWindowPart() != NULL); const mitk::Image* input = GetCurrentInputImage(); const mitk::ModelBase::TimeGridType timeGrid = GetCurrentTimeGrid(); //image data curve if (m_selectedNodeTime > m_lastRefreshTime || m_currentFitTime > m_lastRefreshTime || m_currentPositionTime > m_lastRefreshTime) { m_ImagePlotCurve.clear(); if (input && m_validSelectedPosition) { QmitkPlotWidget::XYDataVector pointData; for (unsigned int t = 0; t < timeGrid.size(); ++t) { double x = timeGrid[t]; double y = ReadVoxel(input, m_currentSelectedPosition, t); pointData.push_back(std::make_pair(x, y)); } m_ImagePlotCurve = pointData; } changed = true; } //model curve if (m_currentFitTime > m_lastRefreshTime || m_currentPositionTime > m_lastRefreshTime) { m_ModelPlotCurve.clear(); if (m_currentFit.IsNotNull() && m_validSelectedPosition) { QmitkPlotWidget::XYDataVector curveData; // Interpolate time grid (x values) so the curve looks smooth const mitk::ModelBase::TimeGridType interpolatedTimeGrid = GenerateInterpolatedTimeGrid(timeGrid); if (m_currentModelParameterizer.IsNotNull()) { // Use model instead of formula parser m_currentModelParameterizer->SetDefaultTimeGrid(interpolatedTimeGrid); curveData = CalcCurveFromModel(m_currentSelectedPosition); } else { // Use formula parser to parse function string try { curveData = CalcCurveFromFunction(m_currentSelectedPosition, interpolatedTimeGrid); } - catch (const FormulaParsing::FormulaParserException& e) + catch (const mitk::FormulaParserException& e) { MITK_ERROR << "Error while parsing modelfit function: " << e.what(); } } m_ModelPlotCurve = curveData; } changed = true; } // input data curve if (m_currentFitTime > m_lastRefreshTime) { m_InputDataPlotCurves.clear(); if (m_currentFit.IsNotNull()) { for (mitk::ScalarListLookupTable::LookupTableType::const_iterator pos = m_currentFit->inputData.GetLookupTable().begin(); pos != m_currentFit->inputData.GetLookupTable().end(); ++pos) { if (pos->second.size() != timeGrid.size()) { MITK_ERROR << "Error while refreshing input data for visualization. Size of data and input image time grid differ. Invalid data name: " << pos->first; } else { QmitkPlotWidget::XYDataVector pointData; for (unsigned int t = 0; t < timeGrid.size(); ++t) { double x = timeGrid[t]; double y = pos->second[t]; pointData.push_back(std::make_pair(x, y)); } m_InputDataPlotCurves.insert(std::make_pair(pos->first, pointData)); } } } changed = true; } if (m_selectedNodeTime > m_lastRefreshTime || m_currentFitTime > m_lastRefreshTime || m_currentPositionTime > m_lastRefreshTime) { InitDataTable(); } m_lastRefreshTime.Modified(); } return changed; } void ModelFitInspectorView::RenderFitInfo() { assert(m_renderWindowPart != nullptr); // configure fit information if (m_currentFit.IsNull()) { m_Controls.lFitType->setText(""); m_Controls.lFitUID->setText(""); m_Controls.lModelName->setText(""); m_Controls.lModelType->setText(""); } else { m_Controls.lFitType->setText(QString::fromStdString(m_currentFit->fitType)); m_Controls.lFitUID->setText(QString::fromStdString(m_currentFit->uid)); m_Controls.lModelName->setText(QString::fromStdString(m_currentFit->modelName)); m_Controls.lModelType->setText(QString::fromStdString(m_currentFit->modelType)); } // print results std::stringstream infoOutput; m_Controls.grpParams->setVisible(false); m_Controls.groupSettings->setVisible(false); m_Controls.tableResults->clearContents(); if (m_currentFit.IsNull()) { infoOutput << "No fit selected. Only raw image data is plotted."; } else if (!m_validSelectedPosition) { infoOutput << "Current position is outside of the input image of the selected fit.\nInspector is deactivated."; } else { m_Controls.grpParams->setVisible(true); // Set sorting to false. Wait with sorting until all parameters are set in the table. m_Controls.tableResults->setSortingEnabled(false); m_Controls.tableResults->setRowCount(m_currentFit->GetParameters().size() + m_currentFit->staticParamMap.Size()); unsigned int timestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()->GetPos(); unsigned int rowIndex = 0; assert(GetRenderWindowPart() != NULL); for (mitk::modelFit::ModelFitInfo::ConstIterType paramIter = m_currentFit->GetParameters().begin(); paramIter != m_currentFit->GetParameters().end(); ++paramIter, ++rowIndex) { mitk::modelFit::Parameter::ConstPointer p = static_cast(*paramIter); double value = ReadVoxel(p->image, m_currentSelectedPosition, timestep); std::string paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_PARAMETER(); if (p->type == mitk::modelFit::Parameter::DerivedType) { paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_DERIVED_PARAMETER(); } else if (p->type == mitk::modelFit::Parameter::CriterionType) { paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_CRITERION(); } else if (p->type == mitk::modelFit::Parameter::EvaluationType) { paramType = mitk::ModelFitConstants::PARAMETER_TYPE_VALUE_EVALUATION_PARAMETER(); } QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString(p->name)); m_Controls.tableResults->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(QString::number(value)); m_Controls.tableResults->setItem(rowIndex, 1, newItem); newItem = new QTableWidgetItem(QString::fromStdString(paramType)); m_Controls.tableResults->setItem(rowIndex, 2, newItem); } foreach(const mitk::modelFit::StaticParameterMap::StaticParameterType& var, m_currentFit->staticParamMap) { const std::string& name = var.first; const mitk::modelFit::StaticParameterMap::ValueType& list = var.second; QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString(name)); m_Controls.tableResults->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(QString::number(list.front())); m_Controls.tableResults->setItem(rowIndex, 1, newItem); newItem = new QTableWidgetItem(tr("static")); m_Controls.tableResults->setItem(rowIndex, 2, newItem); ++rowIndex; } // Here the sorting function is enabled and the table entries are sorted alphabetically in descending order. // This way the parameters are always displayed in the same order. m_Controls.tableResults->setSortingEnabled(true); m_Controls.tableResults->sortItems(0, Qt::DescendingOrder); } // configure data table m_Controls.tableInputData->clearContents(); if (m_currentFit.IsNull()) { infoOutput << "No fit selected. Only raw image data is plotted."; } else { m_Controls.groupSettings->setVisible(true); m_Controls.tableInputData->setRowCount(m_InputDataPlotCurves.size()); unsigned int rowIndex = 0; for (CurveMapType::const_iterator pos = m_InputDataPlotCurves.begin(); pos != m_InputDataPlotCurves.end(); ++pos, ++rowIndex) { QColor dataColor; if (pos->first == "ROI") { dataColor = QColor(0, 190, 0); } else { //Use HSV schema of QColor to calculate a different color depending on the //number of already existing free iso lines. dataColor.setHsv(((rowIndex + 1) * 85) % 360, 255, 255); } QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString(pos->first)); m_Controls.tableInputData->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(); newItem->setBackgroundColor(dataColor); m_Controls.tableInputData->setItem(rowIndex, 1, newItem); } } m_Controls.lInfo->setText(QString::fromStdString(infoOutput.str())); } void ModelFitInspectorView::RenderPlot() { m_Controls.widgetPlot->Clear(); std::string xAxis = "Time [s]"; std::string yAxis = "Intensity"; std::string plotTitle = "Raw data plot: no data"; if (m_currentSelectedNode.IsNotNull()) { plotTitle = "Raw data plot: " + m_currentSelectedNode->GetName(); } if (m_currentFit.IsNotNull()) { plotTitle = m_currentFit->modelName.c_str(); xAxis = m_currentFit->xAxisName; if (!m_currentFit->xAxisUnit.empty()) { xAxis += " [" + m_currentFit->xAxisUnit + "]"; } yAxis = m_currentFit->yAxisName; if (!m_currentFit->yAxisUnit.empty()) { yAxis += " [" + m_currentFit->yAxisUnit + "]"; } } m_Controls.widgetPlot->SetAxisTitle(QwtPlot::xBottom, xAxis.c_str()); m_Controls.widgetPlot->SetAxisTitle(QwtPlot::yLeft, yAxis.c_str()); m_Controls.widgetPlot->SetPlotTitle(plotTitle.c_str()); // Draw input data points unsigned int colorIndex = 0; for (CurveMapType::const_iterator pos = m_InputDataPlotCurves.begin(); pos != m_InputDataPlotCurves.end(); ++pos) { QColor dataColor; unsigned int curveId = m_Controls.widgetPlot->InsertCurve(pos->first.c_str()); m_Controls.widgetPlot->SetCurveData(curveId, pos->second); if (pos->first == "ROI") { dataColor = QColor(0, 190, 0); QPen pen; pen.setColor(dataColor); pen.setStyle(Qt::SolidLine); m_Controls.widgetPlot->SetCurvePen(curveId, pen); } else { //Use HSV schema of QColor to calculate a different color depending on the //number of already existing free iso lines. dataColor.setHsv((++colorIndex * 85) % 360, 255, 150); m_Controls.widgetPlot->SetCurvePen(curveId, QPen(Qt::NoPen)); } // QwtSymbol needs to passed as a real pointer from MITK v2013.09.0 on // (QwtPlotCurve deletes it on destruction and assignment). QwtSymbol* dataSymbol = new QwtSymbol(QwtSymbol::Triangle, dataColor, dataColor, QSize(8, 8)); m_Controls.widgetPlot->SetCurveSymbol(curveId, dataSymbol); // Again, there is no way to set a curve's legend attributes via QmitkPlotWidget so this // gets unnecessarily complicated. QwtPlotCurve* measurementCurve = dynamic_cast(m_Controls.widgetPlot-> GetPlot()->itemList(QwtPlotItem::Rtti_PlotCurve).back()); measurementCurve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol); measurementCurve->setLegendIconSize(QSize(8, 8)); } // Draw image points if (!m_ImagePlotCurve.empty()) { unsigned int curveId = m_Controls.widgetPlot->InsertCurve("measurement"); m_Controls.widgetPlot->SetCurveData(curveId, m_ImagePlotCurve); m_Controls.widgetPlot->SetCurvePen(curveId, QPen(Qt::NoPen)); // QwtSymbol needs to passed as a real pointer from MITK v2013.09.0 on // (QwtPlotCurve deletes it on destruction and assignment). QwtSymbol* redSymbol = new QwtSymbol(QwtSymbol::Diamond, QColor(Qt::red), QColor(Qt::red), QSize(8, 8)); m_Controls.widgetPlot->SetCurveSymbol(curveId, redSymbol); // Again, there is no way to set a curve's legend attributes via QmitkPlotWidget so this // gets unnecessarily complicated. QwtPlotCurve* measurementCurve = dynamic_cast(m_Controls.widgetPlot-> GetPlot()->itemList(QwtPlotItem::Rtti_PlotCurve).back()); measurementCurve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol); measurementCurve->setLegendIconSize(QSize(8, 8)); // Highlight the current time step unsigned int timestep = m_renderWindowPart->GetTimeNavigationController()->GetTime()-> GetPos(); if (timestep < m_ImagePlotCurve.size()) { QwtSymbol* blueSymbol = new QwtSymbol(QwtSymbol::Diamond, QColor(Qt::blue), QColor(Qt::blue), QSize(8, 8)); QwtPlotMarker* marker = new QwtPlotMarker(); marker->setSymbol(blueSymbol); marker->setValue(m_ImagePlotCurve[timestep].first, m_ImagePlotCurve[timestep].second); marker->attach(m_Controls.widgetPlot->GetPlot()); } for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.size(); ++i) { m_Controls.tableData->item(i, 1)->setText(QString::number(m_ImagePlotCurve[i].second)); } } //draw model curve if (!m_ModelPlotCurve.empty()) { QPen pen; pen.setColor(QColor(Qt::black)); pen.setWidth(2); unsigned int curveId = m_Controls.widgetPlot->InsertCurve("fit"); m_Controls.widgetPlot->SetCurveData(curveId, m_ModelPlotCurve); m_Controls.widgetPlot->SetCurvePen(curveId, pen); // Manually set the legend attribute to use the symbol as the legend icon and alter its // size. Otherwise it would revert to default which is drawing a square which is the color // of the curve's pen, so in this case none which defaults to black. // Unfortunately, QmitkPlotWidget offers no way to set the legend attribute and icon size so // this looks a bit hacky. QwtPlotCurve* fitCurve = dynamic_cast(m_Controls.widgetPlot->GetPlot()-> itemList(QwtPlotItem::Rtti_PlotCurve).back()); fitCurve->setLegendAttribute(QwtPlotCurve::LegendShowLine); for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.size(); ++i) { m_Controls.tableData->item(i, 2)->setText(QString::number(m_ModelPlotCurve[i * INTERPOLATION_STEPS].second)); } } QwtLegend* legend = new QwtLegend(); legend->setFrameShape(QFrame::Box); legend->setFrameShadow(QFrame::Sunken); legend->setLineWidth(1); m_Controls.widgetPlot->SetLegend(legend, QwtPlot::BottomLegend); m_Controls.widgetPlot->Replot(); } void ModelFitInspectorView::InitDataTable() { QStringList headers; headers.push_back(QString("Time")); headers.push_back(QString("Input image")); if (!this->m_ModelPlotCurve.empty()) { headers.push_back(QString("Model")); } for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos) { headers.push_back(QString::fromStdString(pos->first)); } m_Controls.tableData->setRowCount(m_ImagePlotCurve.size()); m_Controls.tableData->setColumnCount(headers.size()); m_Controls.tableData->setHorizontalHeaderLabels(headers); m_Controls.tableData->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); for (QmitkPlotWidget::XYDataVector::size_type i = 0; i < m_ImagePlotCurve.size(); ++i) { int column = 0; QTableWidgetItem* newItem = new QTableWidgetItem(QString::number(m_ImagePlotCurve[i].first)); m_Controls.tableData->setItem(i, column++, newItem); newItem = new QTableWidgetItem(QString::number(m_ImagePlotCurve[i].second)); m_Controls.tableData->setItem(i, column++, newItem); if (!m_ModelPlotCurve.empty()) { newItem = new QTableWidgetItem(QString::number(m_ModelPlotCurve[i * INTERPOLATION_STEPS].second)); m_Controls.tableData->setItem(i, column++, newItem); } for (CurveMapType::const_iterator pos = this->m_InputDataPlotCurves.begin(); pos != this->m_InputDataPlotCurves.end(); ++pos, ++column) { newItem = new QTableWidgetItem(QString::number(pos->second[i].second)); m_Controls.tableData->setItem(i, column, newItem); } } } void ModelFitInspectorView::OnClipboardResultsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); std::stringstream infoOutput; QString clipboard; QVector< QVector > resultsTable; if (m_currentFit.IsNotNull()) { int rowNumber = m_currentFit->GetParameters().size() + m_currentFit->staticParamMap.Size(); // set headline as in results table: "Name", "Value", "Type" QStringList headlineText; QVector headline; // Create Headline headlineText << "Name" << "Value" << "Type"; for (int i = 0; i < headlineText.size(); i++) { headline.append(headlineText.at(i)); } resultsTable.append(headline); // fill table with values from the displayed results table for (int i = 0; i < rowNumber; i++) { QVector rowValues; for (int j = 0; j < 3; j++) { rowValues.append((m_Controls.tableResults->item(i, j)->text())); } resultsTable.append(rowValues); } // Create output string for clipboard for (int i = 0; i < resultsTable.size(); i++) { for (int t = 0; t < resultsTable.at(i).size(); t++) { clipboard.append(resultsTable.at(i).at(t)); clipboard.append("\t"); } clipboard.append("\n"); } QApplication::clipboard()->setText(clipboard, QClipboard::Clipboard); } else { infoOutput << "Results table is empty."; } QLocale::setDefault(tempLocal); }