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
57def initModuleFromTemplate( targetModuleName, templateModuleName=None, fieldValueOverrides=None,
58 alsoInitNonEditableFields=False ):
59 context = Base.getTestCaseContext()
60 ASSERT_TRUE( context.hasModule( targetModuleName ),
61 msg="Target module {} not found!".format( targetModuleName ), logOnSuccess=False )
62 if templateModuleName is None:
63 templateModuleName = "{}_ParameterTemplate".format( targetModuleName )
64 #
65 def ___applyTemplateParameters():
66 ASSERT_TRUE( context.hasModule( templateModuleName ),
67 msg="Template module {} not found!".format( templateModuleName ), logOnSuccess=False )
68 for f in context.module( templateModuleName ).parameterFields():
69 if f.name != "instanceName" and f.type != "Trigger" and ( alsoInitNonEditableFields or f.isEditable() ):
70 value = f.vectorValue() if "vec" in f.type.lower() else f.value
71 context.field( "{}.{}".format( targetModuleName, f.name ) ).updateValue( value )
72 #
73 def ___applyOverrideParameters():
74 targetModule = context.module( targetModuleName )
75 for fieldName, fieldValue in fieldValueOverrides.items():
76 ASSERT_TRUE( targetModule.hasField( fieldName ),
77 msg = "Invalid field value override: Field {} does not exist.".format( fieldName ), logOnSuccess=False )
78 targetModule.field( fieldName ).updateValue( fieldValue )
79 #
80 ___applyTemplateParameters()
81 if fieldValueOverrides:
82 ___applyOverrideParameters()
83
84
85
90def getMinMax(fv1, fv2 = None):
91 """Determines max/min value of given fill value(s)."""
92 if isinstance(fv1, str):
93 fv1 = sorted([float(comp) for comp in fv1.split()])
94 else:
95 fv1 = sorted(fv1)
96 max1 = fv1[len(fv1)-1]
97 min1 = fv1[0]
98
99 if fv2:
100 if isinstance(fv2, str):
101 fv2 = sorted([float(comp) for comp in fv2.split()])
102 else:
103 fv2 = sorted(fv2)
104 max2 = fv2[len(fv2)-1]
105 min2 = fv2[0]
106 min = min1 if min1 < min2 else min2
107 max = max1 if max1 > max2 else max2
108 return (min, max)
109 else:
110 return (min1, max1)
111
112
113
117 """Returns the number of components of the image data type."""
118 mlType = imageField.image().dataType()
119 return MLAB.ML.MLTypeGetNumComponents(mlType)
120
121
122
126 if "int" in dataType or "float" in dataType or "double" in dataType:
127 num = 1
128 elif "complex" in dataType:
129 num = 2
130 elif "quaternion" in dataType:
131 num = 4
132 else:
133 num = ""
134 for i in dataType:
135 if i in "0123456789":
136 num += i
137 elif num != "":
138 break
139 #Matrix data types: num*num
140 num = int(num)**2 if "mat" in dataType else int(num)
141 return num
142
143
144
148def getNumpyTypeFromImage(imageField, context = None):
149 """Returns a NumPy data type object used with the image."""
150 #Std data types
151 if MLAB.ML.MLIsStandardType(imageField.image().dataType()):
152 return type(imageField.image().getTile((0, 0, 0), (1, 1, 1))[0][0][0])
153 #Adv data types
154 else:
155 try:
156 td = context.addModule(MLAB.moduleLiteral("TypeDecomposer64"))
157 except:
158 raise Exception("Use parameter \"context = ctx\" with \"getNumpyType()\" because the image uses an adv. data type!")
159 td.field("input0").connectFrom(imageField)
160 npType = type(td.field("output0").image().getTile((0, 0, 0), (1, 1, 1))[0][0][0])
161 td.remove()
162 return npType
163
164
165
169 if "unsigned" in dataType:
170 if "8" in dataType:
171 npType = numpy.uint8
172 elif "16" in dataType:
173 npType = numpy.uint16
174 elif "32" in dataType:
175 npType = numpy.uint32
176 elif "64" in dataType:
177 npType = numpy.uint64
178
179 elif "int8" in dataType or "i8" in dataType:
180 npType = numpy.int8
181 elif "int16" in dataType or "i16" in dataType:
182 npType = numpy.int16
183 elif "int32" in dataType or "i32" in dataType:
184 npType = numpy.int32
185 elif "int64" in dataType or "i64" in dataType:
186 npType = numpy.int64
187
188 elif dataType in ("float", "complexf", "quaternionf") or "vecf" in dataType or "matf" in dataType:
189 npType = numpy.float32
190 elif dataType in ("double", "complexd", "quaterniond") or "vec" in dataType or "mat" in dataType:
191 npType = numpy.float64
192 elif dataType in ("long double", "complexld", "quaternionld"):
193 npType = numpy.float64
194
195 elif "stringuchar" in dataType:
196 npType = numpy.uint8
197
198 return npType
199
200
201
207def castToNumpy(value, numpyType):
208 """Returns the value casted to the given NumPy data type."""
209 typeName = numpyType.__name__
210 #if convert to int and outside int32 or to uint and outside uint32
211 if ("uint" in typeName and (value < INTTYPES["uint32"][0] or value > INTTYPES["uint32"][1])) or \
212 ("uint" not in typeName and "int" in typeName and (value < INTTYPES["int32"][0] or value > INTTYPES["int32"][1])):
213 if sys.version_info.major == 2:
214 value = long(value)
215 else:
216 value = int(value)
217 #newValue = value % (intMax + 1) + intMin * (value / (intMax + 1) % 2)
218 value = value % (INTTYPES[typeName][1]+1) + \
219 INTTYPES[typeName][0] * (value // (INTTYPES[typeName][1]+1) % 2)
220 return numpyType(value)
221
222
223
228def convertVoxelType(voxel, oldType, newType):
229 """Coverts fill values of any input data type to std output data type."""
230 try:
231 numComp = len(voxel)
232 except:
233 numComp = 1
234 voxel = tuple(voxel)
235
236 numpyType = getNumpyTypeFromString(newType)
237 newVoxel = voxel[0] if "quaternion" not in oldType else voxel[3]
238 newVoxel = castToNumpy(newVoxel, numpyType)
239 return newVoxel
240
241
242
247def getImage3D(imageField, imageStart = (0, 0, 0), imageSize = None, context = None):
248 image = []
249 if not imageSize:
250 imageSize = imageField.size3D()
251 numComp = getNumComponentsFromImage(imageField)
252 #adv. data type
253 if numComp > 1:
254 try:
255 td = context.addModule(MLAB.moduleLiteral("TypeDecomposer64"))
256 except:
257 raise Exception("Use parameter \"context = ctx\" with \"getImage3D()\" because the image uses an adv. data type!")
258
259 td.field("input0").connectFrom(imageField)
260 for comp in range(numComp):
261 image.append(td.field("output{0}".format(comp)).image().getTile(imageStart, imageSize))
262 td.remove()
263 #std. data type
264 else:
265 image.append(imageField.image().getTile(imageStart, imageSize))
266 return image
267
268
269
274def getImage6D(imageField, imageStart = (0, 0, 0, 0, 0, 0), imageSize = None, context = None):
275 image = []
276 if not imageSize:
277 imageSize = imageField.size6D()
278 numComp = getNumComponentsFromImage(imageField)
279 #adv. data type
280 if numComp > 1:
281 try:
282 td = context.addModule(MLAB.moduleLiteral("TypeDecomposer64"))
283 except:
284 raise Exception("Use parameter \"context = ctx\" with \"getImage6D()\" because the image uses an adv. data type!")
285
286 td.field("input0").connectFrom(imageField)
287 for comp in range(numComp):
288 image.append(td.field("output{0}".format(comp)).image().getTile(imageStart, imageSize))
289 td.remove()
290 #std. data type
291 else:
292 image.append(imageField.image().getTile(imageStart, imageSize))
293 return image
294
295
296
300def compareImages3D(resultImage, expectedImage):
301 isEqual = True
302 numErrors = 0
303 #get # of components and extent of expected image; changes shape to [component][z][y][x] if it is not
304 try:
305 numComp = len(expectedImage)
306 expectedSize = [len(expectedImage[0][0][0]),
307 len(expectedImage[0][0]),
308 len(expectedImage[0])]
309 except TypeError:
310 numComp = 1
311 expectedImage = [expectedImage]
312 expectedSize = [len(expectedImage[0][0][0]),
313 len(expectedImage[0][0]),
314 len(expectedImage[0])]
315 #get extent of result image; changes shape to [component][z][y][x] if it is not
316 try:
317 resultSize = [len(resultImage[0][0][0]),
318 len(resultImage[0][0]),
319 len(resultImage[0])]
320 except TypeError:
321 resultImage = [resultImage]
322 resultSize = [len(resultImage[0][0][0]),
323 len(resultImage[0][0]),
324 len(resultImage[0])]
325
326 if resultSize != expectedSize:
327 ERROR("Image compare failed: expected and output image are not not of the same size >> " \
328 + "result {0} != expected {1}".format(resultSize, expectedSize))
329 return False
330
331 elif numComp != len(resultImage):
332 ERROR("Image compare failed: expected and output image have different number of data type components >> " \
333 + "result {0} != expected {1}".format(numComp, len(resultImage)))
334 return False
335
336 if not numpy.allclose(expectedImage, resultImage, 0, EPSILON) or LOGONSUCCESS:
337 for z in range(resultSize[2]):
338 for y in range(resultSize[1]):
339 for x in range(resultSize[0]):
340 for comp in range(numComp):
341 #get expected and out voxel are a number or inf/nan as bool
342 finiteExp = numpy.isfinite(expectedImage[comp][z][y][x])
343 finiteOut = numpy.isfinite(resultImage[comp][z][y][x])
344 #check for difference between voxel
345 if finiteExp != finiteOut \
346 or (finiteExp, finiteOut) == (True, True) \
347 and abs(resultImage[comp][z][y][x] - expectedImage[comp][z][y][x]) > EPSILON:
348 if LOGVOXEL:
349 ERROR("""Voxel ({0},{1},{2}) of Result and Expected image are not equal (Epsilon = {3}):
350 >> {4} == {5} : False""".format(x, y, z, EPSILON, \
351 [resultImage[comp][z][y][x] for comp in range(numComp)], \
352 [expectedImage[comp][z][y][x] for comp in range(numComp)]))
353 isEqual = False
354 numErrors += 1
355 break
356 #voxel are equal
357 elif LOGVOXEL and LOGONSUCCESS and comp == numComp-1:
358 INFO("""Voxel ({0},{1},{2}) of Result and Expected image are equal (Epsilon = {3}):
359 >> {4} == {5} : True""".format(x, y, z, EPSILON, \
360 [resultImage[comp][z][y][x] for comp in range(numComp)], \
361 [expectedImage[comp][z][y][x] for comp in range(numComp)]))
362
363 if isEqual:
364 INFO("Image compare passed: expected and out image are equal")
365 else:
366 ERROR("Image compare failed: expected and output image are not equal >> " \
367 + "{0} of {1} voxels are different".format(numErrors, resultSize[0] * resultSize[1] * resultSize[2]))
368 return isEqual
369
370
371
375def compareImages6D(resultImage, expectedImage):
376 isEqual = True
377 numErrors = 0
378 #get # of components and extent of expected image; changes shape to [component][u][t][c][z][y][x] if it is not
379 try:
380 numComp = len(expectedImage)
381 expectedSize = [len(expectedImage[0][0][0][0][0][0]),
382 len(expectedImage[0][0][0][0][0]),
383 len(expectedImage[0][0][0][0]),
384 len(expectedImage[0][0][0]),
385 len(expectedImage[0][0]),
386 len(expectedImage[0])]
387 except TypeError:
388 numComp = 1
389 expectedImage = [expectedImage]
390 expectedSize = [len(expectedImage[0][0][0][0][0][0]),
391 len(expectedImage[0][0][0][0][0]),
392 len(expectedImage[0][0][0][0]),
393 len(expectedImage[0][0][0]),
394 len(expectedImage[0][0]),
395 len(expectedImage[0])]
396 #get extent of result image; changes shape to [component][u][t][c][z][y][x] if it is not
397 try:
398 resultSize = [len(resultImage[0][0][0][0][0][0]),
399 len(resultImage[0][0][0][0][0]),
400 len(resultImage[0][0][0][0]),
401 len(resultImage[0][0][0]),
402 len(resultImage[0][0]),
403 len(resultImage[0])]
404 except TypeError:
405 numComp = 1
406 resultImage = [resultImage]
407 resultSize = [len(resultImage[0][0][0][0][0][0]),
408 len(resultImage[0][0][0][0][0]),
409 len(resultImage[0][0][0][0]),
410 len(resultImage[0][0][0]),
411 len(resultImage[0][0]),
412 len(resultImage[0])]
413
414 if resultSize != expectedSize:
415 ERROR("Image compare failed: expected and output image are not not of the same size >> " \
416 + "result {0} != expected {1}".format(resultSize, expectedSize))
417 return False
418
419 elif numComp != len(resultImage):
420 ERROR("Image compare failed: expected and output image have different number of data type components >> " \
421 + "result {0} != expected {1}".format(numComp, len(resultImage)))
422 return False
423
424 if not numpy.allclose(expectedImage, resultImage, 0, EPSILON) or LOGONSUCCESS:
425 for u in range(resultSize[5]):
426 for t in range(resultSize[4]):
427 for c in range(resultSize[3]):
428 for z in range(resultSize[2]):
429 for y in range(resultSize[1]):
430 for x in range(resultSize[0]):
431 for comp in range(numComp):
432 #get expected and out voxel are a number or inf/nan as bool
433 finiteExp = numpy.isfinite(expectedImage[comp][u][t][c][z][y][x])
434 finiteOut = numpy.isfinite(resultImage[comp][u][t][c][z][y][x])
435 #check for difference between voxel
436 if finiteExp != finiteOut \
437 or (finiteExp, finiteOut) == (True, True) \
438 and abs(resultImage[comp][u][t][c][z][y][x] - expectedImage[comp][u][t][c][z][y][x]) > EPSILON:
439 if LOGVOXEL:
440 ERROR("""Voxel ({0},{1},{2},{3},{4},{5}) of Result and Expected image are not equal (Epsilon = {6}):
441 >> {7} == {8} : False""".format(x, y, z, c, t, u, EPSILON, \
442 [resultImage[comp][u][t][c][z][y][x] for comp in range(numComp)], \
443 [expectedImage[comp][u][t][c][z][y][x] for comp in range(numComp)]))
444 isEqual = False
445 numErrors += 1
446 break
447 #voxel are equal
448 elif LOGVOXEL and LOGONSUCCESS and comp == numComp-1:
449 INFO("""Voxel ({0},{1},{2},{3},{4},{5}) of Result and Expected image are equal (Epsilon = {6}):
450 >> {7} == {8} : True""".format(x, y, z, c, t, u, EPSILON, \
451 [resultImage[comp][u][t][c][z][y][x] for comp in range(numComp)], \
452 [expectedImage[comp][u][t][c][z][y][x] for comp in range(numComp)]))
453
454 if isEqual:
455 INFO("Image compare passed: expected and out image are equal")
456 else:
457 ERROR("Image compare failed: expected and output image are not equal >> " \
458 + "{0} of {1} voxels are different".format(numErrors, resultSize[0] * resultSize[1] * resultSize[2] * resultSize[3] * resultSize[4] * resultSize[5]))
459 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