diff --git a/.gitpod.yml b/.gitpod.yml index 7a8b61a28..6af3668ce 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,4 +1,4 @@ tasks: - - init: > - pip install -e .[test,pyqt] - pip install -r docs/requirements.txt + - before: sudo apt-get update && sudo apt-get install -y herbstluftwm libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils xvfb + # command: Xvfb :99 -screen 0 640x480x8 -nolisten tcp & herbstluftwm + init: pip install -e .[all,test,docs] && bash ./build_utils/download_data.sh diff --git a/docs/conf.py b/docs/conf.py index b852b24b5..68a28c0d3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,6 +33,7 @@ "sphinx_autodoc_typehints", "PartSegCore.sphinx.auto_parameters", "sphinxcontrib.autodoc_pydantic", + "sphinx.ext.autosectionlabel", ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/index.rst b/docs/index.rst index 8e6e6ffa6..97d5e8748 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,6 +19,7 @@ The documentation is incomplete. Many utilities are undocumented. interface-overview/interface-overview state_store error_reporting + plugins_creation Indices and tables diff --git a/docs/plugins_creation.rst b/docs/plugins_creation.rst new file mode 100644 index 000000000..37503813c --- /dev/null +++ b/docs/plugins_creation.rst @@ -0,0 +1,116 @@ +Creating plugins for PartSeg +============================ + +PartSeg has a plugin system, but because of a lack of documentation, it is not very popular. +This document is an attempt to explain how to create plugins for PartSeg. The list of good plugins that could be inspirration +is available at the :ref:`end of this document`. + +.. note:: + + This document needs to be completed and may need to be corrected. If you have any questions, please inform me by a GitHub issue. + +PartSeg plugin system was designed at the beginning of the project when PartSeg was a python 2.7 project, and still, not +all possibilities given by recent python versions are used. For example, plugin methods are still class-based, +but there is a plan to allow function-based plugins. + +Where plugin could contribute in PartSeg +---------------------------------------- + +Here I describe nine areas where plugins could contribute: + + +* **Threshold algorithms** - algorithms for binarizing single channel data. + +* **Noise Filtering algorithms** - algorithms for filtering noise from a single channel of data. + +* **Flow algorithms** - Watershed-like algorithms for flow from core object to the whole region. + Currently, they are used in "Lower Threshold with Watershed" and "Upper Threshold with Watershed" + segmentation methods + +* **ROI Analysis algorithm** - algorithm available in *ROI Analysis* GUI for ROI extraction. + +* **ROI Analysis load method** - method to load data in *ROI Analysis* GUI. + +* **ROI Analysis save method** - method to save data in *ROI Analysis* GUI. + +* **ROI Mask algorithm** - algorithm available in *ROI Mask* GUI for ROI extraction. + +* **ROI Mask load method** - method to load data in *ROI Mask* GUI. + +* **ROI Mask save method** - method to save data in *ROI Mask* GUI. + + +A person interested in developing a plugin could wound the whole list in :py:mod:`PartSegCore.register` module. + +Plugin detection +---------------- + +PartSeg uses :py:func:`pkg_resources.iter_entry_points` module to detect plugins. +To be detected, the plugin needs to provide one of ``partseg.plugins``, ``PartSeg.plugins``. +``partsegcore.plugins`` or ``PartSegCore.plugins`` entry point in python package metadata. +The example of ``setup.cfg`` configuration from a plugin could be found `here `_ + + + + +``--develop`` mode of PartSeg +----------------------------- + +PartSeg allows to use of plugins in ``--develop`` mode. In this mode is a settings dialog the additional +tab "Develop" is added. In this tab, there is Reload button. After the button press, PartSeg tries +to reload all plugins. This feature allows the development of a plugin without the need of too often restarting of PartSeg. + +This approach is limited and reloads only entries pointed in entry points. +It is the plugin creator's responsibility to reload all other modules required by the plugin. +To detect if the file is during reload, the plugin author could use the following code: + + +.. code-block:: python + + try: + + reloading + except NameError: + reloading = False + else: + reloading = True + +The ``importlib.reload`` function could be used to reload required modules. + +.. code-block:: python + + import importlib + importlib.reload(module) + + +Already existing plugins +------------------------ +Here we list already existing PartSeg plugins. All plugins are also available when using PartSeg as a napari plugin. +New plugin creator may want to look at them to see how to create new plugins. + +PartSeg-smfish +~~~~~~~~~~~~~~ +This is plugin for processing smFISH data. It could be found under https://github.com/4DNucleome/PartSeg-smfish/ page. +This plugin provides a custom segmentation algorithm for smfish data (inspired by bigFISH algorithm that does not work well for our data) +and custom measurement methods. + +The plugin is available from pypi and conda. + +PartSeg-bioimageio +~~~~~~~~~~~~~~~~~~ +PartSeg plugin to run bioimage.io deep learning models. It could be found under https:/github.com/czaki/PartSeg-bioimageio/ page +This plugin allows to selection model saved in bioimage.io format from the disc and runs it on selected data with the test this model in interactive mode. + +As it depends on deep learn libraries, it cannot be used in PartSeg binary distribution. + +In this plugin, plugin creator could see an example of using custom magicgui-based widget for selecting a model. + +The plugin is under active development and currently available only on GitHub. + +Trapalyzer +~~~~~~~~~~ + +This is plugin developed for process neutrophile `data `_. +It provides custom segmentation data to find multiple class of cells in one run and custom measurement methods. + +It could be found on github https://github.com/Czaki/Trapalyzer and pypi. diff --git a/package/PartSegCore/algorithm_describe_base.py b/package/PartSegCore/algorithm_describe_base.py index 6125b2332..35d9030c9 100644 --- a/package/PartSegCore/algorithm_describe_base.py +++ b/package/PartSegCore/algorithm_describe_base.py @@ -130,22 +130,32 @@ def __get__(self, obj, klass): return model -def _partial_abstractmethod(funcobj): - funcobj.__is_partial_abstractmethod__ = True - return funcobj +def _partial_abstractmethod(func_obj): + func_obj.__is_partial_abstractmethod__ = True + return func_obj class AlgorithmDescribeBaseMeta(ABCMeta): - def __new__(cls, name, bases, attrs, **kwargs): - cls2 = super().__new__(cls, name, bases, attrs, **kwargs) + __argument_class__: typing.Optional[typing.Type[PydanticBaseModel]] = None + + def get_fields(self) -> typing.List[typing.Union[AlgorithmProperty, str]]: + """ + This function return list of parameters needed by algorithm. It is used for generate form in User Interface + + :return: list of algorithm parameters and comments + """ + raise NotImplementedError + + def __new__(mcs, name, bases, attrs, **kwargs): + cls = super().__new__(mcs, name, bases, attrs, **kwargs) if ( - not inspect.isabstract(cls2) - and hasattr(cls2.get_fields, "__is_partial_abstractmethod__") - and cls2.__argument_class__ is None + not inspect.isabstract(cls) + and hasattr(cls.get_fields, "__is_partial_abstractmethod__") + and cls.__argument_class__ is None ): raise RuntimeError("class need to have __argument_class__ set or get_fields functions defined") - cls2.__new_style__ = getattr(cls2.get_fields, "__is_partial_abstractmethod__", False) - return cls2 + cls.__new_style__ = getattr(cls.get_fields, "__is_partial_abstractmethod__", False) + return cls class AlgorithmDescribeBase(ABC, metaclass=AlgorithmDescribeBaseMeta): @@ -253,7 +263,7 @@ def __init__(self, *args: AlgorithmType, class_methods=None, methods=None, sugge """ :param class_methods: list of method which should be class method :param methods: list of method which should be instance method - :param kwargs: elements passed to OrderedDict constructor (may be initial elements). I suggest to not use this. + :param kwargs: elements passed to OrderedDict constructor (maybe initial elements). I suggest to not use this. """ super().__init__(**kwargs) self.suggested_base_class = suggested_base_class @@ -466,15 +476,15 @@ def __new__(cls, name, bases, attrs, **kwargs): def allow_positional_args(func): @wraps(func) - def _wraps(self, *args, **kwargs): + def _wraps(self, *args, **kwargs_): if args: warnings.warn( "Positional arguments are deprecated, use keyword arguments instead", FutureWarning, stacklevel=2, ) - kwargs.update(dict(zip(self.__fields__, args))) - return func(self, **kwargs) + kwargs_.update(dict(zip(self.__fields__, args))) + return func(self, **kwargs_) return _wraps diff --git a/package/PartSegCore/register.py b/package/PartSegCore/register.py index e23d7185b..c158c14d5 100644 --- a/package/PartSegCore/register.py +++ b/package/PartSegCore/register.py @@ -44,11 +44,19 @@ class RegisterEnum(Enum): flow = 0 #: algorithm for calculation flow from core object to borders. For spiting touching objects, deprecated threshold = 1 #: threshold algorithms. From greyscale array to binary array noise_filtering = 2 #: filter noise from image - analysis_algorithm = 3 #: algorithm for creating segmentation in analysis PartSeg part - mask_algorithm = 4 #: algorithm for creating segmentation in mask PartSeg part + analysis_algorithm = 3 + """ + algorithm for creating segmentation in analysis PartSeg part + (deprecated in favour of roi_analysis_segmentation_algorithm) + """ + mask_algorithm = 4 + """ + algorithm for creating segmentation in mask PartSeg part + (deprecated in favour of roi_mask_segmentation_algorithm) + """ analysis_save = 5 #: save functions for analysis part analysis_load = 6 #: load functions for analysis part - mask_load = 7 #: load functions for mask part + mask_load = 7 #: load functions for mask part) image_transform = 8 #: transform image, like interpolation mask_save_parameters = 9 #: save metadata for mask part (currently creating json file) mask_save_components = 10 #: save each segmentation component in separate file. Save location is directory