Skip to content

Commit abe020f

Browse files
committed
fix: timeFlip in shmolli
1 parent 426361e commit abe020f

File tree

6 files changed

+254
-18
lines changed

6 files changed

+254
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ html
22
latex
33
thirdparty/lmfit/*
44
thirdparty/itk/*
5+
test_package/build/*
56
conan-recipes/*/test_package/build/*
67
/reports/
78
/coverage/

CMakeLists.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ project(Tomato)
2525
# The version number
2626
set(Tomato_VERSION_MAJOR 0)
2727
set(Tomato_VERSION_MINOR 6)
28-
set(Tomato_VERSION_PATCH 5)
28+
set(Tomato_VERSION_PATCH 6)
2929

3030
# Compiler flags
3131
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -224,3 +224,12 @@ if(BUILD_TESTING)
224224
add_subdirectory(tests)
225225
install(TARGETS TomatoLib DESTINATION tests)
226226
endif()
227+
228+
##
229+
#set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
230+
##if(CMAKE_COMPILER_IS_GNUCXX)
231+
# include(CodeCoverage)
232+
# setup_target_for_coverage_lcov(
233+
# NAME ${PROJECT_NAME}Coverage
234+
# EXECUTABLE ${PROJECT_NAME}Tests)
235+
#endif()

conanfile.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from conans import ConanFile, CMake, tools, RunEnvironment
1+
from conans import ConanFile, CMake, tools
22
import os
33

44
class TomatoConan(ConanFile):
55
name = "tomato"
6-
version = "0.6.5"
6+
version = "0.6.6"
77
default_user = "user"
88
default_channel = "testing"
99
license = "MIT <https://github.com/MRKonrad/tomato/blob/master/LICENSE>"
@@ -21,6 +21,7 @@ class TomatoConan(ConanFile):
2121
"use_yaml": [True, False],
2222
"build_app": [True, False],
2323
"build_testing": [True, False],
24+
"build_testing_coverage": [True, False],
2425
"use_tomato_private": [True, False],
2526
}
2627
default_options = {
@@ -31,10 +32,16 @@ class TomatoConan(ConanFile):
3132
"use_yaml": False,
3233
"build_app": False,
3334
"build_testing": True,
35+
"build_testing_coverage": True,
3436
"use_tomato_private": False,
3537
}
3638
generators = "cmake_find_package"
3739

40+
def configure(self):
41+
self.runCoverage = self.options.build_testing_coverage \
42+
and tools.os_info.is_macos \
43+
and self.settings.build_type == "Debug"
44+
3845
def requirements(self):
3946
if self.options.use_itk:
4047
self.requires("itk/4.13.2@%s/%s" % (self.user, self.channel))
@@ -49,57 +56,84 @@ def requirements(self):
4956
def build_requirements(self):
5057
if self.options.build_testing:
5158
self.build_requires("gtest/1.6.0@%s/%s" % (self.user, self.channel))
59+
self.build_requires("libyaml/0.2.5@%s/%s" % (self.user, self.channel))
5260

5361
def source(self):
54-
self.run("git clone https://github.com/MRKonrad/tomato.git --branch T2")
62+
self.run("git clone https://github.com/MRKonrad/tomato.git")
5563
if self.options.use_tomato_private:
5664
token = os.getenv("GH_PERSONAL_ACCESS_TOKEN")
5765
if not token:
5866
self.options.use_tomato_private=False
5967
return
6068
username = "MRKonrad:"+token+"@"
61-
self.run("git clone https://"+username+"github.com/MRKonrad/tomato_private --branch T2")
69+
self.run("git clone https://"+username+"github.com/MRKonrad/tomato_private")
6270

6371
def _configure_cmake(self):
6472
cmake = CMake(self)
6573

66-
cmake.definitions["CMAKE_BUILD_TYPE"] = self.settings.build_type
67-
cmake.definitions["BUILD_SHARED_LIBS"] = self.options.shared
68-
69-
if (tools.os_info.is_macos):
70-
cmake.definitions["CMAKE_MACOSX_RPATH"] = "ON"
71-
7274
if (tools.os_info.is_linux):
73-
cmake.definitions["CMAKE_POSITION_INDEPENDENT_CODE"] = "ON"
75+
cmake.definitions["CMAKE_POSITION_INDEPENDENT_CODE"] = True
7476

75-
#cmake.definitions["CMAKE_CXX_STANDARD"] = "11"
77+
cmake.definitions["CMAKE_BUILD_TYPE"] = self.settings.build_type
78+
cmake.definitions["CMAKE_CXX_STANDARD"] = "11"
7679
cmake.definitions["USE_ITK"] = self.options.use_itk
7780
cmake.definitions["USE_VNL"] = self.options.use_vnl
7881
cmake.definitions["USE_PRIVATE_NR2"] = self.options.use_tomato_private
7982
cmake.definitions["USE_LMFIT"] = self.options.use_lmfit
8083
cmake.definitions["USE_TOMATOFIT"] = True
81-
cmake.definitions["USE_YAML"] = self.options.use_yaml
84+
cmake.definitions["USE_YAML"] = self.options.use_yaml or self.options.build_testing
8285
cmake.definitions["BUILD_APP"] = self.options.build_app
8386
cmake.definitions["BUILD_TESTING"] = self.options.build_testing
87+
if self.runCoverage:
88+
cmake.definitions["CMAKE_CXX_FLAGS"] = "-fprofile-instr-generate -fcoverage-mapping"
89+
cmake.definitions["CMAKE_C_FLAGS"] = "-fprofile-instr-generate -fcoverage-mapping"
8490

8591
cmake.configure(source_folder=self.name)
8692
return cmake
8793

8894
def build(self):
8995
cmake = self._configure_cmake()
9096
cmake.build()
97+
9198
# run tests
9299
if self.options.build_testing:
93100
# tell test executable where to look for the shared libs generated during build
94101
if self.settings.os == "Windows" and self.options.shared:
95102
os.environ['PATH'] += ";%s\%s"%(self.build_folder, self.settings.build_type)
96-
self.run("ctest -V -C %s" % (self.settings.build_type), cwd="tests", run_environment=True)
103+
testingOutputFile = "reports/unitTestsReport_%s.log" % self.settings.build_type
104+
self.run("ctest -V -C %s --output-log %s" % (self.settings.build_type, testingOutputFile), cwd="tests", run_environment=True)
105+
106+
# coverage
107+
if self.runCoverage:
108+
# find llvm-cov
109+
print("Get path to clang. llvm-cov should be in the same dir")
110+
clangPath = os.popen("xcodebuild -find clang").read()
111+
llvmCovLocation = os.path.dirname(clangPath)
112+
print("I assume that llvm-cov is here: " + llvmCovLocation)
113+
114+
# llvm-profdata
115+
self.run("./TomatoTests", cwd="tests", run_environment=True)
116+
self.run("PATH=%s:$PATH llvm-profdata merge -sparse default.profraw -o default.profdata" % (llvmCovLocation), cwd="tests")
117+
118+
# llvm-cov
119+
filesToBeReported = "%s/lib/*.cpp" % os.path.join(self.build_folder, self.name)
120+
coverageOutputFile = "reports/coverageReport.log"
121+
self.run("PATH=%s:$PATH llvm-cov report ./TomatoTests -instr-profile=default.profdata %s > %s" % (llvmCovLocation, filesToBeReported, coverageOutputFile), cwd="tests")
122+
self.run("cat %s" % coverageOutputFile, cwd="tests") # just to display results
97123

98124
def package(self):
99125
cmake = self._configure_cmake()
100126
cmake.definitions["BUILD_TESTING"] = False
127+
cmake.definitions["USE_YAML"] = self.options.use_yaml
128+
if self.runCoverage:
129+
cmake.definitions["CMAKE_CXX_FLAGS"] = ""
130+
cmake.definitions["CMAKE_C_FLAGS"] = ""
101131
cmake.configure(source_folder=self.name)
102132
cmake.install()
103133

134+
self.copy("*", dst="reports", src="tests/reports") # copy coverage results
135+
for bindir in self.deps_cpp_info.bindirs:
136+
self.copy("*.dll", dst="bin", src=bindir)
137+
104138
def package_info(self):
105139
self.cpp_info.libs = ["TomatoLib"]

lib/OxCalculatorT1Shmolli.hxx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ namespace Ox {
193193
nShmolliSamplesUsed = 5;
194194
T1temp = results5["T1"];
195195
chiTemp = results5["ChiSqrt"]; // legacy
196+
if (results5["timeFlip"] == 0) // because signs5[0] = -1;
197+
{
198+
results5["timeFlip"] = 1;
199+
}
196200
} else {
197201
chiTemp = results5["LastValue"]; // legacy, very small amount of pixels (3) influenced by it
198202
}
@@ -227,9 +231,17 @@ namespace Ox {
227231

228232
// assign output values
229233
if (nShmolliSamplesUsed == 5) {
234+
if (results5["timeFlip"] > 1)
235+
{
236+
results5["timeFlip"] = results5["timeFlip"] + 2;
237+
}
230238
this->_Results = results5;
231239
}
232240
else if (nShmolliSamplesUsed == 6) {
241+
if (results6["timeFlip"] > 1)
242+
{
243+
results6["timeFlip"] = results6["timeFlip"] + 1;
244+
}
233245
this->_Results = results6;
234246
}
235247
else if (nShmolliSamplesUsed == 7) {

test_package/conanfile.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ class TomatoTestConan(ConanFile):
1010
def build(self):
1111
cmake = CMake(self)
1212
cmake.definitions["CONAN_DISABLE_CHECK_COMPILER"] = "ON"
13-
# Current dir is "test_package/build/<build_id>" and CMakeLists.txt is
14-
# in "test_package"
1513
cmake.configure()
1614
cmake.build()
1715

tests/T1_test.cpp

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,189 @@ TEST(tomato, T1Shmolli_calculateSignWithoutPhase) {
177177
double correctDeltaB = 0.1775;
178178
double correctDeltaT1star = 5.5996;
179179
double correctDeltaT1 = 11.6167;
180-
double correctTimeFlip = 2;
180+
double correctTimeFlip = 3;
181+
182+
// init the necessary objects
183+
Ox::ModelT1Shmolli<TYPE> model;
184+
Ox::FitterLevenbergMarquardtVnl<TYPE> fitter;
185+
Ox::SignCalculatorNoSign<TYPE> signCalculator;
186+
Ox::StartPointCalculatorShmolli<TYPE> startPointCalculator;
187+
Ox::CalculatorT1Shmolli<TYPE> calculator;
188+
189+
// configure
190+
calculator.setModel(&model);
191+
calculator.setFitter(&fitter);
192+
calculator.setSignCalculator(&signCalculator);
193+
calculator.setStartPointCalculator(&startPointCalculator);
194+
195+
// set the data
196+
calculator.setNSamples(nSamples);
197+
calculator.setInvTimes(time);
198+
calculator.setSigMag(signal);
199+
200+
calculator.calculate();
201+
202+
EXPECT_NEAR(calculator.getResults()["A"], correctA, tolerance);
203+
EXPECT_NEAR(calculator.getResults()["B"], correctB, tolerance);
204+
EXPECT_NEAR(calculator.getResults()["T1star"], correctT1star, tolerance);
205+
EXPECT_NEAR(calculator.getResults()["T1"], correctT1, tolerance);
206+
EXPECT_NEAR(calculator.getResults()["R2Abs"], correctR2Abs, tolerance);
207+
EXPECT_NEAR(calculator.getResults()["deltaA"], correctDeltaA, tolerance);
208+
EXPECT_NEAR(calculator.getResults()["deltaB"], correctDeltaB, tolerance);
209+
EXPECT_NEAR(calculator.getResults()["deltaT1star"], correctDeltaT1star, tolerance);
210+
EXPECT_NEAR(calculator.getResults()["deltaT1"], correctDeltaT1, tolerance);
211+
EXPECT_NEAR(calculator.getResults()["timeFlip"], correctTimeFlip, tolerance);
212+
}
213+
214+
// results from tomato
215+
TEST(tomato, T1Shmolli_calculateSignWithoutPhase_5samples) {
216+
217+
typedef double TYPE;
218+
219+
TYPE signal[] = {
220+
95.665639445300456,
221+
53.529532614278374,
222+
13.85130970724191,
223+
128.0945043656908,
224+
156.30662557781201,
225+
159.62223934257833,
226+
160.15511042629686 };
227+
TYPE time[] = { 100, 180, 260, 1000, 1900, 2800, 3700 };
228+
int nSamples = 7;
229+
230+
double tolerance = 1e-2;
231+
232+
double correctA = 160.2188;
233+
double correctB = 322.2685;
234+
double correctT1star = 433.5508;
235+
double correctT1 = 438.5053;
236+
double correctR2Abs = 0.9998;
237+
double correctDeltaA = 0.0674;
238+
double correctDeltaB = 0.1775;
239+
double correctDeltaT1star = 0.8256;
240+
double correctDeltaT1 = 1.7039;
241+
double correctTimeFlip = 1;
242+
243+
// init the necessary objects
244+
Ox::ModelT1Shmolli<TYPE> model;
245+
Ox::FitterLevenbergMarquardtVnl<TYPE> fitter;
246+
Ox::SignCalculatorNoSign<TYPE> signCalculator;
247+
Ox::StartPointCalculatorShmolli<TYPE> startPointCalculator;
248+
Ox::CalculatorT1Shmolli<TYPE> calculator;
249+
250+
// configure
251+
calculator.setModel(&model);
252+
calculator.setFitter(&fitter);
253+
calculator.setSignCalculator(&signCalculator);
254+
calculator.setStartPointCalculator(&startPointCalculator);
255+
256+
// set the data
257+
calculator.setNSamples(nSamples);
258+
calculator.setInvTimes(time);
259+
calculator.setSigMag(signal);
260+
261+
calculator.calculate();
262+
263+
EXPECT_NEAR(calculator.getResults()["A"], correctA, tolerance);
264+
EXPECT_NEAR(calculator.getResults()["B"], correctB, tolerance);
265+
EXPECT_NEAR(calculator.getResults()["T1star"], correctT1star, tolerance);
266+
EXPECT_NEAR(calculator.getResults()["T1"], correctT1, tolerance);
267+
EXPECT_NEAR(calculator.getResults()["R2Abs"], correctR2Abs, tolerance);
268+
EXPECT_NEAR(calculator.getResults()["deltaA"], correctDeltaA, tolerance);
269+
EXPECT_NEAR(calculator.getResults()["deltaB"], correctDeltaB, tolerance);
270+
EXPECT_NEAR(calculator.getResults()["deltaT1star"], correctDeltaT1star, tolerance);
271+
EXPECT_NEAR(calculator.getResults()["deltaT1"], correctDeltaT1, tolerance);
272+
EXPECT_NEAR(calculator.getResults()["timeFlip"], correctTimeFlip, tolerance);
273+
}
274+
275+
TEST(tomato, T1Shmolli_calculateSignWithoutPhase_6samples) {
276+
277+
typedef double TYPE;
278+
279+
TYPE signal[] = {
280+
197.59609868043603,
281+
135.06540447504304,
282+
23.955249569707401,
283+
24.962134251290877,
284+
62.294320137693632,
285+
111.68445209409064,
286+
140.4084911072863};
287+
TYPE time[] = { 100, 180, 260, 1000, 1900, 2800, 3700 };
288+
int nSamples = 7;
289+
290+
double tolerance = 1e-2;
291+
292+
double correctA = 169.0853;
293+
double correctB = 392.4758;
294+
double correctT1star = 1441.06949;
295+
double correctT1 = 1903.8977;
296+
double correctR2Abs = 0.9998;
297+
double correctDeltaA = 4.1456;
298+
double correctDeltaB = 4.083;
299+
double correctDeltaT1star = 43.2234;
300+
double correctDeltaT1 = 173.9150;
301+
double correctTimeFlip = 4;
302+
303+
// init the necessary objects
304+
Ox::ModelT1Shmolli<TYPE> model;
305+
Ox::FitterLevenbergMarquardtVnl<TYPE> fitter;
306+
Ox::SignCalculatorNoSign<TYPE> signCalculator;
307+
Ox::StartPointCalculatorShmolli<TYPE> startPointCalculator;
308+
Ox::CalculatorT1Shmolli<TYPE> calculator;
309+
310+
// configure
311+
calculator.setModel(&model);
312+
calculator.setFitter(&fitter);
313+
calculator.setSignCalculator(&signCalculator);
314+
calculator.setStartPointCalculator(&startPointCalculator);
315+
316+
// set the data
317+
calculator.setNSamples(nSamples);
318+
calculator.setInvTimes(time);
319+
calculator.setSigMag(signal);
320+
321+
calculator.calculate();
322+
323+
EXPECT_NEAR(calculator.getResults()["A"], correctA, tolerance);
324+
EXPECT_NEAR(calculator.getResults()["B"], correctB, tolerance);
325+
EXPECT_NEAR(calculator.getResults()["T1star"], correctT1star, tolerance);
326+
EXPECT_NEAR(calculator.getResults()["T1"], correctT1, tolerance);
327+
EXPECT_NEAR(calculator.getResults()["R2Abs"], correctR2Abs, tolerance);
328+
EXPECT_NEAR(calculator.getResults()["deltaA"], correctDeltaA, tolerance);
329+
EXPECT_NEAR(calculator.getResults()["deltaB"], correctDeltaB, tolerance);
330+
EXPECT_NEAR(calculator.getResults()["deltaT1star"], correctDeltaT1star, tolerance);
331+
EXPECT_NEAR(calculator.getResults()["deltaT1"], correctDeltaT1, tolerance);
332+
EXPECT_NEAR(calculator.getResults()["timeFlip"], correctTimeFlip, tolerance);
333+
}
334+
335+
TEST(tomato, T1Shmolli_calculateSignWithoutPhase_7samples) {
336+
337+
typedef double TYPE;
338+
339+
TYPE signal[] = {
340+
89.360373295046656,
341+
8.201722900215362,
342+
81.208183776022977,
343+
261.15290739411341,
344+
270.49353912419241,
345+
269.98456568557071,
346+
270.83237616654702,
347+
};
348+
TYPE time[] = { 100, 180, 260, 1000, 1900, 2800, 3700 };
349+
int nSamples = 7;
350+
351+
double tolerance = 1e-2;
352+
353+
double correctA = 270.6357;
354+
double correctB = 537.7456;
355+
double correctT1star = 249.7954;
356+
double correctT1 = 246.5412;
357+
double correctR2Abs = 0.9998;
358+
double correctDeltaA = 0.3358;
359+
double correctDeltaB = 1.8499;
360+
double correctDeltaT1star = 1.4237;
361+
double correctDeltaT1 = 3.7284;
362+
double correctTimeFlip = 1;
181363

182364
// init the necessary objects
183365
Ox::ModelT1Shmolli<TYPE> model;

0 commit comments

Comments
 (0)