Skip to content

Commit

Permalink
fix: timeFlip in shmolli
Browse files Browse the repository at this point in the history
  • Loading branch information
MRKonrad committed Oct 1, 2021
1 parent 426361e commit abe020f
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ html
latex
thirdparty/lmfit/*
thirdparty/itk/*
test_package/build/*
conan-recipes/*/test_package/build/*
/reports/
/coverage/
Expand Down
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
62 changes: 48 additions & 14 deletions conanfile.py
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/MRKonrad/tomato/blob/master/LICENSE>"
Expand All @@ -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 = {
Expand All @@ -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))
Expand All @@ -49,57 +56,84 @@ 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

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"]
12 changes: 12 additions & 0 deletions lib/OxCalculatorT1Shmolli.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 0 additions & 2 deletions test_package/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<build_id>" and CMakeLists.txt is
# in "test_package"
cmake.configure()
cmake.build()

Expand Down
184 changes: 183 additions & 1 deletion tests/T1_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TYPE> model;
Ox::FitterLevenbergMarquardtVnl<TYPE> fitter;
Ox::SignCalculatorNoSign<TYPE> signCalculator;
Ox::StartPointCalculatorShmolli<TYPE> startPointCalculator;
Ox::CalculatorT1Shmolli<TYPE> 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<TYPE> model;
Ox::FitterLevenbergMarquardtVnl<TYPE> fitter;
Ox::SignCalculatorNoSign<TYPE> signCalculator;
Ox::StartPointCalculatorShmolli<TYPE> startPointCalculator;
Ox::CalculatorT1Shmolli<TYPE> 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<TYPE> model;
Ox::FitterLevenbergMarquardtVnl<TYPE> fitter;
Ox::SignCalculatorNoSign<TYPE> signCalculator;
Ox::StartPointCalculatorShmolli<TYPE> startPointCalculator;
Ox::CalculatorT1Shmolli<TYPE> 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<TYPE> model;
Expand Down

0 comments on commit abe020f

Please sign in to comment.