From f302692742d5a8d19f05f3d4479e6cacc56f5eed Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota <bokota+github@gmail.com> Date: Tue, 3 Mar 2020 17:22:32 +0100 Subject: [PATCH] fixes similar functions from lgtm (#28) --- .../PartSeg/common_backend/base_settings.py | 22 ++- package/PartSeg/common_gui/main_window.py | 38 +++- .../common_gui/multiple_file_widget.py | 21 ++- .../segmentation_analysis/main_window.py | 21 +-- .../segmentation_analysis/partseg_settings.py | 29 --- .../segmentation_mask/stack_gui_main.py | 15 +- .../segmentation_mask/stack_settings.py | 28 --- .../PartSegCore/sphinx/reference_resolve.py | 169 ------------------ 8 files changed, 84 insertions(+), 259 deletions(-) delete mode 100644 package/PartSegCore/sphinx/reference_resolve.py diff --git a/package/PartSeg/common_backend/base_settings.py b/package/PartSeg/common_backend/base_settings.py index d96dd9e9..51093523 100644 --- a/package/PartSeg/common_backend/base_settings.py +++ b/package/PartSeg/common_backend/base_settings.py @@ -521,5 +521,23 @@ class BaseSettings(ViewSettings): @staticmethod def verify_image(image: Image, silent=True) -> Union[Image, bool]: - """verify if image is correct (ex. program can not support time data)""" - raise NotImplementedError + if image.is_time: + if image.is_stack: + raise TimeAndStackException() + if silent: + return image.swap_time_and_stack() + else: + raise SwapTimeStackException() + return True + +class SwapTimeStackException(Exception): + """ + Exception which inform that current image shape is not supported, + but can be if time and stack axes were swapped + """ + +class TimeAndStackException(Exception): + """ + Exception which inform that current image has both time + and stack dat which is not supported + """ \ No newline at end of file diff --git a/package/PartSeg/common_gui/main_window.py b/package/PartSeg/common_gui/main_window.py index f709a2f7..0555f2b7 100644 --- a/package/PartSeg/common_gui/main_window.py +++ b/package/PartSeg/common_gui/main_window.py @@ -6,12 +6,13 @@ from qtpy.QtCore import Signal import os from PartSeg.common_gui.about_dialog import AboutDialog +from PartSeg.common_gui.image_adjustment import ImageAdjustmentDialog from PartSeg.common_gui.show_directory_dialog import DirectoryDialog from PartSeg.common_backend.load_backup import import_config from PartSeg.common_gui.waiting_dialog import ExecuteFunctionDialog from PartSegCore.io_utils import ProjectInfoBase from PartSegImage import Image -from PartSeg.common_backend.base_settings import BaseSettings +from PartSeg.common_backend.base_settings import BaseSettings, SwapTimeStackException, TimeAndStackException class BaseMainMenu(QWidget): @@ -42,7 +43,24 @@ class BaseMainMenu(QWidget): ) if resp == QMessageBox.No: return - image = self._settings.verify_image(data.image, False) + try: + image = self._settings.verify_image(data.image, False) + except SwapTimeStackException: + res = QMessageBox.question( + self, + "Not supported", + "Time data are currently not supported. Maybe You would like to treat time as z-stack", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No, + ) + + if res == QMessageBox.Yes: + image = data.image.swap_time_and_stack() + else: + return + except TimeAndStackException: + QMessageBox.warning(self, "image error", "Do not support time and stack image") + return if image: if isinstance(image, Image): # noinspection PyProtectedMember @@ -167,3 +185,19 @@ class BaseMainWindow(QMainWindow): def show_about_dialog(): """Show about dialog.""" AboutDialog().exec() + + @staticmethod + def get_project_info(file_path, image): + raise NotADirectoryError() + + def image_adjust_exec(self): + dial = ImageAdjustmentDialog(self.settings.image) + if dial.exec(): + algorithm = dial.result_val.algorithm + dial2 = ExecuteFunctionDialog( + algorithm.transform, [], {"image": self.settings.image, "arguments": dial.result_val.values} + ) + if dial2.exec(): + result: Image = dial2.get_result() + self.settings.set_project_info(self.get_project_info(result.file_path, result)) + diff --git a/package/PartSeg/common_gui/multiple_file_widget.py b/package/PartSeg/common_gui/multiple_file_widget.py index 37a5dfdc..9d4c2f44 100644 --- a/package/PartSeg/common_gui/multiple_file_widget.py +++ b/package/PartSeg/common_gui/multiple_file_widget.py @@ -23,7 +23,7 @@ from qtpy.QtGui import QFontMetrics, QResizeEvent, QMouseEvent from qtpy.QtCore import Qt, QTimer, Slot, Signal from collections import defaultdict, Counter -from PartSeg.common_backend.base_settings import BaseSettings +from PartSeg.common_backend.base_settings import BaseSettings, SwapTimeStackException, TimeAndStackException from PartSegImage import Image from PartSegCore.io_utils import LoadBase, ProjectInfoBase from .custom_load_dialog import CustomLoadDialog, LoadProperty @@ -165,7 +165,24 @@ class MultipleFileWidget(QWidget): file_name = self.file_list[self.file_view.indexOfTopLevelItem(item.parent())] state_name = item.text(0) project_info = self.state_dict[file_name][state_name] - image = self.settings.verify_image(project_info.image, False) + try: + image = self._settings.verify_image(project_info.image, False) + except SwapTimeStackException: + res = QMessageBox.question( + self, + "Not supported", + "Time data are currently not supported. Maybe You would like to treat time as z-stack", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No, + ) + + if res == QMessageBox.Yes: + image = project_info.image.swap_time_and_stack() + else: + return + except TimeAndStackException: + QMessageBox.warning(self, "image error", "Do not support time and stack image") + return if isinstance(image, Image): project_info = project_info._replace(image=image) self.state_dict[file_name][state_name] = project_info diff --git a/package/PartSeg/segmentation_analysis/main_window.py b/package/PartSeg/segmentation_analysis/main_window.py index fdecde60..203e2f35 100644 --- a/package/PartSeg/segmentation_analysis/main_window.py +++ b/package/PartSeg/segmentation_analysis/main_window.py @@ -21,7 +21,6 @@ from qtpy.QtWidgets import ( ) from PartSeg.common_gui.custom_load_dialog import CustomLoadDialog -from PartSeg.common_gui.image_adjustment import ImageAdjustmentDialog from PartSeg.common_gui.stacked_widget_with_selector import StackedWidgetWithSelector from PartSeg.segmentation_analysis.measurement_widget import MeasurementWidget from PartSegCore.analysis import ProjectTuple @@ -41,7 +40,7 @@ from PartSeg.common_gui.main_window import BaseMainWindow, BaseMainMenu from .advanced_window import SegAdvancedWindow from .batch_window import BatchWindow from .calculation_pipeline_thread import CalculatePipelineThread -from PartSegImage import TiffImageReader, Image +from PartSegImage import TiffImageReader from PartSegCore.algorithm_describe_base import SegmentationProfile from PartSegCore.analysis.analysis_utils import SegmentationPipelineElement, SegmentationPipeline from .image_view import SynchronizeView, ResultImageView, CompareImageView @@ -428,18 +427,6 @@ class MainMenu(BaseMainMenu): ) dial2.exec() - def image_adjust_exec(self): - dial = ImageAdjustmentDialog(self.settings.image) - if dial.exec(): - algorithm = dial.result_val.algorithm - dial2 = ExecuteFunctionDialog( - algorithm.transform, [], {"image": self.settings.image, "arguments": dial.result_val.values} - ) - if dial2.exec(): - result: Image = dial2.get_result() - self.settings.set_project_info(ProjectTuple(result.file_path, result)) - return - def mask_manager(self): if self.settings.segmentation is None: QMessageBox.information(self, "No segmentation", "Cannot create mask without segmentation") @@ -611,7 +598,7 @@ class MainWindow(BaseMainWindow): file_menu.addAction("&Save").triggered.connect(self.main_menu.save_file) file_menu.addAction("Batch processing").triggered.connect(self.main_menu.batch_window) image_menu = menu_bar.addMenu("Image operations") - image_menu.addAction("Image adjustment").triggered.connect(self.main_menu.image_adjust_exec) + image_menu.addAction("Image adjustment").triggered.connect(self.image_adjust_exec) image_menu.addAction("Mask manager").triggered.connect(self.main_menu.mask_manager) help_menu = menu_bar.addMenu("Help") help_menu.addAction("State directory").triggered.connect(self.show_settings_directory) @@ -686,3 +673,7 @@ class MainWindow(BaseMainWindow): self.settings.dump() del self.batch_window del self.advanced_window + + @staticmethod + def get_project_info(file_path, image): + return ProjectTuple(file_path=file_path, image=image) diff --git a/package/PartSeg/segmentation_analysis/partseg_settings.py b/package/PartSeg/segmentation_analysis/partseg_settings.py index 023743c2..bfaad9f0 100644 --- a/package/PartSeg/segmentation_analysis/partseg_settings.py +++ b/package/PartSeg/segmentation_analysis/partseg_settings.py @@ -1,9 +1,7 @@ import typing from copy import deepcopy -from qtpy.QtWidgets import QMessageBox, QWidget from qtpy.QtCore import Signal -from PartSegImage import Image from PartSegCore.analysis.calculation_plan import CalculationPlan from PartSegCore.analysis.io_utils import ProjectTuple, MaskInfo from PartSegCore.io_utils import HistoryElement @@ -137,33 +135,6 @@ class PartSettings(BaseSettings): SaveSettingsDescription("batch_plans_save.json", self.batch_plans_dict), ] - @staticmethod - def verify_image(image: Image, silent=True) -> typing.Union[Image, bool]: - if image.is_time: - if image.is_stack: - if silent: - raise ValueError("Do not support time and stack image") - else: - wid = QWidget() - QMessageBox.warning(wid, "image error", "Do not support time and stack image") - return False - if silent: - return image.swap_time_and_stack() - else: - wid = QWidget() - res = QMessageBox.question( - wid, - "Not supported", - "Time data are currently not supported." " Maybe You would like to treat time as z-stack", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No, - ) - - if res == QMessageBox.Yes: - return image.swap_time_and_stack() - return False - return True - @property def segmentation_pipelines(self) -> typing.Dict[str, SegmentationPipeline]: return self.segmentation_pipelines_dict.get(self.current_segmentation_dict, dict()) diff --git a/package/PartSeg/segmentation_mask/stack_gui_main.py b/package/PartSeg/segmentation_mask/stack_gui_main.py index 308f3193..0d3970e1 100644 --- a/package/PartSeg/segmentation_mask/stack_gui_main.py +++ b/package/PartSeg/segmentation_mask/stack_gui_main.py @@ -29,7 +29,6 @@ from qtpy.QtWidgets import ( ) from PartSeg.common_gui.advanced_tabs import AdvancedWindow -from PartSeg.common_gui.image_adjustment import ImageAdjustmentDialog from PartSeg.common_gui.multiple_file_widget import MultipleFileWidget from PartSeg.segmentation_mask.segmentation_info_dialog import SegmentationInfoDialog from PartSegCore.io_utils import WrongFileTypeException, HistoryElement, HistoryProblem @@ -941,14 +940,6 @@ class MainWindow(BaseMainWindow): def read_drop(self, paths): self._read_drop(paths, io_functions) - def image_adjust_exec(self): - dial = ImageAdjustmentDialog(self.settings.image) - if dial.exec(): - algorithm = dial.result_val.algorithm - dial2 = ExecuteFunctionDialog( - algorithm.transform, [], {"image": self.settings.image, "arguments": dial.result_val.values} - ) - if dial2.exec(): - result: Image = dial2.get_result() - self.settings.set_project_info(SegmentationTuple(result.file_path, result)) - return + @staticmethod + def get_project_info(file_path, image): + return SegmentationTuple(file_path=file_path, image=image) diff --git a/package/PartSeg/segmentation_mask/stack_settings.py b/package/PartSeg/segmentation_mask/stack_settings.py index 493d526e..7d43eca4 100644 --- a/package/PartSeg/segmentation_mask/stack_settings.py +++ b/package/PartSeg/segmentation_mask/stack_settings.py @@ -5,7 +5,6 @@ from os import path import numpy as np from qtpy.QtCore import Signal, Slot -from qtpy.QtWidgets import QMessageBox, QWidget from PartSegCore.algorithm_describe_base import SegmentationProfile from PartSegCore.io_utils import HistoryElement, HistoryProblem @@ -264,33 +263,6 @@ class StackSettings(BaseSettings): self.segmentation = new_segmentation_data self.components_parameters_dict = segmentation_parameters - @staticmethod - def verify_image(image: Image, silent=True) -> typing.Union[Image, bool]: - if image.is_time: - if image.is_stack: - if silent: - raise ValueError("Do not support time and stack image") - else: - wid = QWidget() - QMessageBox.warning(wid, "image error", "Do not support time and stack image") - return False - if silent: - return image.swap_time_and_stack() - else: - wid = QWidget() - res = QMessageBox.question( - wid, - "Not supported", - "Time data are currently not supported. " "Maybe You would like to treat time as z-stack", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No, - ) - - if res == QMessageBox.Yes: - return image.swap_time_and_stack() - return False - return True - def get_mask(segmentation: typing.Optional[np.ndarray], mask: typing.Optional[np.ndarray], selected: typing.List[int]): """ diff --git a/package/PartSegCore/sphinx/reference_resolve.py b/package/PartSegCore/sphinx/reference_resolve.py deleted file mode 100644 index 7d55c0b3..00000000 --- a/package/PartSegCore/sphinx/reference_resolve.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -This module contains sphinx extension supporting for build PartSeg documentation. - -this extensio provides one configuration option: - -`qt_documentation` with possibe values: - - * PyQt - linking to PyQt documentation on https://www.riverbankcomputing.com/static/Docs/PyQt5/api/ (incomplete) - * Qt - linking to Qt documentation on "https://doc.qt.io/qt-5/" (default) - * PySide - linking to PySide documentation on "https://doc.qt.io/qtforpython/PySide2/" -""" -import importlib -import inspect -import re - -from sphinx.application import Sphinx -from sphinx.config import ENUM -from sphinx.environment import BuildEnvironment -from docutils.nodes import Element, TextElement -from docutils import nodes -from typing import List, Optional, Dict, Any -from sphinx.locale import get_translation -from sphinx.ext.intersphinx import InventoryAdapter -from qtpy.QtCore import Signal - -_ = get_translation("sphinx") - -try: - from qtpy import QT_VERSION -except ImportError: - QT_VERSION = None - -# TODO add response to -# https://stackoverflow.com/questions/47102004/how-to-properly-link-to-pyqt5-documentation-using-intersphinx - -signal_slot_uri = { - "Qt": "https://doc.qt.io/qt-5/signalsandslots.html", - "PySide": "https://doc.qt.io/qtforpython/overviews/signalsandslots.html", - "PyQt": "https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html", -} - -signal_name = {"Qt": "Signal", "PySide": "Signal", "PyQt": "pyqtSignal"} - -slot_name = {"Qt": "Slot", "PySide": "Slot", "PyQt": "pyqtSlot"} - -type_translate_dict = {"class": ["class"], "meth": ["method", "signal"], "mod": ["module"]} - -signal_pattern = re.compile(r"((\w+\d?\.QtCore\.)|(QtCore\.)|(\.))?(pyqt)?Signal") -slot_pattern = re.compile(r"((\w+\d?\.QtCore\.)|(QtCore\.)|(\.))?(pyqt)?Slot") - - -# noinspection PyUnusedLocal -def missing_reference( - app: Sphinx, env: BuildEnvironment, node: Element, contnode: TextElement -) -> Optional[nodes.reference]: - """Linking to Qt documentation.""" - target: str = node["reftarget"] - inventories = InventoryAdapter(env) - objtypes: Optional[List[str]] = None - if node["reftype"] == "any": - # we search anything! - objtypes = [ - "%s:%s" % (domain.name, objtype) for domain in env.domains.values() for objtype in domain.object_types - ] - domain = None - else: - domain = node.get("refdomain") - if not domain: - # only objects in domains are in the inventory - return None - objtypes = env.get_domain(domain).objtypes_for_role(node["reftype"]) - if not objtypes: - return None - objtypes = ["%s:%s" % (domain, objtype) for objtype in objtypes] - if target.startswith("PySide2"): - _head, tail = target.split(".", 1) - target = "PyQt5." + tail - if signal_pattern.match(target): - uri = signal_slot_uri[app.config.qt_documentation] - dispname = signal_name[app.config.qt_documentation] - version = QT_VERSION - elif slot_pattern.match(target): - uri = signal_slot_uri[app.config.qt_documentation] - dispname = slot_name[app.config.qt_documentation] - version = QT_VERSION - else: - target_list = [target, "PyQt5." + target] - target_list += [name + "." + target for name in inventories.named_inventory["PyQt"]["sip:module"].keys()] - if node.get("reftype") in type_translate_dict: - type_names = type_translate_dict[node.get("reftype")] - else: - type_names = [node.get("reftype")] - for name in type_names: - obj_type_name = "sip:{}".format(name) - if obj_type_name not in inventories.named_inventory["PyQt"]: - return None - for target_name in target_list: - if target_name in inventories.main_inventory[obj_type_name]: - _proj, version, uri, dispname = inventories.named_inventory["PyQt"][obj_type_name][target_name] - uri = uri.replace("##", "#") - # print(node) # print nodes with unresolved references - break - else: - continue - break - else: - return None - if app.config.qt_documentation == "Qt": - html_name = uri.split("/")[-1] - uri = "https://doc.qt.io/qt-5/" + html_name - elif app.config.qt_documentation == "PySide": - if node.get("reftype") == "meth": - split_tup = target_name.split(".")[1:] - ref_name = ".".join(["PySide2", split_tup[0], "PySide2"] + split_tup) - html_name = "/".join(split_tup[:-1]) + ".html#" + ref_name - else: - html_name = "/".join(target_name.split(".")[1:]) + ".html" - uri = "https://doc.qt.io/qtforpython/PySide2/" + html_name - - # remove this line if you would like straight to pyqt documentation - if version: - reftitle = _("(in %s v%s)") % (app.config.qt_documentation, version) - else: - reftitle = _("(in %s)") % (app.config.qt_documentation,) - newnode = nodes.reference("", "", internal=False, refuri=uri, reftitle=reftitle) - if node.get("refexplicit"): - # use whatever title was given - newnode.append(contnode) - else: - # else use the given display name (used for :ref:) - newnode.append(contnode.__class__(dispname, dispname)) - return newnode - - -re.compile(r" +algorithm_changed *= *Signal(\([^)]*\))") - - -# noinspection PyUnusedLocal -def autodoc_process_signature(app: Sphinx, what, name: str, obj, options, signature, return_annotation): - if isinstance(obj, Signal): - module_name, class_name, signal_name_local = name.rsplit(".", 2) - module = importlib.import_module(module_name) - class_ob = getattr(module, class_name) - reg = re.compile(r" +" + signal_name_local + r" *= *Signal(\([^)]*\))") - match = reg.findall(inspect.getsource(class_ob)) - if match: - return match[0], None - - pos = len(name.rsplit(".", 1)[1]) - return ", ".join([sig[pos:] for sig in obj.signatures]), None - - -def setup(app: Sphinx) -> Dict[str, Any]: - app.setup_extension("sphinx.ext.intersphinx") - if hasattr(app.config, "intersphinx_mapping"): - if "PyQt" not in app.config.intersphinx_mapping: - app.config.intersphinx_mapping["PyQt"] = ("https://www.riverbankcomputing.com/static/Docs/PyQt5", None) - else: - app.config.intersphinx_mapping = {"PyQt": ("https://www.riverbankcomputing.com/static/Docs/PyQt5", None)} - app.connect("missing-reference", missing_reference) - app.connect("autodoc-process-signature", autodoc_process_signature) - # app.connect('doctree-read', doctree_read) - app.add_config_value("qt_documentation", "Qt", True, ENUM("Qt", "PySide", "PyQt")) - return {"version": "0.9", "env_version": 1, "parallel_read_safe": True} - - -# https://doc.qt.io/qtforpython/PySide2/QtWidgets/QListWidget.html#PySide2.QtWidgets.QListWidget.itemDoubleClicked -# https://doc.qt.io/qtforpython/PySide2/QtWidgets/QListWidget.html# -# PySide2.QtWidgets.PySide2.QtWidgets.QListWidget.itemDoubleClicked -- GitLab