1#========================================================================== 2# 3# Copyright Insight Software Consortium 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0.txt 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17#==========================================================================*/ 18 19from __future__ import print_function 20 21import inspect 22import os 23import re 24import sys 25import types 26import collections 27if sys.version_info >= (3, 4): 28 import importlib 29else: 30 import imp 31import warnings 32import itkConfig 33import itkBase 34import itkLazy 35from itkTypes import itkCType 36 37 38def registerNoTpl(name, cl): 39 """Register a class without template 40 41 It can seem not useful to register classes without template (and it wasn't 42 useful until the SmartPointer template was generated), but those classes 43 can be used as template argument of classes with template. 44 """ 45 itkTemplate.__templates__[normalizeName(name)] = cl 46 47 48def normalizeName(name): 49 """Normalize the class name to remove ambiguity 50 51 This function removes the white spaces in the name, and also 52 remove the pointer declaration "*" (it have no sense in python) """ 53 54 name = name.replace(" ", "") 55 name = name.replace("*", "") 56 57 return name 58 59 60class TemplateTypeError(TypeError): 61 def __init__(self, template_type, input_type): 62 def tuple_to_string_type(t): 63 if type(t) == tuple: 64 return ", ".join(itk.python_type(x) for x in t) 65 else: 66 itk.python_type(t) 67 68 import itk 69 # Special case for ITK image readers: Add extra information. 70 extra_eg = "" 71 if template_type in [itk.ImageFileReader, itk.ImageSeriesReader]: 72 extra_eg=""" 73 74or 75 76 e.g.: image = itk.imread(my_input_filename, itk.F) 77""" 78 79 python_template_type = itk.python_type(template_type) 80 python_input_type = tuple_to_string_type(input_type) 81 type_list = "\n".join([itk.python_type(x[0]) for x in template_type.keys()]) 82 eg_type = ", ".join([itk.python_type(x) for x in list(template_type.keys())[0]]) 83 msg = """{template_type} is not wrapped for input type `{input_type}`. 84 85To limit the size of the package, only a limited number of 86types are available in ITK Python. To print the supported 87types, run the following command in your python environment: 88 89 {template_type}.GetTypes() 90 91Possible solutions: 92* If you are an application user: 93** Convert your input image into a supported format (see below). 94** Contact developer to report the issue. 95* If you are an application developer, force input images to be 96loaded in a supported pixel type. 97 98 e.g.: instance = {template_type}[{eg_type}].New(my_input){extra_eg} 99 100* (Advanced) If you are an application developer, build ITK Python yourself and 101turned to `ON` the corresponding CMake option to wrap the pixel type or image 102dimension you need. When configuring ITK with CMake, you can set 103`ITK_WRAP_${{type}}` (replace ${{type}} with appropriate pixel type such as 104`double`). If you need to support images with 4 or 5 dimensions, you can add 105these dimensions to the list of dimensions in the CMake variable 106`ITK_WRAP_IMAGE_DIMS`. 107 108Supported input types: 109 110{type_list} 111""".format(template_type=python_template_type, 112 input_type=python_input_type, 113 type_list=type_list, 114 eg_type=eg_type, 115 extra_eg=extra_eg 116 ) 117 TypeError.__init__(self, msg) 118 119 120class itkTemplate(object): 121 122 """This class manages access to available template arguments of a C++ class. 123 124 This class is generic and does not give help on the methods available in 125 the instantiated class. To get help on a specific ITK class, instantiate an 126 object of that class. 127 128 e.g.: median = itk.MedianImageFilter[ImageType, ImageType].New() 129 help(median) 130 131 There are two ways to access types: 132 133 1. With a dict interface. The user can manipulate template parameters 134 similarly to C++, with the exception that the available parameters sets are 135 chosen at compile time. It is also possible, with the dict interface, to 136 explore the available parameters sets. 137 2. With object attributes. The user can easily find the available parameters 138 sets by pressing tab in interperter like ipython 139 """ 140 __templates__ = collections.OrderedDict() 141 __class_to_template__ = {} 142 __named_templates__ = {} 143 __doxygen_root__ = itkConfig.doxygen_root 144 145 def __new__(cls, name): 146 # Singleton pattern: we only make a single instance of any Template of 147 # a given name. If we have already made the instance, just return it 148 # as-is. 149 if name not in cls.__named_templates__: 150 new_instance = object.__new__(cls) 151 new_instance.__name__ = name 152 new_instance.__template__ = collections.OrderedDict() 153 cls.__named_templates__[name] = new_instance 154 return cls.__named_templates__[name] 155 156 def __add__(self, paramSetString, cl): 157 """Add a new argument set and the resulting class to the template. 158 159 paramSetString is the C++ string which defines the parameters set. 160 cl is the class which corresponds to the couple template-argument set. 161 """ 162 # recreate the full name and normalize it to avoid ambiguity 163 normFullName = normalizeName( 164 self.__name__ + "<" + paramSetString + ">") 165 166 # the full class should not be already registered. If it is, there is a 167 # problem somewhere so warn the user so he can fix the problem 168 if normFullName in itkTemplate.__templates__: 169 message = ( 170 "Template %s\n already defined as %s\n is redefined " 171 "as %s") % (normFullName, self.__templates__[normFullName], cl) 172 warnings.warn(message) 173 # register the class 174 itkTemplate.__templates__[normFullName] = cl 175 176 # __find_param__ will parse the paramSetString and produce a list of 177 # the same parameters transformed in corresponding python classes. 178 # we transform this list in tuple to make it usable as key of the dict 179 param = tuple(self.__find_param__(paramSetString)) 180 181 # once again, warn the user if the tuple of parameter is already 182 # defined so he can fix the problem 183 if param in self.__template__: 184 message = "Warning: template already defined '%s'" % normFullName 185 warnings.warn(message) 186 # and register the parameter tuple 187 self.__template__[param] = cl 188 189 # add in __class_to_template__ dictionary 190 itkTemplate.__class_to_template__[cl] = (self, param) 191 192 # now populate the template 193 # 2 cases: 194 # - the template is a SmartPointer. In that case, the attribute name 195 # will be the full real name of the class without the itk prefix and 196 # _Pointer suffix 197 # - the template is not a SmartPointer. In that case, we keep only the 198 # end of the real class name which is a short string discribing the 199 # template arguments (for example IUC2) 200 if cl.__name__.startswith("itk"): 201 if cl.__name__.endswith("_Pointer"): 202 # it's a SmartPointer 203 attributeName = cl.__name__[len("itk"):-len("_Pointer")] 204 else: 205 # it's not a SmartPointer 206 # we need to now the size of the name to keep only the suffix 207 # short name does not contain :: and nested namespace 208 # itk::Numerics::Sample -> itkSample 209 shortNameSize = len(re.sub(r':.*:', '', self.__name__)) 210 attributeName = cl.__name__[shortNameSize:] 211 else: 212 shortName = re.sub(r':.*:', '', self.__name__) 213 214 if not cl.__name__.startswith(shortName): 215 shortName = re.sub(r'.*::', '', self.__name__) 216 217 attributeName = cl.__name__[len(shortName):] 218 219 if attributeName[0].isdigit(): 220 # the attribute name can't start with a number 221 # add a single x before it to build a valid name. 222 # Adding an underscore would hide the attributeName in IPython 223 attributeName = "x" + attributeName 224 225 # add the attribute to this object 226 self.__dict__[attributeName] = cl 227 228 def __instancecheck__(self, instance): 229 """Overloads `isinstance()` when called on an `itkTemplate` object. 230 231 This function allows to compare an object to a filter without 232 specifying the actual template arguments of the class. It will 233 test all available template parameters that have been wrapped 234 and return `True` if one that corresponds to the object is found. 235 """ 236 for k in self.keys(): 237 if isinstance(instance, self[k]): 238 return True 239 return False 240 241 def __find_param__(self, paramSetString): 242 """Find the parameters of the template. 243 244 paramSetString is the C++ string which defines the parameters set. 245 246 __find_param__ returns a list of itk classes, itkCType, and/or numbers 247 which correspond to the parameters described in paramSetString. 248 The parameters MUST have been registered before calling this method, 249 or __find_param__ will return a string and not the wanted object, and 250 will display a warning. Registration order is important. 251 252 This method is not static only to be able to display the template name 253 in the warning. 254 """ 255 # split the string in a list of parameters 256 paramStrings = [] 257 inner = 0 258 part = paramSetString.split(",") 259 for elt in part: 260 if inner == 0: 261 paramStrings.append(elt) 262 else: 263 paramStrings[-1] += "," + elt 264 inner += elt.count("<") - elt.count(">") 265 266 # convert all string parameters into classes (if possible) 267 parameters = [] 268 for param in paramStrings: 269 # the parameter need to be normalized several time below 270 # do it once here 271 param = param.strip() 272 paramNorm = normalizeName(param) 273 274 if paramNorm in itkTemplate.__templates__: 275 # the parameter is registered. 276 # just get the really class form the dictionary 277 param = itkTemplate.__templates__[paramNorm] 278 279 elif itkCType.GetCType(param): 280 # the parameter is a c type 281 # just get the itkCtype instance 282 param = itkCType.GetCType(param) 283 284 elif paramNorm.isdigit(): 285 # the parameter is a number 286 # convert the string to a number ! 287 param = int(param) 288 289 elif paramNorm == "true": 290 param = True 291 elif paramNorm == "false": 292 param = False 293 294 else: 295 # unable to convert the parameter 296 # use it without changes, but display a warning message, to 297 # incite developer to fix the problem 298 message = ( 299 "Warning: Unknown parameter '%s' in " 300 "template '%s'" % (param, self.__name__)) 301 warnings.warn(message) 302 303 parameters.append(param) 304 305 return parameters 306 307 def __getitem__(self, parameters): 308 """Return the class which corresponds to the given template parameters. 309 310 parameters can be: 311 - a single parameter (Ex: itk.Index[2]) 312 - a list of elements (Ex: itk.Image[itk.UC, 2]) 313 """ 314 315 parameters_type = type(parameters) 316 if not parameters_type is tuple and not parameters_type is list: 317 # parameters is a single element. 318 # include it in a list to manage the 2 cases in the same way 319 parameters = [parameters] 320 321 cleanParameters = [] 322 for param in parameters: 323 # In the case of itk class instance, get the class 324 name = param.__class__.__name__ 325 isclass = inspect.isclass(param) 326 if not isclass and name[:3] == 'itk' and name != "itkCType": 327 param = param.__class__ 328 329 # append the parameter to the list. If it's not a supported type, 330 # it is not in the dictionary and we will raise an exception below 331 cleanParameters.append(param) 332 333 try: 334 return(self.__template__[tuple(cleanParameters)]) 335 except: 336 self._LoadModules() 337 try: 338 return(self.__template__[tuple(cleanParameters)]) 339 except: 340 raise TemplateTypeError(self, tuple(cleanParameters)) 341 342 def __repr__(self): 343 return '<itkTemplate %s>' % self.__name__ 344 345 def __getattr__(self, attr): 346 """Support for lazy loading.""" 347 self._LoadModules() 348 return object.__getattribute__(self, attr) 349 350 351 def _LoadModules(self): 352 """Loads all the module that may have not been loaded by the lazy loading system. 353 354 If multiple modules use the same object, the lazy loading system is only going to 355 load the module in which the object belongs. The other modules will be loaded only when necessary. 356 """ 357 name=self.__name__.split('::')[-1] # Remove 'itk::' or 'itk::Function::' 358 modules = itkBase.lazy_attributes[name] 359 for module in modules: 360 # find the module's name in sys.modules, or create a new module so named 361 if sys.version_info >= (3, 4): 362 this_module = sys.modules.setdefault(module, types.ModuleType(module)) 363 else: 364 this_module = sys.modules.setdefault(module, imp.new_module(module)) 365 namespace = {} 366 if not hasattr(this_module, '__templates_loaded'): 367 itkBase.LoadModule(module, namespace) 368 369 def __dir__(self): 370 """Returns the list of the attributes available in the current template. 371 372 This loads all the modules that might be required by this template first, 373 and then returns the list of attributes. It is used when dir() is called 374 or when it tries to autocomplete attribute names. 375 """ 376 self._LoadModules() 377 378 def get_attrs(obj): 379 if not hasattr(obj, '__dict__'): 380 return [] # slots only 381 if sys.version_info >= (3, 0): 382 dict_types = (dict, types.MappingProxyType) 383 else: 384 dict_types = (dict, types.DictProxyType) 385 if not isinstance(obj.__dict__, dict_types): 386 raise TypeError("%s.__dict__ is not a dictionary" 387 "" % obj.__name__) 388 return obj.__dict__.keys() 389 390 def dir2(obj): 391 attrs = set() 392 if not hasattr(obj, '__bases__'): 393 # obj is an instance 394 if not hasattr(obj, '__class__'): 395 # slots 396 return sorted(get_attrs(obj)) 397 klass = obj.__class__ 398 attrs.update(get_attrs(klass)) 399 else: 400 # obj is a class 401 klass = obj 402 403 for cls in klass.__bases__: 404 attrs.update(get_attrs(cls)) 405 attrs.update(dir2(cls)) 406 attrs.update(get_attrs(obj)) 407 return list(attrs) 408 409 return dir2(self) 410 411 412 def __call__(self, *args, **kwargs): 413 """Deprecated procedural interface function. 414 415 Use snake case function instead. This function is now 416 merely a wrapper around the snake case function (more 417 specifically around `__internal_call__()` to avoid 418 creating a new instance twice). 419 420 Create a process object, update with the inputs and 421 attributes, and return the result. 422 423 The syntax is the same as the one used in New(). 424 425 UpdateLargestPossibleRegion() is execute and the current output, 426 or tuple of outputs if there is more than one, is returned. 427 428 For example, 429 430 outputImage = itk.MedianImageFilter(inputImage, Radius=(1,2)) 431 """ 432 import itkHelpers 433 short_name = re.sub(r'.*::', '', self.__name__) 434 snake = itkHelpers.camel_to_snake_case(short_name) 435 436 warnings.warn("WrapITK warning: itk.%s() is deprecated for procedural" 437 " interface. Use snake case function itk.%s() instead." 438 % (short_name, snake), DeprecationWarning) 439 440 filt = self.New(*args, **kwargs) 441 return filt.__internal_call__() 442 443 def New(self, *args, **kwargs): 444 """Instantiate the template with a type implied from its input. 445 446 Template type specification can be avoided by assuming that the type's 447 first template argument should have the same type as its primary input. 448 This is generally true. If it is not true, then specify the types 449 explicitly. 450 451 For example, instead of the explicit type specification:: 452 453 median = itk.MedianImageFilter[ImageType, ImageType].New() 454 median.SetInput(reader.GetOutput()) 455 456 call:: 457 458 median = itk.MedianImageFilter.New(Input=reader.GetOutput()) 459 460 or, the shortened:: 461 462 median = itk.MedianImageFilter.New(reader.GetOutput()) 463 464 or: 465 466 median = itk.MedianImageFilter.New(reader)""" 467 import itk 468 keys = self.keys() 469 cur = itk.auto_pipeline.current 470 if self.__name__ == "itk::ImageFileReader": 471 return self._NewImageReader(itk.ImageFileReader, False, 'FileName', *args, **kwargs) 472 elif self.__name__ == "itk::ImageSeriesReader": 473 # Only support `FileNames`, not `FileName`, to simplify the logic and avoid having 474 # to deal with checking if both keyword arguments are given. 475 return self._NewImageReader(itk.ImageSeriesReader, True, 'FileNames', *args, **kwargs) 476 primary_input_methods = ('Input', 'InputImage', 'Input1') 477 if 'ttype' in kwargs and keys: 478 # Convert `ttype` argument to `tuple` as filter keys are tuples. 479 # Note that the function `itk.template()` which returns the template 480 # arguments of an object returns tuples and its returned value 481 # should be usable in this context. 482 # However, it is easy for a user to pass a list (e.g. [ImageType, ImageType]) and 483 # this needs to work too. 484 ttype = tuple(kwargs.pop('ttype')) 485 # If there is not the correct number of template parameters, throw an error. 486 if len(ttype) != len(list(keys)[0]): 487 raise RuntimeError("Expected %d template parameters. %d parameters given." 488 %(len(list(keys)[0]), len(ttype))) 489 keys = [k for k in keys if k == ttype] 490 elif len(args) != 0: 491 # try to find a type suitable for the primary input provided 492 input_type = output(args[0]).__class__ 493 keys = [k for k in keys if k[0] == input_type] 494 elif set(primary_input_methods).intersection(kwargs.keys()): 495 for method in primary_input_methods: 496 if method in kwargs: 497 input_type = output(kwargs[method]).__class__ 498 keys = [k for k in keys if k[0] == input_type] 499 break 500 elif cur is not None and len(cur) != 0: 501 # try to find a type suitable for the input provided 502 input_type = output(cur).__class__ 503 keys = [k for k in keys if k[0] == input_type] 504 505 if len(keys) == 0: 506 if not input_type: 507 raise RuntimeError("""No suitable template parameter can be found. 508Please specify an input via the first argument or via one of the following 509keyword arguments: %s""" % ", ".join(primary_input_methods)) 510 else: 511 raise TemplateTypeError(self, input_type) 512 return self[list(keys)[0]].New(*args, **kwargs) 513 514 def _NewImageReader(self, TemplateReaderType, increase_dimension, primaryInputMethod, *args, **kwargs): 515 def firstIfList(arg): 516 if type(arg) in [list, tuple]: 517 return arg[0] 518 else: 519 return arg 520 inputFileName = '' 521 if len(args) != 0: 522 # try to find a type suitable for the primary input provided 523 inputFileName = firstIfList(args[0]) 524 elif primaryInputMethod in kwargs: 525 inputFileName = firstIfList(kwargs[primaryInputMethod]) 526 if not inputFileName: 527 raise RuntimeError("No FileName specified.") 528 import itk 529 if "ImageIO" in kwargs: 530 imageIO = kwargs["ImageIO"] 531 else: 532 imageIO = itk.ImageIOFactory.CreateImageIO( inputFileName, itk.ImageIOFactory.ReadMode ) 533 if not imageIO: 534 msg = "" 535 if not os.path.isfile(inputFileName): 536 msg += ("\nThe file doesn't exist. \n" + 537 "Filename = %s" % inputFileName) 538 raise RuntimeError("Could not create IO object for reading file %s" % inputFileName + msg) 539 componentTypeDic= {"float": itk.F, "double": itk.D, 540 "unsigned_char": itk.UC, "unsigned_short": itk.US, "unsigned_int": itk.UI, 541 "unsigned_long": itk.UL, "unsigned_long_long": itk.ULL, "char": itk.SC, "short": itk.SS, 542 "int": itk.SI, "long": itk.SL, "long_long": itk.SLL} 543 # Read the metadata from the image file. 544 imageIO.SetFileName( inputFileName ) 545 imageIO.ReadImageInformation() 546 dimension = imageIO.GetNumberOfDimensions() 547 # For image series, increase dimension if last dimension is not of size one. 548 if increase_dimension and imageIO.GetDimensions(dimension-1) != 1: 549 dimension += 1 550 componentAsString = imageIO.GetComponentTypeAsString(imageIO.GetComponentType()) 551 component = componentTypeDic[componentAsString] 552 pixel = imageIO.GetPixelTypeAsString(imageIO.GetPixelType()) 553 numberOfComponents = imageIO.GetNumberOfComponents() 554 PixelType = itkTemplate._pixelTypeFromIO(pixel, component, numberOfComponents) 555 ImageType = itk.Image[PixelType, dimension] 556 ReaderType = TemplateReaderType[ImageType] 557 return ReaderType.New(*args, **kwargs) 558 559 @staticmethod 560 def _pixelTypeFromIO(pixel, component, numberOfComponents): 561 import itk 562 if pixel == 'scalar': 563 PixelType = component 564 elif pixel == 'rgb': 565 PixelType = itk.RGBPixel[component] 566 elif pixel == 'rgba': 567 PixelType = itk.RGBAPixel[component] 568 elif pixel == 'offset': 569 PixelType = itk.Offset[numberOfComponents] 570 elif pixel == 'vector': 571 PixelType = itk.Vector[component, numberOfComponents] 572 elif pixel == 'point': 573 PixelType = itk.Point[component, numberOfComponents] 574 elif pixel == 'covariant_vector': 575 PixelType = itk.CovariantVector[component, numberOfComponents] 576 elif pixel == 'symmetric_second_rank_tensor': 577 PixelType = itk.SymmetricSecondRankTensor[component, numberOfComponents] 578 elif pixel == 'diffusion_tensor_3D': 579 PixelType = itk.DiffusionTensor3D[component] 580 elif pixel == 'complex': 581 PixelType = itk.complex[component] 582 elif pixel == 'fixed_array': 583 PixelType = itk.FixedArray[component, numberOfComponents] 584 elif pixel == 'matrix': 585 PixelType = itk.Matrix[component, int(sqrt(numberOfComponents)), int(sqrt(numberOfComponents))] 586 else: 587 raise RuntimeError("Unknown pixel type: %s." % pixel) 588 return PixelType 589 590 def keys(self): 591 return self.__template__.keys() 592 593 # everything after this comment is for dict interface 594 # and is a copy/paste from DictMixin 595 # only methods to edit dictionary are not there 596 def __iter__(self): 597 for k in self.keys(): 598 yield k 599 600 def __contains__(self, key): 601 return key in self 602 603 # third level takes advantage of second level definitions 604 def iteritems(self): 605 for k in self: 606 yield (k, self[k]) 607 608 def iterkeys(self): 609 return self.__iter__() 610 611 # fourth level uses definitions from lower levels 612 def itervalues(self): 613 for _, v in self.iteritems(): 614 yield v 615 616 def values(self): 617 return [v for _, v in self.iteritems()] 618 619 def items(self): 620 return list(self.iteritems()) 621 622 def get(self, key, default=None): 623 try: 624 return self[key] 625 except KeyError: 626 return default 627 628 def __len__(self): 629 return len(self.keys()) 630 631 def GetTypes(self): 632 """Helper method which prints out the available template parameters.""" 633 634 print("<itkTemplate %s>" % self.__name__) 635 print("Options:") 636 for tp in self.GetTypesAsList(): 637 print(" " + str(tp).replace("(", "[").replace(")", "]")) 638 639 def GetTypesAsList(self): 640 """Helper method which returns the available template parameters.""" 641 642 # Make a list of allowed types, and sort them 643 ctypes = [] 644 classes = [] 645 others = [] 646 647 for key_tuple in self.__template__: 648 key = str(key_tuple) 649 if "itkCType" in key: 650 ctypes.append(key) 651 elif "class" in key: 652 classes.append(key) 653 else: 654 others.append(key) 655 # Sort the lists 656 ctypes = sorted(ctypes) 657 classes = sorted(classes) 658 others = sorted(others) 659 660 return ctypes + classes + others 661 662 663# create a new New function which accepts parameters 664def New(self, *args, **kargs): 665 import itk 666 667 itk.set_inputs(self, args, kargs) 668 669 # now, try to add observer to display progress 670 if "auto_progress" in kargs.keys(): 671 if kargs["auto_progress"] in [True, 1]: 672 callback = itk.terminal_progress_callback 673 elif kargs["auto_progress"] == 2: 674 callback = itk.simple_progress_callback 675 else: 676 callback = None 677 elif itkConfig.ProgressCallback: 678 callback = itkConfig.ProgressCallback 679 else: 680 callback = None 681 682 if callback and not issubclass(self.__class__, itk.Command): 683 try: 684 name = self.__class__.__name__ 685 686 def progress(): 687 # self and callback are kept referenced with a closure 688 callback(name, self.GetProgress()) 689 690 self.AddObserver(itk.ProgressEvent(), progress) 691 except: 692 # it seems that something goes wrong... 693 # as this feature is designed for prototyping, it's not really a 694 # problem if an object doesn't have progress reporter, so adding 695 # reporter can silently fail 696 pass 697 698 if itkConfig.NotInPlace and "SetInPlace" in dir(self): 699 self.SetInPlace(False) 700 701 if itk.auto_pipeline.current is not None: 702 itk.auto_pipeline.current.connect(self) 703 704 return self 705 706 707def output(input): 708 try: 709 img = input.GetOutput() 710 except AttributeError: 711 img = input 712 return img 713 714 715def image(input): 716 warnings.warn("WrapITK warning: itk.image() is deprecated. " 717 "Use itk.output() instead.") 718 return output(input) 719