def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master app_variables : AppVariables kwargs """ self.app_variables = app_variables self.current_annotation = None # type: Union[None, LabelFeature] Frame.__init__(self, master, **kwargs) self.update_label_button = Button( self, text='Update Label', command=self.callback_update_label) # type: Button self.update_label_button.grid(row=0, column=0, columnspan=2, sticky='NE', padx=3, pady=3) self.viewer = TreeviewWithScrolling( self, columns=('confidence', ), selectmode=tkinter.BROWSE) # type: TreeviewWithScrolling self.viewer.heading('#0', text='Label') self.viewer.heading('#1', text='Confidence') self.viewer.frame.grid(row=1, column=0, sticky='NSEW', padx=3, pady=3) # NB: reference the frame for packing, since it's already packed into a frame self.label_specifics = LabelSpecificsPanel(self, app_variables, border=1, relief=tkinter.RIDGE) self.label_specifics.grid(row=1, column=1, sticky='NSEW', padx=3, pady=3) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) self.update_annotation() self.viewer.bind('<<TreeviewSelect>>', self.callback_label_selected_on_viewer)
def __init__(self, parent): """ Parameters ---------- parent """ Frame.__init__(self, parent) self.parent = parent self.button_style = ttk.Style() self.button_style.configure('ToggleOff.TButton', font=('Arial', 24), foreground="gray50", background="PaleTurquoise3", width=3, sticky='CENTER') self.button_style.configure('ToggleOn.TButton', font=('Arial', 24), foreground="black", background="PaleTurquoise1", width=3, sticky='CENTER') self.button_rev = Button(self.parent, text="\u25C0", takefocus=0, style='ToggleOff.TButton') self.button_prev = Button(self.parent, text="-1", takefocus=0, style='ToggleOff.TButton') self.button_next = Button(self.parent, text="+1", takefocus=0, style='ToggleOff.TButton') self.button_fwd = Button(self.parent, text="\u25B6", takefocus=0, style='ToggleOff.TButton')
def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master The master widget app_variables : AppVariables kwargs keyword arguments passed through to the Frame constructor """ self.app_variables = app_variables Frame.__init__(self, master, **kwargs) self.button_panel = Frame(self, relief=tkinter.RIDGE, borderwidth=2) # type: Frame self.new_button = Button(self.button_panel, text='New Annotation', width=28) # type: Button self.new_button.grid(row=0, column=0, sticky='NW') self.edit_button = Button(self.button_panel, text='Edit Selected Annotation', width=28) # type: Button self.edit_button.grid(row=1, column=0, sticky='NW') self.zoom_button = Button(self.button_panel, text='Zoom to Selected Annotation', width=28) # type: Button self.zoom_button.grid(row=2, column=0, sticky='NW') self.move_button = Button(self.button_panel, text='Move Selected Annotation', width=28) # type: Button self.move_button.grid(row=3, column=0, sticky='NW') self.frame1 = Frame(self.button_panel) self.units_label = Label(self.frame1, text='Units:', relief=tkinter.RIDGE) self.units_label.pack(side=tkinter.LEFT) self.units_value = Combobox(self.frame1, text='') self.units_value.pack(side=tkinter.LEFT) self.frame1.grid(row=4, column=0, sticky='NW') self.button_panel.grid(row=0, column=0, sticky='NSEW') self.viewer = RCSCollectionViewer(self, app_variables) self.viewer.frame.grid(row=1, column=0, sticky='NSEW') self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) self.units_value.on_selection(self.callback_units_select)
def __init__(self, label_schema, start_item=None): """ Parameters ---------- label_schema : str|LabelSchema start_item : None|str The initial item selected """ # validate label schema input if isinstance(label_schema, str): label_schema = LabelSchema.from_file(label_schema) if not isinstance(label_schema, LabelSchema): raise TypeError( 'label_schema must be either a path to am appropriate .json file or a ' 'LabelSchema object. Got type {}'.format(type(label_schema))) # initialize the selected value option self._selected_value = '' self.root = tkinter.Toplevel() self.root.geometry('250x400') self.root.wm_title('Select Label Schema Entry') self.viewer = SchemaViewer(self.root, label_schema=label_schema) self.viewer.frame.pack(side=tkinter.TOP, expand=tkinter.TRUE, fill=tkinter.BOTH) self.set_selected_value(start_item) self.submit_button = Button(self.root, text='Submit', command=self.set_value) self.submit_button.pack(side=tkinter.BOTTOM, expand=tkinter.TRUE) self.root.grab_set() self.root.wait_window()
class LabelCollectionPanel(AnnotationCollectionPanel): def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master The master widget app_variables : AppVariables kwargs keyword arguments passed through to the Frame constructor """ Frame.__init__(self, master, **kwargs) self.button_panel = Frame(self, relief=tkinter.RIDGE, borderwidth=2) # type: Frame self.new_button = Button(self.button_panel, text='New Annotation', width=28) # type: Button self.new_button.grid(row=0, column=0, sticky='NW') self.edit_button = Button(self.button_panel, text='Edit Selected Annotation', width=28) # type: Button self.edit_button.grid(row=1, column=0, sticky='NW') self.zoom_button = Button(self.button_panel, text='Zoom to Selected Annotation', width=28) # type: Button self.zoom_button.grid(row=2, column=0, sticky='NW') self.move_button = Button(self.button_panel, text='Move Selected Annotation', width=28) # type: Button self.move_button.grid(row=3, column=0, sticky='NW') self.button_panel.grid(row=0, column=0, sticky='NSEW') self.viewer = LabelCollectionViewer(self, app_variables) self.viewer.frame.grid(row=1, column=0, sticky='NSEW') self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1)
def __init__(self, master, label_schema=None, **kwargs): """ Parameters ---------- master : tkinter.Tk|tkinter.TopLevel label_schema : None|str|LabelSchema kwargs keyword arguments for Frame """ self.variables = AppVariables() self.master = master Frame.__init__(self, master, **kwargs) self.frame1 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.version_label = Label(self.frame1, text='Version Number:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.version_label.grid(row=0, column=0, sticky='NEW') self.version_entry = Entry(self.frame1, text='') self.version_entry.grid(row=0, column=1, sticky='NEW') self.version_date_label = Label(self.frame1, text='Version Date:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.version_date_label.grid(row=1, column=0, sticky='NEW') self.version_date_entry = Entry(self.frame1, text='') self.version_date_entry.grid(row=1, column=1, sticky='NEW') self.classification_label = Label(self.frame1, text='Classification:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.classification_label.grid(row=2, column=0, sticky='NEW') self.classification_entry = Entry(self.frame1, text='') self.classification_entry.grid(row=2, column=1, sticky='NEW') self.confidence_label = Label(self.frame1, text='Confidence Values:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.confidence_label.grid(row=3, column=0, sticky='NEW') self.confidence_entry = Entry(self.frame1, text='') self.confidence_entry.grid(row=3, column=1, sticky='NEW') self.geometries_label = Label(self.frame1, text='Geometries:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.geometries_label.grid(row=4, column=0, sticky='NEW') self.geometries_entry = Entry(self.frame1, text='') self.geometries_entry.grid(row=4, column=1, sticky='NEW') self.frame1.grid_columnconfigure(1, weight=1) self.frame1.pack(side=tkinter.TOP, fill=tkinter.X) self.frame2 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.new_button = Button(self.frame2, text='New Entry') self.new_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.edit_button = Button(self.frame2, text='Edit Entry') self.edit_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.delete_button = Button(self.frame2, text='Delete Entry') self.delete_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.move_up_button = Button(self.frame2, text='Move Entry Up') self.move_up_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.move_down_button = Button(self.frame2, text='Move Entry Down') self.move_down_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.frame2.pack(side=tkinter.TOP, fill=tkinter.X) self.schema_viewer = SchemaViewer(self) self.schema_viewer.frame.pack(side=tkinter.BOTTOM, expand=tkinter.TRUE, fill=tkinter.BOTH) # NB: it's already packed inside a frame self.pack(expand=tkinter.YES, fill=tkinter.BOTH) # set up the menu bar menu = tkinter.Menu() filemenu = tkinter.Menu(menu, tearoff=0) filemenu.add_command(label="Open Schema", command=self.callback_open) filemenu.add_command(label="New Schema", command=self.callback_new_schema) filemenu.add_separator() filemenu.add_command(label="Save", command=self.save) filemenu.add_command(label="Save As", command=self.callback_save_as) filemenu.add_separator() filemenu.add_command(label="Exit", command=self.exit) menu.add_cascade(label="File", menu=filemenu) self.master.config(menu=menu) # setup entry configs and some validation callbacks self.schema_viewer.bind('<<TreeviewSelect>>', self.item_selected_on_viewer) self.version_entry.config(validate='focusout', validatecommand=self.register( self._version_entry_validate)) self.version_date_entry.config(state='disabled') self.classification_entry.config(validate='focusout', validatecommand=self.register( self._classification_validate)) self.confidence_entry.config(validate='focusout', validatecommand=self.register( self._confidence_validate)) self.geometries_entry.config( validate='focusout', validatecommand=(self.register(self._geometry_validate), ), invalidcommand=(self.register(self._geometry_invalid), )) self.edit_button.config(command=self.callback_edit_entry) self.new_button.config(command=self.callback_new_entry) self.delete_button.config(command=self.callback_delete_entry) self.move_up_button.config(command=self.callback_move_up) self.move_down_button.config(command=self.callback_move_down) # setup the entry panel self.entry_popup = tkinter.Toplevel(self.master) self.entry = LabelEntryPanel(self.entry_popup, self.variables) self.entry.hide_on_close() self.entry_popup.withdraw() self.entry.save_button.config(command=self.callback_save_entry) self.set_label_schema(label_schema)
class SchemaEditor(Frame): """ An editor for a label schema """ def __init__(self, master, label_schema=None, **kwargs): """ Parameters ---------- master : tkinter.Tk|tkinter.TopLevel label_schema : None|str|LabelSchema kwargs keyword arguments for Frame """ self.variables = AppVariables() self.master = master Frame.__init__(self, master, **kwargs) self.frame1 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.version_label = Label(self.frame1, text='Version Number:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.version_label.grid(row=0, column=0, sticky='NEW') self.version_entry = Entry(self.frame1, text='') self.version_entry.grid(row=0, column=1, sticky='NEW') self.version_date_label = Label(self.frame1, text='Version Date:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.version_date_label.grid(row=1, column=0, sticky='NEW') self.version_date_entry = Entry(self.frame1, text='') self.version_date_entry.grid(row=1, column=1, sticky='NEW') self.classification_label = Label(self.frame1, text='Classification:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.classification_label.grid(row=2, column=0, sticky='NEW') self.classification_entry = Entry(self.frame1, text='') self.classification_entry.grid(row=2, column=1, sticky='NEW') self.confidence_label = Label(self.frame1, text='Confidence Values:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.confidence_label.grid(row=3, column=0, sticky='NEW') self.confidence_entry = Entry(self.frame1, text='') self.confidence_entry.grid(row=3, column=1, sticky='NEW') self.geometries_label = Label(self.frame1, text='Geometries:', relief=tkinter.RIDGE, justify=tkinter.LEFT, padding=5, width=18) self.geometries_label.grid(row=4, column=0, sticky='NEW') self.geometries_entry = Entry(self.frame1, text='') self.geometries_entry.grid(row=4, column=1, sticky='NEW') self.frame1.grid_columnconfigure(1, weight=1) self.frame1.pack(side=tkinter.TOP, fill=tkinter.X) self.frame2 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.new_button = Button(self.frame2, text='New Entry') self.new_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.edit_button = Button(self.frame2, text='Edit Entry') self.edit_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.delete_button = Button(self.frame2, text='Delete Entry') self.delete_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.move_up_button = Button(self.frame2, text='Move Entry Up') self.move_up_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.move_down_button = Button(self.frame2, text='Move Entry Down') self.move_down_button.pack(side=tkinter.LEFT, padx=5, pady=5) self.frame2.pack(side=tkinter.TOP, fill=tkinter.X) self.schema_viewer = SchemaViewer(self) self.schema_viewer.frame.pack(side=tkinter.BOTTOM, expand=tkinter.TRUE, fill=tkinter.BOTH) # NB: it's already packed inside a frame self.pack(expand=tkinter.YES, fill=tkinter.BOTH) # set up the menu bar menu = tkinter.Menu() filemenu = tkinter.Menu(menu, tearoff=0) filemenu.add_command(label="Open Schema", command=self.callback_open) filemenu.add_command(label="New Schema", command=self.callback_new_schema) filemenu.add_separator() filemenu.add_command(label="Save", command=self.save) filemenu.add_command(label="Save As", command=self.callback_save_as) filemenu.add_separator() filemenu.add_command(label="Exit", command=self.exit) menu.add_cascade(label="File", menu=filemenu) self.master.config(menu=menu) # setup entry configs and some validation callbacks self.schema_viewer.bind('<<TreeviewSelect>>', self.item_selected_on_viewer) self.version_entry.config(validate='focusout', validatecommand=self.register( self._version_entry_validate)) self.version_date_entry.config(state='disabled') self.classification_entry.config(validate='focusout', validatecommand=self.register( self._classification_validate)) self.confidence_entry.config(validate='focusout', validatecommand=self.register( self._confidence_validate)) self.geometries_entry.config( validate='focusout', validatecommand=(self.register(self._geometry_validate), ), invalidcommand=(self.register(self._geometry_invalid), )) self.edit_button.config(command=self.callback_edit_entry) self.new_button.config(command=self.callback_new_entry) self.delete_button.config(command=self.callback_delete_entry) self.move_up_button.config(command=self.callback_move_up) self.move_down_button.config(command=self.callback_move_down) # setup the entry panel self.entry_popup = tkinter.Toplevel(self.master) self.entry = LabelEntryPanel(self.entry_popup, self.variables) self.entry.hide_on_close() self.entry_popup.withdraw() self.entry.save_button.config(command=self.callback_save_entry) self.set_label_schema(label_schema) @property def label_schema(self): """ None|LabelSchema: The label schema """ return self.variables.label_schema # some entry validation methods def _version_entry_validate(self): the_value = self.version_entry.get().strip() if the_value != '' and self.label_schema.version != the_value: self.variables.unsaved_edits = True self.label_schema._version = the_value self.label_schema.update_version_date(value=None) self.version_date_entry.set_text(self.label_schema.version_date) return True def _classification_validate(self): the_value = self.classification_entry.get().strip() if self.label_schema.classification != the_value: self.variables.unsaved_edits = True self.label_schema._classification = the_value return True def _confidence_validate(self): the_value = self.confidence_entry.get().strip() if the_value == '': the_values = None else: if ',' in the_value: temp_values = [entry.strip() for entry in the_value.split(',')] else: temp_values = the_value.split() # noinspection PyBroadException try: the_values = [int(entry) for entry in temp_values] except Exception: the_values = temp_values # reformat the contents self.confidence_entry.delete(0, len(the_value)) self.confidence_entry.insert( 0, ', '.join('{}'.format(entry) for entry in the_values)) if self.label_schema.confidence_values != the_values: self.variables.unsaved_edits = True self.label_schema.confidence_values = the_values return True def _geometry_validate(self): the_value = self.geometries_entry.get().strip() if the_value == '': the_values = None else: if ',' in the_value: the_values = [ entry.strip().lower() for entry in the_value.split(',') ] else: the_values = [entry.lower() for entry in the_value.split()] if (self.label_schema.permitted_geometries == the_values) or \ (self.label_schema.permitted_geometries is None and the_values is None): return True try: self.label_schema.permitted_geometries = the_values self.variables.unsaved_edits = True # reformat the contents self.geometries_entry.delete(0, len(the_value)) self.geometries_entry.insert( 0, ', '.join(self.label_schema.permitted_geometries)) return True except ValueError: return False def _geometry_invalid(self): showinfo( 'geometry type guidance', message='The contents for geometry should be a space delimited\n' 'combination of {"point", "line", "polygon"}') self.geometries_entry.focus_set() temp = self.geometries_entry.get() self.geometries_entry.delete(0, len(temp)) if self.label_schema is None or self.label_schema.permitted_geometries is None: self.geometries_entry.insert(0, '') else: self.geometries_entry.insert( 0, ' '.join(self.label_schema.permitted_geometries)) # some helper methods def _set_focus_on_entry_popup(self): self.entry_popup.deiconify() self.entry_popup.focus_set() self.entry_popup.lift() self.entry_popup.grab_set() def _verify_selected(self): if self.variables.current_id is None: showinfo('No Element Selected', message='Choose element from Viewer') return False return True def _check_save_state(self): """ Checks the save state. Returns ------- bool Continue (True) or abort (False) """ if not self.variables.unsaved_edits: return True result = askyesnocancel( title="Unsaved Edits", message="There are unsaved edits. Save before opening a new file?") if result is None: return False if result is True: self.save() return True def _update_schema_display(self): if self.variables.label_schema is None: self.version_entry.config(state='disabled') self.version_entry.set_text('') self.version_date_entry.set_text('') self.classification_entry.config(state='disabled') self.classification_entry.set_text('') self.confidence_entry.config(state='disabled') self.confidence_entry.set_text('') self.geometries_entry.config(state='disabled') self.geometries_entry.set_text('') else: self.version_entry.config(validate='none') self.version_entry.set_text(self.label_schema.version) self.version_entry.config(validate='focusout', state='normal') self.version_date_entry.set_text(self.label_schema.version_date) self.classification_entry.config(validate='none') self.classification_entry.set_text( self.label_schema.classification) self.classification_entry.config(validate='focusout', state='normal') self.confidence_entry.config(validate='none') if self.label_schema.confidence_values is None: self.confidence_entry.set_text('') else: self.confidence_entry.set_text(' '.join( '{}'.format(entry) for entry in self.label_schema.confidence_values)) self.confidence_entry.config(validate='focusout', state='normal') self.geometries_entry.config(validate='none') if self.label_schema.permitted_geometries is None: self.geometries_entry.set_text('') else: self.geometries_entry.set_text(' '.join( self.label_schema.permitted_geometries)) self.geometries_entry.config(validate='focusout', state='normal') self.schema_viewer.fill_from_label_schema(self.variables.label_schema) self.entry.update_label_schema() def prompt_for_filename(self): schema_file = asksaveasfilename( initialdir=self.variables.browse_directory, filetypes=[file_filters.json_files, file_filters.all_files]) if schema_file == '' or schema_file == (): # closed or cancelled return False self.variables.browse_directory = os.path.split(schema_file)[0] self.variables.label_file_name = schema_file # todo: what if this file name exists? It should prompt already? return True def set_label_schema(self, label_schema): """ Sets the label schema value. Parameters ---------- label_schema : None|str|LabelSchema """ if label_schema is None: self.variables.label_file_name = None self.variables.label_schema = LabelSchema() elif isinstance(label_schema, str): the_file = label_schema label_schema = LabelSchema.from_file(the_file) self.variables.label_file_name = the_file self.variables.label_schema = label_schema browse_dir = os.path.split(os.path.abspath(the_file))[0] self.variables.browse_directory = browse_dir elif isinstance(label_schema, LabelSchema): self.variables.label_file_name = None self.variables.label_schema = label_schema else: raise TypeError( 'input must be the path for a label schema file or a LabelSchema instance' ) self.variables.unsaved_edits = False self.variables.current_id = None self._update_schema_display() def set_current_id(self, value): if value == '': value = None if (value is None and self.variables.current_id is None) or \ (value == self.variables.current_id): return self.variables.current_id = value self.entry.update_current_id() if value is not None: self.schema_viewer.set_selection_with_expansion(value) # callbacks and bound methods def save(self): """ Save any current progress. """ if self.variables.label_file_name is None: if not self.prompt_for_filename(): return # they opted to not pick a file self.label_schema.to_file(self.variables.label_file_name) self.variables.unsaved_edits = False def exit(self): """ Exit the application. Returns ------- None """ if self.variables.unsaved_edits: save_state = askyesno('Save Progress', message='There are unsaved edits. Save?') if save_state is True: self.save() self.master.destroy() def callback_open(self): if not self._check_save_state(): return schema_file = askopenfilename( initialdir=self.variables.browse_directory, filetypes=[file_filters.json_files, file_filters.all_files]) if schema_file == '' or schema_file == (): # closed or cancelled return self.set_label_schema(schema_file) def callback_new_schema(self): if not self._check_save_state(): return self.set_label_schema(LabelSchema()) def callback_save_as(self): if self.prompt_for_filename(): # they chose the new filename self.save() def callback_edit_entry(self): if not self._verify_selected(): return self._set_focus_on_entry_popup() def callback_new_entry(self): self.variables.current_id = None self.entry.update_current_id() self._set_focus_on_entry_popup() def callback_delete_entry(self): if not self._verify_selected(): return selected = self.variables.current_id # does the selected entry have any children? children = self.label_schema.subtypes.get(selected, None) if children is not None and len(children) > 0: response = askyesnocancel( 'Delete Children?', message='Selected entry has children. Delete all children?') if response is not True: return self.schema_viewer.delete_entry(selected) self.variables.unsaved_edits = True self.set_current_id(None) def callback_move_up(self): if not self._verify_selected(): return selected = self.variables.current_id result = self.label_schema.reorder_child_element(selected, spaces=-1) if result: self.schema_viewer.rerender_entry(selected) self.variables.unsaved_edits = True def callback_move_down(self): if not self._verify_selected(): return selected = self.variables.current_id result = self.label_schema.reorder_child_element(selected, spaces=1) if result: self.schema_viewer.rerender_entry(selected) self.variables.unsaved_edits = True def callback_save_entry(self): if self.entry.save_function(): self.schema_viewer.rerender_entry(self.entry.id_changed) self.set_current_id(self.entry.id_changed) self.entry.close_window() self.entry_popup.grab_release() # noinspection PyUnusedLocal def item_selected_on_viewer(self, event): item_id = self.schema_viewer.focus() if item_id == '': return self.set_current_id(item_id)
def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master : tkinter.Tk|tkinter.ToplLevel app_variables : AppVariables kwargs keyword arguments passed through for frame """ self._app_variables = app_variables self._current_id = None self._parent_id = None self._new_entry = False self.id_changed = None # state variable for external usage self.master = master Frame.__init__(self, master, **kwargs) self.header_message = Label(self, text='', padding=5) self.header_message.grid(row=0, column=0, sticky='NSEW', padx=3, pady=3) self.frame2 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.id_label = Label(self.frame2, text='ID:', borderwidth=1, relief=tkinter.RIDGE, padding=5, width=10) self.id_label.grid(row=0, column=0, sticky='NW', padx=3, pady=3) self.id_entry = Entry(self.frame2, text='') self.id_entry.grid(row=0, column=1, sticky='NEW', padx=3, pady=3) self.name_label = Label(self.frame2, text='Name:', borderwidth=1, relief=tkinter.RIDGE, padding=5, width=10) self.name_label.grid(row=1, column=0, sticky='NW', padx=3, pady=3) self.name_entry = Entry(self.frame2, text='') self.name_entry.grid(row=1, column=1, sticky='NEW', padx=3, pady=3) self.parent_label = Label(self.frame2, text='Parent:', borderwidth=1, relief=tkinter.RIDGE, padding=5, width=10) self.parent_label.grid(row=2, column=0, sticky='NW', padx=3, pady=3) self.parent_button = Button(self.frame2, text='<Choose>') self.parent_button.grid(row=2, column=1, sticky='NEW', padx=3, pady=3) self.frame2.grid_columnconfigure(1, weight=1) self.frame2.grid(row=1, column=0, sticky='NSEW', padx=3, pady=3) self.frame3 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.cancel_button = Button(self.frame3, text='Cancel') self.cancel_button.pack(side=tkinter.RIGHT) self.save_button = Button(self.frame3, text='Save') self.save_button.pack(side=tkinter.RIGHT) self.frame3.grid(row=2, column=0, sticky='NSEW', padx=3, pady=3) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) self.pack(fill=tkinter.BOTH, expand=tkinter.TRUE) # callbacks self.parent_button.config(command=self.parent_callback) self.cancel_button.config(command=self.cancel_callback) # save_button bound by controlling parent self.update_label_schema()
class LabelEntryPanel(Frame): """ Panel for viewing and editing the details of a given label schema entry. """ def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master : tkinter.Tk|tkinter.ToplLevel app_variables : AppVariables kwargs keyword arguments passed through for frame """ self._app_variables = app_variables self._current_id = None self._parent_id = None self._new_entry = False self.id_changed = None # state variable for external usage self.master = master Frame.__init__(self, master, **kwargs) self.header_message = Label(self, text='', padding=5) self.header_message.grid(row=0, column=0, sticky='NSEW', padx=3, pady=3) self.frame2 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.id_label = Label(self.frame2, text='ID:', borderwidth=1, relief=tkinter.RIDGE, padding=5, width=10) self.id_label.grid(row=0, column=0, sticky='NW', padx=3, pady=3) self.id_entry = Entry(self.frame2, text='') self.id_entry.grid(row=0, column=1, sticky='NEW', padx=3, pady=3) self.name_label = Label(self.frame2, text='Name:', borderwidth=1, relief=tkinter.RIDGE, padding=5, width=10) self.name_label.grid(row=1, column=0, sticky='NW', padx=3, pady=3) self.name_entry = Entry(self.frame2, text='') self.name_entry.grid(row=1, column=1, sticky='NEW', padx=3, pady=3) self.parent_label = Label(self.frame2, text='Parent:', borderwidth=1, relief=tkinter.RIDGE, padding=5, width=10) self.parent_label.grid(row=2, column=0, sticky='NW', padx=3, pady=3) self.parent_button = Button(self.frame2, text='<Choose>') self.parent_button.grid(row=2, column=1, sticky='NEW', padx=3, pady=3) self.frame2.grid_columnconfigure(1, weight=1) self.frame2.grid(row=1, column=0, sticky='NSEW', padx=3, pady=3) self.frame3 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.cancel_button = Button(self.frame3, text='Cancel') self.cancel_button.pack(side=tkinter.RIGHT) self.save_button = Button(self.frame3, text='Save') self.save_button.pack(side=tkinter.RIGHT) self.frame3.grid(row=2, column=0, sticky='NSEW', padx=3, pady=3) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) self.pack(fill=tkinter.BOTH, expand=tkinter.TRUE) # callbacks self.parent_button.config(command=self.parent_callback) self.cancel_button.config(command=self.cancel_callback) # save_button bound by controlling parent self.update_label_schema() @property def label_schema(self): """ None|LabelSchema : The label schema. """ return self._app_variables.label_schema def update_label_schema(self): self.update_current_id() @property def current_id(self): """ None|str: The current id. """ return self._current_id def _set_parent_text(self): if self._parent_id is None or self._parent_id == '': self.parent_button.set_text('<Top Level>') else: self.parent_button.set_text( self.label_schema.labels[self._parent_id]) def update_current_id(self): self._current_id = self._app_variables.current_id if self.label_schema is None: self._new_entry = False self._parent_id = None self.header_message.set_text('No label schema defined.') self.id_entry.set_text('') self.id_entry.config(state='disabled') self.name_entry.set_text('') self.name_entry.config(state='disabled') self.parent_button.set_text('') self.parent_button.config(state='disabled') elif self._current_id is None: self._parent_id = None self._new_entry = True self.header_message.set_text( 'New entry - <ID> is immutable once initialized and <Name> is for simple interpretation.' ) id_suggestion = self.label_schema.suggested_next_id str_id_suggestion = '<ID>' if id_suggestion is None else str( id_suggestion) self.id_entry.set_text(str_id_suggestion) self.id_entry.config(state='normal') self.name_entry.set_text('<Name>') self.name_entry.config(state='normal') self._set_parent_text() self.parent_button.config(state='normal') else: self._new_entry = False self._parent_id = self.label_schema.get_parent(self._current_id) self.header_message.set_text( '<ID> is immutable, <Name> for simple interpretation.') self.id_entry.set_text(self._current_id) self.id_entry.config(state='disabled') self.name_entry.set_text( self.label_schema.labels[self._current_id]) self.name_entry.config(state='normal') self._set_parent_text() self.parent_button.config(state='normal') def parent_callback(self): """ Edit or populate the parent id. """ if self.label_schema is None: return self._parent_id = select_schema_entry(self.label_schema, start_id=self._parent_id) self._set_parent_text() def cancel_callback(self): if self.label_schema is None: return self.update_current_id() self.close_window() self.master.grab_release() def save_function(self): self.id_changed = None if self.label_schema is None: return True the_id = self.id_entry.get() the_name = self.name_entry.get() the_parent = '' if self._parent_id is None else self._parent_id if self._new_entry: # if this is a new entry, then verify that both id and name are set if the_id == '<ID>' or the_name == '<Name>': showinfo('Entries Not Initialized', message='Both `ID` and `Name` must be set.') return False try: self.label_schema.add_entry(the_id, the_name, the_parent=the_parent) self.id_changed = the_id except Exception as e: showinfo("Creation Error", message="Creation error - {}".format(e)) return False self._new_entry = False self.update_current_id() else: try: if self.label_schema.change_entry(the_id, the_name, the_parent): self.id_changed = the_id except Exception as e: showinfo("Edit Error", message="Editing error - {}".format(e)) return False return True def hide_on_close(self): self.master.protocol("WM_DELETE_WINDOW", self.close_window) def close_window(self): self.master.withdraw()
class _SchemaSelectionWidget(object): """ Class for interactive label schema selection widget. """ def __init__(self, label_schema, start_item=None): """ Parameters ---------- label_schema : str|LabelSchema start_item : None|str The initial item selected """ # validate label schema input if isinstance(label_schema, str): label_schema = LabelSchema.from_file(label_schema) if not isinstance(label_schema, LabelSchema): raise TypeError( 'label_schema must be either a path to am appropriate .json file or a ' 'LabelSchema object. Got type {}'.format(type(label_schema))) # initialize the selected value option self._selected_value = '' self.root = tkinter.Toplevel() self.root.geometry('250x400') self.root.wm_title('Select Label Schema Entry') self.viewer = SchemaViewer(self.root, label_schema=label_schema) self.viewer.frame.pack(side=tkinter.TOP, expand=tkinter.TRUE, fill=tkinter.BOTH) self.set_selected_value(start_item) self.submit_button = Button(self.root, text='Submit', command=self.set_value) self.submit_button.pack(side=tkinter.BOTTOM, expand=tkinter.TRUE) self.root.grab_set() self.root.wait_window() def set_value(self): self._selected_value = self.viewer.focus() self.root.destroy() @property def selected_value(self): """ str: The id of the selected element. """ return self._selected_value def set_selected_value(self, selected_value): """ Set the treeview selection to the provided id. Parameters ---------- selected_value : None|str Returns ------- None """ self.viewer.set_selection_with_expansion(selected_value)
def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master app_variables : AppVariables kwargs """ self.app_variables = app_variables self.current_metadata_list = None # type: Union[None, LabelMetadataList] self.in_progess_metadata = None # type: Union[None, LabelMetadata] self.has_confidence = False Frame.__init__(self, master, **kwargs) self.object_type_label = Label(self, text='Type:', relief=tkinter.RIDGE, width=15, padding=3) self.object_type_label.grid(row=0, column=0, sticky='NEW', padx=3, pady=3) self.object_type_button = Button(self, text='<Choose>') self.object_type_button.grid(row=0, column=1, sticky='NEW', padx=3, pady=3) self.comment_label = Label(self, text='Comment:', relief=tkinter.RIDGE, width=15, padding=3) self.comment_label.grid(row=1, column=0, sticky='NEW', padx=3, pady=3) self.comment_text = TextWithScrolling(self, height=5) self.comment_text.frame.grid(row=1, column=1, sticky='NSEW', padx=3, pady=3) self.confidence_label = Label(self, text='Confidence:', relief=tkinter.RIDGE, width=15, padding=3) self.confidence_label.grid(row=2, column=0, sticky='NEW', padx=3, pady=3) self.confidence_combo = Combobox(self) self.confidence_combo.grid(row=2, column=1, sticky='NEW', padx=3, pady=3) self.user_id_label = Label(self, text='User ID:', relief=tkinter.RIDGE, width=15, padding=3) self.user_id_label.grid(row=3, column=0, sticky='NEW', padx=3, pady=3) self.user_id_value = Entry(self, text='', relief=tkinter.RIDGE, width=15, state='disabled') self.user_id_value.grid(row=3, column=1, sticky='NEW', padx=3, pady=3) self.timestamp_label = Label(self, text='Timestamp:', relief=tkinter.RIDGE, width=15, padding=3) self.timestamp_label.grid(row=4, column=0, sticky='NEW', padx=3, pady=3) self.timestamp_value = Entry(self, text='', relief=tkinter.RIDGE, width=15, state='disabled') self.timestamp_value.grid(row=4, column=1, sticky='NEW', padx=3, pady=3) self.frame1 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.cancel_button = Button(self.frame1, text='Cancel') self.cancel_button.pack(side=tkinter.RIGHT) self.submit_button = Button(self.frame1, text='Submit') self.submit_button.pack(side=tkinter.RIGHT) self.frame1.grid(row=5, column=0, columnspan=2, sticky='NSEW') self.grid_rowconfigure(2, weight=1) self.grid_columnconfigure(1, weight=1) self.object_type_button.config( command=self.callback_select_object_type) self.cancel_button.config(command=self.callback_cancel)
class LabelSpecificsPanel(Frame): """ Edit/Display widget for LabelMetadata object """ def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master app_variables : AppVariables kwargs """ self.app_variables = app_variables self.current_metadata_list = None # type: Union[None, LabelMetadataList] self.in_progess_metadata = None # type: Union[None, LabelMetadata] self.has_confidence = False Frame.__init__(self, master, **kwargs) self.object_type_label = Label(self, text='Type:', relief=tkinter.RIDGE, width=15, padding=3) self.object_type_label.grid(row=0, column=0, sticky='NEW', padx=3, pady=3) self.object_type_button = Button(self, text='<Choose>') self.object_type_button.grid(row=0, column=1, sticky='NEW', padx=3, pady=3) self.comment_label = Label(self, text='Comment:', relief=tkinter.RIDGE, width=15, padding=3) self.comment_label.grid(row=1, column=0, sticky='NEW', padx=3, pady=3) self.comment_text = TextWithScrolling(self, height=5) self.comment_text.frame.grid(row=1, column=1, sticky='NSEW', padx=3, pady=3) self.confidence_label = Label(self, text='Confidence:', relief=tkinter.RIDGE, width=15, padding=3) self.confidence_label.grid(row=2, column=0, sticky='NEW', padx=3, pady=3) self.confidence_combo = Combobox(self) self.confidence_combo.grid(row=2, column=1, sticky='NEW', padx=3, pady=3) self.user_id_label = Label(self, text='User ID:', relief=tkinter.RIDGE, width=15, padding=3) self.user_id_label.grid(row=3, column=0, sticky='NEW', padx=3, pady=3) self.user_id_value = Entry(self, text='', relief=tkinter.RIDGE, width=15, state='disabled') self.user_id_value.grid(row=3, column=1, sticky='NEW', padx=3, pady=3) self.timestamp_label = Label(self, text='Timestamp:', relief=tkinter.RIDGE, width=15, padding=3) self.timestamp_label.grid(row=4, column=0, sticky='NEW', padx=3, pady=3) self.timestamp_value = Entry(self, text='', relief=tkinter.RIDGE, width=15, state='disabled') self.timestamp_value.grid(row=4, column=1, sticky='NEW', padx=3, pady=3) self.frame1 = Frame(self, borderwidth=1, relief=tkinter.RIDGE) self.cancel_button = Button(self.frame1, text='Cancel') self.cancel_button.pack(side=tkinter.RIGHT) self.submit_button = Button(self.frame1, text='Submit') self.submit_button.pack(side=tkinter.RIGHT) self.frame1.grid(row=5, column=0, columnspan=2, sticky='NSEW') self.grid_rowconfigure(2, weight=1) self.grid_columnconfigure(1, weight=1) self.object_type_button.config( command=self.callback_select_object_type) self.cancel_button.config(command=self.callback_cancel) def _populate_values_into_inprogress(self): msg = '' status = True if self.in_progess_metadata.label_id is None: msg += 'The label ID must be set\n' status = False comment = self.comment_text.get_value() self.in_progess_metadata.comment = None if comment == '' else comment if self.has_confidence: confidence = self.confidence_combo.get() self.in_progess_metadata.confidence = None if confidence == '' else confidence return status, msg def _fill_metadata(self, entry): """ Populates the values from entry. Parameters ---------- entry : None|LabelMetadata """ if entry is None: self.object_type_button.set_text('') self.comment_text.set_value('') self.confidence_combo.set_text('') self.user_id_value.set_text('') self.timestamp_value.set_text('') else: self.object_type_button.set_text( '<Choose>' if entry.label_id is None else self.app_variables. get_label(entry.label_id)) self.comment_text.set_value( '' if entry.comment is None else entry.comment) self.confidence_combo.set_text( '' if entry.confidence is None else str(entry.confidence)) self.user_id_value.set_text(entry.user_id) self.timestamp_value.set_text( datetime.utcfromtimestamp(entry.timestamp).isoformat('T')) def set_entry(self, index): """ Sets the entry to display Parameters ---------- index : None|int """ def disable(): self.object_type_button.config(state='disabled') self.comment_text.config(state='disabled') self.confidence_combo.config(state='disabled') self.cancel_button.config(state='disabled') self.submit_button.config(state='disabled') def enable(): self.object_type_button.config(state='normal') self.comment_text.config(state='normal') if self.has_confidence: self.confidence_combo.config(state='normal') self.cancel_button.config(state='normal') self.submit_button.config(state='normal') if index is not None: entry = self.current_metadata_list[index] self._fill_metadata(entry) disable() return if self.current_metadata_list is None: # nothing to be done self._fill_metadata(None) disable() return if len(self.current_metadata_list) == 0: self.in_progess_metadata = LabelMetadata() else: self.in_progess_metadata = self.current_metadata_list[0].replicate( ) self.in_progess_metadata.comment = None self._fill_metadata(self.in_progess_metadata) enable() def update_annotation(self): feature = self.app_variables.get_current_annotation_object() if feature is None: self.current_metadata_list = None self.set_entry(None) else: self.current_metadata_list = feature.properties.parameters if len(self.current_metadata_list) > 0: self.set_entry(0) else: self.set_entry(None) def update_annotation_collection(self): if self.app_variables.file_annotation_collection is not None: confidence_values = self.app_variables.file_annotation_collection.label_schema.confidence_values if confidence_values is None: self.has_confidence = False self.confidence_combo.update_combobox_values([]) else: self.has_confidence = True self.confidence_combo.update_combobox_values( ['{}'.format(entry) for entry in confidence_values]) self.update_annotation() def callback_select_object_type(self): if self.app_variables is None or \ self.app_variables.file_annotation_collection is None or \ self.in_progess_metadata is None: return # this would be broken state, and should not happen current_value = self.in_progess_metadata.label_id value = select_schema_entry(self.app_variables.label_schema, start_id=current_value) if value is None: return self.in_progess_metadata.label_id = value self.object_type_button.set_text(self.app_variables.get_label(value)) def callback_cancel(self): self.in_progess_metadata = None self.update_annotation() def callback_submit(self): # NB: this should be called by the controlling parent for holistic state change everywhere status, msg = self._populate_values_into_inprogress() if not status: showinfo('Incomplete data population', message=msg) return status self.current_metadata_list.insert_new_element(self.in_progess_metadata) self.in_progess_metadata = None return status
class LabelDetailsPanel(Frame): def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master app_variables : AppVariables kwargs """ self.app_variables = app_variables self.current_annotation = None # type: Union[None, LabelFeature] Frame.__init__(self, master, **kwargs) self.update_label_button = Button( self, text='Update Label', command=self.callback_update_label) # type: Button self.update_label_button.grid(row=0, column=0, columnspan=2, sticky='NE', padx=3, pady=3) self.viewer = TreeviewWithScrolling( self, columns=('confidence', ), selectmode=tkinter.BROWSE) # type: TreeviewWithScrolling self.viewer.heading('#0', text='Label') self.viewer.heading('#1', text='Confidence') self.viewer.frame.grid(row=1, column=0, sticky='NSEW', padx=3, pady=3) # NB: reference the frame for packing, since it's already packed into a frame self.label_specifics = LabelSpecificsPanel(self, app_variables, border=1, relief=tkinter.RIDGE) self.label_specifics.grid(row=1, column=1, sticky='NSEW', padx=3, pady=3) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) self.update_annotation() self.viewer.bind('<<TreeviewSelect>>', self.callback_label_selected_on_viewer) def _set_focus(self, uid): self.viewer.set_selection_with_expansion(uid) def _empty_entries(self): """ Empty all entries - for the purpose of reinitializing. """ self.viewer.delete(*self.viewer.get_children()) def _fill_treeview(self): """ Fills the treeview based on the current annotation. """ if self.current_annotation is None or \ self.current_annotation.properties.parameters is None or \ len(self.current_annotation.properties.parameters) == 0: self._empty_entries() self.label_specifics.update_annotation() self.label_specifics.set_entry(None) return self._empty_entries() label_list = self.current_annotation.properties.parameters for i, entry in enumerate(label_list): label = self.app_variables.get_label(entry.label_id) conf = '' if entry.confidence is None else entry.confidence self.viewer.insert('', 'end', iid=str(i), text=label, values=(conf, )) if len(label_list) > 0: self._set_focus('0') # noinspection PyUnusedLocal def callback_label_selected_on_viewer(self, event): label_index = self.viewer.focus() if label_index == '': return self.label_specifics.set_entry(int(label_index)) def callback_update_label(self): self.label_specifics.set_entry(None) def set_annotation_feature(self, annotation_feature): self.current_annotation = annotation_feature self._fill_treeview() self.label_specifics.update_annotation() def update_annotation(self): annotation_feature = self.app_variables.get_current_annotation_object() self.set_annotation_feature(annotation_feature) def cancel(self): self._fill_treeview() # probably unnecessary def save(self): self._fill_treeview() # probably unnecessary
class RCSCollectionPanel(AnnotationCollectionPanel): def __init__(self, master, app_variables, **kwargs): """ Parameters ---------- master The master widget app_variables : AppVariables kwargs keyword arguments passed through to the Frame constructor """ self.app_variables = app_variables Frame.__init__(self, master, **kwargs) self.button_panel = Frame(self, relief=tkinter.RIDGE, borderwidth=2) # type: Frame self.new_button = Button(self.button_panel, text='New Annotation', width=28) # type: Button self.new_button.grid(row=0, column=0, sticky='NW') self.edit_button = Button(self.button_panel, text='Edit Selected Annotation', width=28) # type: Button self.edit_button.grid(row=1, column=0, sticky='NW') self.zoom_button = Button(self.button_panel, text='Zoom to Selected Annotation', width=28) # type: Button self.zoom_button.grid(row=2, column=0, sticky='NW') self.move_button = Button(self.button_panel, text='Move Selected Annotation', width=28) # type: Button self.move_button.grid(row=3, column=0, sticky='NW') self.frame1 = Frame(self.button_panel) self.units_label = Label(self.frame1, text='Units:', relief=tkinter.RIDGE) self.units_label.pack(side=tkinter.LEFT) self.units_value = Combobox(self.frame1, text='') self.units_value.pack(side=tkinter.LEFT) self.frame1.grid(row=4, column=0, sticky='NW') self.button_panel.grid(row=0, column=0, sticky='NSEW') self.viewer = RCSCollectionViewer(self, app_variables) self.viewer.frame.grid(row=1, column=0, sticky='NSEW') self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) self.units_value.on_selection(self.callback_units_select) # noinspection PyUnusedLocal def callback_units_select(self, event): self.app_variables.rcs_viewer_units = self.units_value.get() self.update_annotation_collection() def update_annotation_collection(self): """ Should be called on an update of the annotation collection """ current_units = self.units_value.get() values = self.app_variables.get_rcs_units() self.units_value.update_combobox_values(values) if len(values) == 0: self.units_value.set_text('') self.app_variables.rcs_viewer_units = '' elif current_units in values: self.units_value.set_text(current_units) self.app_variables.rcs_viewer_units = current_units elif self.app_variables.rcs_viewer_units in values: self.units_value.set_text(self.app_variables.rcs_viewer_units) else: self.units_value.set(values[0]) self.app_variables.rcs_viewer_units = values[0] self.viewer.update_annotation_collection()