def __init__(self, json_path):
     super().__init__()
     self.current_segmentation_dict = "default"
     self.segmentation_dict = ProfileDict()
     self.json_folder_path = json_path
     self.last_executed_algorithm = ""
     self.history: List[HistoryElement] = []
     self.history_index = -1
 def test_dump_custom_types(self):
     prof_dict = ProfileDict()
     prof_dict.set("a.b.c", 1)
     data = {"a": RadiusType.R2D, "b": prof_dict}
     text = json.dumps(data, cls=ProfileEncoder)
     loaded = json.loads(text, object_hook=profile_hook)
     assert loaded["a"] == RadiusType.R2D
     assert loaded["b"].get("a.b.c") == 1
Exemple #3
0
 def __init__(self):
     super().__init__()
     self.color_map = []
     self.border_val = []
     self.current_profile_dict = "default"
     self.view_settings_dict = ProfileDict()
     self.colormap_dict = ColormapDict(self.get_from_profile("custom_colormap", {}))
     self.label_color_dict = LabelColorDict(self.get_from_profile("custom_label_colors", {}))
     self.cached_labels: Optional[Tuple[str, np.ndarray]] = None
Exemple #4
0
    def test_serialize(self, tmp_path):
        dkt = ProfileDict()
        dkt.set("a.b.c", 1)
        dkt.set("a.b.a", 2)
        with open(tmp_path / "test.json", "w") as f_p:
            json.dump(dkt, f_p, cls=ProfileEncoder)
        with open(tmp_path / "test.json") as f_p:
            dkt2 = json.load(f_p, object_hook=profile_hook)

        assert dkt.my_dict == dkt2.my_dict
Exemple #5
0
 def __init__(self, json_path):
     super().__init__(json_path)
     self._mask = None
     self.compare_segmentation = None
     self.segmentation_pipelines_dict = ProfileDict()
     self.segmentation_profiles_dict = ProfileDict()
     self.batch_plans_dict = ProfileDict()
     self.measurement_profiles_dict = ProfileDict()
Exemple #6
0
 def __init__(self,
              json_path: Union[Path, str],
              profile_name: str = "default"):
     """
     :param json_path: path to store
     :param profile_name: name of profile to be used. default value is "default"
     """
     super().__init__()
     napari_path = os.path.dirname(json_path) if os.path.basename(
         json_path) in ["analysis", "mask"] else json_path
     self.napari_settings: "NapariSettings" = napari_get_settings(
         napari_path)
     self._current_roi_dict = profile_name
     self._roi_dict = ProfileDict()
     self.json_folder_path = json_path
     self.last_executed_algorithm = ""
     self.history: List[HistoryElement] = []
     self.history_index = -1
     self.last_executed_algorithm = ""
     self._points = None
 def __init__(self, json_path, profile_name="default"):
     super().__init__(json_path, profile_name)
     self._mask = None
     self.compare_segmentation = None
     self._segmentation_pipelines_dict = ProfileDict()
     self._segmentation_profiles_dict = ProfileDict()
     self._batch_plans_dict = ProfileDict()
     self._measurement_profiles_dict = ProfileDict()
     self._segmentation_profiles_dict.connect(
         "", self.roi_profiles_changed.emit, maxargs=0)
     self._segmentation_pipelines_dict.connect(
         "", self.roi_pipelines_changed.emit, maxargs=0)
     self._measurement_profiles_dict.connect(
         "", self.measurement_profiles_changed.emit, maxargs=0)
     self._batch_plans_dict.connect("",
                                    self.batch_plans_changed.emit,
                                    maxargs=0)
Exemple #8
0
 def test_update(self):
     dkt = ProfileDict()
     dkt.update(a=1, b=2, c=3)
     assert dkt.my_dict == {"a": 1, "b": 2, "c": 3}
     dkt2 = ProfileDict()
     dkt2.update(c=4, d={"a": 2, "e": 7})
     assert dkt2.get("d.e") == 7
     dkt.update(dkt2)
     assert dkt.get("d.e") == 7
     assert dkt.get("c") == 4
     dkt.update({"g": 1, "h": 4})
     assert dkt.get("g") == 1
     dkt.update({"w": 1, "z": 4}, w=3)
     assert dkt.get("w") == 3
     assert dkt.verify_data()
     assert dkt.filter_data() == []
     dkt.set("e.h.l", {"aaa": 1, "__error__": True})
     assert not dkt.verify_data()
     assert dkt.filter_data() == ["e.h"]
Exemple #9
0
 def test_simple(self):
     dkt = ProfileDict()
     dkt.set("a.b.c", 1)
     dkt.set("a.b.a", 2)
     assert dkt.get("a.b.c") == 1
     with pytest.raises(KeyError):
         dkt.get("a.b.d")
     dkt.get("a.b.d", 3)
     assert dkt.get("a.b.d") == 3
     assert dkt.get("a.b") == {"a": 2, "c": 1, "d": 3}
     with pytest.raises(TypeError):
         dkt.set("a.b.c.d", 3)
