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