From 663b761fb4cbbed6bf48a34041996f758887ae91 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Sun, 13 Nov 2022 20:48:26 +0100 Subject: [PATCH 1/8] clean code part 1 --- .../PartSegCore/algorithm_describe_base.py | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/package/PartSegCore/algorithm_describe_base.py b/package/PartSegCore/algorithm_describe_base.py index b17bb32d3..da2720ffb 100644 --- a/package/PartSegCore/algorithm_describe_base.py +++ b/package/PartSegCore/algorithm_describe_base.py @@ -112,22 +112,22 @@ 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) + 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): @@ -225,7 +225,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 @@ -272,8 +272,9 @@ def register( self.check_function(value, "get_name", True) try: name = value.get_name() - except NotImplementedError: - raise ValueError(f"Class {value} need to implement get_name class method") + except NotImplementedError as e: + raise ValueError(f"Class {value} need to implement get_name class method") from e + if name in self and not replace: raise ValueError( f"Object {self[name]} with this name: {name} already exist and register is not in replace mode" @@ -309,8 +310,9 @@ def __setitem__(self, key: str, value: AlgorithmType): self.check_function(value, "get_fields", True) try: val = value.get_name() - except NotImplementedError: - raise ValueError(f"Method get_name of class {value} need to be implemented") + except NotImplementedError as e: + raise ValueError(f"Method get_name of class {value} need to be implemented") from e + if not isinstance(val, str): raise ValueError(f"Function get_name of class {value} need return string not {type(val)}") if key != val: @@ -320,8 +322,9 @@ def __setitem__(self, key: str, value: AlgorithmType): val = value.get_fields() if not isinstance(val, list): raise ValueError(f"Function get_fields of class {value} need return list not {type(val)}") - except NotImplementedError: - raise ValueError(f"Method get_fields of class {value} need to be implemented") + except NotImplementedError as exc: + raise ValueError(f"Method get_fields of class {value} need to be implemented") from exc + for el in self.class_methods: self.check_function(value, el, True) for el in self.methods: @@ -337,16 +340,16 @@ def get_default(self) -> str: """ try: return next(iter(self.keys())) - except StopIteration: - raise ValueError("Register does not contain any algorithm.") + except StopIteration as e: + raise ValueError("Register does not contain any algorithm.") from e class AddRegisterMeta(ModelMetaclass): - def __new__(cls, name, bases, attrs, **kwargs): + def __new__(mcs, name, bases, attrs, **kwargs): methods = kwargs.pop("methods", []) suggested_base_class = kwargs.pop("suggested_base_class", None) class_methods = kwargs.pop("class_methods", []) - cls2 = super().__new__(cls, name, bases, attrs, **kwargs) + cls2 = super().__new__(mcs, name, bases, attrs, **kwargs) cls2.__register__ = Register( class_methods=class_methods, methods=methods, suggested_base_class=suggested_base_class ) @@ -418,12 +421,12 @@ def get_default(cls): class ROIExtractionProfileMeta(ModelMetaclass): - def __new__(cls, name, bases, attrs, **kwargs): - cls2 = super().__new__(cls, name, bases, attrs, **kwargs) + def __new__(mcs, name, bases, attrs, **kwargs): + cls2 = super().__new__(mcs, 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", @@ -431,7 +434,7 @@ def _wraps(self, *args, **kwargs): stacklevel=2, ) kwargs.update(dict(zip(self.__fields__, args))) - return func(self, **kwargs) + return func(self, **kwargs_) return _wraps From e456d72498f308cefd335080b0007fddfd06a064 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 14 Nov 2022 11:16:21 +0100 Subject: [PATCH 2/8] fix test --- package/PartSegCore/algorithm_describe_base.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/package/PartSegCore/algorithm_describe_base.py b/package/PartSegCore/algorithm_describe_base.py index da2720ffb..ce94b4f4e 100644 --- a/package/PartSegCore/algorithm_describe_base.py +++ b/package/PartSegCore/algorithm_describe_base.py @@ -118,6 +118,16 @@ def _partial_abstractmethod(func_obj): class AlgorithmDescribeBaseMeta(ABCMeta): + __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 ( @@ -433,7 +443,7 @@ def _wraps(self, *args, **kwargs_): FutureWarning, stacklevel=2, ) - kwargs.update(dict(zip(self.__fields__, args))) + kwargs_.update(dict(zip(self.__fields__, args))) return func(self, **kwargs_) return _wraps From 76b302bc279714be801bb66a72c323a0bc5fabab Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 15 Nov 2022 11:56:00 +0100 Subject: [PATCH 3/8] update gitpod yaml --- .gitpod.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index 7a8b61a28..3a948904e 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 + command: herbstluftwm + init: pip install -e .[all,test,docs] From caaf30b7cdc81a6a429c8016cc87d4ee2f1558c5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 15 Nov 2022 12:03:24 +0100 Subject: [PATCH 4/8] add xvfb call --- .gitpod.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index 3a948904e..dc1a79b82 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,4 +1,6 @@ tasks: - 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 - command: herbstluftwm - init: pip install -e .[all,test,docs] + command: Xvfb :99 -screen 0 640x480x8 -nolisten tcp & herbstluftwm + init: > + pip install -e .[all,test,docs] + bash build_utils/download_data.sh From 45fb87dec0d8d1f513b00051263cd8a7f0caea66 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 15 Nov 2022 12:10:19 +0100 Subject: [PATCH 5/8] install xvfb --- .gitpod.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index dc1a79b82..7353129aa 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,4 @@ tasks: - - 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 + - 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 + init: pip install -e .[all,test,docs] && bash ./build_utils/download_data.sh From 4411e649f3309fb4b82c0d095befa4e7ee08e2d5 Mon Sep 17 00:00:00 2001 From: "sourcery-ai[bot]" <58596630+sourcery-ai[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:18:05 +0100 Subject: [PATCH 6/8] Add docs for plugin create (Sourcery refactored) (#796) Co-authored-by: Sourcery AI <> --- package/PartSegCore/algorithm_describe_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/PartSegCore/algorithm_describe_base.py b/package/PartSegCore/algorithm_describe_base.py index ce94b4f4e..c4b06c77e 100644 --- a/package/PartSegCore/algorithm_describe_base.py +++ b/package/PartSegCore/algorithm_describe_base.py @@ -355,11 +355,11 @@ def get_default(self) -> str: class AddRegisterMeta(ModelMetaclass): - def __new__(mcs, name, bases, attrs, **kwargs): + def __new__(cls, name, bases, attrs, **kwargs): methods = kwargs.pop("methods", []) suggested_base_class = kwargs.pop("suggested_base_class", None) class_methods = kwargs.pop("class_methods", []) - cls2 = super().__new__(mcs, name, bases, attrs, **kwargs) + cls2 = super().__new__(cls, name, bases, attrs, **kwargs) cls2.__register__ = Register( class_methods=class_methods, methods=methods, suggested_base_class=suggested_base_class ) From 657c9624740419e65a21c3e2e9a75b9b0c2ff49f Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 29 Nov 2022 23:16:01 +0000 Subject: [PATCH 7/8] comment out xvfb start --- .gitpod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitpod.yml b/.gitpod.yml index 7353129aa..6af3668ce 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,4 +1,4 @@ tasks: - 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 + # command: Xvfb :99 -screen 0 640x480x8 -nolisten tcp & herbstluftwm init: pip install -e .[all,test,docs] && bash ./build_utils/download_data.sh From 33b4ff2452adce6e970ab7d38b61345d010e36aa Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 7 Dec 2022 13:20:44 +0100 Subject: [PATCH 8/8] add basic information about plugin creation --- docs/conf.py | 1 + docs/index.rst | 1 + docs/plugins_creation.rst | 116 ++++++++++++++++++++++++++++++++ package/PartSegCore/register.py | 15 ++++- 4 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 docs/plugins_creation.rst diff --git a/docs/conf.py b/docs/conf.py index b65ca5b79..a3573faf3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,6 +43,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/register.py b/package/PartSegCore/register.py index 119e12b36..aee28df96 100644 --- a/package/PartSegCore/register.py +++ b/package/PartSegCore/register.py @@ -12,6 +12,7 @@ register_dict: holds information where register given operation type. Strongly suggest to use register function instead. """ + from enum import Enum from typing import Type @@ -43,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