Exemple #10
0
class PartSettings(BaseSettings):
    """
    last_executed_algorithm - parameter for caring last used algorithm
    """

    compare_segmentation_change = Signal(ROIInfo)
    json_encoder_class = PartEncoder
    load_metadata = staticmethod(load_metadata)
    last_executed_algorithm: str
    save_locations_keys = [
        "open_directory",
        "save_directory",
        "export_directory",
        "batch_plan_directory",
        "multiple_open_directory",
    ]

    def __init__(self, json_path):
        super().__init__(json_path)
        self._mask = None
        self.compare_segmentation = None
        self.segmentation_pipelines_dict = ProfileDict()
        self.segmentation_profiles_dict = ProfileDict()
        self.batch_plans_dict = ProfileDict()
        self.measurement_profiles_dict = ProfileDict()

    def fix_history(self, algorithm_name, algorithm_values):
        """
        set new algorithm parameters to

        :param str algorithm_name:
        :param dict algorithm_values:
        """
        self.history[self.history_index +
                     1] = self.history[self.history_index +
                                       1].replace_(segmentation_parameters={
                                           "algorithm_name": algorithm_name,
                                           "values": algorithm_values
                                       })

    @staticmethod
    def cmp_history_element(el1: HistoryElement, el2: HistoryElement):
        return el1.mask_property == el2.mask_property

    def set_segmentation_to_compare(self, segmentation: ROIInfo):
        self.compare_segmentation = segmentation
        self.compare_segmentation_change.emit(segmentation)

    @property
    def use_physical_unit(self):
        return self.get("use_physical_unit", False)

    def set_use_physical_unit(self, value):
        self.set("use_physical_unit", value)

    def _image_changed(self):
        super()._image_changed()
        self._mask = None

    def get_project_info(self) -> ProjectTuple:
        algorithm_name = self.last_executed_algorithm
        if algorithm_name:
            algorithm_val = {
                "algorithm_name": algorithm_name,
                "values": deepcopy(self.get(f"algorithms.{algorithm_name}")),
            }
        else:
            algorithm_val = {}
        return ProjectTuple(
            file_path=self.image.file_path,
            image=self.image.substitute(),
            roi=self.roi,
            roi_info=self.roi_info,
            additional_layers=self.additional_layers,
            mask=self.mask,
            history=self.history[:self.history_index + 1],
            algorithm_parameters=algorithm_val,
        )

    def set_project_info(self, data: typing.Union[ProjectTuple, MaskInfo]):
        if isinstance(data, ProjectTuple):
            if self.image.file_path == data.image.file_path and self.image.shape == data.image.shape:
                if data.roi is not None:
                    try:
                        self.image.fit_array_to_image(data.roi)
                        self.mask = data.mask
                    except ValueError:
                        self.image = data.image.substitute()
                else:
                    self.mask = data.mask
            else:
                self.image = data.image.substitute(mask=data.mask)
            self.roi = data.roi
            self._additional_layers = data.additional_layers
            self.set_history(data.history[:])
            if data.algorithm_parameters:
                self.last_executed_algorithm = data.algorithm_parameters[
                    "algorithm_name"]
                self.set(f"algorithms.{self.last_executed_algorithm}",
                         deepcopy(data.algorithm_parameters["values"]))
                self.algorithm_changed.emit()
        elif isinstance(data, MaskInfo):
            self.mask = data.mask_array

    def get_save_list(self) -> typing.List[SaveSettingsDescription]:
        return super().get_save_list() + [
            SaveSettingsDescription("segmentation_pipeline_save.json",
                                    self.segmentation_pipelines_dict),
            SaveSettingsDescription("segmentation_profiles_save.json",
                                    self.segmentation_profiles_dict),
            SaveSettingsDescription("statistic_profiles_save.json",
                                    self.measurement_profiles_dict),
            SaveSettingsDescription("batch_plans_save.json",
                                    self.batch_plans_dict),
        ]

    @property
    def segmentation_pipelines(self) -> typing.Dict[str, SegmentationPipeline]:
        return self.segmentation_pipelines_dict.get(self._current_roi_dict, {})

    @property
    def segmentation_profiles(self) -> typing.Dict[str, ROIExtractionProfile]:
        return self.segmentation_profiles_dict.get(self._current_roi_dict, {})

    @property
    def batch_plans(self) -> typing.Dict[str, CalculationPlan]:
        return self.batch_plans_dict.get(self._current_roi_dict, {})

    @property
    def measurement_profiles(self) -> typing.Dict[str, MeasurementProfile]:
        return self.measurement_profiles_dict.get(self._current_roi_dict, {})
