diff --git a/.gitignore b/.gitignore index e08ac9e5..772cd764 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ html latex thirdparty/lmfit/* thirdparty/itk/* +test_package/build/* conan-recipes/*/test_package/build/* /reports/ /coverage/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 965b67fd..e9b1f467 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ project(Tomato) # The version number set(Tomato_VERSION_MAJOR 0) set(Tomato_VERSION_MINOR 6) -set(Tomato_VERSION_PATCH 5) +set(Tomato_VERSION_PATCH 6) # Compiler flags set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -224,3 +224,12 @@ if(BUILD_TESTING) add_subdirectory(tests) install(TARGETS TomatoLib DESTINATION tests) endif() + +## +#set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +##if(CMAKE_COMPILER_IS_GNUCXX) +# include(CodeCoverage) +# setup_target_for_coverage_lcov( +# NAME ${PROJECT_NAME}Coverage +# EXECUTABLE ${PROJECT_NAME}Tests) +#endif() diff --git a/conanfile.py b/conanfile.py index f35ba64b..835389f5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,9 +1,9 @@ -from conans import ConanFile, CMake, tools, RunEnvironment +from conans import ConanFile, CMake, tools import os class TomatoConan(ConanFile): name = "tomato" - version = "0.6.5" + version = "0.6.6" default_user = "user" default_channel = "testing" license = "MIT " @@ -21,6 +21,7 @@ class TomatoConan(ConanFile): "use_yaml": [True, False], "build_app": [True, False], "build_testing": [True, False], + "build_testing_coverage": [True, False], "use_tomato_private": [True, False], } default_options = { @@ -31,10 +32,16 @@ class TomatoConan(ConanFile): "use_yaml": False, "build_app": False, "build_testing": True, + "build_testing_coverage": True, "use_tomato_private": False, } generators = "cmake_find_package" + def configure(self): + self.runCoverage = self.options.build_testing_coverage \ + and tools.os_info.is_macos \ + and self.settings.build_type == "Debug" + def requirements(self): if self.options.use_itk: self.requires("itk/4.13.2@%s/%s" % (self.user, self.channel)) @@ -49,38 +56,37 @@ def requirements(self): def build_requirements(self): if self.options.build_testing: self.build_requires("gtest/1.6.0@%s/%s" % (self.user, self.channel)) + self.build_requires("libyaml/0.2.5@%s/%s" % (self.user, self.channel)) def source(self): - self.run("git clone https://github.com/MRKonrad/tomato.git --branch T2") + self.run("git clone https://github.com/MRKonrad/tomato.git") if self.options.use_tomato_private: token = os.getenv("GH_PERSONAL_ACCESS_TOKEN") if not token: self.options.use_tomato_private=False return username = "MRKonrad:"+token+"@" - self.run("git clone https://"+username+"github.com/MRKonrad/tomato_private --branch T2") + self.run("git clone https://"+username+"github.com/MRKonrad/tomato_private") def _configure_cmake(self): cmake = CMake(self) - cmake.definitions["CMAKE_BUILD_TYPE"] = self.settings.build_type - cmake.definitions["BUILD_SHARED_LIBS"] = self.options.shared - - if (tools.os_info.is_macos): - cmake.definitions["CMAKE_MACOSX_RPATH"] = "ON" - if (tools.os_info.is_linux): - cmake.definitions["CMAKE_POSITION_INDEPENDENT_CODE"] = "ON" + cmake.definitions["CMAKE_POSITION_INDEPENDENT_CODE"] = True - #cmake.definitions["CMAKE_CXX_STANDARD"] = "11" + cmake.definitions["CMAKE_BUILD_TYPE"] = self.settings.build_type + cmake.definitions["CMAKE_CXX_STANDARD"] = "11" cmake.definitions["USE_ITK"] = self.options.use_itk cmake.definitions["USE_VNL"] = self.options.use_vnl cmake.definitions["USE_PRIVATE_NR2"] = self.options.use_tomato_private cmake.definitions["USE_LMFIT"] = self.options.use_lmfit cmake.definitions["USE_TOMATOFIT"] = True - cmake.definitions["USE_YAML"] = self.options.use_yaml + cmake.definitions["USE_YAML"] = self.options.use_yaml or self.options.build_testing cmake.definitions["BUILD_APP"] = self.options.build_app cmake.definitions["BUILD_TESTING"] = self.options.build_testing + if self.runCoverage: + cmake.definitions["CMAKE_CXX_FLAGS"] = "-fprofile-instr-generate -fcoverage-mapping" + cmake.definitions["CMAKE_C_FLAGS"] = "-fprofile-instr-generate -fcoverage-mapping" cmake.configure(source_folder=self.name) return cmake @@ -88,18 +94,46 @@ def _configure_cmake(self): def build(self): cmake = self._configure_cmake() cmake.build() + # run tests if self.options.build_testing: # tell test executable where to look for the shared libs generated during build if self.settings.os == "Windows" and self.options.shared: os.environ['PATH'] += ";%s\%s"%(self.build_folder, self.settings.build_type) - self.run("ctest -V -C %s" % (self.settings.build_type), cwd="tests", run_environment=True) + testingOutputFile = "reports/unitTestsReport_%s.log" % self.settings.build_type + self.run("ctest -V -C %s --output-log %s" % (self.settings.build_type, testingOutputFile), cwd="tests", run_environment=True) + + # coverage + if self.runCoverage: + # find llvm-cov + print("Get path to clang. llvm-cov should be in the same dir") + clangPath = os.popen("xcodebuild -find clang").read() + llvmCovLocation = os.path.dirname(clangPath) + print("I assume that llvm-cov is here: " + llvmCovLocation) + + # llvm-profdata + self.run("./TomatoTests", cwd="tests", run_environment=True) + self.run("PATH=%s:$PATH llvm-profdata merge -sparse default.profraw -o default.profdata" % (llvmCovLocation), cwd="tests") + + # llvm-cov + filesToBeReported = "%s/lib/*.cpp" % os.path.join(self.build_folder, self.name) + coverageOutputFile = "reports/coverageReport.log" + self.run("PATH=%s:$PATH llvm-cov report ./TomatoTests -instr-profile=default.profdata %s > %s" % (llvmCovLocation, filesToBeReported, coverageOutputFile), cwd="tests") + self.run("cat %s" % coverageOutputFile, cwd="tests") # just to display results def package(self): cmake = self._configure_cmake() cmake.definitions["BUILD_TESTING"] = False + cmake.definitions["USE_YAML"] = self.options.use_yaml + if self.runCoverage: + cmake.definitions["CMAKE_CXX_FLAGS"] = "" + cmake.definitions["CMAKE_C_FLAGS"] = "" cmake.configure(source_folder=self.name) cmake.install() + self.copy("*", dst="reports", src="tests/reports") # copy coverage results + for bindir in self.deps_cpp_info.bindirs: + self.copy("*.dll", dst="bin", src=bindir) + def package_info(self): self.cpp_info.libs = ["TomatoLib"] diff --git a/lib/OxCalculatorT1Shmolli.hxx b/lib/OxCalculatorT1Shmolli.hxx index c3fe24b9..98911f50 100644 --- a/lib/OxCalculatorT1Shmolli.hxx +++ b/lib/OxCalculatorT1Shmolli.hxx @@ -193,6 +193,10 @@ namespace Ox { nShmolliSamplesUsed = 5; T1temp = results5["T1"]; chiTemp = results5["ChiSqrt"]; // legacy + if (results5["timeFlip"] == 0) // because signs5[0] = -1; + { + results5["timeFlip"] = 1; + } } else { chiTemp = results5["LastValue"]; // legacy, very small amount of pixels (3) influenced by it } @@ -227,9 +231,17 @@ namespace Ox { // assign output values if (nShmolliSamplesUsed == 5) { + if (results5["timeFlip"] > 1) + { + results5["timeFlip"] = results5["timeFlip"] + 2; + } this->_Results = results5; } else if (nShmolliSamplesUsed == 6) { + if (results6["timeFlip"] > 1) + { + results6["timeFlip"] = results6["timeFlip"] + 1; + } this->_Results = results6; } else if (nShmolliSamplesUsed == 7) { diff --git a/test_package/conanfile.py b/test_package/conanfile.py index 84c02243..178afc54 100644 --- a/test_package/conanfile.py +++ b/test_package/conanfile.py @@ -10,8 +10,6 @@ class TomatoTestConan(ConanFile): def build(self): cmake = CMake(self) cmake.definitions["CONAN_DISABLE_CHECK_COMPILER"] = "ON" - # Current dir is "test_package/build/" and CMakeLists.txt is - # in "test_package" cmake.configure() cmake.build() diff --git a/tests/T1_test.cpp b/tests/T1_test.cpp index e114e8be..d9036b12 100644 --- a/tests/T1_test.cpp +++ b/tests/T1_test.cpp @@ -177,7 +177,189 @@ TEST(tomato, T1Shmolli_calculateSignWithoutPhase) { double correctDeltaB = 0.1775; double correctDeltaT1star = 5.5996; double correctDeltaT1 = 11.6167; - double correctTimeFlip = 2; + double correctTimeFlip = 3; + + // init the necessary objects + Ox::ModelT1Shmolli model; + Ox::FitterLevenbergMarquardtVnl fitter; + Ox::SignCalculatorNoSign signCalculator; + Ox::StartPointCalculatorShmolli startPointCalculator; + Ox::CalculatorT1Shmolli calculator; + + // configure + calculator.setModel(&model); + calculator.setFitter(&fitter); + calculator.setSignCalculator(&signCalculator); + calculator.setStartPointCalculator(&startPointCalculator); + + // set the data + calculator.setNSamples(nSamples); + calculator.setInvTimes(time); + calculator.setSigMag(signal); + + calculator.calculate(); + + EXPECT_NEAR(calculator.getResults()["A"], correctA, tolerance); + EXPECT_NEAR(calculator.getResults()["B"], correctB, tolerance); + EXPECT_NEAR(calculator.getResults()["T1star"], correctT1star, tolerance); + EXPECT_NEAR(calculator.getResults()["T1"], correctT1, tolerance); + EXPECT_NEAR(calculator.getResults()["R2Abs"], correctR2Abs, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaA"], correctDeltaA, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaB"], correctDeltaB, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaT1star"], correctDeltaT1star, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaT1"], correctDeltaT1, tolerance); + EXPECT_NEAR(calculator.getResults()["timeFlip"], correctTimeFlip, tolerance); +} + +// results from tomato +TEST(tomato, T1Shmolli_calculateSignWithoutPhase_5samples) { + + typedef double TYPE; + + TYPE signal[] = { + 95.665639445300456, + 53.529532614278374, + 13.85130970724191, + 128.0945043656908, + 156.30662557781201, + 159.62223934257833, + 160.15511042629686 }; + TYPE time[] = { 100, 180, 260, 1000, 1900, 2800, 3700 }; + int nSamples = 7; + + double tolerance = 1e-2; + + double correctA = 160.2188; + double correctB = 322.2685; + double correctT1star = 433.5508; + double correctT1 = 438.5053; + double correctR2Abs = 0.9998; + double correctDeltaA = 0.0674; + double correctDeltaB = 0.1775; + double correctDeltaT1star = 0.8256; + double correctDeltaT1 = 1.7039; + double correctTimeFlip = 1; + + // init the necessary objects + Ox::ModelT1Shmolli model; + Ox::FitterLevenbergMarquardtVnl fitter; + Ox::SignCalculatorNoSign signCalculator; + Ox::StartPointCalculatorShmolli startPointCalculator; + Ox::CalculatorT1Shmolli calculator; + + // configure + calculator.setModel(&model); + calculator.setFitter(&fitter); + calculator.setSignCalculator(&signCalculator); + calculator.setStartPointCalculator(&startPointCalculator); + + // set the data + calculator.setNSamples(nSamples); + calculator.setInvTimes(time); + calculator.setSigMag(signal); + + calculator.calculate(); + + EXPECT_NEAR(calculator.getResults()["A"], correctA, tolerance); + EXPECT_NEAR(calculator.getResults()["B"], correctB, tolerance); + EXPECT_NEAR(calculator.getResults()["T1star"], correctT1star, tolerance); + EXPECT_NEAR(calculator.getResults()["T1"], correctT1, tolerance); + EXPECT_NEAR(calculator.getResults()["R2Abs"], correctR2Abs, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaA"], correctDeltaA, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaB"], correctDeltaB, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaT1star"], correctDeltaT1star, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaT1"], correctDeltaT1, tolerance); + EXPECT_NEAR(calculator.getResults()["timeFlip"], correctTimeFlip, tolerance); +} + +TEST(tomato, T1Shmolli_calculateSignWithoutPhase_6samples) { + + typedef double TYPE; + + TYPE signal[] = { + 197.59609868043603, + 135.06540447504304, + 23.955249569707401, + 24.962134251290877, + 62.294320137693632, + 111.68445209409064, + 140.4084911072863}; + TYPE time[] = { 100, 180, 260, 1000, 1900, 2800, 3700 }; + int nSamples = 7; + + double tolerance = 1e-2; + + double correctA = 169.0853; + double correctB = 392.4758; + double correctT1star = 1441.06949; + double correctT1 = 1903.8977; + double correctR2Abs = 0.9998; + double correctDeltaA = 4.1456; + double correctDeltaB = 4.083; + double correctDeltaT1star = 43.2234; + double correctDeltaT1 = 173.9150; + double correctTimeFlip = 4; + + // init the necessary objects + Ox::ModelT1Shmolli model; + Ox::FitterLevenbergMarquardtVnl fitter; + Ox::SignCalculatorNoSign signCalculator; + Ox::StartPointCalculatorShmolli startPointCalculator; + Ox::CalculatorT1Shmolli calculator; + + // configure + calculator.setModel(&model); + calculator.setFitter(&fitter); + calculator.setSignCalculator(&signCalculator); + calculator.setStartPointCalculator(&startPointCalculator); + + // set the data + calculator.setNSamples(nSamples); + calculator.setInvTimes(time); + calculator.setSigMag(signal); + + calculator.calculate(); + + EXPECT_NEAR(calculator.getResults()["A"], correctA, tolerance); + EXPECT_NEAR(calculator.getResults()["B"], correctB, tolerance); + EXPECT_NEAR(calculator.getResults()["T1star"], correctT1star, tolerance); + EXPECT_NEAR(calculator.getResults()["T1"], correctT1, tolerance); + EXPECT_NEAR(calculator.getResults()["R2Abs"], correctR2Abs, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaA"], correctDeltaA, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaB"], correctDeltaB, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaT1star"], correctDeltaT1star, tolerance); + EXPECT_NEAR(calculator.getResults()["deltaT1"], correctDeltaT1, tolerance); + EXPECT_NEAR(calculator.getResults()["timeFlip"], correctTimeFlip, tolerance); +} + +TEST(tomato, T1Shmolli_calculateSignWithoutPhase_7samples) { + + typedef double TYPE; + + TYPE signal[] = { + 89.360373295046656, + 8.201722900215362, + 81.208183776022977, + 261.15290739411341, + 270.49353912419241, + 269.98456568557071, + 270.83237616654702, + }; + TYPE time[] = { 100, 180, 260, 1000, 1900, 2800, 3700 }; + int nSamples = 7; + + double tolerance = 1e-2; + + double correctA = 270.6357; + double correctB = 537.7456; + double correctT1star = 249.7954; + double correctT1 = 246.5412; + double correctR2Abs = 0.9998; + double correctDeltaA = 0.3358; + double correctDeltaB = 1.8499; + double correctDeltaT1star = 1.4237; + double correctDeltaT1 = 3.7284; + double correctTimeFlip = 1; // init the necessary objects Ox::ModelT1Shmolli model;