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