Exemple #11
0
class BaseSettings(ViewSettings):
    """
    :ivar json_folder_path: default location for saving/loading settings data
    :ivar last_executed_algorithm: name of last executed algorithm.
    :cvar save_locations_keys: list of names of distinct save location.
        location are stored in "io"

    """

    mask_changed = Signal()
    points_changed = Signal()
    request_load_files = Signal(list)
    """:py:class:`~.Signal` mask changed signal"""
    json_encoder_class = ProfileEncoder
    load_metadata = staticmethod(load_metadata_base)
    algorithm_changed = Signal()
    """:py:class:`~.Signal` emitted when current algorithm should be changed"""
    save_locations_keys = []

    def __init__(self,
                 json_path: Union[Path, str],
                 profile_name: str = "default"):
        """
        :param json_path: path to store
        :param profile_name: name of profile to be used. default value is "default"
        """
        super().__init__()
        napari_path = os.path.dirname(json_path) if os.path.basename(
            json_path) in ["analysis", "mask"] else json_path
        self.napari_settings: "NapariSettings" = napari_get_settings(
            napari_path)
        self._current_roi_dict = profile_name
        self._roi_dict = ProfileDict()
        self.json_folder_path = json_path
        self.last_executed_algorithm = ""
        self.history: List[HistoryElement] = []
        self.history_index = -1
        self.last_executed_algorithm = ""
        self._points = None

    def _image_changed(self):
        super()._image_changed()
        self.points = None

    @property
    def points(self):
        return self._points

    @points.setter
    def points(self, value):
        self._points = value if value is not None else None
        self.points_changed.emit()

    @property
    def theme_name(self) -> str:
        try:
            theme = self.napari_settings.appearance.theme
            if self.get_from_profile("first_start", True):
                theme = "light"
                self.napari_settings.appearance.theme = theme
                self.set_in_profile("first_start", False)
            return theme
        except AttributeError:  # pragma: no cover
            return "light"

    @theme_name.setter
    def theme_name(self, value: str):
        self.napari_settings.appearance.theme = value

    def set_segmentation_result(self, result: ROIExtractionResult):
        if (result.file_path is not None and result.file_path != "" and
                result.file_path != self.image.file_path):  # pragma: no cover
            if self._parent is not None:
                # TODO change to non disrupting popup
                QMessageBox().warning(
                    self._parent, "Result file bug",
                    "It looks like one try to set ROI form another file.")
            return
        if result.info_text and self._parent is not None:
            QMessageBox().information(self._parent, "Algorithm info",
                                      result.info_text)

        self._additional_layers = result.additional_layers
        self.last_executed_algorithm = result.parameters.algorithm
        self.set(f"algorithms.{result.parameters.algorithm}",
                 result.parameters.values)
        # Fixme not use EventedDict here
        try:
            roi_info = result.roi_info.fit_to_image(self.image)
        except ValueError:  # pragma: no cover
            raise ValueError(ROI_NOT_FIT)
        if result.points is not None:
            self.points = result.points
        self._roi_info = roi_info
        self.roi_changed.emit(self._roi_info)

    def _load_files_call(self, files_list: List[str]):
        self.request_load_files.emit(files_list)

    def add_history_element(self, elem: HistoryElement) -> None:
        self.history_index += 1
        if self.history_index < len(self.history) and self.cmp_history_element(
                elem, self.history[self.history_index]):
            self.history[self.history_index] = elem
        else:
            self.history = self.history[:self.history_index]
            self.history.append(elem)

    def history_size(self) -> int:
        return self.history_index + 1

    def history_redo_size(self) -> int:
        if self.history_index + 1 == len(self.history):
            return 0
        return len(self.history[self.history_index + 1:])

    def history_redo_clean(self) -> None:
        self.history = self.history[:self.history_size()]

    def history_current_element(self) -> HistoryElement:
        return self.history[self.history_index]

    def history_next_element(self) -> HistoryElement:
        return self.history[self.history_index + 1]

    def history_pop(self) -> Optional[HistoryElement]:
        if self.history_index != -1:
            self.history_index -= 1
            return self.history[self.history_index + 1]
        return None

    def set_history(self, history: List[HistoryElement]):
        self.history = history
        self.history_index = len(self.history) - 1

    def get_history(self) -> List[HistoryElement]:
        return self.history[:self.history_index + 1]

    @staticmethod
    def cmp_history_element(el1, el2):
        return False

    @property
    def mask(self):
        return self._image.mask

    @mask.setter
    def mask(self, value):
        try:
            self._image.set_mask(value)
            self.mask_changed.emit()
        except ValueError:
            raise ValueError("mask do not fit to image")

    def get_save_list(self) -> List[SaveSettingsDescription]:
        """List of files in which program save the state."""
        return [
            SaveSettingsDescription("segmentation_settings.json",
                                    self._roi_dict),
            SaveSettingsDescription("view_settings.json",
                                    self.view_settings_dict),
        ]

    def get_path_history(self) -> List[str]:
        """
        return list containing last 10 elements added with :py:meth:`.add_path_history` and
        last opened in each category form :py:attr:`save_location_keys`
        """
        res = self.get(DIR_HISTORY, [])[:]
        for name in self.save_locations_keys:
            val = self.get("io." + name, str(Path.home()))
            if val not in res:
                res = res + [val]
        return res

    @staticmethod
    def _add_elem_to_list(data_list: list, value: Any, keep_len=10) -> list:
        try:
            data_list.remove(value)
        except ValueError:
            data_list = data_list[:keep_len - 1]
        return [value] + data_list

    def get_last_files(self) -> List[Tuple[Tuple[Union[str, Path], ...], str]]:
        return self.get(FILE_HISTORY, [])

    def add_load_files_history(self, file_path: Sequence[Union[str, Path]],
                               load_method: str):  # pragma: no cover
        warnings.warn("`add_load_files_history` is deprecated",
                      FutureWarning,
                      stacklevel=2)
        return self.add_last_files(file_path, load_method)

    def add_last_files(self, file_path: Sequence[Union[str, Path]],
                       load_method: str):
        self.set(
            FILE_HISTORY,
            self._add_elem_to_list(self.get(FILE_HISTORY, []),
                                   [list(file_path), load_method]))
        # keep list of files as list because json serialize tuple to list
        self.add_path_history(os.path.dirname(file_path[0]))

    def get_last_files_multiple(
            self) -> List[Tuple[Tuple[Union[str, Path], ...], str]]:
        return self.get(MULTIPLE_FILES_OPEN_HISTORY, [])

    def add_last_files_multiple(self, file_paths: List[Union[str, Path]],
                                load_method: str):
        self.set(
            MULTIPLE_FILES_OPEN_HISTORY,
            self._add_elem_to_list(self.get(MULTIPLE_FILES_OPEN_HISTORY, []),
                                   [list(file_paths), load_method],
                                   keep_len=30),
        )
        # keep list of files as list because json serialize tuple to list
        self.add_path_history(os.path.dirname(file_paths[0]))

    def add_path_history(self, dir_path: Union[str, Path]):
        """Save path in history of visited directories. Store only 10 last"""
        dir_path = str(dir_path)
        self.set(DIR_HISTORY,
                 self._add_elem_to_list(self.get(DIR_HISTORY, []), dir_path))

    def set(self, key_path: str, value):
        """
        function for saving general state (not visualization). This is accessor to
        :py:meth:`~.ProfileDict.set` of inner variable.

        :param key_path: dot separated path
        :param value: value to store. The value need to be json serializable.
        """
        self._roi_dict.set(f"{self._current_roi_dict}.{key_path}", value)

    def get(self, key_path: str, default=None):
        """
        Function for getting general state (not visualization). This is accessor to
        :py:meth:`~.ProfileDict.get` of inner variable.

        :param key_path: dot separated path
        :param default: default value if key is missed
        """
        return self._roi_dict.get(f"{self._current_roi_dict}.{key_path}",
                                  default)

    def connect_(self, key_path, callback):
        # TODO  fixme fix when introduce switch profiles
        self._roi_dict.connect(key_path, callback)

    def dump_part(self, file_path, path_in_dict, names=None):
        data = self.get(path_in_dict)
        if names is not None:
            data = {name: data[name] for name in names}
        os.makedirs(os.path.dirname(file_path), exist_ok=True)
        with open(file_path, "w", encoding="utf-8") as ff:
            json.dump(data, ff, cls=self.json_encoder_class, indent=2)

    @classmethod
    def load_part(cls, file_path):
        data = cls.load_metadata(file_path)
        bad_key = []
        if isinstance(data, MutableMapping) and not check_loaded_dict(data):
            for k, v in data.items():
                if not check_loaded_dict(v):
                    bad_key.append(k)
            for el in bad_key:
                del data[el]
        elif isinstance(data, ProfileDict) and not data.verify_data():
            bad_key = data.filter_data()
        return data, bad_key

    def dump(self, folder_path: Union[Path, str, None] = None):
        """
        Save current application settings to disc.

        :param folder_path: path to directory in which data should be saved.
            If is None then use :py:attr:`.json_folder_path`
        """
        if self.napari_settings.save is not None:
            self.napari_settings.save()
        else:
            self.napari_settings._save()  # pylint: disable=W0212
        if folder_path is None:
            folder_path = self.json_folder_path
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
        errors_list = []
        for el in self.get_save_list():
            try:
                dump_string = json.dumps(el.values,
                                         cls=self.json_encoder_class,
                                         indent=2)
                with open(os.path.join(folder_path, el.file_name),
                          "w",
                          encoding="utf-8") as ff:
                    ff.write(dump_string)
            except Exception as e:  # pylint: disable=W0703
                errors_list.append((e, os.path.join(folder_path,
                                                    el.file_name)))
        if errors_list:
            logger.error(errors_list)
        return errors_list

    def load(self, folder_path: Union[Path, str, None] = None):
        """
        Load settings state from given directory

        :param folder_path: path to directory in which data should be saved.
            If is None then use :py:attr:`.json_folder_path`
        """
        if folder_path is None:
            folder_path = self.json_folder_path
        errors_list = []
        for el in self.get_save_list():
            file_path = os.path.join(folder_path, el.file_name)
            if not os.path.exists(file_path):
                continue
            error = False
            try:
                data: ProfileDict = self.load_metadata(file_path)
                if not data.verify_data():
                    errors_list.append((file_path, data.filter_data()))
                    error = True
                el.values.update(data)
            except Exception as e:  # pylint: disable=W0703
                error = True
                errors_list.append((file_path, e))
            finally:
                if error:
                    timestamp = datetime.today().strftime("%Y-%m-%d_%H_%M_%S")
                    base_path, ext = os.path.splitext(file_path)
                    os.rename(file_path, base_path + "_" + timestamp + ext)

        if errors_list:
            logger.error(errors_list)
        return errors_list

    def get_project_info(self) -> ProjectInfoBase:
        """Get all information needed to save project"""
        raise NotImplementedError  # pragma: no cover

    def set_project_info(self, data: ProjectInfoBase):
        """Set project info"""
        raise NotImplementedError  # pragma: no cover

    @staticmethod
    def verify_image(image: Image, silent=True) -> Union[Image, bool]:
        if image.is_time:
            if image.is_stack:
                raise TimeAndStackException()
            if silent:
                return image.swap_time_and_stack()
            raise SwapTimeStackException()
        return True
