def __init__(self, browser, regions, parent): QFrame.__init__(self, parent) layout = QGridLayout(self) layout.setContentsMargins(5, 5, 5, 5) self.browser = browser box_detect = QCheckBox('Detect Objects', self) box_detect.setChecked(False) box_detect.toggled.connect(self._on_detect_objects) layout.addWidget(box_detect, 0, 0) self._box_detect = box_detect self._box_region = QComboBox(self) self._box_region.setEnabled(box_detect.checkState() == Qt.Checked) self.update_regionbox(regions) self._box_region.activated[int].connect( self._on_current_region_changed) if len(regions) > 0: self._box_region.setCurrentIndex(0) layout.addWidget(self._box_region, 1, 0, 1, 2) layout.addWidget(QLabel('Show Contours:'), 2, 0) box = QRadioButton('by color', self) box.setEnabled(False) box.setChecked(True) box.toggled.connect(self._on_show_by_color) layout.addWidget(box, 3, 0) self._box_contours = box self._btn_contour_color = ColorButton(None, self) self._btn_contour_color.setEnabled( box_detect.checkState() == Qt.Checked) color = QColor('white') color.setAlphaF(1) self._btn_contour_color.set_color(color) self._btn_contour_color.color_changed.connect( \ self._on_contour_color_changed) layout.addWidget(self._btn_contour_color, 3, 1) box_classify = QRadioButton('by classification', self) box.setEnabled(False) box.setChecked(False) box_classify.toggled.connect(self._on_show_by_classification) box_classify.setChecked(False) layout.addWidget(box_classify, 4, 0) self._box_classify = box_classify box_show_contours = QCheckBox('show all', self) box_show_contours.setChecked(True) box_show_contours.toggled.connect(self._on_toggle_show_contours) box_show_contours.setEnabled(box_detect.checkState() == Qt.Checked) layout.addWidget(box_show_contours, 5, 0) self._box_show_contours = box_show_contours
def __init__(self, browser, regions, parent): QFrame.__init__(self, parent) layout = QGridLayout(self) layout.setContentsMargins(5, 5, 5, 5) self.browser = browser box_detect = QCheckBox('Detect Objects', self) box_detect.setChecked(False) box_detect.toggled.connect(self._on_detect_objects) layout.addWidget(box_detect, 0, 0) self._box_detect = box_detect self._box_region = QComboBox(self) self._box_region.setEnabled(box_detect.checkState() == Qt.Checked) self.update_regionbox(regions) self._box_region.activated[int].connect(self._on_current_region_changed) if len(regions) > 0: self._box_region.setCurrentIndex(0) layout.addWidget(self._box_region, 1, 0, 1, 2) layout.addWidget(QLabel('Show Contours:'), 2, 0) box = QRadioButton('by color', self) box.setEnabled(False) box.setChecked(True) box.toggled.connect(self._on_show_by_color) layout.addWidget(box, 3, 0) self._box_contours = box self._btn_contour_color = ColorButton(None, self) self._btn_contour_color.setEnabled(box_detect.checkState() == Qt.Checked) color = QColor('white') color.setAlphaF(1) self._btn_contour_color.set_color(color) self._btn_contour_color.color_changed.connect( \ self._on_contour_color_changed) layout.addWidget(self._btn_contour_color, 3, 1) box_classify = QRadioButton('by classification', self) box.setEnabled(False) box.setChecked(False) box_classify.toggled.connect(self._on_show_by_classification) box_classify.setChecked(False) layout.addWidget(box_classify, 4, 0) self._box_classify = box_classify box_show_contours = QCheckBox('show all', self) box_show_contours.setChecked(True) box_show_contours.toggled.connect(self._on_toggle_show_contours) box_show_contours.setEnabled(box_detect.checkState() == Qt.Checked) layout.addWidget(box_show_contours, 5, 0) self._box_show_contours = box_show_contours
class ObjectsFrame(QFrame): object_region_changed = pyqtSignal("PyQt_PyObject", "PyQt_PyObject") def __init__(self, browser, regions, parent): QFrame.__init__(self, parent) layout = QGridLayout(self) layout.setContentsMargins(5, 5, 5, 5) self.browser = browser box_detect = QCheckBox('Detect Objects', self) box_detect.setChecked(False) box_detect.toggled.connect(self._on_detect_objects) layout.addWidget(box_detect, 0, 0) self._box_detect = box_detect self._box_region = QComboBox(self) self._box_region.setEnabled(box_detect.checkState() == Qt.Checked) self.update_regionbox(regions) self._box_region.activated[int].connect( self._on_current_region_changed) if len(regions) > 0: self._box_region.setCurrentIndex(0) layout.addWidget(self._box_region, 1, 0, 1, 2) layout.addWidget(QLabel('Show Contours:'), 2, 0) box = QRadioButton('by color', self) box.setEnabled(False) box.setChecked(True) box.toggled.connect(self._on_show_by_color) layout.addWidget(box, 3, 0) self._box_contours = box self._btn_contour_color = ColorButton(None, self) self._btn_contour_color.setEnabled( box_detect.checkState() == Qt.Checked) color = QColor('white') color.setAlphaF(1) self._btn_contour_color.set_color(color) self._btn_contour_color.color_changed.connect( \ self._on_contour_color_changed) layout.addWidget(self._btn_contour_color, 3, 1) box_classify = QRadioButton('by classification', self) box.setEnabled(False) box.setChecked(False) box_classify.toggled.connect(self._on_show_by_classification) box_classify.setChecked(False) layout.addWidget(box_classify, 4, 0) self._box_classify = box_classify box_show_contours = QCheckBox('show all', self) box_show_contours.setChecked(True) box_show_contours.toggled.connect(self._on_toggle_show_contours) box_show_contours.setEnabled(box_detect.checkState() == Qt.Checked) layout.addWidget(box_show_contours, 5, 0) self._box_show_contours = box_show_contours def update_regionbox(self, regions): current = self._box_region.currentText() self._box_region.clear() for k, v in regions.iteritems(): self._box_region.addItem(k, v) try: # try to keep the current setting self._box_region.setCurrentIndex( \ self._box_region.findText(current, flags=Qt.MatchExactly)) except Exception: pass def _on_show_by_color(self, state): if state: self.browser._show_objects_by = 'color' self.browser.on_refresh() def _on_show_by_classification(self, state): if state: self.browser._show_objects_by = 'classification' self.browser.on_refresh() def _on_current_region_changed(self, index): channel, region = self._box_region.itemData(index) self.object_region_changed.emit(channel, region) def _on_detect_objects(self, state): # Enable depending buttons self._box_region.setEnabled(state) self._box_contours.setEnabled(state) self._box_classify.setEnabled(state) self._box_show_contours.setEnabled(state) self._btn_contour_color.setEnabled(state) self.browser._detect_objects = state self.browser.detect_objects_toggled(state) def _on_set_contour_state(self, state): self._box_contours.blockSignals(True) self._box_contours.setChecked(state) self._box_contours.blockSignals(False) def _on_contour_color_changed(self, color): self.browser._contour_color = color self.browser.image_viewer.set_contour_color(color) def _on_toggle_show_contours(self, state): self.browser._show_objects = state self.browser.image_viewer.show_contours = state self.browser.image_viewer._update_contours()
def __init__(self, parent, browser, settings, imagecontainer): Module.__init__(self, parent, browser) self._current_class = None self._detect_objects = False self._current_scene_items = set() self._settings = settings self._imagecontainer = imagecontainer self._annotations = Annotations() self._object_items = {} splitter = QSplitter(Qt.Vertical, self) grp_box = QGroupBox('Classes', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) class_table = QTableWidget(grp_box) class_table.setEditTriggers(QTableWidget.NoEditTriggers) class_table.setSelectionMode(QTableWidget.SingleSelection) class_table.setSelectionBehavior(QTableWidget.SelectRows) #class_table.setSortingEnabled(True) class_table.setColumnCount(4) class_table.setHorizontalHeaderLabels(['Name', 'Label', 'Color', 'Samples', ]) class_table.resizeColumnsToContents() class_table.currentItemChanged.connect(self._on_class_changed) class_table.setStyleSheet('font-size: 10px;') layout.addWidget(class_table) self._class_table = class_table frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) self._import_class_definitions_btn = QPushButton('Import class definitions') layout2.addWidget(self._import_class_definitions_btn) self._import_class_definitions_btn.clicked.connect(self._on_import_class_definitions) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) self._class_sbox = QSpinBox(frame2) self._class_color_btn = ColorButton(None, frame2) self._class_sbox.setRange(0, 1000) self._class_text = QLineEdit(frame2) layout2.addWidget(self._class_color_btn) layout2.addWidget(self._class_sbox) layout2.addWidget(self._class_text) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) btn = QPushButton('Apply', frame2) btn.clicked.connect(self._on_class_apply) layout2.addWidget(btn) btn = QPushButton('Add', frame2) btn.clicked.connect(self._on_class_add) layout2.addWidget(btn) btn = QPushButton('Remove', frame2) btn.clicked.connect(self._on_class_remove) layout2.addWidget(btn) layout.addWidget(frame2) splitter.addWidget(grp_box) grp_box = QGroupBox('Annotations', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) ann_table = QTableWidget(grp_box) ann_table.setEditTriggers(QTableWidget.NoEditTriggers) ann_table.setSelectionMode(QTableWidget.SingleSelection) ann_table.setSelectionBehavior(QTableWidget.SelectRows) #ann_table.setSortingEnabled(True) column_names = ['Position', 'Frame', 'Samples'] if self._imagecontainer.has_multiple_plates: column_names = ['Plate'] + column_names ann_table.setColumnCount(len(column_names)) ann_table.setHorizontalHeaderLabels(column_names) ann_table.resizeColumnsToContents() ann_table.currentItemChanged.connect(self._on_anntable_changed) ann_table.setStyleSheet('font-size: 10px;') layout.addWidget(ann_table) self._ann_table = ann_table splitter.addWidget(grp_box) frame = QFrame(grp_box) layout_frame = QBoxLayout(QBoxLayout.LeftToRight, frame) layout_frame.setContentsMargins(0, 0, 0, 0) btn = QPushButton('New', frame) btn.clicked.connect(self._on_new_classifier) layout_frame.addWidget(btn) #layout_frame.addSpacing(5) btn = QPushButton('Open', frame) btn.clicked.connect(self._on_open_classifier) layout_frame.addWidget(btn) btn = QPushButton('Save', frame) btn.clicked.connect(self._on_save_classifier) layout_frame.addWidget(btn) #layout_frame.addSpacing(5) btn = QPushButton('Save as', frame) btn.pressed.connect(self._on_saveas_classifier) layout_frame.addWidget(btn) layout.addWidget(frame) layout = QBoxLayout(QBoxLayout.TopToBottom, self) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(splitter) self._learner = self._init_new_classifier() self._action_grp = QActionGroup(browser) class_fct = lambda id: lambda : self._on_shortcut_class_selected(id) for x in range(1,11): action = browser.create_action( 'Select Class Label %d' % x, shortcut=QKeySequence(str(x) if x < 10 else '0'), slot=class_fct(x)) self._action_grp.addAction(action) browser.addAction(action) browser.coordinates_changed.connect(self._on_coordinates_changed) browser.show_objects_toggled.connect(self._on_show_objects) browser.show_contours_toggled.connect(self._on_show_contours_toggled)
class AnnotationModule(Module): NAME = 'Annotation' COLUMN_CLASS_NAME = 0 COLUMN_CLASS_LABEL = 1 COLUMN_CLASS_COLOR = 2 COLUMN_CLASS_COUNT = 3 COLUMN_ANN_PLATE = 0 COLUMN_ANN_POSITION = 1 COLUMN_ANN_TIME = 2 COLUMN_ANN_SAMPLES = 3 def __init__(self, parent, browser, settings, imagecontainer): Module.__init__(self, parent, browser) self._current_class = None self._detect_objects = False self._current_scene_items = set() self._settings = settings self._imagecontainer = imagecontainer self._annotations = Annotations() self._object_items = {} splitter = QSplitter(Qt.Vertical, self) grp_box = QGroupBox('Classes', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) class_table = QTableWidget(grp_box) class_table.setEditTriggers(QTableWidget.NoEditTriggers) class_table.setSelectionMode(QTableWidget.SingleSelection) class_table.setSelectionBehavior(QTableWidget.SelectRows) #class_table.setSortingEnabled(True) class_table.setColumnCount(4) class_table.setHorizontalHeaderLabels(['Name', 'Label', 'Color', 'Samples', ]) class_table.resizeColumnsToContents() class_table.currentItemChanged.connect(self._on_class_changed) class_table.setStyleSheet('font-size: 10px;') layout.addWidget(class_table) self._class_table = class_table frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) self._import_class_definitions_btn = QPushButton('Import class definitions') layout2.addWidget(self._import_class_definitions_btn) self._import_class_definitions_btn.clicked.connect(self._on_import_class_definitions) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) self._class_sbox = QSpinBox(frame2) self._class_color_btn = ColorButton(None, frame2) self._class_sbox.setRange(0, 1000) self._class_text = QLineEdit(frame2) layout2.addWidget(self._class_color_btn) layout2.addWidget(self._class_sbox) layout2.addWidget(self._class_text) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) btn = QPushButton('Apply', frame2) btn.clicked.connect(self._on_class_apply) layout2.addWidget(btn) btn = QPushButton('Add', frame2) btn.clicked.connect(self._on_class_add) layout2.addWidget(btn) btn = QPushButton('Remove', frame2) btn.clicked.connect(self._on_class_remove) layout2.addWidget(btn) layout.addWidget(frame2) splitter.addWidget(grp_box) grp_box = QGroupBox('Annotations', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) ann_table = QTableWidget(grp_box) ann_table.setEditTriggers(QTableWidget.NoEditTriggers) ann_table.setSelectionMode(QTableWidget.SingleSelection) ann_table.setSelectionBehavior(QTableWidget.SelectRows) #ann_table.setSortingEnabled(True) column_names = ['Position', 'Frame', 'Samples'] if self._imagecontainer.has_multiple_plates: column_names = ['Plate'] + column_names ann_table.setColumnCount(len(column_names)) ann_table.setHorizontalHeaderLabels(column_names) ann_table.resizeColumnsToContents() ann_table.currentItemChanged.connect(self._on_anntable_changed) ann_table.setStyleSheet('font-size: 10px;') layout.addWidget(ann_table) self._ann_table = ann_table splitter.addWidget(grp_box) frame = QFrame(grp_box) layout_frame = QBoxLayout(QBoxLayout.LeftToRight, frame) layout_frame.setContentsMargins(0, 0, 0, 0) btn = QPushButton('New', frame) btn.clicked.connect(self._on_new_classifier) layout_frame.addWidget(btn) #layout_frame.addSpacing(5) btn = QPushButton('Open', frame) btn.clicked.connect(self._on_open_classifier) layout_frame.addWidget(btn) btn = QPushButton('Save', frame) btn.clicked.connect(self._on_save_classifier) layout_frame.addWidget(btn) #layout_frame.addSpacing(5) btn = QPushButton('Save as', frame) btn.pressed.connect(self._on_saveas_classifier) layout_frame.addWidget(btn) layout.addWidget(frame) layout = QBoxLayout(QBoxLayout.TopToBottom, self) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(splitter) self._learner = self._init_new_classifier() self._action_grp = QActionGroup(browser) class_fct = lambda id: lambda : self._on_shortcut_class_selected(id) for x in range(1,11): action = browser.create_action( 'Select Class Label %d' % x, shortcut=QKeySequence(str(x) if x < 10 else '0'), slot=class_fct(x)) self._action_grp.addAction(action) browser.addAction(action) browser.coordinates_changed.connect(self._on_coordinates_changed) browser.show_objects_toggled.connect(self._on_show_objects) browser.show_contours_toggled.connect(self._on_show_contours_toggled) def _find_items_in_class_table(self, value, column, match=Qt.MatchExactly): items = self._class_table.findItems(value, match) return [item for item in items if item.column() == column] def _on_import_class_definitions(self): if self._on_new_classifier(): path = self._learner.clf_dir if path is None: path = os.path.expanduser('~') result = QFileDialog.getExistingDirectory( self, 'Open classifier directory', os.path.abspath(path)) if result: learner = self._load_classifier(result) if learner is not None: self._learner = learner self._update_class_definition_table() self._learner.unset_clf_dir() def _update_class_definition_table(self): class_table = self._class_table class_table.clearContents() class_table.setRowCount(len(self._learner.class_labels)) select_item = None for idx, class_label in enumerate(self._learner.class_names): samples = 0 class_name = self._learner.class_names[class_label] item = QTableWidgetItem(class_name) class_table.setItem(idx, self.COLUMN_CLASS_NAME, item) if select_item is None: select_item = item class_table.setItem(idx, self.COLUMN_CLASS_LABEL, QTableWidgetItem(str(class_label))) class_table.setItem(idx, self.COLUMN_CLASS_COUNT, QTableWidgetItem(str(samples))) item = QTableWidgetItem(' ') item.setBackground( QBrush(QColor(self._learner.hexcolors[class_name]))) class_table.setItem(idx, self.COLUMN_CLASS_COLOR, item) class_table.resizeColumnsToContents() class_table.resizeRowsToContents() self._activate_objects_for_image(False, clear=True) def _on_class_apply(self): ''' Apply class changes ''' learner = self._learner class_name_new = str(self._class_text.text()) class_label_new = self._class_sbox.value() class_name = self._current_class if not class_name is None: class_label = learner.class_labels[class_name] class_labels = learner.class_labels.values() class_labels.remove(class_label) class_names = learner.class_names.values() class_names.remove(class_name) if len(class_name_new) == 0: warning(self, "Invalid class name", info="The class name must not be empty!") elif (not class_name_new in class_names and not class_label_new in class_labels): del learner.class_names[class_label] del learner.class_labels[class_name] del learner.hexcolors[class_name] item = self._find_items_in_class_table(class_name, self.COLUMN_CLASS_NAME)[0] learner.class_names[class_label_new] = class_name_new learner.class_labels[class_name_new] = class_label_new class_color = self._class_color_btn.current_color learner.hexcolors[class_name_new] = \ qcolor_to_hex(class_color) item.setText(class_name_new) item2 = self._class_table.item(item.row(), self.COLUMN_CLASS_LABEL) item2.setText(str(class_label_new)) item2 = self._class_table.item(item.row(), self.COLUMN_CLASS_COLOR) item2.setBackground(QBrush(class_color)) col = get_qcolor_hicontrast(class_color) self._class_table.resizeRowsToContents() self._class_table.resizeColumnsToContents() self._class_table.scrollToItem(item) css = "selection-background-color: %s; selection-color: %s;" %\ (qcolor_to_hex(class_color), qcolor_to_hex(col)) self._class_table.setStyleSheet(css) self._ann_table.setStyleSheet(css) self._annotations.rename_class(class_name, class_name_new) self._current_class = class_name_new self._activate_objects_for_image(False) self._activate_objects_for_image(True) else: warning(self, "Class names and labels must be unique!", info="Class name '%s' or label '%s' already used." %\ (class_name_new, class_label_new)) def _on_class_add(self): """Add a new class to definition""" learner = self._learner class_name_new = str(self._class_text.text()) class_label_new = self._class_sbox.value() class_labels = learner.class_labels.values() class_names = learner.class_names.values() if len(class_name_new) == 0: warning(self, "Invalid class name", info="The class name must not be empty!") elif (not class_name_new in class_names and not class_label_new in class_labels): self._current_class = class_name_new learner.class_names[class_label_new] = class_name_new learner.class_labels[class_name_new] = class_label_new class_color = self._class_color_btn.current_color learner.hexcolors[class_name_new] = \ qcolor_to_hex(class_color) row = self._class_table.rowCount() self._class_table.insertRow(row) self._class_table.setItem(row, self.COLUMN_CLASS_NAME, QTableWidgetItem(class_name_new)) self._class_table.setItem(row, self.COLUMN_CLASS_LABEL, QTableWidgetItem(str(class_label_new))) self._class_table.setItem(row, self.COLUMN_CLASS_COUNT, QTableWidgetItem('0')) item = QTableWidgetItem() item.setBackground(QBrush(class_color)) self._class_table.setItem(row, self.COLUMN_CLASS_COLOR, item) self._class_table.resizeRowsToContents() self._class_table.resizeColumnsToContents() self._class_table.setCurrentItem(item) ncl = len(learner.class_names)+1 self._class_text.setText('class%d' %ncl) self._class_sbox.setValue(ncl) else: warning(self, "Class names and labels must be unique!", info="Class name '%s' or label '%s' already used." %\ (class_name_new, class_label_new)) def _on_class_remove(self): ''' Remove a class and all its annotations ''' class_name = self._current_class if (not class_name is None and question(self, "Do you really want to remove class '%s'?" % \ class_name, info="All %d annotations will be lost." % \ self._annotations.get_count_for_class(class_name))): self._activate_objects_for_image(False) learner = self._learner class_label = learner.class_labels[class_name] del learner.class_labels[class_name] del learner.class_names[class_label] del learner.hexcolors[class_name] item = self._find_items_in_class_table(class_name, self.COLUMN_CLASS_NAME)[0] row = item.row() self._class_table.removeRow(row) self._annotations.remove_class(class_name) self._activate_objects_for_image(True, clear=True) row_count = self._class_table.rowCount() if row_count > 0: row = row if row < row_count else row_count-1 item = self._class_table.item(row, self.COLUMN_CLASS_NAME) self._class_table.setCurrentItem(item) else: self._update_annotation_table() def _init_new_classifier(self): learner = BaseLearner(None, None, None) self._current_class = None self._class_sbox.setValue(1) self._class_text.setText('class1') self._class_color_btn.set_color(QColor('red')) class_table = self._class_table class_table.clearContents() class_table.setRowCount(0) ann_table = self._ann_table ann_table.clearContents() ann_table.setRowCount(0) if self._detect_objects: self._activate_objects_for_image(False, clear=True) self._annotations.remove_all() return learner def _on_new_classifier(self): ok = False if question(self, 'New classifier', 'Are you sure to setup a new classifer?\nAll annotations ' 'will be lost.'): self._learner = self._init_new_classifier() ok = True return ok def _on_open_classifier(self): path = self._learner.clf_dir result = QFileDialog.getExistingDirectory(self, 'Open classifier directory', os.path.abspath(path)) if result: learner = self._load_classifier(result) if not learner is None: self._learner = learner self._update_class_definition_table() self._activate_objects_for_image(False, clear=True) path2 = learner.annotations_dir try: has_invalid = self._annotations.import_from_xml(path2, learner.class_names, self._imagecontainer) except: exception(self, "Problems loading annotation data...") self._learner = self._init_new_classifier() else: self._activate_objects_for_image(True) self._update_class_table() if self._class_table.rowCount() > 0: self._class_table.setCurrentCell(0, self.COLUMN_CLASS_NAME) else: self._current_class = None information(self, "Classifier successfully loaded", "Class definitions and annotations " "successfully loaded from '%s'." % result) finally: coord = self.browser.get_coordinate() self._imagecontainer.set_plate(coord.plate) def _on_save_classifier(self): learner = self._learner path = learner.clf_dir self._on_saveas_classifier(path) def _on_saveas_classifier(self, path=None): learner = self._learner if path is None: path = os.path.expanduser("~") result = QFileDialog.getExistingDirectory( self, 'Save to classifier directory', os.path.abspath(path)) else: result = path if result: if self._save_classifier(result): try: path2 = learner.annotations_dir filenames = os.listdir(path2) filenames = [os.path.join(path2, f) for f in filenames if os.path.isfile(os.path.join(path2, f)) and os.path.splitext(f)[1].lower() == '.xml'] fmt = time.strftime('_backup__%Y%m%d_%H%M%S') path_backup = os.path.join(path2, fmt) safe_mkdirs(path_backup) for filename in filenames: shutil.copy2(filename, path_backup) os.remove(filename) self._annotations.export_to_xml(path2, learner.class_labels, self._imagecontainer) except: exception(self, "Problems saving annotation data...") else: information(self, "Classifier successfully saved", "Class definitions and annotations " "successfully saved to '%s'." % result) finally: coord = self.browser.get_coordinate() self._imagecontainer.set_plate(coord.plate) def _activate_objects_for_image(self, state, clear=False): ''' activate or ''' if clear: self._object_items.clear() coordinate = self.browser.get_coordinate() for class_name, tpl in self._annotations.iter_items(coordinate): point = QPointF(*tpl) item = self.browser.image_viewer.get_object_item(point) if not item is None: if not item in self._object_items: self._object_items[item] = point self._activate_object(item, point, class_name, state=state) def _update_class_table(self): ''' update the class count for the class table ''' counts = self._annotations.get_class_counts() for class_name in self._learner.class_names.values(): if class_name in counts: items = self._find_items_in_class_table(class_name, self.COLUMN_CLASS_NAME) item = self._class_table.item(items[0].row(), self.COLUMN_CLASS_COUNT) item.setText(str(counts[class_name])) #self._class_table.update() def _update_annotation_table(self): ''' update the annotation table. set the annotation coordinates for the current class ''' per_class = \ self._annotations.get_annotations_per_class(self._current_class) ann_table = self._ann_table ann_table.blockSignals(True) ann_table.clearContents() ann_table.setRowCount(len(per_class)) for idx, data in enumerate(per_class): plate, position, time, nr_samples = data #m = self._imagecontainer.get_meta_data(plate) # plateid in m.plateids #if position in m.positions and time in m.times: flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable tooltip = 'Jump to coordinate to see the annotation.' #else: # flags = Qt.NoItemFlags # tooltip = 'Coordinate not found in this data set.' # make plate information dependent whether the data set contains # multiple plates if self._imagecontainer.has_multiple_plates: item = QTableWidgetItem(plate) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_PLATE, item) offset = 0 else: offset = 1 item = QTableWidgetItem(position) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_POSITION - offset, item) item = QTableWidgetItem(str(time)) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_TIME - offset, item) item = QTableWidgetItem(str(nr_samples)) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_SAMPLES - offset, item) ann_table.resizeColumnsToContents() ann_table.resizeRowsToContents() #ann_table.setStyleSheet(css) coordinate = self.browser.get_coordinate() self._find_annotation_row(coordinate) ann_table.blockSignals(False) def _find_annotation_row(self, coordinate): if self._imagecontainer.has_multiple_plates: items1 = self._ann_table.findItems(coordinate.plate, Qt.MatchExactly) rows1 = set(x.row() for x in items1) offset = 0 else: rows1 = set(range(self._ann_table.rowCount())) offset = 1 items2 = self._ann_table.findItems(coordinate.position, Qt.MatchExactly) items3 = self._ann_table.findItems(str(coordinate.time), Qt.MatchExactly) items2 = [x for x in items2 if x.row() in rows1 and x.column() == 1-offset] rows2 = set(x.row() for x in items2) items3 = [x for x in items3 if x.row() in rows2 and x.column() == 2-offset] assert len(items3) in [0,1] if len(items3) == 1: self._ann_table.setCurrentItem(items3[0]) else: self._ann_table.clearSelection() def _on_new_point(self, point, button, modifier): item = self.browser.image_viewer.get_object_item(point) #print(item,point,item in self._object_items) if button == Qt.LeftButton and not item is None: coordinate = self.browser.get_coordinate() old_class = None # remove the item if already present if item in self._object_items: point2 = self._object_items[item] tpl = (int(point2.x()), int(point2.y())) old_class = \ self._annotations.get_class_name(coordinate, tpl) self._annotations.remove(coordinate, tpl) del self._object_items[item] self._activate_object(item, point, self._current_class, False) # mark a new item only if the shift-key is not pressed, a class # is currently active and the class name is different if (modifier != Qt.ShiftModifier and not self._current_class is None and old_class != self._current_class): tpl = (int(point.x()), int(point.y())) self._annotations.add(coordinate, self._current_class, tpl) self._object_items[item] = point self._activate_object(item, point, self._current_class, True) self._update_class_table() self._update_annotation_table() def _on_dbl_clk(self, point): items = self.image_viewer.items(point) print(items) def _activate_object(self, item, point, class_name, state=True): if state: color = \ QColor(*hexToRgb(self._learner.hexcolors[class_name])) #color.setAlphaF(1.0) label = self._learner.class_labels[class_name] # item2 = QGraphicsEllipseItem(point.x(), point.y(), 3, 3,item) # item2.setPen(QPen(color)) # item2.setBrush(QBrush(color)) # item2.show() item2 = QGraphicsSimpleTextItem(str(label), item) rect = item2.boundingRect() # center the text item at the annotated point item2.setPos(point - QPointF(rect.width()/2, rect.height()/2)) item2.setPen(QPen(color)) item2.setBrush(QBrush(color)) item2.show() else: color = self.browser.image_viewer.contour_color scene = item.scene() for item2 in item.childItems(): scene.removeItem(item2) item.set_pen_color(color) obj_id = item.data(0) return obj_id def _on_coordinates_changed(self, coordinate): self._find_annotation_row(coordinate) def _on_anntable_changed(self, current, previous): if not current is None: if self._imagecontainer.has_multiple_plates: offset = 0 plate = self._ann_table.item(current.row(), self.COLUMN_ANN_PLATE).text() else: offset = 1 plate = self._imagecontainer.plates[0] col = self.COLUMN_ANN_POSITION - offset position = self._ann_table.item(current.row(), col).text() col = self.COLUMN_ANN_TIME - offset time = int(self._ann_table.item(current.row(), col).text()) coordinate = Coordinate(plate=plate, position=position, time=time) try: self.browser.set_coordinate(coordinate) except: exception(self, "Selected coordinate was not found. " "Make sure the data and annotation match and " "that the data was scanned/imported correctly.") def _on_class_changed(self, current, previous): if not current is None: item = self._class_table.item(current.row(), self.COLUMN_CLASS_NAME) class_name = str(item.text()) self._current_class = class_name hex_col = self._learner.hexcolors[class_name] col = get_qcolor_hicontrast(QColor(hex_col)) class_table = self._class_table css = "selection-background-color: %s; selection-color: %s;" % \ (hex_col, qcolor_to_hex(col)) class_table.scrollToItem(item) self._class_text.setText(class_name) class_label = self._learner.class_labels[class_name] self._class_sbox.setValue(class_label) self._class_color_btn.set_color(QColor(hex_col)) class_table.setStyleSheet(css) self._update_annotation_table() self._ann_table.setStyleSheet(css) else: self._current_class = None def _on_show_contours_toggled(self, state): if self.isVisible(): self._activate_objects_for_image(True) def _on_show_objects(self, state): if not state: self._object_items.clear() self._detect_objects = state def _on_shortcut_class_selected(self, class_label): items = self._find_items_in_class_table(str(class_label), self.COLUMN_CLASS_LABEL) if len(items) == 1: self._class_table.setCurrentItem(items[0]) def _load_classifier(self, path): learner = None try: learner = BaseLearner(path, None, None) except: exception(self, 'Error on loading classifier') else: result = learner.check() #if result['has_arff']: # self._learner.importFromArff() if result['has_definition']: learner.loadDefinition() return learner def _save_classifier(self, path): learner = self._learner success = True try: learner.clf_dir = path learner.saveDefinition() except: exception(self, 'Error on saving classifier') success = False return success def set_coords(self): if self.isVisible(): self._activate_objects_for_image(True, clear=True) self._update_class_table() def activate(self): super(AnnotationModule, self).activate() self._activate_objects_for_image(True, clear=True) self._update_class_table() self.browser.image_viewer.image_mouse_pressed.connect(self._on_new_point) self._action_grp.setEnabled(True) self._find_annotation_row(self.browser.get_coordinate()) def deactivate(self): super(AnnotationModule, self).deactivate() self._activate_objects_for_image(False, clear=True) self.browser.image_viewer.image_mouse_pressed.disconnect(self._on_new_point) self.browser.image_viewer.purify_objects() self._action_grp.setEnabled(False)
class ObjectsFrame(QFrame): object_region_changed = pyqtSignal("PyQt_PyObject", "PyQt_PyObject") def __init__(self, browser, regions, parent): QFrame.__init__(self, parent) layout = QGridLayout(self) layout.setContentsMargins(5, 5, 5, 5) self.browser = browser box_detect = QCheckBox("Detect Objects", self) box_detect.setChecked(False) box_detect.toggled.connect(self._on_detect_objects) layout.addWidget(box_detect, 0, 0) self._box_detect = box_detect self._box_region = QComboBox(self) self._box_region.setEnabled(box_detect.checkState() == Qt.Checked) self.update_regionbox(regions) self._box_region.activated[int].connect(self._on_current_region_changed) if len(regions) > 0: self._box_region.setCurrentIndex(0) layout.addWidget(self._box_region, 1, 0, 1, 2) layout.addWidget(QLabel("Show Contours:"), 2, 0) box = QRadioButton("by color", self) box.setEnabled(False) box.setChecked(True) box.toggled.connect(self._on_show_by_color) layout.addWidget(box, 3, 0) self._box_contours = box self._btn_contour_color = ColorButton(None, self) self._btn_contour_color.setEnabled(box_detect.checkState() == Qt.Checked) color = QColor("white") color.setAlphaF(1) self._btn_contour_color.set_color(color) self._btn_contour_color.color_changed.connect(self._on_contour_color_changed) layout.addWidget(self._btn_contour_color, 3, 1) box_classify = QRadioButton("by classification", self) box.setEnabled(False) box.setChecked(False) box_classify.toggled.connect(self._on_show_by_classification) box_classify.setChecked(False) layout.addWidget(box_classify, 4, 0) self._box_classify = box_classify box_show_contours = QCheckBox("show all", self) box_show_contours.setChecked(True) box_show_contours.toggled.connect(self._on_toggle_show_contours) box_show_contours.setEnabled(box_detect.checkState() == Qt.Checked) layout.addWidget(box_show_contours, 5, 0) self._box_show_contours = box_show_contours def update_regionbox(self, regions): current = self._box_region.currentText() self._box_region.clear() for k in sorted(regions): self._box_region.addItem(k, regions[k]) found_old_idx = self._box_region.findText(current, flags=Qt.MatchExactly) if found_old_idx < 0: self._box_region.setCurrentIndex(0) else: self._box_region.setCurrentIndex(found_old_idx) self._object_region = regions.values()[found_old_idx] def _on_show_by_color(self, state): if state: self.browser._show_objects_by = "color" self.browser.on_refresh() def _on_show_by_classification(self, state): if state: self.browser._show_objects_by = "classification" self.browser.on_refresh() def _on_current_region_changed(self, index): channel, region = self._box_region.itemData(index) self.object_region_changed.emit(channel, region) def _on_detect_objects(self, state): # Enable depending buttons self._box_region.setEnabled(state) self._box_contours.setEnabled(state) self._box_classify.setEnabled(state) self._box_show_contours.setEnabled(state) self._btn_contour_color.setEnabled(state) self.browser._detect_objects = state self.browser.detect_objects_toggled(state) def _on_set_contour_state(self, state): self._box_contours.blockSignals(True) self._box_contours.setChecked(state) self._box_contours.blockSignals(False) def _on_contour_color_changed(self, color): self.browser._contour_color = color self.browser.image_viewer.set_contour_color(color) def _on_toggle_show_contours(self, state): self.browser._show_objects = state self.browser.image_viewer.show_contours = state self.browser.image_viewer._update_contours()
class ObjectsFrame(QFrame): object_region_changed = pyqtSignal("PyQt_PyObject", "PyQt_PyObject") def __init__(self, browser, regions, parent): QFrame.__init__(self, parent) layout = QGridLayout(self) layout.setContentsMargins(5, 5, 5, 5) self.browser = browser box_detect = QCheckBox('Detect Objects', self) box_detect.setChecked(False) box_detect.toggled.connect(self._on_detect_objects) layout.addWidget(box_detect, 0, 0) self._box_detect = box_detect self._box_region = QComboBox(self) self._box_region.setEnabled(box_detect.checkState() == Qt.Checked) self.update_regionbox(regions) self._box_region.activated[int].connect(self._on_current_region_changed) if len(regions) > 0: self._box_region.setCurrentIndex(0) layout.addWidget(self._box_region, 1, 0, 1, 2) layout.addWidget(QLabel('Show Contours:'), 2, 0) box = QRadioButton('by color', self) box.setEnabled(False) box.setChecked(True) box.toggled.connect(self._on_show_by_color) layout.addWidget(box, 3, 0) self._box_contours = box self._btn_contour_color = ColorButton(None, self) self._btn_contour_color.setEnabled(box_detect.checkState() == Qt.Checked) color = QColor('white') color.setAlphaF(1) self._btn_contour_color.set_color(color) self._btn_contour_color.color_changed.connect( \ self._on_contour_color_changed) layout.addWidget(self._btn_contour_color, 3, 1) box_classify = QRadioButton('by classification', self) box.setEnabled(False) box.setChecked(False) box_classify.toggled.connect(self._on_show_by_classification) box_classify.setChecked(False) layout.addWidget(box_classify, 4, 0) self._box_classify = box_classify box_show_contours = QCheckBox('show all', self) box_show_contours.setChecked(True) box_show_contours.toggled.connect(self._on_toggle_show_contours) box_show_contours.setEnabled(box_detect.checkState() == Qt.Checked) layout.addWidget(box_show_contours, 5, 0) self._box_show_contours = box_show_contours def update_regionbox(self, regions): current = self._box_region.currentText() self._box_region.clear() for k, v in regions.iteritems(): self._box_region.addItem(k, v) try: # try to keep the current setting self._box_region.setCurrentIndex( \ self._box_region.findText(current, flags=Qt.MatchExactly)) except Exception: pass def _on_show_by_color(self, state): if state: self.browser._show_objects_by = 'color' self.browser.on_refresh() def _on_show_by_classification(self, state): if state: self.browser._show_objects_by = 'classification' self.browser.on_refresh() def _on_current_region_changed(self, index): channel, region = self._box_region.itemData(index) self.object_region_changed.emit(channel, region) def _on_detect_objects(self, state): # Enable depending buttons self._box_region.setEnabled(state) self._box_contours.setEnabled(state) self._box_classify.setEnabled(state) self._box_show_contours.setEnabled(state) self._btn_contour_color.setEnabled(state) self.browser._detect_objects = state self.browser.detect_objects_toggled(state) def _on_set_contour_state(self, state): self._box_contours.blockSignals(True) self._box_contours.setChecked(state) self._box_contours.blockSignals(False) def _on_contour_color_changed(self, color): self.browser._contour_color = color self.browser.image_viewer.set_contour_color(color) def _on_toggle_show_contours(self, state): self.browser._show_objects = state self.browser.image_viewer.show_contours = state self.browser.image_viewer._update_contours()
class AnnotationModule(Module): NAME = 'Annotation' COLUMN_CLASS_NAME = 0 COLUMN_CLASS_LABEL = 1 COLUMN_CLASS_COLOR = 2 COLUMN_CLASS_COUNT = 3 COLUMN_ANN_PLATE = 0 COLUMN_ANN_POSITION = 1 COLUMN_ANN_TIME = 2 COLUMN_ANN_SAMPLES = 3 DEFAULT_CLASS_COLS = ["#4daf4a", "#ffff33", "#ff7f00", "#f781bf", "#984ea3", "#377eb8", "#e41a1c", "#a65628", "#999999"] def __init__(self, parent, browser, settings, imagecontainer): super(AnnotationModule, self).__init__(parent, browser) self._current_class = None self._detect_objects = False self._current_scene_items = set() self._lastdir = os.path.expanduser('~/') self._settings = settings self._imagecontainer = imagecontainer self._annotations = Annotations() self._object_items = {} splitter = QSplitter(Qt.Vertical, self) grp_box = QGroupBox('Classes', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) class_table = QTableWidget(grp_box) class_table.setEditTriggers(QTableWidget.NoEditTriggers) class_table.setSelectionMode(QTableWidget.SingleSelection) class_table.setSelectionBehavior(QTableWidget.SelectRows) class_table.setColumnCount(4) class_table.setHorizontalHeaderLabels(['Name', 'Label', 'Color', '#']) class_table.resizeColumnsToContents() class_table.currentItemChanged.connect(self._on_class_changed) layout.addWidget(class_table) self._class_table = class_table frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.TopToBottom, frame2) layout2.setContentsMargins(0,0,0,0) self._import_class_definitions_btn = QPushButton('Import class definitions') layout2.addWidget(self._import_class_definitions_btn) self._import_class_definitions_btn.clicked.connect(self._on_import_class_definitions) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) self._class_sbox = QSpinBox(frame2) self._class_color_btn = ColorButton(None, frame2) self._class_sbox.setRange(0, 1000) self._class_text = QLineEdit(frame2) layout2.addWidget(self._class_color_btn) layout2.addWidget(self._class_sbox) layout2.addWidget(self._class_text) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0,0,0,0) btn = QPushButton('Apply', frame2) btn.clicked.connect(self._on_class_apply) layout2.addWidget(btn) btn = QPushButton('Add', frame2) btn.clicked.connect(self._on_class_add) layout2.addWidget(btn) btn = QPushButton('Remove', frame2) btn.clicked.connect(self._on_class_remove) layout2.addWidget(btn) layout.addWidget(frame2) splitter.addWidget(grp_box) grp_box = QGroupBox('Annotations', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) ann_table = QTableWidget(grp_box) ann_table.setEditTriggers(QTableWidget.NoEditTriggers) ann_table.setSelectionMode(QTableWidget.SingleSelection) ann_table.setSelectionBehavior(QTableWidget.SelectRows) column_names = ['Position', 'Frame', 'Samples'] if self._imagecontainer.has_multiple_plates: column_names = ['Plate'] + column_names ann_table.setColumnCount(len(column_names)) ann_table.setHorizontalHeaderLabels(column_names) ann_table.resizeColumnsToContents() ann_table.currentItemChanged.connect(self._on_anntable_changed) layout.addWidget(ann_table) self._ann_table = ann_table splitter.addWidget(grp_box) frame = QFrame(grp_box) layout_frame = QBoxLayout(QBoxLayout.LeftToRight, frame) layout_frame.setContentsMargins(0, 0, 0, 0) btn = QPushButton('New', frame) btn.clicked.connect(self._on_new_classifier) layout_frame.addWidget(btn) btn = QPushButton('Open', frame) btn.clicked.connect(self._on_open_classifier) layout_frame.addWidget(btn) btn = QPushButton('Save', frame) btn.clicked.connect(self._on_save_classifier) layout_frame.addWidget(btn) btn = QPushButton('Save as', frame) btn.pressed.connect(self._on_saveas_classifier) layout_frame.addWidget(btn) layout.addWidget(frame) layout = QBoxLayout(QBoxLayout.TopToBottom, self) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(splitter) self._init_new_classifier() self.classdef = ClassDefinition() self.browser._action_grp = QActionGroup(browser) class_fct = lambda id: lambda : self._on_shortcut_class_selected(id) for x in range(1,11): action = browser.create_action( 'Select Class Label %d' % x, shortcut=QKeySequence(str(x) if x < 10 else '0'), slot=class_fct(x)) self.browser._action_grp.addAction(action) browser.addAction(action) browser.show_objects_toggled.connect(self._on_show_objects) browser.show_contours_toggled.connect(self._on_show_contours_toggled) def _find_items_in_class_table(self, value, column, match=Qt.MatchExactly): items = self._class_table.findItems(value, match) return [item for item in items if item.column() == column] def _on_import_class_definitions(self): if self._on_new_classifier(): dir_ = QFileDialog.getExistingDirectory( self, 'Open classifier directory', self._lastdir) if dir_: try: self._load_classifier(dir_) except RuntimeError as e: pass self._update_class_definition_table() def _update_class_definition_table(self): class_table = self._class_table class_table.clearContents() class_table.setRowCount(len(self.classdef)) select_item = None for idx, class_label in enumerate(self.classdef.names): samples = 0 class_name = self.classdef.names[class_label] item = QTableWidgetItem(class_name) class_table.setItem(idx, self.COLUMN_CLASS_NAME, item) if select_item is None: select_item = item class_table.setItem(idx, self.COLUMN_CLASS_LABEL, QTableWidgetItem(str(class_label))) class_table.setItem(idx, self.COLUMN_CLASS_COUNT, QTableWidgetItem(str(samples))) item = QTableWidgetItem(' ') item.setBackground( QBrush(QColor(self.classdef.colors[class_name]))) class_table.setItem(idx, self.COLUMN_CLASS_COLOR, item) class_table.resizeColumnsToContents() class_table.resizeRowsToContents() self._activate_objects_for_image(False, clear=True) def _on_class_apply(self): """Apply class changes""" class_name_new = str(self._class_text.text()) class_label_new = self._class_sbox.value() class_name = self._current_class if not class_name is None: class_label = self.classdef.labels[class_name] class_labels = self.classdef.labels.values() class_labels.remove(class_label) class_names = self.classdef.names.values() class_names.remove(class_name) if len(class_name_new) == 0: QMessageBox.warning(self, "Invalid class name", "The class name must not be empty!") elif (not class_name_new in class_names and not class_label_new in class_labels): self.classdef.removeClass(class_name) item = self._find_items_in_class_table(class_name, self.COLUMN_CLASS_NAME)[0] class_color = self._class_color_btn.current_color.name() self.classdef.addClass(class_name_new, class_label_new, class_color) item.setText(class_name_new) item2 = self._class_table.item(item.row(), self.COLUMN_CLASS_LABEL) item2.setText(str(class_label_new)) item2 = self._class_table.item(item.row(), self.COLUMN_CLASS_COLOR) item2.setBackground(QBrush(QColor(class_color))) self._class_table.resizeRowsToContents() self._class_table.resizeColumnsToContents() self._class_table.scrollToItem(item) self._annotations.rename_class(class_name, class_name_new) self._current_class = class_name_new self._activate_objects_for_image(False) self._activate_objects_for_image(True) else: QMessageBox.warning( self, "Class names and labels must be unique!", "Class name '%s' or label '%s' already used." %(class_name_new, class_label_new)) def _on_class_add(self): """Add a new class to definition""" class_name_new = str(self._class_text.text()) class_label_new = self._class_sbox.value() if len(class_name_new) == 0: QMessageBox.warning(self, "Invalid class name", "The class name must not be empty!") elif (not class_name_new in self.classdef.names.values() and not class_label_new in self.classdef.names.keys()): self._current_class = class_name_new class_color = self._class_color_btn.current_color self.classdef.addClass( class_name_new, class_label_new, class_color.name()) row = self._class_table.rowCount() self._class_table.insertRow(row) self._class_table.setItem(row, self.COLUMN_CLASS_NAME, QTableWidgetItem(class_name_new)) self._class_table.setItem(row, self.COLUMN_CLASS_LABEL, QTableWidgetItem(str(class_label_new))) self._class_table.setItem(row, self.COLUMN_CLASS_COUNT, QTableWidgetItem('0')) item = QTableWidgetItem() item.setBackground(QBrush(class_color)) self._class_table.setItem(row, self.COLUMN_CLASS_COLOR, item) self._class_table.resizeRowsToContents() self._class_table.resizeColumnsToContents() self._class_table.setCurrentItem(item) ncl = len(self.classdef) + 1 self._class_text.setText('class%d' % ncl) self._class_sbox.setValue(ncl) self._class_color_btn.set_color( QColor(AnnotationModule.DEFAULT_CLASS_COLS[(ncl -1) \ % len(AnnotationModule.DEFAULT_CLASS_COLS)])) else: QMessageBox.warning(self, "Class names and labels must be unique!", ("Class name '%s' or label '%s' already used." %(class_name_new, class_label_new))) def _on_class_remove(self): """Remove a class and all its annotations.""" class_name = self._current_class if class_name is None: return ret = QMessageBox.question(self, "Question", ("Do you really want to remove class '%s' " "and all its annotations?" %class_name)) if class_name is not None and ret == QMessageBox.Yes: self._activate_objects_for_image(False) self.classdef.removeClass(class_name) item = self._find_items_in_class_table(class_name, self.COLUMN_CLASS_NAME)[0] row = item.row() self._class_table.removeRow(row) self._annotations.remove_class(class_name) self._activate_objects_for_image(True, clear=True) row_count = self._class_table.rowCount() if row_count > 0: row = row if row < row_count else row_count-1 item = self._class_table.item(row, self.COLUMN_CLASS_NAME) self._class_table.setCurrentItem(item) else: self._update_annotation_table() def _init_new_classifier(self): classdef = ClassDefinition() self._current_class = None self._class_sbox.setValue(1) self._class_text.setText('class1') self._class_color_btn.set_color(QColor(self.DEFAULT_CLASS_COLS[0])) class_table = self._class_table class_table.clearContents() class_table.setRowCount(0) ann_table = self._ann_table ann_table.clearContents() ann_table.setRowCount(0) if self._detect_objects: self._activate_objects_for_image(False, clear=True) self._annotations.remove_all() return classdef def _on_new_classifier(self): ok = False if len(self.classdef) > 0: ret = QMessageBox.question(self, 'New classifier', ('Are you sure to setup a new classifer?' '\nAll annotations will be lost.')) else: ret = QMessageBox.Yes if ret == QMessageBox.Yes: self._init_new_classifier() self.classdef.clear() ok = True return ok def _on_open_classifier(self): dir_ = QFileDialog.getExistingDirectory( self, 'Open classifier directory', self._lastdir) if dir_: try: self._load_classifier(dir_) except RuntimeError as e: return else: self._update_class_definition_table() self._activate_objects_for_image(False, clear=True) path2 = os.path.join(dir_, self.classdef.Annotations) try: has_invalid = self._annotations.import_from_xml( path2, self.classdef.names, self._imagecontainer) except Exception as e: import traceback traceback.print_exc() QMessageBox.critical(self, "Error", str(e)) self._init_new_classifier() else: self._activate_objects_for_image(True) self._update_class_table() if self._class_table.rowCount() > 0: self._class_table.setCurrentCell( 0, self.COLUMN_CLASS_NAME) else: self._current_class = None QMessageBox.information( self, "Classifier successfully loaded", "Class definitions and annotations " "successfully loaded from '%s'." %dir_) finally: coord = self.browser.get_coordinate() self._imagecontainer.set_plate(coord.plate) def _on_save_classifier(self): self._on_saveas_classifier(self._lastdir) def _on_saveas_classifier(self, path=None): if path is None: path = QFileDialog.getExistingDirectory( self, 'Save to classifier directory', self._lastdir) if path: path2 = os.path.join(path, ClassDefinition.Annotations) if not os.path.isdir(path2): os.mkdir(path2) if self._save_classifier(path): try: # write/overwrite backup filenames = glob.glob(path2+"/*.xml") for filename in filenames: shutil.copy2(filename, filename.replace('xml', 'xml.backup')) self._annotations.export_to_xml( path2, self.classdef.labels, self._imagecontainer) except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: self._lastdir = path QMessageBox.information(self, "Classifier successfully saved", "Class definitions and annotations " "successfully saved to '%s'." %path) finally: coord = self.browser.get_coordinate() self._imagecontainer.set_plate(coord.plate) def _activate_objects_for_image(self, state, clear=False): if clear: self._object_items.clear() coordinate = self.browser.get_coordinate() for class_name, tpl in self._annotations.iter_items(coordinate): point = QPointF(*tpl) item = self.browser.image_viewer.get_object_item(point) if not item is None: if not item in self._object_items: self._object_items[item] = point self._activate_object(item, point, class_name, state=state) def _update_class_table(self): """Udate the class count for the class table""" counts = self._annotations.get_class_counts() for class_name in self.classdef.names.values(): if class_name in counts: items = self._find_items_in_class_table(class_name, self.COLUMN_CLASS_NAME) item = self._class_table.item(items[0].row(), self.COLUMN_CLASS_COUNT) item.setText(str(counts[class_name])) def _update_annotation_table(self): ''' update the annotation table. set the annotation coordinates for the current class ''' per_class = \ self._annotations.get_annotations_per_class(self._current_class) ann_table = self._ann_table ann_table.blockSignals(True) ann_table.clearContents() ann_table.setRowCount(len(per_class)) for idx, data in enumerate(per_class): plate, position, time, nr_samples = data # plateid in m.plateids #if position in m.positions and time in m.times: flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable tooltip = 'Jump to coordinate to see the annotation.' # make plate information dependent whether the data set contains # multiple plates if self._imagecontainer.has_multiple_plates: item = QTableWidgetItem(plate) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_PLATE, item) offset = 0 else: offset = 1 item = QTableWidgetItem(position) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_POSITION - offset, item) item = QTableWidgetItem(str(time)) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_TIME - offset, item) item = QTableWidgetItem(str(nr_samples)) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_SAMPLES - offset, item) ann_table.resizeColumnsToContents() ann_table.resizeRowsToContents() coordinate = self.browser.get_coordinate() self._find_annotation_row(coordinate) ann_table.blockSignals(False) def _find_annotation_row(self, coordinate): if self._imagecontainer.has_multiple_plates: items1 = self._ann_table.findItems(coordinate.plate, Qt.MatchExactly) rows1 = set(x.row() for x in items1) offset = 0 else: rows1 = set(range(self._ann_table.rowCount())) offset = 1 items2 = self._ann_table.findItems(coordinate.position, Qt.MatchExactly) items3 = self._ann_table.findItems(str(coordinate.time), Qt.MatchExactly) items2 = [x for x in items2 if x.row() in rows1 and x.column() == 1-offset] rows2 = set(x.row() for x in items2) items3 = [x for x in items3 if x.row() in rows2 and x.column() == 2-offset] assert len(items3) in [0,1] if len(items3) == 1: self._ann_table.setCurrentItem(items3[0]) else: self._ann_table.clearSelection() def _on_new_point(self, point, button, modifier): item = self.browser.image_viewer.get_object_item(point) if button == Qt.LeftButton and not item is None: coordinate = self.browser.get_coordinate() old_class = None # remove the item if already present if item in self._object_items: point2 = self._object_items[item] tpl = (int(point2.x()), int(point2.y())) old_class = \ self._annotations.get_class_name(coordinate, tpl) self._annotations.remove(coordinate, tpl) del self._object_items[item] self._activate_object(item, point, self._current_class, False) # mark a new item only if the shift-key is not pressed, a class # is currently active and the class name is different if (modifier != Qt.ShiftModifier and not self._current_class is None and old_class != self._current_class): tpl = (int(point.x()), int(point.y())) self._annotations.add(coordinate, self._current_class, tpl) self._object_items[item] = point self._activate_object(item, point, self._current_class, True) self._update_class_table() self._update_annotation_table() def _on_dbl_clk(self, point): pass def _activate_object(self, item, point, class_name, state=True): if state: color = QColor(*hex2rgb(self.classdef.colors[class_name])) label = self.classdef.labels[class_name] item2 = QGraphicsSimpleTextItem(str(label), item) rect = item2.boundingRect() # center the text item at the annotated point item2.setPos(point - QPointF(rect.width()/2, rect.height()/2)) item2.setPen(QPen(color)) item2.setBrush(QBrush(color)) item2.show() else: color = self.browser.image_viewer.contour_color scene = item.scene() for item2 in item.childItems(): scene.removeItem(item2) item.set_pen_color(color) obj_id = item.data(0) return obj_id def _on_coordinates_changed(self, coordinate): self._find_annotation_row(coordinate) def _on_anntable_changed(self, current, previous): if not current is None: if self._imagecontainer.has_multiple_plates: offset = 0 plate = self._ann_table.item(current.row(), self.COLUMN_ANN_PLATE).text() else: offset = 1 plate = self._imagecontainer.plates[0] col = self.COLUMN_ANN_POSITION - offset position = self._ann_table.item(current.row(), col).text() col = self.COLUMN_ANN_TIME - offset time = int(self._ann_table.item(current.row(), col).text()) coordinate = Coordinate(plate=plate, position=position, time=time) try: self.browser.set_coordinate(coordinate) except: QMessageBox.critical( self, "Selected coordinate was not found. " "Make sure the data and annotation match and " "that the data was scanned/imported correctly.") def _on_class_changed(self, current, previous): if current is not None: item = self._class_table.item(current.row(), self.COLUMN_CLASS_NAME) class_name = str(item.text()) self._current_class = class_name hex_col = self.classdef.colors[class_name] class_table = self._class_table class_table.scrollToItem(item) self._class_text.setText(class_name) class_label = self.classdef.labels[class_name] self._class_sbox.setValue(class_label) self._class_color_btn.set_color(QColor(hex_col)) self._update_annotation_table() else: self._current_class = None def _on_show_contours_toggled(self, state): if self.isVisible(): self._activate_objects_for_image(True) def _on_show_objects(self, state): if not state: self._object_items.clear() self._detect_objects = state def _on_shortcut_class_selected(self, class_label): items = self._find_items_in_class_table(str(class_label), self.COLUMN_CLASS_LABEL) if len(items) == 1: self._class_table.setCurrentItem(items[0]) def _load_classifier(self, path): try: file_ = os.path.join(path, ClassDefinition.Definition) self.classdef = ClassDefinition.from_txt(file_) except Exception as e: import traceback QMessageBox.critical(self, 'Error', traceback.format_exc()) raise RuntimeError(str(e)) else: self._lastdir = path def _save_classifier(self, path): try: self.classdef.save2csv(path) except Exception as e: QMessageBox.critical(self, "Error on saving classifier", str(e)) return False else: return True def set_coords(self): if self.isVisible(): self._activate_objects_for_image(True, clear=True) self._update_class_table() def activate(self): super(AnnotationModule, self).activate() self._activate_objects_for_image(True, clear=True) self._update_class_table() self.browser.image_viewer.image_mouse_pressed.connect( self._on_new_point) self.browser._action_grp.setEnabled(True) self._find_annotation_row(self.browser.get_coordinate()) def deactivate(self): super(AnnotationModule, self).deactivate() self._activate_objects_for_image(False, clear=True) self.browser.image_viewer.image_mouse_pressed.disconnect( self._on_new_point) self.browser.image_viewer.purify_objects() self.browser._action_grp.setEnabled(False)
class AnnotationModule(Module): NAME = 'Annotation' COLUMN_CLASS_NAME = 0 COLUMN_CLASS_LABEL = 1 COLUMN_CLASS_COLOR = 2 COLUMN_CLASS_COUNT = 3 COLUMN_ANN_PLATE = 0 COLUMN_ANN_POSITION = 1 COLUMN_ANN_TIME = 2 COLUMN_ANN_SAMPLES = 3 DEFAULT_CLASS_COLS = [ "#4daf4a", "#ffff33", "#ff7f00", "#f781bf", "#984ea3", "#377eb8", "#e41a1c", "#a65628", "#999999" ] def __init__(self, parent, browser, settings, imagecontainer): super(AnnotationModule, self).__init__(parent, browser) self._current_class = None self._detect_objects = False self._current_scene_items = set() self._lastdir = os.path.expanduser('~/') self._settings = settings self._imagecontainer = imagecontainer self._annotations = Annotations() self._object_items = {} splitter = QSplitter(Qt.Vertical, self) grp_box = QGroupBox('Classes', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) class_table = QTableWidget(grp_box) class_table.setEditTriggers(QTableWidget.NoEditTriggers) class_table.setSelectionMode(QTableWidget.SingleSelection) class_table.setSelectionBehavior(QTableWidget.SelectRows) class_table.setColumnCount(4) class_table.setHorizontalHeaderLabels(['Name', 'Label', 'Color', '#']) class_table.resizeColumnsToContents() class_table.currentItemChanged.connect(self._on_class_changed) layout.addWidget(class_table) self._class_table = class_table frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.TopToBottom, frame2) layout2.setContentsMargins(0, 0, 0, 0) self._import_class_definitions_btn = QPushButton( 'Import class definitions') layout2.addWidget(self._import_class_definitions_btn) self._import_class_definitions_btn.clicked.connect( self._on_import_class_definitions) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0, 0, 0, 0) self._class_sbox = QSpinBox(frame2) self._class_color_btn = ColorButton(None, frame2) self._class_sbox.setRange(0, 1000) self._class_text = QLineEdit(frame2) layout2.addWidget(self._class_color_btn) layout2.addWidget(self._class_sbox) layout2.addWidget(self._class_text) layout.addWidget(frame2) frame2 = QFrame(grp_box) layout2 = QBoxLayout(QBoxLayout.LeftToRight, frame2) layout2.setContentsMargins(0, 0, 0, 0) btn = QPushButton('Apply', frame2) btn.clicked.connect(self._on_class_apply) layout2.addWidget(btn) btn = QPushButton('Add', frame2) btn.clicked.connect(self._on_class_add) layout2.addWidget(btn) btn = QPushButton('Remove', frame2) btn.clicked.connect(self._on_class_remove) layout2.addWidget(btn) layout.addWidget(frame2) splitter.addWidget(grp_box) grp_box = QGroupBox('Annotations', splitter) layout = QBoxLayout(QBoxLayout.TopToBottom, grp_box) layout.setContentsMargins(5, 10, 5, 5) ann_table = QTableWidget(grp_box) ann_table.setEditTriggers(QTableWidget.NoEditTriggers) ann_table.setSelectionMode(QTableWidget.SingleSelection) ann_table.setSelectionBehavior(QTableWidget.SelectRows) column_names = ['Position', 'Frame', 'Samples'] if self._imagecontainer.has_multiple_plates: column_names = ['Plate'] + column_names ann_table.setColumnCount(len(column_names)) ann_table.setHorizontalHeaderLabels(column_names) ann_table.resizeColumnsToContents() ann_table.currentItemChanged.connect(self._on_anntable_changed) layout.addWidget(ann_table) self._ann_table = ann_table splitter.addWidget(grp_box) frame = QFrame(grp_box) layout_frame = QBoxLayout(QBoxLayout.LeftToRight, frame) layout_frame.setContentsMargins(0, 0, 0, 0) btn = QPushButton('New', frame) btn.clicked.connect(self._on_new_classifier) layout_frame.addWidget(btn) btn = QPushButton('Open', frame) btn.clicked.connect(self._on_open_classifier) layout_frame.addWidget(btn) btn = QPushButton('Save', frame) btn.clicked.connect(self._on_save_classifier) layout_frame.addWidget(btn) btn = QPushButton('Save as', frame) btn.pressed.connect(self._on_saveas_classifier) layout_frame.addWidget(btn) layout.addWidget(frame) layout = QBoxLayout(QBoxLayout.TopToBottom, self) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(splitter) self._init_new_classifier() self.classdef = ClassDefinition() self.browser._action_grp = QActionGroup(browser) class_fct = lambda id: lambda: self._on_shortcut_class_selected(id) for x in range(1, 11): action = browser.create_action( 'Select Class Label %d' % x, shortcut=QKeySequence(str(x) if x < 10 else '0'), slot=class_fct(x)) self.browser._action_grp.addAction(action) browser.addAction(action) browser.show_objects_toggled.connect(self._on_show_objects) browser.show_contours_toggled.connect(self._on_show_contours_toggled) def _find_items_in_class_table(self, value, column, match=Qt.MatchExactly): items = self._class_table.findItems(value, match) return [item for item in items if item.column() == column] def _on_import_class_definitions(self): if self._on_new_classifier(): dir_ = QFileDialog.getExistingDirectory( self, 'Open classifier directory', self._lastdir) if dir_: try: self._load_classifier(dir_) except RuntimeError as e: pass self._update_class_definition_table() def _update_class_definition_table(self): class_table = self._class_table class_table.clearContents() class_table.setRowCount(len(self.classdef)) select_item = None for idx, class_label in enumerate(self.classdef.names): samples = 0 class_name = self.classdef.names[class_label] item = QTableWidgetItem(class_name) class_table.setItem(idx, self.COLUMN_CLASS_NAME, item) if select_item is None: select_item = item class_table.setItem(idx, self.COLUMN_CLASS_LABEL, QTableWidgetItem(str(class_label))) class_table.setItem(idx, self.COLUMN_CLASS_COUNT, QTableWidgetItem(str(samples))) item = QTableWidgetItem(' ') item.setBackground(QBrush(QColor( self.classdef.colors[class_name]))) class_table.setItem(idx, self.COLUMN_CLASS_COLOR, item) class_table.resizeColumnsToContents() class_table.resizeRowsToContents() self._activate_objects_for_image(False, clear=True) def _on_class_apply(self): """Apply class changes""" class_name_new = str(self._class_text.text()) class_label_new = self._class_sbox.value() class_name = self._current_class if not class_name is None: class_label = self.classdef.labels[class_name] class_labels = self.classdef.labels.values() class_labels.remove(class_label) class_names = self.classdef.names.values() class_names.remove(class_name) if len(class_name_new) == 0: QMessageBox.warning(self, "Invalid class name", "The class name must not be empty!") elif (not class_name_new in class_names and not class_label_new in class_labels): self.classdef.removeClass(class_name) item = self._find_items_in_class_table( class_name, self.COLUMN_CLASS_NAME)[0] class_color = self._class_color_btn.current_color.name() self.classdef.addClass(class_name_new, class_label_new, class_color) item.setText(class_name_new) item2 = self._class_table.item(item.row(), self.COLUMN_CLASS_LABEL) item2.setText(str(class_label_new)) item2 = self._class_table.item(item.row(), self.COLUMN_CLASS_COLOR) item2.setBackground(QBrush(QColor(class_color))) self._class_table.resizeRowsToContents() self._class_table.resizeColumnsToContents() self._class_table.scrollToItem(item) self._annotations.rename_class(class_name, class_name_new) self._current_class = class_name_new self._activate_objects_for_image(False) self._activate_objects_for_image(True) else: QMessageBox.warning( self, "Class names and labels must be unique!", "Class name '%s' or label '%s' already used." % (class_name_new, class_label_new)) def _on_class_add(self): """Add a new class to definition""" class_name_new = str(self._class_text.text()) class_label_new = self._class_sbox.value() if len(class_name_new) == 0: QMessageBox.warning(self, "Invalid class name", "The class name must not be empty!") elif (not class_name_new in self.classdef.names.values() and not class_label_new in self.classdef.names.keys()): self._current_class = class_name_new class_color = self._class_color_btn.current_color self.classdef.addClass(class_name_new, class_label_new, class_color.name()) row = self._class_table.rowCount() self._class_table.insertRow(row) self._class_table.setItem(row, self.COLUMN_CLASS_NAME, QTableWidgetItem(class_name_new)) self._class_table.setItem(row, self.COLUMN_CLASS_LABEL, QTableWidgetItem(str(class_label_new))) self._class_table.setItem(row, self.COLUMN_CLASS_COUNT, QTableWidgetItem('0')) item = QTableWidgetItem() item.setBackground(QBrush(class_color)) self._class_table.setItem(row, self.COLUMN_CLASS_COLOR, item) self._class_table.resizeRowsToContents() self._class_table.resizeColumnsToContents() self._class_table.setCurrentItem(item) ncl = len(self.classdef) + 1 self._class_text.setText('class%d' % ncl) self._class_sbox.setValue(ncl) self._class_color_btn.set_color( QColor(AnnotationModule.DEFAULT_CLASS_COLS[(ncl -1) \ % len(AnnotationModule.DEFAULT_CLASS_COLS)])) else: QMessageBox.warning( self, "Class names and labels must be unique!", ("Class name '%s' or label '%s' already used." % (class_name_new, class_label_new))) def _on_class_remove(self): """Remove a class and all its annotations.""" class_name = self._current_class if class_name is None: return ret = QMessageBox.question(self, "Question", ("Do you really want to remove class '%s' " "and all its annotations?" % class_name)) if class_name is not None and ret == QMessageBox.Yes: self._activate_objects_for_image(False) self.classdef.removeClass(class_name) item = self._find_items_in_class_table(class_name, self.COLUMN_CLASS_NAME)[0] row = item.row() self._class_table.removeRow(row) self._annotations.remove_class(class_name) self._activate_objects_for_image(True, clear=True) row_count = self._class_table.rowCount() if row_count > 0: row = row if row < row_count else row_count - 1 item = self._class_table.item(row, self.COLUMN_CLASS_NAME) self._class_table.setCurrentItem(item) else: self._update_annotation_table() def _init_new_classifier(self): classdef = ClassDefinition() self._current_class = None self._class_sbox.setValue(1) self._class_text.setText('class1') self._class_color_btn.set_color(QColor(self.DEFAULT_CLASS_COLS[0])) class_table = self._class_table class_table.clearContents() class_table.setRowCount(0) ann_table = self._ann_table ann_table.clearContents() ann_table.setRowCount(0) if self._detect_objects: self._activate_objects_for_image(False, clear=True) self._annotations.remove_all() return classdef def _on_new_classifier(self): ok = False if len(self.classdef) > 0: ret = QMessageBox.question( self, 'New classifier', ('Are you sure to setup a new classifer?' '\nAll annotations will be lost.')) else: ret = QMessageBox.Yes if ret == QMessageBox.Yes: self._init_new_classifier() self.classdef.clear() ok = True return ok def _on_open_classifier(self): dir_ = QFileDialog.getExistingDirectory(self, 'Open classifier directory', self._lastdir) if dir_: try: self._load_classifier(dir_) except RuntimeError as e: return else: self._update_class_definition_table() self._activate_objects_for_image(False, clear=True) path2 = os.path.join(dir_, self.classdef.Annotations) try: has_invalid = self._annotations.import_from_xml( path2, self.classdef.names, self._imagecontainer) except Exception as e: import traceback traceback.print_exc() QMessageBox.critical(self, "Error", str(e)) self._init_new_classifier() else: self._activate_objects_for_image(True) self._update_class_table() if self._class_table.rowCount() > 0: self._class_table.setCurrentCell( 0, self.COLUMN_CLASS_NAME) else: self._current_class = None QMessageBox.information( self, "Classifier successfully loaded", "Class definitions and annotations " "successfully loaded from '%s'." % dir_) finally: coord = self.browser.get_coordinate() self._imagecontainer.set_plate(coord.plate) def _on_save_classifier(self): self._on_saveas_classifier(self._lastdir) def _on_saveas_classifier(self, path=None): if path is None: path = QFileDialog.getExistingDirectory( self, 'Save to classifier directory', self._lastdir) if path: path2 = os.path.join(path, ClassDefinition.Annotations) if not os.path.isdir(path2): os.mkdir(path2) if self._save_classifier(path): try: # write/overwrite backup filenames = glob.glob(path2 + "/*.xml") for filename in filenames: shutil.copy2(filename, filename.replace('xml', 'xml.backup')) self._annotations.export_to_xml(path2, self.classdef.labels, self._imagecontainer) except Exception as e: QMessageBox.critical(self, "Error", str(e)) else: self._lastdir = path QMessageBox.information( self, "Classifier successfully saved", "Class definitions and annotations " "successfully saved to '%s'." % path) finally: coord = self.browser.get_coordinate() self._imagecontainer.set_plate(coord.plate) def _activate_objects_for_image(self, state, clear=False): if clear: self._object_items.clear() coordinate = self.browser.get_coordinate() for class_name, tpl in self._annotations.iter_items(coordinate): point = QPointF(*tpl) item = self.browser.image_viewer.get_object_item(point) if not item is None: if not item in self._object_items: self._object_items[item] = point self._activate_object(item, point, class_name, state=state) def _update_class_table(self): """Udate the class count for the class table""" counts = self._annotations.get_class_counts() for class_name in self.classdef.names.values(): if class_name in counts: items = self._find_items_in_class_table( class_name, self.COLUMN_CLASS_NAME) item = self._class_table.item(items[0].row(), self.COLUMN_CLASS_COUNT) item.setText(str(counts[class_name])) def _update_annotation_table(self): ''' update the annotation table. set the annotation coordinates for the current class ''' per_class = \ self._annotations.get_annotations_per_class(self._current_class) ann_table = self._ann_table ann_table.blockSignals(True) ann_table.clearContents() ann_table.setRowCount(len(per_class)) for idx, data in enumerate(per_class): plate, position, time, nr_samples = data # plateid in m.plateids #if position in m.positions and time in m.times: flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable tooltip = 'Jump to coordinate to see the annotation.' # make plate information dependent whether the data set contains # multiple plates if self._imagecontainer.has_multiple_plates: item = QTableWidgetItem(plate) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_PLATE, item) offset = 0 else: offset = 1 item = QTableWidgetItem(position) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_POSITION - offset, item) item = QTableWidgetItem(str(time)) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_TIME - offset, item) item = QTableWidgetItem(str(nr_samples)) item.setFlags(flags) item.setToolTip(tooltip) ann_table.setItem(idx, self.COLUMN_ANN_SAMPLES - offset, item) ann_table.resizeColumnsToContents() ann_table.resizeRowsToContents() coordinate = self.browser.get_coordinate() self._find_annotation_row(coordinate) ann_table.blockSignals(False) def _find_annotation_row(self, coordinate): if self._imagecontainer.has_multiple_plates: items1 = self._ann_table.findItems(coordinate.plate, Qt.MatchExactly) rows1 = set(x.row() for x in items1) offset = 0 else: rows1 = set(range(self._ann_table.rowCount())) offset = 1 items2 = self._ann_table.findItems(coordinate.position, Qt.MatchExactly) items3 = self._ann_table.findItems(str(coordinate.time), Qt.MatchExactly) items2 = [ x for x in items2 if x.row() in rows1 and x.column() == 1 - offset ] rows2 = set(x.row() for x in items2) items3 = [ x for x in items3 if x.row() in rows2 and x.column() == 2 - offset ] assert len(items3) in [0, 1] if len(items3) == 1: self._ann_table.setCurrentItem(items3[0]) else: self._ann_table.clearSelection() def _on_new_point(self, point, button, modifier): item = self.browser.image_viewer.get_object_item(point) if button == Qt.LeftButton and not item is None: coordinate = self.browser.get_coordinate() old_class = None # remove the item if already present if item in self._object_items: point2 = self._object_items[item] tpl = (int(point2.x()), int(point2.y())) old_class = \ self._annotations.get_class_name(coordinate, tpl) self._annotations.remove(coordinate, tpl) del self._object_items[item] self._activate_object(item, point, self._current_class, False) # mark a new item only if the shift-key is not pressed, a class # is currently active and the class name is different if (modifier != Qt.ShiftModifier and not self._current_class is None and old_class != self._current_class): tpl = (int(point.x()), int(point.y())) self._annotations.add(coordinate, self._current_class, tpl) self._object_items[item] = point self._activate_object(item, point, self._current_class, True) self._update_class_table() self._update_annotation_table() def _on_dbl_clk(self, point): pass def _activate_object(self, item, point, class_name, state=True): if state: color = QColor(*hex2rgb(self.classdef.colors[class_name])) label = self.classdef.labels[class_name] item2 = QGraphicsSimpleTextItem(str(label), item) rect = item2.boundingRect() # center the text item at the annotated point item2.setPos(point - QPointF(rect.width() / 2, rect.height() / 2)) item2.setPen(QPen(color)) item2.setBrush(QBrush(color)) item2.show() else: color = self.browser.image_viewer.contour_color scene = item.scene() for item2 in item.childItems(): scene.removeItem(item2) item.set_pen_color(color) obj_id = item.data(0) return obj_id def _on_coordinates_changed(self, coordinate): self._find_annotation_row(coordinate) def _on_anntable_changed(self, current, previous): if not current is None: if self._imagecontainer.has_multiple_plates: offset = 0 plate = self._ann_table.item(current.row(), self.COLUMN_ANN_PLATE).text() else: offset = 1 plate = self._imagecontainer.plates[0] col = self.COLUMN_ANN_POSITION - offset position = self._ann_table.item(current.row(), col).text() col = self.COLUMN_ANN_TIME - offset time = int(self._ann_table.item(current.row(), col).text()) coordinate = Coordinate(plate=plate, position=position, time=time) try: self.browser.set_coordinate(coordinate) except: QMessageBox.critical( self, "Selected coordinate was not found. " "Make sure the data and annotation match and " "that the data was scanned/imported correctly.") def _on_class_changed(self, current, previous): if current is not None: item = self._class_table.item(current.row(), self.COLUMN_CLASS_NAME) class_name = str(item.text()) self._current_class = class_name hex_col = self.classdef.colors[class_name] class_table = self._class_table class_table.scrollToItem(item) self._class_text.setText(class_name) class_label = self.classdef.labels[class_name] self._class_sbox.setValue(class_label) self._class_color_btn.set_color(QColor(hex_col)) self._update_annotation_table() else: self._current_class = None def _on_show_contours_toggled(self, state): if self.isVisible(): self._activate_objects_for_image(True) def _on_show_objects(self, state): if not state: self._object_items.clear() self._detect_objects = state def _on_shortcut_class_selected(self, class_label): items = self._find_items_in_class_table(str(class_label), self.COLUMN_CLASS_LABEL) if len(items) == 1: self._class_table.setCurrentItem(items[0]) def _load_classifier(self, path): try: file_ = os.path.join(path, ClassDefinition.Definition) self.classdef = ClassDefinition.from_txt(file_) except Exception as e: import traceback QMessageBox.critical(self, 'Error', traceback.format_exc()) raise RuntimeError(str(e)) else: self._lastdir = path def _save_classifier(self, path): try: self.classdef.save2csv(path) except Exception as e: QMessageBox.critical(self, "Error on saving classifier", str(e)) return False else: return True def set_coords(self): if self.isVisible(): self._activate_objects_for_image(True, clear=True) self._update_class_table() def activate(self): super(AnnotationModule, self).activate() self._activate_objects_for_image(True, clear=True) self._update_class_table() self.browser.image_viewer.image_mouse_pressed.connect( self._on_new_point) self.browser._action_grp.setEnabled(True) self._find_annotation_row(self.browser.get_coordinate()) def deactivate(self): super(AnnotationModule, self).deactivate() self._activate_objects_for_image(False, clear=True) self.browser.image_viewer.image_mouse_pressed.disconnect( self._on_new_point) self.browser.image_viewer.purify_objects() self.browser._action_grp.setEnabled(False)