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