Exemple #12
0
class ViewSettings(ImageSettings):
    colormap_changes = Signal()
    labels_changed = Signal()
    theme_changed = Signal()
    profile_data_changed = Signal(str, object)
    """Signal about changes in stored data (set with set_in_profile)"""
    def __init__(self):
        super().__init__()
        self.color_map = []
        self.border_val = []
        self.current_profile_dict = "default"
        self.view_settings_dict = ProfileDict()
        self.colormap_dict = ColormapDict(
            self.get_from_profile("custom_colormap", {}))
        self.label_color_dict = LabelColorDict(
            self.get_from_profile("custom_label_colors", {}))
        self.cached_labels: Optional[Tuple[str, np.ndarray]] = None

    @property
    def theme_name(self) -> str:
        """Name of current theme."""
        return self.get_from_profile("theme", "light")

    @property
    def theme(self):
        """Theme as structure."""
        try:
            return get_theme(self.theme_name, as_dict=False)
        except TypeError:  # pragma: no cover
            theme = get_theme(self.theme_name)
            return Namespace(
                **{
                    k: Color(v)
                    if isinstance(v, str) and v.startswith("rgb") else v
                    for k, v in theme.items()
                })

    @property
    def style_sheet(self):
        """QSS style sheet for current theme."""
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", FutureWarning)
            theme = get_theme(self.theme_name)
        # TODO understand qss overwrite mechanism
        return napari_template(
            "\n".join(register.qss_list) + get_stylesheet() +
            "\n".join(register.qss_list), **theme)

    @theme_name.setter
    def theme_name(self, value: str):
        """Name of current theme."""
        if value not in napari.utils.theme.available_themes():
            raise ValueError(
                f"Unsupported theme {value}. Supported one: {self.theme_list()}"
            )
        if value == self.theme_name:
            return
        self.set_in_profile("theme", value)
        self.theme_changed.emit()

    @staticmethod
    def theme_list():
        """Sequence of available themes"""
        try:
            return napari.utils.theme.available_themes()
        except:  # noqa: E722  # pylint: disable=W0702  # pragma: no cover
            return ("light", )

    @property
    def chosen_colormap(self):
        """Sequence of selected colormap to be available in dropdown"""
        data = self.get_from_profile("colormaps", starting_colors[:])
        res = [x for x in data if x in self.colormap_dict]
        if len(res) != data:
            if not res:
                res = starting_colors[:]
            self.set_in_profile("colormaps", res)
        return res

    @chosen_colormap.setter
    def chosen_colormap(self, val):
        self.set_in_profile("colormaps", val)
        self.colormap_changes.emit()

    @property
    def current_labels(self):
        """Current labels scheme for marking ROI"""
        return self.get_from_profile("labels_used", "default")

    @current_labels.setter
    def current_labels(self, val):
        if val not in self.label_color_dict:
            raise ValueError(f"Unknown label scheme name '{val}'")
        self.set_in_profile("labels_used", val)
        self.labels_changed.emit()

    @property
    def label_colors(self):
        key = self.current_labels
        if key not in self.label_color_dict:
            key = "default"
            self.current_labels = key

        if not (self.cached_labels and key == self.cached_labels[0]):
            self.cached_labels = key, self.label_color_dict.get_array(key)

        return self.cached_labels[1]

    def chosen_colormap_change(self, name, visibility):
        colormaps = set(self.chosen_colormap)
        if visibility:
            colormaps.add(name)
        else:
            with suppress(KeyError):
                colormaps.remove(name)
        # TODO update sorting rule
        self.chosen_colormap = list(
            sorted(colormaps, key=self.colormap_dict.get_position))

    def get_channel_info(
            self,
            view: str,
            num: int,
            default: Optional[str] = None) -> str:  # pragma: no cover
        warnings.warn(
            "get_channel_info is deprecated, use get_channel_colormap_name instead",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return self.get_channel_colormap_name(view, num, default)

    def get_channel_colormap_name(self,
                                  view: str,
                                  num: int,
                                  default: Optional[str] = None) -> str:
        cm = self.chosen_colormap
        if default is None:
            default = cm[num % len(cm)]
        resp = self.get_from_profile(f"{view}.cmap.num{num}", default)
        if resp not in self.colormap_dict:
            resp = cm[num % len(cm)]
            self.set_in_profile(f"{view}.cmap.num{num}", resp)
        return resp

    def set_channel_info(self, view: str, num, value: str):  # pragma: no cover
        warnings.warn(
            "set_channel_info is deprecated, use set_channel_colormap_name instead",
            category=DeprecationWarning,
            stacklevel=2,
        )
        self.set_channel_colormap_name(view, num, value)

    def set_channel_colormap_name(self, view: str, num, value: str):
        self.set_in_profile(f"{view}.cmap.num{num}", value)

    def connect_channel_colormap_name(self, view: str, fun: Callable):
        self.connect_to_profile(f"{view}.cmap", fun)

    @property
    def available_colormaps(self):
        return list(self.colormap_dict.keys())

    def _image_changed(self):
        self.border_val = self.image.get_ranges()
        super()._image_changed()

    def change_profile(self, name):
        self.current_profile_dict = name
        self.view_settings_dict.profile_change()

    def set_in_profile(self, key_path, value):
        """
        Function for saving information used in visualization. This is accessor to
        :py:meth:`~.ProfileDict.set` of inner variable.

        :param key_path: dot separated path
        :param value: value to store. The value need to be json serializable."""
        self.view_settings_dict.set(f"{self.current_profile_dict}.{key_path}",
                                    value)
        self.profile_data_changed.emit(key_path, value)

    def get_from_profile(self, key_path, default=None):
        """
        Function for getting information used in visualization. This is accessor to
        :py:meth:`~.ProfileDict.get` of inner variable.

        :param key_path: dot separated path
        :param default: default value if key is missed
        """
        return self.view_settings_dict.get(
            f"{self.current_profile_dict}.{key_path}", default)

    def connect_to_profile(self, key_path, callback):
        # TODO  fixme fix when introduce switch profiles
        self.view_settings_dict.connect(key_path, callback)
Exemple #13
0
    def test_callback(self):
        def dummy_call():
            receiver.dummy()

        receiver = MagicMock()

        dkt = ProfileDict()
        dkt.connect("", receiver.empty)
        dkt.connect("", dummy_call)
        dkt.connect("b", receiver.b)
        dkt.connect(["d", "c"], receiver.dc)

        dkt.set("test.a", 1)
        assert receiver.empty.call_count == 1
        assert receiver.dummy.call_count == 1
        receiver.empty.assert_called_with("a")
        receiver.dummy.assert_called_with()
        dkt.set("test.a", 1)
        assert receiver.empty.call_count == 1
        receiver.b.assert_not_called()
        dkt.set("test2.a", 1)
        assert receiver.empty.call_count == 2
        receiver.b.assert_not_called()
        dkt.set(["test", "b"], 1)
        assert receiver.empty.call_count == 3
        assert receiver.b.call_count == 1
        dkt.set("test.d.c", 1)
        receiver.dc.assert_called_once()
        dkt.set("test.a", 2)
        assert receiver.empty.call_count == 5
class BaseSettings(ViewSettings):
    """
    :ivar json_folder_path: default location for saving/loading settings data
    :ivar last_executed_algorithm: name of last executed algorithm.
    :cvar save_locations_keys: list of names of distinct save location.
        location are stored in "io"

    """

    mask_changed = Signal()
    mask_representation_changed = Signal()
    """:py:class:`~.Signal` mask changed signal"""
    json_encoder_class = ProfileEncoder
    load_metadata = staticmethod(load_metadata_base)
    algorithm_changed = Signal()
    """:py:class:`~.Signal` emitted when current algorithm should be changed"""
    save_locations_keys = []

    def __init__(self, json_path):
        super().__init__()
        self.current_segmentation_dict = "default"
        self.segmentation_dict = ProfileDict()
        self.json_folder_path = json_path
        self.last_executed_algorithm = ""
        self.history: List[HistoryElement] = []
        self.history_index = -1

    def mask_representation_changed_emit(self):
        self.mask_representation_changed.emit()

    def add_history_element(self, elem: HistoryElement) -> None:
        self.history_index += 1
        if self.history_index < len(self.history) and self.cmp_history_element(
                elem, self.history[self.history_index]):
            self.history[self.history_index] = elem
        else:
            self.history = self.history[:self.history_index]
            self.history.append(elem)

    def history_size(self) -> int:
        return self.history_index + 1

    def history_redo_size(self) -> int:
        if self.history_index + 1 == len(self.history):
            return 0
        return len(self.history[self.history_index + 1:])

    def history_redo_clean(self) -> None:
        self.history = self.history[:self.history_size()]

    def history_current_element(self) -> HistoryElement:
        return self.history[self.history_index]

    def history_next_element(self) -> HistoryElement:
        return self.history[self.history_index + 1]

    def history_pop(self) -> Optional[HistoryElement]:
        if self.history_index != -1:
            self.history_index -= 1
            return self.history[self.history_index + 1]
        return None

    def set_history(self, history: List[HistoryElement]):
        self.history = history
        self.history_index = len(self.history) - 1

    def get_history(self) -> List[HistoryElement]:
        return self.history[:self.history_index + 1]

    @staticmethod
    def cmp_history_element(el1, el2):
        return False

    @property
    def mask(self):
        return self._image.mask

    @mask.setter
    def mask(self, value):
        try:
            self._image.set_mask(value)
            self.mask_changed.emit()
        except ValueError:
            raise ValueError("mask do not fit to image")

    def get_save_list(self) -> List[SaveSettingsDescription]:
        """List of files in which program save the state."""
        return [
            SaveSettingsDescription("segmentation_settings.json",
                                    self.segmentation_dict),
            SaveSettingsDescription("view_settings.json",
                                    self.view_settings_dict),
        ]

    def get_path_history(self) -> List[str]:
        """
        return list containing last 10 elements added with :py:meth:`.add_path_history` and
        last opened in each category form :py:attr:`save_location_keys`
        """
        res = self.get("io.history", [])[:]
        for name in self.save_locations_keys:
            val = self.get("io." + name, str(Path.home()))
            if val not in res:
                res = res + [val]
        return res

    def add_path_history(self, dir_path: str):
        """Save path in history of visited directories. Store only 10 last"""
        history: List[str] = self.get("io.history", [])
        try:
            history.remove(dir_path)
        except ValueError:
            history = history[:9]

        self.set("io.history", [dir_path] + history[-9:])

    def set(self, key_path: str, value):
        """
        function for saving general state (not visualization). This is accessor to
        :py:meth:`~.ProfileDict.set` of inner variable.

        :param key_path: dot separated path
        :param value: value to store. The value need to be json serializable.
         """
        self.segmentation_dict.set(
            f"{self.current_segmentation_dict}.{key_path}", value)

    def get(self, key_path: str, default=None):
        """
        Function for getting general state (not visualization). This is accessor to
        :py:meth:`~.ProfileDict.get` of inner variable.

        :param key_path: dot separated path
        :param default: default value if key is missed
        """
        return self.segmentation_dict.get(
            f"{self.current_segmentation_dict}.{key_path}", default)

    def dump_part(self, file_path, path_in_dict, names=None):
        data = self.get(path_in_dict)
        if names is not None:
            data = {name: data[name] for name in names}
        with open(file_path, "w") as ff:
            json.dump(data, ff, cls=self.json_encoder_class, indent=2)

    def load_part(self, file_path):
        data = self.load_metadata(file_path)
        bad_key = []
        if isinstance(data, dict):
            if not check_loaded_dict(data):
                for k, v in data.items():
                    if not check_loaded_dict(v):
                        bad_key.append(k)
                for el in bad_key:
                    del data[el]
        elif isinstance(data, ProfileDict):
            if not data.verify_data():
                bad_key = data.filter_data()
        return data, bad_key

    def dump(self, folder_path: Optional[str] = None):
        """
        Save current application settings to disc.

        :param folder_path: path to directory in which data should be saved.
            If is None then use :py:attr:`.json_folder_path`
        """
        if folder_path is None:
            folder_path = self.json_folder_path
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
        errors_list = []
        for el in self.get_save_list():
            try:
                dump_string = json.dumps(el.values,
                                         cls=self.json_encoder_class,
                                         indent=2)
                with open(os.path.join(folder_path, el.file_name), "w") as ff:
                    ff.write(dump_string)
            except Exception as e:
                errors_list.append((e, os.path.join(folder_path,
                                                    el.file_name)))
        if errors_list:
            print(errors_list, file=sys.stderr)
        return errors_list

    def load(self, folder_path: Optional[str] = None):
        """
        Load settings state from given directory

        :param folder_path: path to directory in which data should be saved.
            If is None then use :py:attr:`.json_folder_path`
        """
        if folder_path is None:
            folder_path = self.json_folder_path
        errors_list = []
        for el in self.get_save_list():
            file_path = os.path.join(folder_path, el.file_name)
            if not os.path.exists(file_path):
                continue
            error = False
            try:
                data: ProfileDict = self.load_metadata(file_path)
                if not data.verify_data():
                    errors_list.append((file_path, data.filter_data()))
                    error = True
                el.values.update(data)
            except Exception as e:
                error = True
                errors_list.append((file_path, e))
            finally:
                if error:
                    timestamp = datetime.today().strftime("%Y-%m-%d_%H_%M_%S")
                    base_path, ext = os.path.splitext(file_path)
                    os.rename(file_path, base_path + "_" + timestamp + ext)

        if errors_list:
            print(errors_list, file=sys.stderr)
        return errors_list

    def get_project_info(self) -> ProjectInfoBase:
        """Get all information needed to save project"""
        raise NotImplementedError

    def set_project_info(self, data: ProjectInfoBase):
        """Set project info"""
        raise NotImplementedError

    @staticmethod
    def verify_image(image: Image, silent=True) -> Union[Image, bool]:
        if image.is_time:
            if image.is_stack:
                raise TimeAndStackException()
            if silent:
                return image.swap_time_and_stack()
            else:
                raise SwapTimeStackException()
        return True
 def change_profile(self, name):
     self.current_profile_dict = name
     if self.current_profile_dict not in self.view_settings_dict:
         self.view_settings_dict = {
             self.current_profile_dict: ProfileDict()
         }
class ViewSettings(ImageSettings):
    colormap_changes = Signal()
    labels_changed = Signal()
    theme_changed = Signal()

    def __init__(self):
        super().__init__()
        self.color_map = []
        self.border_val = []
        self.current_profile_dict = "default"
        self.view_settings_dict = ProfileDict()
        self.colormap_dict = ColormapDict(
            self.get_from_profile("custom_colormap", {}))
        self.label_color_dict = LabelColorDict(
            self.get_from_profile("custom_label_colors", {}))
        self.cached_labels: Optional[Tuple[str, np.ndarray]] = None

    @property
    def theme_name(self) -> str:
        return self.get_from_profile("theme", "light")

    @property
    def style_sheet(self):
        palette = napari.utils.theme.palettes[self.theme_name]
        palette["canvas"] = "black"
        return napari_template(get_stylesheet(), **palette)

    @theme_name.setter
    def theme_name(self, value: str):
        if value not in napari.utils.theme.palettes:
            raise ValueError(
                f"Unsupported theme {value}. Supported one: {self.theme_list()}"
            )
        if value == self.theme_name:
            return
        self.set_in_profile("theme", value)
        self.theme_changed.emit()

    @staticmethod
    def theme_list():
        return list(napari.utils.theme.palettes.keys())

    @property
    def chosen_colormap(self):
        data = self.get_from_profile("colormaps", starting_colors[:])
        res = [x for x in data if x in self.colormap_dict]
        if len(res) != data:
            if len(res) == 0:
                res = starting_colors[:]
            self.set_in_profile("colormaps", res)
        return res

    @chosen_colormap.setter
    def chosen_colormap(self, val):
        self.set_in_profile("colormaps", val)
        self.colormap_changes.emit()

    @property
    def current_labels(self):
        return self.get_from_profile("labels_used", "default")

    @current_labels.setter
    def current_labels(self, val):
        if val not in self.label_color_dict:
            raise ValueError(f"Unknown label scheme name '{val}'")
        self.set_in_profile("labels_used", val)
        self.labels_changed.emit()

    @property
    def label_colors(self):
        key = self.current_labels
        if key not in self.label_color_dict:
            key = "default"

        if not (self.cached_labels and key == self.cached_labels[0]):
            self.cached_labels = key, self.label_color_dict.get_array(key)

        return self.cached_labels[1]

    def chosen_colormap_change(self, name, visibility):
        colormaps = set(self.chosen_colormap)
        if visibility:
            colormaps.add(name)
        else:
            try:
                colormaps.remove(name)
            except KeyError:
                pass
        # TODO update sorting rule
        self.chosen_colormap = list(
            sorted(colormaps, key=self.colormap_dict.get_position))

    def get_channel_info(self,
                         view: str,
                         num: int,
                         default: Optional[str] = None) -> List[str]:
        cm = self.chosen_colormap
        if default is None:
            default = cm[num % len(cm)]
        resp = self.get_from_profile(f"{view}.cmap{num}", default)
        if resp not in self.colormap_dict:
            resp = cm[num % len(cm)]
            self.set_in_profile(f"{view}.cmap{num}", resp)
        return resp

    def set_channel_info(self, view: str, num, value: str):
        self.set_in_profile(f"{view}.cmap{num}", value)

    @property
    def available_colormaps(self):
        return list(self.colormap_dict.keys())

    def _image_changed(self):
        self.border_val = self.image.get_ranges()
        super()._image_changed()

    def change_profile(self, name):
        self.current_profile_dict = name
        if self.current_profile_dict not in self.view_settings_dict:
            self.view_settings_dict = {
                self.current_profile_dict: ProfileDict()
            }

    def set_in_profile(self, key_path, value):
        """
        Function for saving information used in visualization. This is accessor to
        :py:meth:`~.ProfileDict.set` of inner variable.

        :param key_path: dot separated path
        :param value: value to store. The value need to be json serializable. """
        self.view_settings_dict.set(f"{self.current_profile_dict}.{key_path}",
                                    value)

    def get_from_profile(self, key_path, default=None):
        """
        Function for getting information used in visualization. This is accessor to
        :py:meth:`~.ProfileDict.get` of inner variable.

        :param key_path: dot separated path
        :param default: default value if key is missed
        """
        return self.view_settings_dict.get(
            f"{self.current_profile_dict}.{key_path}", default)

    def dump_view_profiles(self):
        # return json.dumps(self.profile_dict, cls=ProfileEncoder)
        return self.view_settings_dict