def _on_import_ontology_name(self): if not hasattr(self, "ontology_browser_diag"): self.ontology_browser_diag = CecogOntologyBrowserDialog( parent=self.browser) self.ontology_browser_diag.tw.trigger_add.connect( self._class_text.setText) self.ontology_browser_diag.show()
def _on_import_ontology_name(self): if not hasattr(self, "ontology_browser_diag"): self.ontology_browser_diag = CecogOntologyBrowserDialog(parent=self.browser) self.ontology_browser_diag.tw.trigger_add.connect(self._class_text.setText) self.ontology_browser_diag.show()
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): 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.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) self._import_ontology_name_btn = QPushButton( 'Choose class name from ontology') self._import_ontology_name_btn.setToolTip( "Choose class name from ontology. E.g., CMPO\nNote: It might take some time starting the dialog the first time." ) layout2.addWidget(self._import_ontology_name_btn) self._import_ontology_name_btn.clicked.connect( self._on_import_ontology_name) 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._learner = self._init_new_classifier() 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_ontology_name(self): if not hasattr(self, "ontology_browser_diag"): self.ontology_browser_diag = CecogOntologyBrowserDialog( parent=self.browser) self.ontology_browser_diag.tw.trigger_add.connect( self._class_text.setText) self.ontology_browser_diag.show() def _on_import_class_definitions(self): if self._on_new_classifier(): path = None try: path = self._learner.clf_dir except: pass 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) 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) self._class_color_btn.set_color( QColor(AnnotationModule.DEFAULT_CLASS_COLS[(ncl -1) \ % len(AnnotationModule.DEFAULT_CLASS_COLS)])) 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(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 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): try: path = os.path.abspath(self._learner.clf_dir) except: path = os.path.expanduser('~/') result = QFileDialog.getExistingDirectory( \ self, 'Open classifier directory', 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) makedirs(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): """Udate 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])) 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._learner.hexcolors[class_name])) label = self._learner.class_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: 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 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)) 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): learner = None try: learner = BaseLearner(path, None, None) except: exception(self, 'Error on loading classifier') else: state = learner.state if state['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.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): 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.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) self._import_ontology_name_btn = QPushButton('Choose class name from ontology') self._import_ontology_name_btn.setToolTip("Choose class name from ontology. E.g., CMPO\nNote: It might take some time starting the dialog the first time.") layout2.addWidget(self._import_ontology_name_btn) self._import_ontology_name_btn.clicked.connect(self._on_import_ontology_name) 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._learner = self._init_new_classifier() 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_ontology_name(self): if not hasattr(self, "ontology_browser_diag"): self.ontology_browser_diag = CecogOntologyBrowserDialog(parent=self.browser) self.ontology_browser_diag.tw.trigger_add.connect(self._class_text.setText) self.ontology_browser_diag.show() def _on_import_class_definitions(self): if self._on_new_classifier(): path = None try: path = self._learner.clf_dir except: pass 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) 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) self._class_color_btn.set_color( QColor(AnnotationModule.DEFAULT_CLASS_COLS[(ncl -1) \ % len(AnnotationModule.DEFAULT_CLASS_COLS)])) 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(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 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): try: path = os.path.abspath(self._learner.clf_dir) except: path = os.path.expanduser('~/') result = QFileDialog.getExistingDirectory( \ self, 'Open classifier directory', 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) makedirs(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): """Udate 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])) 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._learner.hexcolors[class_name])) label = self._learner.class_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: 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 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)) 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): learner = None try: learner = BaseLearner(path, None, None) except: exception(self, 'Error on loading classifier') else: state = learner.state if state['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.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)