Source code for arthropod_describer.common.state

import typing
from dataclasses import dataclass
from typing import Dict

import PySide2
import numpy as np
from PySide2.QtCore import QObject, Signal, Qt
from PySide2.QtGui import QTransform

from arthropod_describer.common.label_hierarchy import LabelHierarchy, Node
from arthropod_describer.common.label_image import LabelImg
from arthropod_describer.common.local_storage import Storage
from arthropod_describer.common.photo import Photo
from arthropod_describer.common.units import UnitStore


[docs]class LabelConstraint(QObject): constraint_changed = Signal('LabelConstraint') def __init__(self, label_name: str, parent: typing.Optional[PySide2.QtCore.QObject] = None): super().__init__(parent) self.label_name: str = label_name self._constraint_label_name: typing.Optional[str] = None self.label_level: int = 0 self._label_node: typing.Optional[Node] = None self.primary_label: int = 0 @property def constraint_label_name(self) -> typing.Optional[str]: return self._constraint_label_name @constraint_label_name.setter def constraint_label_name(self, lab_name: typing.Optional[str]): self._constraint_label_name = lab_name # self.constraint_changed.emit(self) @property def label_node(self) -> typing.Optional[Node]: return self._label_node @label_node.setter def label_node(self, label_node: typing.Optional[Node]): self._label_node = label_node # self.constraint_changed.emit(self) @property def label(self) -> int: return 0 if self._label_node is None else self._label_node.label
[docs]class LabelState: def __init__(self, label_name: str): self._label_name: str = label_name self.current_label: int = 0 self.current_level: int = 0 self.constraint: LabelConstraint = LabelConstraint(self._label_name) @property def label_name(self) -> str: return self._label_name
[docs]class State(QObject): colormap_changed = Signal([Dict[int, typing.Tuple[int, int, int]]]) storage_changed = Signal([Storage, Storage]) photo_changed = Signal(Photo) photo_index_changed = Signal(int) label_img_changed = Signal(LabelImg) label_hierarchy_changed = Signal(LabelHierarchy) label_constraint_changed = Signal(LabelConstraint) update_used_label_list = Signal() new_label_constraint = Signal(int) primary_label_changed = Signal(int) tags_filter_changed = Signal(list) def __init__(self, parent: QObject = None): from arthropod_describer.common.tool import Tool QObject.__init__(self, parent=parent) self._storage: typing.Optional[Storage] = None self._current_photo: typing.Optional[Photo] = None self._current_photo_idx: int = -1 self._current_label_img: typing.Optional[LabelImg] = None self._primary_label: int = 0 self._secondary_label: int = 0 self._label_hierarchy: typing.Optional[LabelHierarchy] = None # self.current_label_level: int = 0 self._current_label_name: str = '' self._label_constraint: LabelConstraint = None self.redraw_canvas: bool = True self._constraint_label: int = 0 self.constraints: Dict[str, LabelConstraint] = {} self.label_states: Dict[str, LabelState] = {} self.units: UnitStore = UnitStore() #self.viz_layer: typing.Optional[VisualizationLayer] = None self.key_modifier: typing.Optional[Qt.Key] = None self.current_tool: typing.Optional[Tool] = None self.current_view_transform: QTransform = QTransform() self._active_tags_filter: typing.List[str] = [] self._hidden_photos_count: int = 0 @property def colormap(self) -> typing.Optional[Dict[int, typing.Tuple[int, int, int]]]: if self._storage is None: return None return self.storage.get_label_hierarchy2(self.current_label_name).colormap @property def storage(self) -> Storage: return self._storage @storage.setter def storage(self, storage: Storage): old_storage = self._storage self._storage = storage # This if should not be necessary #if self._storage.label_hierarchy is None: # self._storage.label_hierarchy = self._label_hierarchy self.constraints.clear() for label_name in self._storage.label_image_names: lab_state = LabelState(label_name) # constr = LabelConstraint(label_name) # self.constraints[label_name] = constr self.label_states[label_name] = lab_state self._current_label_name = self._storage.default_label_image self.label_hierarchy = self._storage.get_label_hierarchy2(self.current_label_name) if self.label_hierarchy is not None: self.current_label_level = len(self.label_hierarchy.masks) - 1 self.storage_changed.emit(self._storage, old_storage) self._storage.storage_update.connect(self._handle_storage_update) @property def current_photo(self) -> Photo: return self._current_photo @current_photo.setter def current_photo(self, photo: Photo): self._current_photo = photo self.photo_changed.emit(self._current_photo) @property def current_photo_index(self) -> int: return self._current_photo_idx @current_photo_index.setter def current_photo_index(self, idx: int): self._current_photo_idx = idx self.current_photo = self.storage.get_photo_by_idx(self._current_photo_idx) self.photo_index_changed.emit(self._current_photo_idx) @property def label_img(self) -> LabelImg: return self._current_label_img @label_img.setter def label_img(self, _label_img: LabelImg): self._current_label_img = _label_img if self.colormap is not None: #ulabels = set(list(np.unique(self._current_photo.regions_image.label_img))) ulabels = set(list(np.unique(self._current_photo[self.current_label_name].label_image))) # TODO REMOVE #self.colormap._used_labels = ulabels self.update_used_label_list.emit() self.label_img_changed.emit(self._current_label_img) @property def primary_label(self) -> int: # return self.constraints[self.current_label_name].primary_label return self.label_states[self.current_label_name].current_label @primary_label.setter def primary_label(self, label: int): # self.constraints[self.current_label_name].primary_label = label self.label_states[self.current_label_name].current_label = label self.primary_label_changed.emit(label) @property def secondary_label(self) -> int: return self._secondary_label @secondary_label.setter def secondary_label(self, label: int): self._secondary_label = label @property def label_hierarchy(self) -> LabelHierarchy: if self.storage is None: return None return self.storage.get_label_hierarchy2(self.current_label_name) @label_hierarchy.setter def label_hierarchy(self, lab_hier: LabelHierarchy): self._label_hierarchy = lab_hier if self._storage is not None: self._storage.label_hierarchy = lab_hier self.label_hierarchy_changed.emit(self._label_hierarchy) @property def current_label_name(self) -> str: return self._current_label_name @current_label_name.setter def current_label_name(self, lab_name: str): self._current_label_name = lab_name self._label_constraint = self.label_states[lab_name].constraint lbl_info = self.storage.label_img_info[self._current_label_name] if lbl_info.can_be_constrained: if lbl_info.constrain_to is not None: self.current_constraint.label_name = lbl_info.constrain_to constr_lab_hier = self.storage.get_label_hierarchy2(lbl_info.constrain_to) self.current_constraint.label_node = constr_lab_hier.mask_label self.constraint_label = constr_lab_hier.mask_label.label self.current_constraint.label_node = constr_lab_hier.mask_label # else: # # TODO maybe restore the latest constraint for the particular image:label combination? # self.current_constraint.label_name = None # self.current_constraint.label_node = None # self.current_constraint.label_level = -1 # elif lbl_info.can_constrain_to is not None: # self.current_constraint.label_name = lbl_info.can_constrain_to[0] # TODO maybe emit a signal @property def current_constraint(self) -> LabelConstraint: return self.label_states[self.current_label_name].constraint @property def constraint_label(self) -> int: return self.current_constraint.label @constraint_label.setter def constraint_label(self, label: int): # self._constraint_label = label self.current_constraint.label_node = self.label_hierarchy.nodes[label] #print('emitting') if self.storage.image_count > 0 and self.current_photo is not None: self.new_label_constraint.emit(self.current_constraint.label_node.label) @property def label_can_be_constrained(self) -> bool: if self.storage is None: return False return self.storage.label_img_info[self.current_label_name].can_be_constrained
[docs] def set_label_constraint(self, label_name: typing.Optional[str], emit: bool = False): if label_name == '': return if label_name is None or label_name == 'None': self.current_constraint.label_name = None self.set_level_constraint(-1, emit) else: self.current_constraint.label_name = label_name self.set_level_constraint(self.current_label_level - 1, emit)
[docs] def set_level_constraint(self, label_level: int, emit: bool = False): self.current_constraint.label_level = label_level if emit: self.label_constraint_changed.emit(self.current_constraint)
@property def current_label_level(self) -> int: return self.label_states[self.current_label_name].current_level @current_label_level.setter def current_label_level(self, level: int): if len(self.label_states) == 0: return self.label_states[self.current_label_name].current_level = level @property def active_tags_filter(self) -> typing.List[str]: return self._active_tags_filter @active_tags_filter.setter def active_tags_filter(self, tags: typing.Iterable[str]): self._active_tags_filter = list(sorted(tags)) self._update_hidden_photos_count() self.tags_filter_changed.emit(self._active_tags_filter)
[docs] def add_filtering_tag(self, tag: str): self._active_tags_filter.append(tag) self._active_tags_filter = list(sorted(set(self._active_tags_filter))) self._update_hidden_photos_count() self.tags_filter_changed.emit(self._active_tags_filter)
[docs] def remove_filtering_tag(self, tag: str): if tag not in self._active_tags_filter: return self._active_tags_filter.remove(tag) self._update_hidden_photos_count() self.tags_filter_changed.emit(self._active_tags_filter)
[docs] def toggle_filtering_tag(self, tag: str, active: bool): if active: self._active_tags_filter.append(tag) self._active_tags_filter = sorted(self._active_tags_filter) else: self._active_tags_filter.remove(tag) self._update_hidden_photos_count() self.tags_filter_changed.emit(self._active_tags_filter)
[docs] def clear_tag_filter(self): self._active_tags_filter.clear() self._update_hidden_photos_count() self.tags_filter_changed.emit(self._active_tags_filter)
def _update_hidden_photos_count(self): shown_photos = self.storage.photos_satisfying_tags(set(self._active_tags_filter)) self._hidden_photos_count = self.storage.image_count - len(shown_photos) def _handle_storage_update(self, data: typing.Dict[str, typing.Any]): if 'tags' in data: for tag in data['tags'].setdefault('deleted', []): self.remove_filtering_tag(tag) @property def hidden_photos_count(self) -> int: return self._hidden_photos_count