ITK Pythonic interface - filter output

Hello,
with the new wrappers for ITK 5.0 Beta and all the facilities with the new pythonic interface I was wondering how can I especify a filter’s output type on the parameters.

The idea is to change this:
rescaler = itk.RescaleIntensityImageFilter[itk.Image.UC2, itk.Image.US2].New()

into something like this:
rescaler = itk.rescale_intensity_image_filter(input_image, output_type=itk.Image.US2)

when I want to control the output type of a filter because the one he defaults to doesn’t fit the next one on the pipeline

1 Like

Hello @mafuentes,

I am glad you are suggesting this idea because @matt.mccormick and I just talked about this idea. Glad other people share the same idea! I think more generally, the used should be able to pass not only the output_type, but in general the template type. This would allow all template parameters to be set in a pythonic way, and would avoid having to hard code a lot of euristic rules such as:

  • input type is the first template parameter
  • output type is the second template parameter
  • What do we do if we have only one template parameter or if we have more than just for the input and output…

Anybody is more than welcome to give ideas and even start contributing by creating a pull-request :slight_smile:

2 Likes

Good ideas!

As @fbudin mentioned regarding the implementation, the input type is nearly universally the first template parameter for a filter, but the output type is not necessarily the second or last C++ template parameter, so an output_type argument would be difficult to implement.

Perhaps a type_params argument that provides the types that correspond to the C++ template arguments? What do you think?

1 Like

A type_params would do the trick nicely. I took a look inside the process of instantiation of objects of the new form:

filter = itk.danielsson_distance_map_image_filter(input_image)

and found that inside the itkTemplate.py file, a search is conducted in a dictionary where the keys are pairs (input_tye, output_type) and it takes the first key that matches the input type. That seems easy to change… though I don’t know what consecuences would have for developers to change that bit of code to try and match the complete pair when a type_params argument is provided.

to help us understand, what are the specific lines referenced here?

Sorry, I should’ve been more specific there…
The code for the matching process I was referring to, is on the New() method of the itkTemplate class. The

keys = [k for k in keys if k[0] == input_type]

variable ends up holding a list of all matching (input_type, output_type) pairs, and the first match is the one that’s instantiated on return. It goes as follows:

def New(self, *args, **kwargs):
    ### docstring here
    import itk
    keys = self.keys()
    cur = itk.auto_pipeline.current
    ### some code here for the ImageFileReader and ImageSeriesReader cases
    primary_input_methods = ('Input', 'InputImage', 'Input1')
    if len(args) != 0:
        # try to find a type suitable for the primary input provided
        input_type = output(args[0]).__class__
        keys = [k for k in keys if k[0] == input_type]
    elif set(primary_input_methods).intersection(kwargs.keys()):
        for method in primary_input_methods:
            if method in kwargs:
                input_type = output(kwargs[method]).__class__
                keys = [k for k in keys if k[0] == input_type]
                break
    elif cur is not None and len(cur) != 0:
        # try to find a type suitable for the input provided
        input_type = output(cur).__class__
        keys = [k for k in keys if k[0] == input_type]
    if len(keys) == 0:
        raise RuntimeError("No suitable template parameter can be found.")
    return self[list(keys)[0]].New(*args, **kwargs)

@mafuentes thanks for the detailed pointer! :abcd:

Correct – for the case of the DanielssonDistanceMapImageFilter, the first template argument is the input image type and the second template argument is the output image type. However, as previously discussed, this is not always true, so we cannot assume that we could use, e.g.

self[list(keys)[0][0], kwargs['output_type']].New(*args, **kwargs)

But, we could use a type_params keyword argument that takes the first n template parameters, and instantiate the first match.

This would look something like this (untested):

primary_input_methods = ('Input', 'InputImage', 'Input1')
if 'type_params' in kwargs:
    type_params = kwargs.pop('type_params')
    number_type_params = len(type_params)
    keys = [k for k in keys if k[:number_type_params] == type_params]
    return self[list(keys)[0]].New(*args, **kwargs)]
elif len(args) != 0:
[...]

Would you like try contributing a patch?

1 Like

I’ll take a look at the docs on contributing, because I have very (very!) little experience with Git. It would be nice to put my grain of salt though, since I’ve been a user of ITK python wraps one way or another for years now.

1 Like

As a personal preference, I would rather us not adding some magic guessing (and I would even vote for deprecating the current magic that is used when instantiating the first type that works if multiple types are available). I do really like the idea of having that extra parameter, but I would add a check that it contains all the required templates (i.e. if the class requires two types, than type_params would also have to contain two types).

My thoughts about this was to add it directly inside the functional implementation, and leave the “Object” instantiation with New() untouched, but I don’t have a strong opinion about this and I could easily be convinced that adding it inside New() makes sense.

One thing that needs to be implemented is a test that ensures that no ITK filter actually uses the keyword we actually select as it would cause issues if a filter had a method called SetTemplateParams or whatever keyword we select. This test should be fairly easy to write. I have a work in progress branch in which I am writing similar tests. I will add a reference to those tests in this conversation when I push that branch.

Final thought, I would have used the keyword ttype instead of template_params to stay close to a defacto standard Python developers are used to: NumPy with dtype (assuming dtype stands for “data type”). In our case ttype would stand for “template type”.

1 Like

Here is a tentative PR to address this. @mafuentes : I wanted to add you as a reviewer but I don’t know your GitHub username.

1 Like

Thanks! My GitHub username is: @manuelcastr. I’ll take a look.

1 Like

You should have received a GitHub invite to join the ITK community. Once you have joined it, I should be able to add you as a reviewer.

2 Likes