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
20import re
21
22# The following line defines an ascii string used for dynamically refreshing
23# the import and progress callbacks on the same terminal line.
24# See http://www.termsys.demon.co.uk/vtansi.htm
25# \033 is the C-style octal code for an escape character
26# [2000D moves the cursor back 2000 columns, this is a brute force way of
27# getting to the start of the line.
28# [K erases the end of the line
29clrLine = "\033[2000D\033[K"
30
31
32def auto_not_in_place(v=True):
33    """Force it to not run in place
34    """
35    import itkConfig
36    itkConfig.NotInPlace = v
37
38
39def auto_progress(progress_type=1):
40    """Set up auto progress report
41
42    progress_type:
43        1 or True -> auto progress be used in a terminal
44        2 -> simple auto progress (without special characters)
45        0 or False -> disable auto progress
46    """
47    import itkConfig
48
49    if progress_type is True or progress_type == 1:
50        itkConfig.ImportCallback = terminal_import_callback
51        itkConfig.ProgressCallback = terminal_progress_callback
52
53    elif progress_type == 2:
54        itkConfig.ImportCallback = simple_import_callback
55        itkConfig.ProgressCallback = simple_progress_callback
56
57    elif progress_type is False or progress_type == 0:
58        itkConfig.ImportCallback = None
59        itkConfig.ProgressCallback = None
60
61    else:
62        raise ValueError("Invalid auto progress type: " + repr(progress_type))
63
64
65def terminal_progress_callback(name, p):
66    """Display the progress of an object and clean the display once complete
67
68    This function can be used with itkConfig.ProgressCallback
69    """
70    import sys
71    print(clrLine + "%s: %f" % (name, p), file=sys.stderr, end="")
72    if p == 1:
73        print(clrLine, file=sys.stderr, end="")
74
75
76def terminal_import_callback(name, p):
77    """Display the loading of a module and clean the display once complete
78
79    This function can be used with itkConfig.ImportCallback
80    """
81    import sys
82    print(clrLine + "Loading %s... " % name, file=sys.stderr, end="")
83    if p == 1:
84        print(clrLine, file=sys.stderr, end="")
85
86
87def simple_import_callback(name, p):
88    """Print a message when a module is loading
89
90    This function can be used with itkConfig.ImportCallback
91    """
92    import sys
93    if p == 0:
94        print("Loading %s... " % name, file=sys.stderr, end="")
95    elif p == 1:
96        print("done", file=sys.stderr)
97
98
99def simple_progress_callback(name, p):
100    """Print a message when an object is running
101
102    This function can be used with itkConfig.ProgressCallback
103    """
104    import sys
105    if p == 0:
106        print("Running %s... " % name, file=sys.stderr, end="")
107    elif p == 1:
108        print("done", file=sys.stderr)
109
110
111def force_load():
112    """force itk to load all the submodules"""
113    import itk
114    for k in dir(itk):
115        getattr(itk, k)
116
117
118import sys
119
120
121def echo(object, f=sys.stderr):
122    """Print an object is f
123
124    If the object has a method Print(), this method is used.
125    repr(object) is used otherwise
126    """
127    print(f, object)
128del sys
129
130
131def size(image_or_filter):
132    """Return the size of an image, or of the output image of a filter
133
134    This method take care of updating the needed informations
135    """
136    # we don't need the entire output, only its size
137    image_or_filter.UpdateOutputInformation()
138    img = output(image_or_filter)
139    return img.GetLargestPossibleRegion().GetSize()
140
141
142def physical_size(image_or_filter):
143    """Return the physical size of an image, or of the output image of a filter
144
145    This method take care of updating the needed informations
146    """
147    # required because range is overloaded in this module
148    import sys
149    if sys.version_info >= (3, 0):
150      from builtins import range
151    else:
152      from __builtin__ import range
153    spacing_ = spacing(image_or_filter)
154    size_ = size(image_or_filter)
155    result = []
156    for i in range(0, spacing_.Size()):
157        result.append(spacing_.GetElement(i) * size_.GetElement(i))
158    return result
159
160
161def spacing(image_or_filter):
162    """Return the spacing of an image, or of the output image of a filter
163
164    This method take care of updating the needed informations
165    """
166    # we don't need the entire output, only its size
167    image_or_filter.UpdateOutputInformation()
168    img = output(image_or_filter)
169    return img.GetSpacing()
170
171
172def origin(image_or_filter):
173    """Return the origin of an image, or of the output image of a filter
174
175    This method take care of updating the needed informations
176    """
177    # we don't need the entire output, only its size
178    image_or_filter.UpdateOutputInformation()
179    img = output(image_or_filter)
180    return img.GetOrigin()
181
182
183def index(image_or_filter):
184    """Return the index of an image, or of the output image of a filter
185
186    This method take care of updating the needed informations
187    """
188    # we don't need the entire output, only its size
189    image_or_filter.UpdateOutputInformation()
190    img = output(image_or_filter)
191    return img.GetLargestPossibleRegion().GetIndex()
192
193
194def region(image_or_filter):
195    """Return the region of an image, or of the output image of a filter
196
197    This method take care of updating the needed informations
198    """
199    # we don't need the entire output, only its size
200    image_or_filter.UpdateOutputInformation()
201    img = output(image_or_filter)
202    return img.GetLargestPossibleRegion()
203
204HAVE_NUMPY = True
205try:
206    import numpy
207except ImportError:
208    HAVE_NUMPY = False
209
210def _get_itk_pixelid(numpy_array_type):
211    """Returns a ITK PixelID given a numpy array."""
212
213    if not HAVE_NUMPY:
214        raise ImportError('Numpy not available.')
215    import itk
216    # This is a Mapping from numpy array types to itk pixel types.
217    _np_itk = {numpy.uint8:itk.UC,
218                numpy.uint16:itk.US,
219                numpy.uint32:itk.UI,
220                numpy.uint64:itk.UL,
221                numpy.int8:itk.SC,
222                numpy.int16:itk.SS,
223                numpy.int32:itk.SI,
224                numpy.int64:itk.SL,
225                numpy.float32:itk.F,
226                numpy.float64:itk.D,
227                numpy.complex64:itk.complex[itk.F],
228                numpy.complex128:itk.complex[itk.D]
229                }
230    try:
231        return _np_itk[numpy_array_type.dtype.type]
232    except KeyError as e:
233        for key in _np_itk:
234            if numpy.issubdtype(numpy_array_type.dtype.type, key):
235                return _np_itk[key]
236            raise e
237
238def _GetArrayFromImage(image_or_filter, function, keep_axes, update):
239    """Get an Array with the content of the image buffer
240    """
241    # Check for numpy
242    if not HAVE_NUMPY:
243        raise ImportError('Numpy not available.')
244    # Finds the image type
245    import itk
246    keys = [k for k in itk.PyBuffer.keys() if k[0] == output(image_or_filter).__class__]
247    if len(keys ) == 0:
248        raise RuntimeError("No suitable template parameter can be found.")
249    ImageType = keys[0]
250    # Create a numpy array of the type of the input image
251    templatedFunction = getattr(itk.PyBuffer[keys[0]], function)
252    return templatedFunction(output(image_or_filter), keep_axes, update)
253
254def GetArrayFromImage(image_or_filter, keep_axes=False, update=True):
255    """Get an array with the content of the image buffer
256    """
257    return _GetArrayFromImage(image_or_filter, "GetArrayFromImage", keep_axes, update)
258
259array_from_image = GetArrayFromImage
260
261def GetArrayViewFromImage(image_or_filter, keep_axes=False, update=True):
262    """Get an array view with the content of the image buffer
263    """
264    return _GetArrayFromImage(image_or_filter, "GetArrayViewFromImage", keep_axes, update)
265
266array_view_from_image = GetArrayViewFromImage
267
268def _GetImageFromArray(arr, function, is_vector):
269    """Get an ITK image from a Python array.
270    """
271    if not HAVE_NUMPY:
272        raise ImportError('Numpy not available.')
273    import itk
274    PixelType = _get_itk_pixelid(arr)
275    if is_vector:
276        Dimension = arr.ndim - 1
277        if arr.flags['C_CONTIGUOUS']:
278            VectorDimension = arr.shape[-1]
279        else:
280            VectorDimension = arr.shape[0]
281        if PixelType == itk.UC:
282            if VectorDimension == 3:
283                ImageType = itk.Image[ itk.RGBPixel[itk.UC], Dimension ]
284            elif VectorDimension == 4:
285                ImageType = itk.Image[ itk.RGBAPixel[itk.UC], Dimension ]
286        else:
287            ImageType = itk.Image[ itk.Vector[PixelType, VectorDimension] , Dimension]
288    else:
289        Dimension = arr.ndim
290        ImageType = itk.Image[PixelType, Dimension]
291    templatedFunction = getattr(itk.PyBuffer[ImageType], function)
292    return templatedFunction(arr, is_vector)
293
294def GetImageFromArray(arr, is_vector=False):
295    """Get an ITK image from a Python array.
296    """
297    return _GetImageFromArray(arr, "GetImageFromArray", is_vector)
298
299image_from_array = GetImageFromArray
300
301def GetImageViewFromArray(arr, is_vector=False):
302    """Get an ITK image view from a Python array.
303    """
304    return _GetImageFromArray(arr, "GetImageViewFromArray", is_vector)
305
306image_view_from_array = GetImageFromArray
307
308def _GetArrayFromVnlObject(vnl_object, function):
309    """Get an array with the content of vnl_object
310    """
311    # Check for numpy
312    if not HAVE_NUMPY:
313        raise ImportError('Numpy not available.')
314    # Finds the vnl object type
315    import itk
316    PixelType = itk.template(vnl_object)[1][0]
317    keys = [k for k in itk.PyVnl.keys() if k[0] == PixelType]
318    if len(keys ) == 0:
319        raise RuntimeError("No suitable template parameter can be found.")
320    # Create a numpy array of the type of the vnl object
321    templatedFunction = getattr(itk.PyVnl[keys[0]], function)
322    return templatedFunction(vnl_object)
323
324def GetArrayFromVnlVector(vnl_vector):
325    """Get an array with the content of vnl_vector
326    """
327    return _GetArrayFromVnlObject(vnl_vector, "GetArrayFromVnlVector")
328
329array_from_vnl_vector = GetArrayFromVnlVector
330
331def GetArrayViewFromVnlVector(vnl_vector):
332    """Get an array view of vnl_vector
333    """
334    return _GetArrayFromVnlObject(vnl_vector, "GetArrayViewFromVnlVector")
335
336array_view_from_vnl_vector = GetArrayFromVnlVector
337
338def GetArrayFromVnlMatrix(vnl_matrix):
339    """Get an array with the content of vnl_matrix
340    """
341    return _GetArrayFromVnlObject(vnl_matrix, "GetArrayFromVnlMatrix")
342
343def GetArrayViewFromVnlMatrix(vnl_matrix):
344    """Get an array view of vnl_matrix
345    """
346    return _GetArrayFromVnlObject(vnl_matrix, "GetArrayViewFromVnlMatrix")
347
348array_from_vnl_matrix = GetArrayFromVnlMatrix
349
350def _GetVnlObjectFromArray(arr, function):
351    """Get a vnl object from a Python array.
352    """
353    if not HAVE_NUMPY:
354        raise ImportError('Numpy not available.')
355    import itk
356    PixelType = _get_itk_pixelid(arr)
357    templatedFunction = getattr(itk.PyVnl[PixelType], function)
358    return templatedFunction(arr)
359
360def GetVnlVectorFromArray(arr):
361    """Get a vnl vector from a Python array.
362    """
363    return _GetVnlObjectFromArray(arr, "GetVnlVectorFromArray")
364
365vnl_vector_from_array = GetVnlVectorFromArray
366
367def GetVnlMatrixFromArray(arr):
368    """Get a vnl matrix from a Python array.
369    """
370    return _GetVnlObjectFromArray(arr, "GetVnlMatrixFromArray")
371
372vnl_matrix_from_array = GetVnlMatrixFromArray
373
374def GetArrayFromMatrix(itk_matrix):
375    return GetArrayFromVnlMatrix(itk_matrix.GetVnlMatrix().as_matrix())
376
377array_from_matrix = GetArrayFromMatrix
378
379def GetMatrixFromArray(arr):
380    import itk
381    vnl_matrix = GetVnlMatrixFromArray(arr)
382    dims = arr.shape
383    PixelType = _get_itk_pixelid(arr)
384    m = itk.Matrix[PixelType, dims[0], dims[1]](vnl_matrix)
385    return m
386
387matrix_from_array = GetMatrixFromArray
388
389# return an image
390from itkTemplate import image, output
391
392
393def template(cl):
394    """Return the template of a class (or of the class of an object) and
395    its parameters
396
397    template() returns a tuple with 2 elements:
398        - the first one is the itkTemplate object
399        - the second is a tuple containing the template parameters
400    """
401    from itkTemplate import itkTemplate
402    return itkTemplate.__class_to_template__[class_(cl)]
403
404
405def ctype(s):
406    """Return the c type corresponding to the string passed in parameter
407
408    The string can contain some extra spaces.
409    see also itkCType
410    """
411    from itkTypes import itkCType
412
413    ret = itkCType.GetCType(" ".join(s.split()))
414    if ret is None:
415        raise KeyError("Unrecognized C type '%s'" % s)
416    return ret
417
418
419def class_(obj):
420    """Return a class from an object
421
422    Often in itk, the __class__ is not what the user is expecting.
423    class_() should do a better job
424    """
425    import inspect
426    if inspect.isclass(obj):
427        # obj is already a class !
428        return obj
429    else:
430        return obj.__class__
431
432def python_type(obj):
433    """Returns the Python type name of an object
434
435    The Python name corresponding to the given instantiated object is printed.
436    This includes both the Python name and the parameters of the object. A user
437    can copy and paste the printed value to instantiate a new object of the
438    same type."""
439    import itkTemplate
440    from itkTypes import itkCType
441
442    def in_itk(name):
443        import itk
444        # Remove "itk::" and "std::" from template name.
445        # Only happens for ITK objects.
446        shortname = name.split('::')[-1]
447        shortname = shortname.split('itk')[-1]
448
449        namespace = itk
450        # A type cannot be part of ITK if its name was not modified above. This
451        # check avoids having an input of type `list` and return `itk.list` that
452        # also exists.
453        likely_itk = (shortname != name or name[:3] == 'vnl')
454        if likely_itk and hasattr(namespace, shortname):
455            return namespace.__name__ + '.' + shortname  # Prepend name with 'itk.'
456        else:
457            return name
458
459    def recursive(obj, level):
460        try:
461            T, P = template(obj)
462            name = in_itk(T.__name__)
463            parameters = []
464            for t in P:
465                parameters.append(recursive(t, level+1))
466            return name + "[" + ",".join(parameters) + "]"
467        except KeyError:
468            if isinstance(obj, itkCType):  # Handles CTypes differently
469                return 'itk.' + obj.short_name
470            elif hasattr(obj, "__name__"):
471                # This should be where most ITK types end up.
472                return in_itk(obj.__name__)
473            elif (not isinstance(obj, type)
474                  and type(obj) != itkTemplate.itkTemplate and level != 0):
475                # obj should actually be considered a value, not a type,
476                # or it is already an itkTemplate type.
477                # A value can be an integer that is a template parameter.
478                # This does not happen at the first level of the recursion
479                # as it is not possible that this object would be a template
480                # parameter. Checking the level `0` allows e.g. to find the
481                # type of an object that is a `list` or an `int`.
482                return str(obj)
483            else:
484                return in_itk(type(obj).__name__)
485    return recursive(obj, 0)
486
487
488def range(image_or_filter):
489    """Return the range of values in a image of in the output image of a filter
490
491    The minimum and maximum values are returned in a tuple: (min, max)
492    range() take care of updating the pipeline
493    """
494    import itk
495    img = output(image_or_filter)
496    img.UpdateOutputInformation()
497    img.Update()
498    # don't put that calculator in the automatic pipeline
499    tmp_auto_pipeline = auto_pipeline.current
500    auto_pipeline.current = None
501    comp = itk.MinimumMaximumImageCalculator[img].New(Image=img)
502    auto_pipeline.current = tmp_auto_pipeline
503    comp.Compute()
504    return (comp.GetMinimum(), comp.GetMaximum())
505
506
507def imwrite(image_or_filter, filename, compression=False):
508    """Write a image or the output image of a filter to a file.
509
510    The writer is instantiated with the image type of the image in
511    parameter (or, again, with the output image of the filter in parameter).
512    """
513    import itk
514    img = output(image_or_filter)
515    img.UpdateOutputInformation()
516    # don't put that writer in the automatic pipeline
517    tmp_auto_pipeline = auto_pipeline.current
518    auto_pipeline.current = None
519    writer = itk.ImageFileWriter[img].New(
520        Input=img,
521        FileName=filename,
522        UseCompression=compression)
523    auto_pipeline.current = tmp_auto_pipeline
524    writer.Update()
525
526def imread(filename, pixel_type=None, fallback_only=False):
527    """Read an image from a file or series of files and return an itk.Image.
528
529    The reader is instantiated with the image type of the image file if
530    `pixel_type` is not provided (default). The dimension of the image is
531    automatically found. If the given filename is a list or a tuple, the
532    reader will use an itk.ImageSeriesReader object to read the files.
533
534    If `fallback_only` is set to `True`, `imread()` will first try to
535    automatically deduce the image pixel_type, and only use the given
536    `pixel_type` if the automatic deduction fail. Failures typically
537    happen if the pixel type is not supported (e.g. it is not currently
538    wrapped).
539    """
540    import itk
541    if fallback_only == True:
542        if pixel_type is None:
543            raise Exception("`pixel_type` must be set when using `fallback_only`"
544                " option")
545        try:
546            return imread(filename)
547        except KeyError:
548            pass
549    if type(filename) in [list, tuple]:
550        TemplateReaderType=itk.ImageSeriesReader
551        io_filename=filename[0]
552        increase_dimension=True
553        kwargs={'FileNames':filename}
554    else:
555        TemplateReaderType=itk.ImageFileReader
556        io_filename=filename
557        increase_dimension=False
558        kwargs={'FileName':filename}
559    if pixel_type:
560        imageIO = itk.ImageIOFactory.CreateImageIO(io_filename, itk.ImageIOFactory.ReadMode)
561        if not imageIO:
562            raise RuntimeError("No ImageIO is registered to handle the given file.")
563        imageIO.SetFileName(io_filename)
564        imageIO.ReadImageInformation()
565        dimension = imageIO.GetNumberOfDimensions()
566        # Increase dimension if last dimension is not of size one.
567        if increase_dimension and imageIO.GetDimensions(dimension-1) != 1:
568            dimension += 1
569        ImageType=itk.Image[pixel_type,dimension]
570        reader = TemplateReaderType[ImageType].New(**kwargs)
571    else:
572        reader = TemplateReaderType.New(**kwargs)
573    reader.Update()
574    return reader.GetOutput()
575
576def search(s, case_sensitive=False):  # , fuzzy=True):
577    """Search for a class name in the itk module.
578    """
579    s = s.replace(" ", "")
580    if not case_sensitive:
581        s = s.lower()
582    import itk
583    names = sorted(dir(itk))
584    # exact match first
585    if case_sensitive:
586        res = [n for n in names if s == n]
587    else:
588        res = [n for n in names if s == n.lower()]
589    # then exact match inside the name
590    if case_sensitive:
591        res += [n for n in names if s in n and s != n]
592    else:
593        res += [n for n in names if s in n.lower() and s != n.lower()]
594#     if fuzzy:
595#         try:
596# everything now requires editdist
597#             import editdist
598#             if case_sensitive:
599#                 res.sort(key=lambda x: editdist.distance(x, s))
600#             else:
601#                 res.sort(key=lambda x: (editdist.distance(x.lower(), s), x))
602#         except:
603#             pass
604    return res
605
606
607# Helpers for set_inputs snake case to CamelCase keyword argument conversion
608_snake_underscore_re = re.compile('(_)([a-z0-9A-Z])')
609def _underscore_upper(matchobj):
610    return matchobj.group(2).upper()
611def _snake_to_camel(keyword):
612    camel = keyword[0].upper()
613    if _snake_underscore_re.search(keyword[1:]):
614        return camel + _snake_underscore_re.sub(_underscore_upper, keyword[1:])
615    return camel + keyword[1:]
616
617def set_inputs(new_itk_object, args=[], kargs={}):
618    """Set the inputs of the given objects, according to the non named or the
619    named parameters in args and kargs
620
621    This function tries to assign all the non named parameters in the input of
622    the new_itk_object
623    - the first non named parameter in the first input, etc.
624
625    The named parameters are used by calling the method with the same name
626    prefixed by 'Set'.
627    set_inputs( obj, kargs={'Threshold': 10} ) calls obj.SetThreshold(10)
628
629    This is the function use in the enhanced New() method to manage the inputs.
630    It can be used to produce a similar behavior:
631
632    def SetInputs(self, *args, **kargs):
633        import itk
634        itk.set_inputs(self, *args, **kargs)
635    """
636    # try to get the images from the filters in args
637    args = [output(arg) for arg in args]
638
639    # args without name are filter used to set input image
640    #
641    # count SetInput calls to call SetInput, SetInput2, SetInput3, ...
642    # useful with filter which take 2 input (or more) like SubtractImageFiler
643    # Ex: subtract image2.png to image1.png and save the result in result.png
644    # r1 = itk.ImageFileReader.US2.New(FileName='image1.png')
645    # r2 = itk.ImageFileReader.US2.New(FileName='image2.png')
646    # s = itk.SubtractImageFilter.US2US2US2.New(r1, r2)
647    # itk.ImageFileWriter.US2.New(s, FileName='result.png').Update()
648    try:
649        for setInputNb, arg in enumerate(args):
650            methodName = 'SetInput%i' % (setInputNb + 1)
651            if methodName in dir(new_itk_object):
652                # first try to use methods called SetInput1, SetInput2, ...
653                # those method should have more chances to work in case of
654                # multiple input types
655                getattr(new_itk_object, methodName)(arg)
656            else:
657                # no method called SetInput?
658                # try with the standard SetInput(nb, input)
659                new_itk_object.SetInput(setInputNb, arg)
660    except TypeError as e:
661        # the exception have (at least) to possible reasons:
662        # + the filter don't take the input number as first argument
663        # + arg is an object of wrong type
664        #
665        # if it's not the first input, re-raise the exception
666        if setInputNb != 0:
667            raise e
668        # it's the first input, try to use the SetInput() method without input
669        # number
670        new_itk_object.SetInput(args[0])
671        # but raise an exception if there is more than 1 argument
672        if len(args) > 1:
673            raise TypeError('Object accept only 1 input.')
674    except AttributeError:
675        # There is no SetInput() method, try SetImage
676        # but before, check the number of inputs
677        if len(args) > 1:
678            raise TypeError('Object accept only 1 input.')
679        methodList = ['SetImage', 'SetInputImage']
680        methodName = None
681        for m in methodList:
682            if m in dir(new_itk_object):
683                methodName = m
684        if methodName:
685            getattr(new_itk_object, methodName)(args[0])
686        else:
687            raise AttributeError('No method found to set the input.')
688
689    # named args : name is the function name, value is argument(s)
690    for attribName, value in kargs.items():
691        # use Set as prefix. It allow to use a shorter and more intuitive
692        # call (Ex: itk.ImageFileReader.UC2.New(FileName='image.png')) than
693        # with the full name
694        # (Ex: itk.ImageFileReader.UC2.New(SetFileName='image.png'))
695        if attribName not in ["auto_progress", "template_parameters"]:
696            if attribName.islower():
697                attribName = _snake_to_camel(attribName)
698            attrib = getattr(new_itk_object, 'Set' + attribName)
699
700           # Do not use try-except mechanism as this leads to
701            # segfaults. Instead limit the number of types that are
702            # tested. The list of tested type could maybe be replaced by
703            # a test that would check for iterables.
704            if type(value) in [list, tuple]:
705                try:
706                    output_value = [output(x) for x in value]
707                    attrib(*output_value)
708                except:
709                    attrib(output(value))
710            else:
711                attrib(output(value))
712
713class templated_class:
714
715    """This class is used to mimic the behavior of the templated C++ classes.
716
717    It is used this way:
718
719    class CustomClass:
720        # class definition here
721    CustomClass = templated_class(CustomClass)
722
723    customObject = CustomClass[template, parameters].New()
724
725    The template parameters are passed to the custom class constructor as a
726    named parameter 'template_parameters' in a tuple.
727
728    The custom class may implement a static method
729    check_template_parameters(parameters) which should raise an exception if
730    the template parameters provided are not suitable to instantiate the custom
731    class.
732    """
733
734    def __init__(self, cls):
735        """cls is the custom class
736        """
737        self.__cls__ = cls
738        self.__templates__ = {}
739
740    def New(self, *args, **kargs):
741        """Use the parameters to infer the types of the template parameters.
742        """
743        # extract the types from the arguments to instantiate the class
744        import itk
745        types = tuple(itk.class_(o) for o in args)
746        return self[types].New(*args, **kargs)
747
748    def __getitem__(self, template_parameters):
749        """Return a pair class-template parameters ready to be instantiated.
750
751        The template parameters may be validated if the custom class provide
752        the static method check_template_parameters(parameters).
753        """
754        if not isinstance(template_parameters, tuple):
755            template_parameters = (template_parameters,)
756        return (
757            templated_class.__templated_class_and_parameters__(
758                self,
759                template_parameters)
760        )
761
762    def check_template_parameters(self, template_parameters):
763        """Check the template parameters passed in parameter.
764        """
765        # this method is there mainly to make possible to reuse it in the
766        # custom class constructor after having used templated_class().
767        # Without that, the following example doesn't work:
768        #
769        # class CustomClass:
770        #     def __init__(self, *args, **kargs):
771        #         template_parameters = kargs["template_parameters"]
772        #         CustomClass.check_template_parameters(template_parameters)
773        # other init stuff
774        #     def check_template_parameters(template_parameters):
775        # check, really
776        #         pass
777        #    CustomClass = templated_class(CustomClass)
778        #
779        self.__cls__.check_template_parameters(template_parameters)
780
781    def add_template(self, name, params):
782        if not isinstance(params, list) and not isinstance(params, tuple):
783            params = (params,)
784        params = tuple(params)
785        val = self[params]
786        self.__templates__[params] = val
787        setattr(self, name, val)
788
789    def add_image_templates(self, *args):
790        import itk
791        if args == []:
792            return
793        combinations = [[t] for t in args[0]]
794        for types in args[1:]:
795            temp = []
796            for t in types:
797                for c in combinations:
798                    temp.append(c + [t])
799            combinations = temp
800        for d in itk.DIMS:
801            for c in combinations:
802                parameters = []
803                name = ""
804                for t in c:
805                    parameters.append(itk.Image[t, d])
806                    name += "I" + t.short_name + str(d)
807                self.add_template(name, tuple(parameters))
808
809    class __templated_class_and_parameters__:
810
811        """Inner class used to store the pair class-template parameters ready
812        to instantiate.
813        """
814
815        def __init__(self, templated_class, template_parameters):
816            self.__templated_class__ = templated_class
817            self.__template_parameters__ = template_parameters
818            if "check_template_parameters" in dir(templated_class.__cls__):
819                templated_class.__cls__.check_template_parameters(
820                    template_parameters)
821
822        def New(self, *args, **kargs):
823            """A New() method to mimic the ITK default behavior, even if the
824            class doesn't provide any New() method.
825            """
826            kargs["template_parameters"] = self.__template_parameters__
827            if "New" in dir(self.__templated_class__.__cls__):
828                obj = self.__templated_class__.__cls__.New(*args, **kargs)
829            else:
830                obj = self.__templated_class__.__cls__(*args, **kargs)
831            setattr(
832                obj,
833                "__template_parameters__",
834                self.__template_parameters__)
835            setattr(obj, "__templated_class__", self.__templated_class__)
836            return obj
837
838        def __call__(self, *args, **kargs):
839            return self.New(*args, **kargs)
840
841    def keys(self):
842        return self.__templates__.keys()
843
844    # everything after this comment is for dict interface
845    # and is a copy/paste from DictMixin
846    # only methods to edit dictionary are not there
847    def __iter__(self):
848        for k in self.keys():
849            yield k
850
851    def has_key(self, key):
852        try:
853            value = self[key]
854        except KeyError:
855            return False
856        return True
857
858    def __contains__(self, key):
859        return key in self
860
861    # third level takes advantage of second level definitions
862    def iteritems(self):
863        for k in self:
864            yield (k, self[k])
865
866    def iterkeys(self):
867        return self.__iter__()
868
869    # fourth level uses definitions from lower levels
870    def itervalues(self):
871        for _, v in self.iteritems():
872            yield v
873
874    def values(self):
875        return [v for _, v in self.iteritems()]
876
877    def items(self):
878        return list(self.iteritems())
879
880    def get(self, key, default=None):
881        try:
882            return self[key]
883        except KeyError:
884            return default
885
886    def __len__(self):
887        return len(self.keys())
888
889
890class pipeline:
891
892    """A convenient class to store the reference to the filters of a pipeline
893
894    With this class, a method can create a pipeline of several filters and
895    return it without losing the references to the filters in this pipeline.
896    The pipeline object act almost like a filter (it has a GetOutput() method)
897    and thus can be simply integrated in another pipeline.
898    """
899
900    def __init__(self, *args, **kargs):
901        self.clear()
902        self.input = None
903        set_inputs(self, args, kargs)
904
905    def connect(self, filter):
906        """Connect a new filter to the pipeline
907
908        The output of the first filter will be used as the input of this
909        one and the filter passed as parameter will be added to the list
910        """
911        if self.GetOutput() is not None:
912            set_inputs(filter, [self.GetOutput()])
913        self.append(filter)
914
915    def append(self, filter):
916        """Add a new filter to the pipeline
917
918        The new filter will not be connected. The user must connect it.
919        """
920        self.filters.append(filter)
921
922    def clear(self):
923        """Clear the filter list
924        """
925        self.filters = []
926
927    def GetOutput(self, index=0):
928        """Return the output of the pipeline
929
930        If another output is needed, use
931        pipeline.filters[-1].GetAnotherOutput() instead of this method,
932        subclass pipeline to implement another GetOutput() method, or use
933        expose()
934        """
935        if len(self.filters) == 0:
936            return self.GetInput()
937        else:
938            filter = self.filters[-1]
939            if hasattr(filter, "__getitem__"):
940                return filter[index]
941            try:
942                return filter.GetOutput(index)
943            except:
944                if index == 0:
945                    return filter.GetOutput()
946                else:
947                    raise ValueError("Index can only be 0 on that object")
948
949    def GetNumberOfOutputs(self):
950        """Return the number of outputs
951        """
952        if len(self.filters) == 0:
953            return 1
954        else:
955            return self.filters[-1].GetNumberOfOutputs()
956
957    def SetInput(self, input):
958        """Set the input of the pipeline
959        """
960        if len(self.filters) != 0:
961            set_inputs(self.filters[0], [input])
962        self.input = input
963
964    def GetInput(self):
965        """Get the input of the pipeline
966        """
967        return self.input
968
969    def Update(self):
970        """Update the pipeline
971        """
972        if len(self.filters) > 0:
973            return self.filters[-1].Update()
974
975    def UpdateLargestPossibleRegion(self):
976        """Update the pipeline
977        """
978        if len(self.filters) > 0:
979            return self.filters[-1].UpdateLargestPossibleRegion()
980
981    def UpdateOutputInformation(self):
982        if "UpdateOutputInformation" in dir(self.filters[-1]):
983            self.filters[-1].UpdateOutputInformation()
984        else:
985            self.Update()
986
987    def __len__(self):
988        return self.GetNumberOfOutputs()
989
990    def __getitem__(self, item):
991        return self.GetOutput(item)
992
993    def __call__(self, *args, **kargs):
994        set_inputs(self, args, kargs)
995        self.UpdateLargestPossibleRegion()
996        return self
997
998    def expose(self, name, new_name=None, position=-1):
999        """Expose an attribute from a filter of the minipeline.
1000
1001        Once called, the pipeline instance has a new Set/Get set of methods to
1002        access directly the corresponding method of one of the filter of the
1003        pipeline.
1004        Ex: p.expose( "Radius" )
1005                p.SetRadius( 5 )
1006                p.GetRadius( 5 )
1007        By default, the attribute usable on the pipeline instance has the same
1008        name than the one of the filter, but it can be changed by providing a
1009        value to new_name.
1010        The last filter of the pipeline is used by default, but another one may
1011        be used by giving its position.
1012        Ex: p.expose("Radius", "SmoothingNeighborhood", 2)
1013            p.GetSmoothingNeighborhood()
1014        """
1015        if new_name is None:
1016            new_name = name
1017        src = self.filters[position]
1018        ok = False
1019        set_name = "Set" + name
1020        if set_name in dir(src):
1021            setattr(self, "Set" + new_name, getattr(src, set_name))
1022            ok = True
1023        get_name = "Get" + name
1024        if get_name in dir(src):
1025            setattr(self, "Get" + new_name, getattr(src, get_name))
1026            ok = True
1027        if not ok:
1028            raise RuntimeError(
1029                "No attribute %s at position %s." %
1030                (name, position))
1031
1032
1033class auto_pipeline(pipeline):
1034    current = None
1035
1036    def __init__(self, *args, **kargs):
1037        pipeline.__init__(self, *args, **kargs)
1038        self.Start()
1039
1040    def Start(self):
1041        auto_pipeline.current = self
1042
1043    def Stop(self):
1044        auto_pipeline.current = None
1045
1046
1047def down_cast(obj):
1048    """Down cast an itkLightObject (or a object of a subclass) to its most
1049    specialized type.
1050    """
1051    import itk
1052    import itkTemplate
1053    className = obj.GetNameOfClass()
1054    t = getattr(itk, className)
1055    if isinstance(t, itkTemplate.itkTemplate):
1056        for c in t.values():
1057            try:
1058                return c.cast(obj)
1059            except:
1060                # fail silently for now
1061                pass
1062        raise RuntimeError(
1063            "Can't downcast to a specialization of %s" %
1064            className)
1065    else:
1066        return t.cast(obj)
1067
1068
1069def attribute_list(i, name):
1070    """Returns a list of the specified attributes for the objects in the image.
1071
1072    i: the input LabelImage
1073    name: the attribute name
1074    """
1075    import itk
1076    i = itk.output(i)
1077    relabel = itk.StatisticsRelabelLabelMapFilter[i].New(
1078        i,
1079        Attribute=name,
1080        ReverseOrdering=True,
1081        InPlace=False)
1082    relabel.UpdateLargestPossibleRegion()
1083    r = relabel.GetOutput()
1084    l = []
1085    for i in range(1, r.GetNumberOfLabelObjects() + 1):
1086        l.append(r.GetLabelObject(i).__getattribute__("Get" + name)())
1087    return l
1088
1089
1090def attributes_list(i, names):
1091    """Returns a list of the specified attributes for the objects in the image.
1092
1093    i: the input LabelImage
1094    name: the attribute name
1095    """
1096    import itk
1097    i = itk.output(i)
1098    relabel = itk.StatisticsRelabelLabelMapFilter[i].New(
1099        i,
1100        Attribute=names[0],
1101        ReverseOrdering=True,
1102        InPlace=False)
1103    relabel.UpdateLargestPossibleRegion()
1104    r = relabel.GetOutput()
1105    l = []
1106    for i in range(1, r.GetNumberOfLabelObjects() + 1):
1107        attrs = []
1108        for name in names:
1109            attrs.append(r.GetLabelObject(i).__getattribute__("Get" + name)())
1110        l.append(tuple(attrs))
1111    return l
1112
1113
1114def attribute_dict(i, name):
1115    """Returns a dict with the attribute values in keys and a list of the
1116    corresponding objects in value
1117
1118    i: the input LabelImage
1119    name: the name of the attribute
1120    """
1121    import itk
1122    i = itk.output(i)
1123    relabel = itk.StatisticsRelabelLabelMapFilter[i].New(
1124        i,
1125        Attribute=name,
1126        ReverseOrdering=True,
1127        InPlace=False)
1128    relabel.UpdateLargestPossibleRegion()
1129    r = relabel.GetOutput()
1130    d = {}
1131    for i in range(1, r.GetNumberOfLabelObjects() + 1):
1132        lo = r.GetLabelObject(i)
1133        v = lo.__getattribute__("Get" + name)()
1134        l = d.get(v, [])
1135        l.append(lo)
1136        d[v] = l
1137    return d
1138
1139
1140def number_of_objects(i):
1141    """Returns the number of objets in the image.
1142
1143    i: the input LabelImage
1144    """
1145    import itk
1146    i.UpdateLargestPossibleRegion()
1147    i = itk.output(i)
1148    return i.GetNumberOfLabelObjects()
1149
1150
1151def ipython_kw_matches(text):
1152    """Match named ITK object's named parameters"""
1153    import IPython
1154    import itk
1155    import re
1156    import inspect
1157    import itkTemplate
1158    regexp = re.compile(r'''
1159                    '.*?' |  # single quoted strings or
1160                    ".*?" |  # double quoted strings or
1161                    \w+     |  # identifier
1162                    \S  # other characters
1163                    ''', re.VERBOSE | re.DOTALL)
1164    ip = IPython.get_ipython()
1165    if "." in text:  # a parameter cannot be dotted
1166        return []
1167    # 1. Find the nearest identifier that comes before an unclosed
1168    # parenthesis e.g. for "foo (1+bar(x), pa", the candidate is "foo".
1169    if ip.Completer.readline:
1170        textUntilCursor = ip.Completer.readline.get_line_buffer()[:ip.Completer.readline.get_endidx()]
1171    else:
1172        # IPython >= 5.0.0, which is based on the Python Prompt Toolkit
1173        textUntilCursor = ip.Completer.text_until_cursor
1174
1175    tokens = regexp.findall(textUntilCursor)
1176    tokens.reverse()
1177    iterTokens = iter(tokens)
1178    openPar = 0
1179    for token in iterTokens:
1180        if token == ')':
1181            openPar -= 1
1182        elif token == '(':
1183            openPar += 1
1184            if openPar > 0:
1185                # found the last unclosed parenthesis
1186                break
1187    else:
1188        return []
1189    # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
1190    ids = []
1191    isId = re.compile(r'\w+$').match
1192    while True:
1193        try:
1194            ids.append(iterTokens.next())
1195            if not isId(ids[-1]):
1196                ids.pop()
1197                break
1198            if not iterTokens.next() == '.':
1199                break
1200        except StopIteration:
1201            break
1202    # lookup the candidate callable matches either using global_matches
1203    # or attr_matches for dotted names
1204    if len(ids) == 1:
1205        callableMatches = ip.Completer.global_matches(ids[0])
1206    else:
1207        callableMatches = ip.Completer.attr_matches('.'.join(ids[::-1]))
1208    argMatches = []
1209    for callableMatch in callableMatches:
1210        # drop the .New at this end, so we can search in the class members
1211        if callableMatch.endswith(".New"):
1212            callableMatch = callableMatch[:-4]
1213        elif not re.findall('([A-Z])', callableMatch):  # True if snake case
1214            # Split at the last '.' occurence
1215            splitted = callableMatch.split('.')
1216            namespace = splitted[:-1]
1217            function_name = splitted[-1]
1218            # Find corresponding object name
1219            object_name = _snake_to_camel(function_name)
1220            # Check that this object actually exists
1221            try:
1222                objectCallableMatch = ".".join(namespace + [object_name])
1223                eval(objectCallableMatch, ip.Completer.namespace)
1224                # Reconstruct full object name
1225                callableMatch = objectCallableMatch
1226            except AttributeError:
1227                # callableMatch is not a snake case function with a
1228                # corresponding object.
1229                pass
1230        try:
1231            object = eval(callableMatch, ip.Completer.namespace)
1232            if isinstance(object, itkTemplate.itkTemplate):
1233                # this is a template - lets grab the first entry to search for
1234                # the methods
1235                object = object.values()[0]
1236            namedArgs = []
1237            isin = isinstance(object, itk.LightObject)
1238            if inspect.isclass(object):
1239                issub = issubclass(object, itk.LightObject)
1240            if isin or (inspect.isclass(object) and issub):
1241                namedArgs = [n[3:] for n in dir(object) if n.startswith("Set")]
1242        except Exception as e:
1243            print(e)
1244            continue
1245        for namedArg in namedArgs:
1246            if namedArg.startswith(text):
1247                argMatches.append(u"%s=" % namedArg)
1248    return argMatches
1249
1250# install progress callback and custom completer if we are in ipython
1251# interpreter
1252try:
1253    import itkConfig
1254    import IPython
1255    if IPython.get_ipython():
1256        IPython.get_ipython().Completer.matchers.insert(0, ipython_kw_matches)
1257    # some cleanup
1258    del itkConfig, IPython
1259except (ImportError, AttributeError):
1260    # fail silently
1261    pass
1262