TestCenter Reference
HelperFunctions.py
Go to the documentation of this file.
2# Copyright 2011, MeVis Medical Solutions AG
3#
4# The user may use this file in accordance with the license agreement provided with
5# the Software or, alternatively, in accordance with the terms contained in a
6# written agreement between the user and MeVis Medical Solutions AG.
7#
8# For further information use the contact form at https://www.mevislab.de/contact
9#
10
11from mevis import MLAB
12from TestSupport import Base
13from TestSupport.Macros import INFO, ERROR, ASSERT_TRUE
14import numpy
15import sys
16
17LOGVOXEL = False # trigger for logging voxel comparison
18LOGONSUCCESS = False # trigger for logging voxel comparison if success (gLogVoxel needs to be True)
19EPSILON = 0.0001 # absolute value to compensate floating point imprecisions with voxel comparison
20
21INTTYPES = {
22 "int8": (-128, 127),
23 "int16": (-32768, 32767),
24 "int32": (-2147483648, 2147483647),
25 "int64": (-9223372036854775808, 9223372036854775807),
26 "uint8": (0, 255),
27 "uint16": (0, 65535),
28 "uint32": (0, 4294967295),
29 "uint64": (0, 18446744073709551615),
30}
31
32
33
37def generateTestDictionary(function, testDictionaries):
38 if isinstance(testDictionaries, (list, tuple)):
39 testDict = {}
40 for d in testDictionaries:
41 testDict.update(d)
42 else:
43 testDict = testDictionaries
44 for key in testDict:
45 testDict[key][0] = function
46 return testDict
47
48
49
58 targetModuleName, templateModuleName=None, fieldValueOverrides=None, alsoInitNonEditableFields=False
59):
60 context = Base.getTestCaseContext()
61 ASSERT_TRUE(
62 context.hasModule(targetModuleName),
63 msg="Target module {} not found!".format(targetModuleName),
64 logOnSuccess=False,
65 )
66 if templateModuleName is None:
67 templateModuleName = "{}_ParameterTemplate".format(targetModuleName)
68
69 #
70 def ___applyTemplateParameters():
71 ASSERT_TRUE(
72 context.hasModule(templateModuleName),
73 msg="Template module {} not found!".format(templateModuleName),
74 logOnSuccess=False,
75 )
76 for f in context.module(templateModuleName).parameterFields():
77 if f.name != "instanceName" and f.type != "Trigger" and (alsoInitNonEditableFields or f.isEditable()):
78 value = f.vectorValue() if "vec" in f.type.lower() else f.value
79 context.field("{}.{}".format(targetModuleName, f.name)).updateValue(value)
80
81 #
82 def ___applyOverrideParameters():
83 targetModule = context.module(targetModuleName)
84 for fieldName, fieldValue in fieldValueOverrides.items():
85 ASSERT_TRUE(
86 targetModule.hasField(fieldName),
87 msg="Invalid field value override: Field {} does not exist.".format(fieldName),
88 logOnSuccess=False,
89 )
90 targetModule.field(fieldName).updateValue(fieldValue)
91
92 #
93 ___applyTemplateParameters()
94 if fieldValueOverrides:
95 ___applyOverrideParameters()
96
97
98
103def getMinMax(fv1, fv2=None):
104 """Determines max/min value of given fill value(s)."""
105 if isinstance(fv1, str):
106 fv1 = sorted([float(comp) for comp in fv1.split()])
107 else:
108 fv1 = sorted(fv1)
109 max1 = fv1[len(fv1) - 1]
110 min1 = fv1[0]
111
112 if fv2:
113 if isinstance(fv2, str):
114 fv2 = sorted([float(comp) for comp in fv2.split()])
115 else:
116 fv2 = sorted(fv2)
117 max2 = fv2[len(fv2) - 1]
118 min2 = fv2[0]
119 min = min1 if min1 < min2 else min2
120 max = max1 if max1 > max2 else max2
121 return (min, max)
122 else:
123 return (min1, max1)
124
125
126
130 """Returns the number of components of the image data type."""
131 mlType = imageField.image().dataType()
132 return MLAB.ML.MLTypeGetNumComponents(mlType)
133
134
135
139 if "int" in dataType or "float" in dataType or "double" in dataType:
140 num = 1
141 elif "complex" in dataType:
142 num = 2
143 elif "quaternion" in dataType:
144 num = 4
145 else:
146 num = ""
147 for i in dataType:
148 if i in "0123456789":
149 num += i
150 elif num != "":
151 break
152 # Matrix data types: num*num
153 num = int(num) ** 2 if "mat" in dataType else int(num)
154 return num
155
156
157
161def getNumpyTypeFromImage(imageField, context=None):
162 """Returns a NumPy data type object used with the image."""
163 # Std data types
164 if MLAB.ML.MLIsStandardType(imageField.image().dataType()):
165 return type(imageField.image().getTile((0, 0, 0), (1, 1, 1))[0][0][0])
166 # Adv data types
167 else:
168 try:
169 td = context.addModule(MLAB.moduleLiteral("TypeDecomposer64"))
170 except:
171 raise Exception(
172 'Use parameter "context = ctx" with "getNumpyType()" because the image uses an adv. data type!'
173 )
174 td.field("input0").connectFrom(imageField)
175 npType = type(td.field("output0").image().getTile((0, 0, 0), (1, 1, 1))[0][0][0])
176 td.remove()
177 return npType
178
179
180
184 if "unsigned" in dataType:
185 if "8" in dataType:
186 npType = numpy.uint8
187 elif "16" in dataType:
188 npType = numpy.uint16
189 elif "32" in dataType:
190 npType = numpy.uint32
191 elif "64" in dataType:
192 npType = numpy.uint64
193
194 elif "int8" in dataType or "i8" in dataType:
195 npType = numpy.int8
196 elif "int16" in dataType or "i16" in dataType:
197 npType = numpy.int16
198 elif "int32" in dataType or "i32" in dataType:
199 npType = numpy.int32
200 elif "int64" in dataType or "i64" in dataType:
201 npType = numpy.int64
202
203 elif dataType in ("float", "complexf", "quaternionf") or "vecf" in dataType or "matf" in dataType:
204 npType = numpy.float32
205 elif dataType in ("double", "complexd", "quaterniond") or "vec" in dataType or "mat" in dataType:
206 npType = numpy.float64
207 elif dataType in ("long double", "complexld", "quaternionld"):
208 npType = numpy.float64
209
210 elif "stringuchar" in dataType:
211 npType = numpy.uint8
212
213 return npType
214
215
216
222def castToNumpy(value, numpyType):
223 """Returns the value casted to the given NumPy data type."""
224 typeName = numpyType.__name__
225 # if convert to int and outside int32 or to uint and outside uint32
226 if ("uint" in typeName and (value < INTTYPES["uint32"][0] or value > INTTYPES["uint32"][1])) or (
227 "uint" not in typeName and "int" in typeName and (value < INTTYPES["int32"][0] or value > INTTYPES["int32"][1])
228 ):
229 if sys.version_info.major == 2:
230 value = long(value)
231 else:
232 value = int(value)
233 # newValue = value % (intMax + 1) + intMin * (value / (intMax + 1) % 2)
234 value = value % (INTTYPES[typeName][1] + 1) + INTTYPES[typeName][0] * (value // (INTTYPES[typeName][1] + 1) % 2)
235 return numpyType(value)
236
237
238
243def convertVoxelType(voxel, oldType, newType):
244 """Coverts fill values of any input data type to std output data type."""
245 try:
246 numComp = len(voxel)
247 except:
248 numComp = 1
249 voxel = tuple(voxel)
250
251 numpyType = getNumpyTypeFromString(newType)
252 newVoxel = voxel[0] if "quaternion" not in oldType else voxel[3]
253 newVoxel = castToNumpy(newVoxel, numpyType)
254 return newVoxel
255
256
257
262def getImage3D(imageField, imageStart=(0, 0, 0), imageSize=None, context=None):
263 image = []
264 if not imageSize:
265 imageSize = imageField.size3D()
266 numComp = getNumComponentsFromImage(imageField)
267 # adv. data type
268 if numComp > 1:
269 try:
270 td = context.addModule(MLAB.moduleLiteral("TypeDecomposer64"))
271 except:
272 raise Exception(
273 'Use parameter "context = ctx" with "getImage3D()" because the image uses an adv. data type!'
274 )
275
276 td.field("input0").connectFrom(imageField)
277 for comp in range(numComp):
278 image.append(td.field("output{0}".format(comp)).image().getTile(imageStart, imageSize))
279 td.remove()
280 # std. data type
281 else:
282 image.append(imageField.image().getTile(imageStart, imageSize))
283 return image
284
285
286
291def getImage6D(imageField, imageStart=(0, 0, 0, 0, 0, 0), imageSize=None, context=None):
292 image = []
293 if not imageSize:
294 imageSize = imageField.size6D()
295 numComp = getNumComponentsFromImage(imageField)
296 # adv. data type
297 if numComp > 1:
298 try:
299 td = context.addModule(MLAB.moduleLiteral("TypeDecomposer64"))
300 except:
301 raise Exception(
302 'Use parameter "context = ctx" with "getImage6D()" because the image uses an adv. data type!'
303 )
304
305 td.field("input0").connectFrom(imageField)
306 for comp in range(numComp):
307 image.append(td.field("output{0}".format(comp)).image().getTile(imageStart, imageSize))
308 td.remove()
309 # std. data type
310 else:
311 image.append(imageField.image().getTile(imageStart, imageSize))
312 return image
313
314
315
319def compareImages3D(resultImage, expectedImage):
320 isEqual = True
321 numErrors = 0
322 # get # of components and extent of expected image; changes shape to [component][z][y][x] if it is not
323 try:
324 numComp = len(expectedImage)
325 expectedSize = [len(expectedImage[0][0][0]), len(expectedImage[0][0]), len(expectedImage[0])]
326 except TypeError:
327 numComp = 1
328 expectedImage = [expectedImage]
329 expectedSize = [len(expectedImage[0][0][0]), len(expectedImage[0][0]), len(expectedImage[0])]
330 # get extent of result image; changes shape to [component][z][y][x] if it is not
331 try:
332 resultSize = [len(resultImage[0][0][0]), len(resultImage[0][0]), len(resultImage[0])]
333 except TypeError:
334 resultImage = [resultImage]
335 resultSize = [len(resultImage[0][0][0]), len(resultImage[0][0]), len(resultImage[0])]
336
337 if resultSize != expectedSize:
338 ERROR(
339 "Image compare failed: expected and output image are not not of the same size >> "
340 + "result {0} != expected {1}".format(resultSize, expectedSize)
341 )
342 return False
343
344 elif numComp != len(resultImage):
345 ERROR(
346 "Image compare failed: expected and output image have different number of data type components >> "
347 + "result {0} != expected {1}".format(numComp, len(resultImage))
348 )
349 return False
350
351 if not numpy.allclose(expectedImage, resultImage, 0, EPSILON) or LOGONSUCCESS:
352 for z in range(resultSize[2]):
353 for y in range(resultSize[1]):
354 for x in range(resultSize[0]):
355 for comp in range(numComp):
356 # get expected and out voxel are a number or inf/nan as bool
357 finiteExp = numpy.isfinite(expectedImage[comp][z][y][x])
358 finiteOut = numpy.isfinite(resultImage[comp][z][y][x])
359 # check for difference between voxel
360 if (
361 finiteExp != finiteOut
362 or (finiteExp, finiteOut) == (True, True)
363 and abs(resultImage[comp][z][y][x] - expectedImage[comp][z][y][x]) > EPSILON
364 ):
365 if LOGVOXEL:
366 ERROR(
367 """Voxel ({0},{1},{2}) of Result and Expected image are not equal (Epsilon = {3}):
368 >> {4} == {5} : False""".format(
369 x,
370 y,
371 z,
372 EPSILON,
373 [resultImage[comp][z][y][x] for comp in range(numComp)],
374 [expectedImage[comp][z][y][x] for comp in range(numComp)],
375 )
376 )
377 isEqual = False
378 numErrors += 1
379 break
380 # voxel are equal
381 elif LOGVOXEL and LOGONSUCCESS and comp == numComp - 1:
382 INFO(
383 """Voxel ({0},{1},{2}) of Result and Expected image are equal (Epsilon = {3}):
384 >> {4} == {5} : True""".format(
385 x,
386 y,
387 z,
388 EPSILON,
389 [resultImage[comp][z][y][x] for comp in range(numComp)],
390 [expectedImage[comp][z][y][x] for comp in range(numComp)],
391 )
392 )
393
394 if isEqual:
395 INFO("Image compare passed: expected and out image are equal")
396 else:
397 ERROR(
398 "Image compare failed: expected and output image are not equal >> "
399 + "{0} of {1} voxels are different".format(numErrors, resultSize[0] * resultSize[1] * resultSize[2])
400 )
401 return isEqual
402
403
404
408def compareImages6D(resultImage, expectedImage):
409 isEqual = True
410 numErrors = 0
411 # get # of components and extent of expected image; changes shape to [component][u][t][c][z][y][x] if it is not
412 try:
413 numComp = len(expectedImage)
414 expectedSize = [
415 len(expectedImage[0][0][0][0][0][0]),
416 len(expectedImage[0][0][0][0][0]),
417 len(expectedImage[0][0][0][0]),
418 len(expectedImage[0][0][0]),
419 len(expectedImage[0][0]),
420 len(expectedImage[0]),
421 ]
422 except TypeError:
423 numComp = 1
424 expectedImage = [expectedImage]
425 expectedSize = [
426 len(expectedImage[0][0][0][0][0][0]),
427 len(expectedImage[0][0][0][0][0]),
428 len(expectedImage[0][0][0][0]),
429 len(expectedImage[0][0][0]),
430 len(expectedImage[0][0]),
431 len(expectedImage[0]),
432 ]
433 # get extent of result image; changes shape to [component][u][t][c][z][y][x] if it is not
434 try:
435 resultSize = [
436 len(resultImage[0][0][0][0][0][0]),
437 len(resultImage[0][0][0][0][0]),
438 len(resultImage[0][0][0][0]),
439 len(resultImage[0][0][0]),
440 len(resultImage[0][0]),
441 len(resultImage[0]),
442 ]
443 except TypeError:
444 numComp = 1
445 resultImage = [resultImage]
446 resultSize = [
447 len(resultImage[0][0][0][0][0][0]),
448 len(resultImage[0][0][0][0][0]),
449 len(resultImage[0][0][0][0]),
450 len(resultImage[0][0][0]),
451 len(resultImage[0][0]),
452 len(resultImage[0]),
453 ]
454
455 if resultSize != expectedSize:
456 ERROR(
457 "Image compare failed: expected and output image are not not of the same size >> "
458 + "result {0} != expected {1}".format(resultSize, expectedSize)
459 )
460 return False
461
462 elif numComp != len(resultImage):
463 ERROR(
464 "Image compare failed: expected and output image have different number of data type components >> "
465 + "result {0} != expected {1}".format(numComp, len(resultImage))
466 )
467 return False
468
469 if not numpy.allclose(expectedImage, resultImage, 0, EPSILON) or LOGONSUCCESS:
470 for u in range(resultSize[5]):
471 for t in range(resultSize[4]):
472 for c in range(resultSize[3]):
473 for z in range(resultSize[2]):
474 for y in range(resultSize[1]):
475 for x in range(resultSize[0]):
476 for comp in range(numComp):
477 # get expected and out voxel are a number or inf/nan as bool
478 finiteExp = numpy.isfinite(expectedImage[comp][u][t][c][z][y][x])
479 finiteOut = numpy.isfinite(resultImage[comp][u][t][c][z][y][x])
480 # check for difference between voxel
481 if (
482 finiteExp != finiteOut
483 or (finiteExp, finiteOut) == (True, True)
484 and abs(
485 resultImage[comp][u][t][c][z][y][x] - expectedImage[comp][u][t][c][z][y][x]
486 )
487 > EPSILON
488 ):
489 if LOGVOXEL:
490 ERROR(
491 """Voxel ({0},{1},{2},{3},{4},{5}) of Result and Expected image are not equal (Epsilon = {6}):
492 >> {7} == {8} : False""".format(
493 x,
494 y,
495 z,
496 c,
497 t,
498 u,
499 EPSILON,
500 [resultImage[comp][u][t][c][z][y][x] for comp in range(numComp)],
501 [expectedImage[comp][u][t][c][z][y][x] for comp in range(numComp)],
502 )
503 )
504 isEqual = False
505 numErrors += 1
506 break
507 # voxel are equal
508 elif LOGVOXEL and LOGONSUCCESS and comp == numComp - 1:
509 INFO(
510 """Voxel ({0},{1},{2},{3},{4},{5}) of Result and Expected image are equal (Epsilon = {6}):
511 >> {7} == {8} : True""".format(
512 x,
513 y,
514 z,
515 c,
516 t,
517 u,
518 EPSILON,
519 [resultImage[comp][u][t][c][z][y][x] for comp in range(numComp)],
520 [expectedImage[comp][u][t][c][z][y][x] for comp in range(numComp)],
521 )
522 )
523
524 if isEqual:
525 INFO("Image compare passed: expected and out image are equal")
526 else:
527 ERROR(
528 "Image compare failed: expected and output image are not equal >> "
529 + "{0} of {1} voxels are different".format(
530 numErrors, resultSize[0] * resultSize[1] * resultSize[2] * resultSize[3] * resultSize[4] * resultSize[5]
531 )
532 )
533 return isEqual
initModuleFromTemplate(targetModuleName, templateModuleName=None, fieldValueOverrides=None, alsoInitNonEditableFields=False)
Copies all values from a reference module's editable parameter fields to the target ('test') module.
compareImages3D(resultImage, expectedImage)
Compares voxels of a result image with an expected image in 3 dimensions.
generateTestDictionary(function, testDictionaries)
Concatenates test dictionaries and sets a function that needs to be tested in the final dictionary.
getMinMax(fv1, fv2=None)
Determines max/min value of given fill value(s).
compareImages6D(resultImage, expectedImage)
Compares voxels of a result image with an expected image in 6 dimensions.
castToNumpy(value, numpyType)
Casts a value to a NumPy type.
getImage3D(imageField, imageStart=(0, 0, 0), imageSize=None, context=None)
Returns ML image output of given module in 3 dimensions.
getNumpyTypeFromString(dataType)
Returns the NumPy data type appropriate to a data type given as string.
convertVoxelType(voxel, oldType, newType)
Converts a voxel to a new data type.
getNumpyTypeFromImage(imageField, context=None)
Returns the NumPy data type of an ML-Image.
getImage6D(imageField, imageStart=(0, 0, 0, 0, 0, 0), imageSize=None, context=None)
Returns ML image output of given module in 6 dimensions.
getNumComponentsFromString(dataType)
Returns the number of components of a data type given as string.
getNumComponentsFromImage(imageField)
Returns the number of components of an ML-image data type.
Adds GoogleTest like methods.
Definition Macros.py:1