haesleinhuepf / napari-process-points-and-surfaces

Process and analyze surfaces using vedo in napari.
BSD 3-Clause "New" or "Revised" License
22 stars 4 forks source link

Make a decorator for surfaceData / surface-layer conversion #9

Open haesleinhuepf opened 2 years ago

haesleinhuepf commented 2 years ago

It should be possible to formulate a function decorator that takes functions like this:

@plugin_function
def operation(surface:SurfaceData, points:PointsData)

make appear them like that to the outside:

def operation(surface:SurfaceLayer, points:PointsLayer)

By implementing this plugin_function we could also solve #6 (without if-else blocks in every function) and multiply the surfaceData and pointsData with the layer's scaling. The plugin_function could also convert back pointsData and surfaceData to layers.

There are other examples where similar stuff is done:

Johannes @jo-mueller , if you're interested in implementing this, let me know! Otherwise I can also do it.

jo-mueller commented 2 years ago

I'll have my hand at it :)

jo-mueller commented 2 years ago

Hi @haesleinhuepf ,

I've tried to formulate this similarly as in the linked repos (Thanks for the hints), and the result would look something like this - is this what you had in mind?

from functools import wraps
from toolz import curry
import inspect

from napari.layers import Surface, Points

@curry
def convert_annotation(function) -> callable:

    @wraps(function)
    def wrapper(*args, **kwargs):

        # From https://github.com/haesleinhuepf/napari-simpleitk-image-processing/blob/main/src/napari_simpleitk_image_processing/_simpleitk_image_processing.py
        sig = inspect.signature(function)
        # create mapping from position and keyword arguments to parameters
        # will raise a TypeError if the provided arguments do not match the signature
        # https://docs.python.org/3/library/inspect.html#inspect.Signature.bind
        bound = sig.bind(*args, **kwargs)
        # set default values for missing arguments
        # https://docs.python.org/3/library/inspect.html#inspect.BoundArguments.apply_defaults
        bound.apply_defaults()

        # copy images to napari.types
        for key, value in bound.arguments.items():

            arg = None
            if isinstance(value, Surface):
                arg = (value.data[0], value.data[1])
            if isinstance(value, Points):
                arg = (value.data)

            if arg is not None:
                bound.arguments[key] = arg

        # call the decorated function
        return function(*bound.args, **bound.kwargs)

    return wrapper
haesleinhuepf commented 2 years ago

Hi Johannes @jo-mueller ,

the code looks interesting. Have you tried to run it? Does it exist as repo/branch somewhere? That would make it easier to discuss about it ;-)

The next challenge might then be to modify the signature of the function to achieve that magicgui works. It should ask the user for a Surface layer, even though the function specifies SurfaceData as type. Hence, I would try to replace SurfaceData annotations with SurfaceLayer annotations.

Best, Robert

jo-mueller commented 2 years ago

Hey @haesleinhuepf ,

the code for this lives here. I added the decorator to two functions to test the behavior and it seems to work (e.g., passes a simple test function). I haven't tried it from the viewer, yet.

It should ask the user for a Surface layer, even though the function specifies SurfaceData as type.

So...I would have to add a Surface-annotated argument in the outer function? Or replace the respective argument annotation in the wrapped function?

haesleinhuepf commented 2 years ago

I think you want to modifiy the signature of wrapper in your code.