class RefObjectBSC(Frame): def __init__(self, parent, controller): Frame.__init__(self, parent) self.controller = controller self.title = "Configure the image's scale" self.responsive_image = None self.boxes = [] self.box = None self.stage1_widgets = [] self.stage2_widgets = [] self.initialize_widgets() self.bind("<<ShowFrame>>", self.on_show_frame) def initialize_widgets(self): # WATCHERS # Update state of navigation buttons self.current_contour_var = IntVar() self.current_contour_var.trace("w", self.update_navigation) # Transition between stages self.selected_object_var = StringVar() self.selected_object_var.trace("w", self.update_stage) # Switch between horizontal and vertical bisections self.dimension_type = StringVar() self.dimension_type.set("horizontal") self.dimension_type.trace("w", self.update_image) # Update confirm button state self.real_dimension_var = StringVar() self.real_dimension_var.trace("w", self.update_confirm_button) # Min size of object title column self.grid_columnconfigure(0, minsize=150) # Min size of change button column self.grid_columnconfigure(1, minsize=100) # title of ref object image self.ref_object_var = StringVar() self.ref_object_title = Label(self, textvariable=self.ref_object_var, font=self.controller.header_font) self.ref_object_title.grid(row=0, column=0, sticky=W, padx=20, pady=20) # STAGE 1 # Controls to navigate contours self.prev_button = GreenButton(self, text="Previous", image=self.controller.arrow_left, compound=LEFT, command=lambda: self.show_contour( self.current_contour_var.get() - 1)) self.stage1_widgets.append( (self.prev_button, lambda: self.prev_button.grid( row=0, column=1, sticky=SE, padx=5, pady=20))) self.next_button = GreenButton(self, text="Next", image=self.controller.arrow_right, compound=RIGHT, command=lambda: self.show_contour( self.current_contour_var.get() + 1)) self.stage1_widgets.append( (self.next_button, lambda: self.next_button.grid(row=0, column=2, sticky=SW, pady=20) )) # image in row=1, col=0, colspan=3, rowspan varies with stage # instructions self.instructions_var = StringVar() self.instructions = Label(self, textvariable=self.instructions_var, relief=GROOVE, padx=10, pady=10) self.instructions.grid(row=1, column=3, padx=40) # select reference object self.select_button = YellowButton( self, text="Use this object as reference", command=lambda: self.selected_object_var.set( self.current_contour_var.get())) self.stage1_widgets.append( (self.select_button, lambda: self.select_button.grid(row=2, column=3, sticky=N))) # STAGE 2 # Change reference object self.change_button = GreenButton( self, text="Change", command=lambda: self.selected_object_var.set("")) self.stage2_widgets.append( (self.change_button, lambda: self.change_button.grid( row=0, column=1, columnspan=2, sticky=SE, pady=20))) # select dimension to enter for the reference object self.dimension_label = Label(self, text="Select dimension", font=self.controller.header_font, anchor=SW) self.stage2_widgets.append( (self.dimension_label, lambda: self.dimension_label.grid( row=2, column=3, sticky=NSEW, padx=40, pady=10))) self.dimension_width = Radiobutton(self, text="Width", variable=self.dimension_type, value="horizontal", cursor="hand2", anchor=SW) self.dimension_height = Radiobutton(self, text="Height", variable=self.dimension_type, value="vertical", cursor="hand2", anchor=NW) self.stage2_widgets.append( (self.dimension_width, lambda: self.dimension_width.grid( row=3, column=3, sticky=NSEW, padx=60))) self.stage2_widgets.append( (self.dimension_height, lambda: self.dimension_height.grid( row=4, column=3, sticky=NSEW, padx=60))) # user-entered dimension (for pixels-per-metric) self.real_dimension_label = Label( self, text="True measure of pink line (in centimeters)", font=self.controller.header_font, anchor=SW) self.stage2_widgets.append( (self.real_dimension_label, lambda: self.real_dimension_label.grid( row=5, column=3, padx=40, pady=20, sticky=NSEW))) # %d = Type of action (1=insert, 0=delete, -1 for others) # %P = value of the entry if the edit is allowed # %S = the text string being inserted or deleted, if any validate_cmd = (self.register(self.validate_dimension), '%d', '%P', '%S') self.real_dimension = EntryWithPlaceholder( self, text_var=self.real_dimension_var, placeholder_text="0.00", validate="key", validatecommand=validate_cmd, textvariable=self.real_dimension_var) self.stage2_widgets.append( (self.real_dimension, lambda: self.real_dimension.grid( row=6, column=3, padx=60, sticky=NW))) # Invalid dimension message self.invalid_dimension = Label( self, text="Dimension must be between 1 and 28 centimeters", fg="red", anchor=W) # confirm button self.confirm_button = YellowButton(self, text="CONFIRM", command=self.confirm) self.stage2_widgets.append( (self.confirm_button, lambda: self.confirm_button.grid( row=8, column=3, sticky=SE, padx=20, pady=20))) # set dimension entry placeholder self.real_dimension.set_placeholder() # start in stage 1 self.selected_object_var.set("") def on_show_frame(self, event=None): # fetch all reference objects self.boxes = render_boxes() # Show the last object we were browsing, or the 1st one if this is a fresh session try: self.show_contour(self.current_contour_var.get()) # In case something weird happens except IndexError: print("could not show contour; resetting") self.reset() self.on_show_frame() def show_contour(self, index): self.box = self.boxes[index] self.current_contour_var.set(index) self.update_image() def update_image(self, *args): if self.box is not None: image = self.box[self.dimension_type.get()][0] if self.responsive_image is not None: self.responsive_image.destroy() self.responsive_image = ResponsiveImage(self, image) # stage 2 if self.selected_object_var.get(): self.responsive_image.grid(row=1, column=0, rowspan=8, columnspan=3, sticky=NSEW, pady=20) # stage 1 else: self.responsive_image.grid(row=1, column=0, rowspan=2, columnspan=3, sticky=NSEW, pady=20) def update_navigation(self, *args): current = self.current_contour_var.get() # update image title self.ref_object_var.set("Object #" + str(current + 1)) # toggle prev if current == 0: self.prev_button.configure(state=DISABLED, cursor="arrow") else: self.prev_button.configure(state=NORMAL, cursor="hand2") # toggle next if current == len(self.boxes) - 1: self.next_button.configure(state=DISABLED, cursor="arrow") else: self.next_button.configure(state=NORMAL, cursor="hand2") def update_stage(self, *args): # Object has been selected if self.selected_object_var.get(): # Show stage 2 self.set_stage2() else: # Show stage 1 self.set_stage1() def set_stage1(self): current = self.current_contour_var.get() # Update image title self.ref_object_var.set("Object #" + str(current + 1)) # Update instructions self.instructions_var.set( "Choose an object for which you know its width or height.\n" "This will allow to calculate the real dimensions of the bamboo slice." ) # Hide stage 2 for (widget, grid_command) in self.stage2_widgets: widget.grid_forget() # Hide invalid message self.invalid_dimension.grid_forget() # Show stage 1 for (widget, grid_command) in self.stage1_widgets: grid_command() # Update responsive image if self.responsive_image is not None: self.responsive_image.grid(row=1, column=0, rowspan=2, columnspan=3, sticky=NSEW, pady=20) # Update responsive reset_both_responsive(self) make_columns_responsive(self, ignored=[1, 2]) make_rows_responsive(self, ignored=[0]) def set_stage2(self): # Update image title self.ref_object_var.set("Selected Reference Object") # Update instructions self.instructions_var.set( "Now please tell us how long is the dimension given by the pink line in the image.\n" "Provide the most decimals for more accurate results!") # Hide stage 1 for (widget, grid_command) in self.stage1_widgets: widget.grid_forget() # Show stage 2 for (widget, grid_command) in self.stage2_widgets: grid_command() # Update responsive image if self.responsive_image is not None: self.responsive_image.grid(row=1, column=0, rowspan=8, columnspan=3, sticky=NSEW, pady=20) # Update responsive reset_both_responsive(self) make_columns_responsive(self, ignored=[0, 1]) make_rows_responsive(self, ignored=[0, 2, 3, 4, 5, 6, 7]) def validate_dimension(self, action, value_if_allowed, text): # only when inserting if (action == "1"): if text in "0123456789.": try: # maximum set by size of scanner (8.5 x 11 inches) if float(value_if_allowed) >= 1.0 and float( value_if_allowed) <= 28.0: # remove invalid message self.invalid_dimension.grid_forget() return True else: # Make system bell sound self.bell() # Show invalid message self.invalid_dimension.grid(row=7, column=3, sticky=NSEW, padx=60, pady=20) return False except ValueError: self.bell() return False else: self.bell() return False else: return True def update_confirm_button(self, *args): dimension = self.real_dimension_var.get() try: # not empty string and greater than 1 if dimension and float(dimension) >= 1.0: self.confirm_button.configure(state=NORMAL, cursor="hand2") else: self.confirm_button.configure(state=DISABLED, cursor="arrow") except ValueError: print("cannot cast dimension value to float") self.confirm_button.configure(state=DISABLED, cursor="arrow") def confirm(self): # pixels per metric = distance in pixels / distance in centimeters ppm = self.box[self.dimension_type.get()][1] / float( self.real_dimension.get()) set_pixels_per_metric(ppm) # Show results self.controller.show_frame("ResultsBSC") def reset(self): # destroy the image container if self.responsive_image is not None: self.responsive_image.grid_forget() self.responsive_image.destroy() self.responsive_image = None # reset real dimension entry field self.real_dimension.set_placeholder() # Default to width self.dimension_type.set("horizontal") # clear selected object; start in stage 1 self.selected_object_var.set("") # clear ref objects self.boxes.clear() self.box = None # Start showing 1st contour box self.current_contour_var.set(0)
class ResultsBSC(Frame): def __init__(self, parent, controller): Frame.__init__(self, parent) self.controller = controller self.title = "Slice Results" self.responsive_image = None self.initialize_widgets() self.bind("<<ShowFrame>>", self.on_show_frame) def initialize_widgets(self): # Result image row=0, col=0, columnspan=2 # Save button self.save_button = YellowButton(self, text="Save coordinates", command=self.save, image=self.controller.save_icon, compound=LEFT) self.save_button.grid(row=2, column=0, sticky=E, padx=10, pady=20) # Discard button self.discard_button = RedButton(self, text="DISCARD", command=self.discard) self.discard_button.grid(row=2, column=1, sticky=W, padx=10, pady=20) # min size of buttons row self.grid_rowconfigure(2, minsize=80) make_rows_responsive(self, ignored=[0]) make_columns_responsive(self) def on_show_frame(self, event=None): # generate polar coordinates and avg diameter of circumferences data_circumferences = circumferences_to_polar_and_avg_diameter() # create plot figure = Figure(figsize=(5,5), dpi=100) ax = figure.add_subplot(111, projection="polar") # ax.set_title("Circumferences' polar coordinates") # plot both circumferences for (r, theta, _) in data_circumferences: ax.plot(theta, r) # Create a Tk canvas of the plot self.polar_plot = FigureCanvasTkAgg(figure, self) self.polar_plot.show() self.polar_plot.get_tk_widget().grid(row=1, column=1, sticky=NSEW, padx=20) # Show some controls for the figure self.toolbar_container = Frame(self) self.plot_toolbar = NavigationToolbar(self.polar_plot, self.toolbar_container) self.plot_toolbar.update() self.toolbar_container.grid(row=0, column=1, sticky=NSEW, padx=20, pady=20) # original image with both circumferences outlined self.image = get_slice_roi() self.responsive_image = ResponsiveImage(self, self.image) self.responsive_image.grid(row=1, column=0, sticky=NSEW, padx=20, pady=20) def save(self): date = datetime.now().strftime('%Y-%m-%d_%H%M%S') save_path = filedialog.asksaveasfilename(title="Save as", defaultextension=".txt", initialfile="BSC_" + date) # make sure the user didn't cancel the dialog if len(save_path) > 0: if generate_text_file(save_path): # all good messagebox.showinfo("Success!", "File was generated successfully.") # reset BSC self.controller.reset_BSC() # go to home screen self.controller.show_frame("Home") else: messagebox.showerror("Error generating text file", "Make sure you have access to the selected destination.") def discard(self): result = messagebox.askokcancel("Discard results?", "All progress will be lost.", default="cancel", icon="warning") if result: # reset BSC self.controller.reset_BSC() # go to home screen self.controller.show_frame("Home") def reset(self): # destroy the image container if self.responsive_image is not None: self.responsive_image.destroy() self.responsive_image = None self.image = None # destroy the plot self.polar_plot = None
class PickCircumferencesBSC(Frame): def __init__(self, parent, controller): Frame.__init__(self, parent) self.controller = controller self.title = "Select the slice's circumferences" self.circumferences = [] self.selected_circumferences = [] self.responsive_image = None self.initialize_widgets() self.bind("<<ShowFrame>>", self.on_show_frame) def initialize_widgets(self): # Watchers # Update state of navigation buttons self.current_circumference_var = IntVar() self.current_circumference_var.trace("w", self.update_navigation) # Update count label self.selected_count_var = IntVar() self.selected_count_var.trace("w", self.update_count_text) # Update buttons self.selected_count_text = StringVar() self.selected_count_text.trace("w", self.update_buttons) # title of circumference image self.circumference_title_var = StringVar() self.circumference_title = Label( self, textvariable=self.circumference_title_var, font=self.controller.header_font) self.circumference_title.grid(row=0, column=0, sticky=W, padx=20, pady=20) # Controls to navigate contours self.prev_button = GreenButton( self, text="Previous", image=self.controller.arrow_left, compound=LEFT, command=lambda: self.show_circumference( self.current_circumference_var.get() - 1)) self.prev_button.grid(row=0, column=1, sticky=SE, padx=5, pady=20) self.next_button = GreenButton( self, text="Next", image=self.controller.arrow_right, compound=RIGHT, command=lambda: self.show_circumference( self.current_circumference_var.get() + 1)) self.next_button.grid(row=0, column=2, sticky=SW, pady=20) # image in row=1, col=0, colspan=3, rowspan=4 # instructions instructions_text = "More than 2 circumferences were found.\n" instructions_text += "Select the inner and outer circumference of the bamboo slice." self.instructions = Label(self, text=instructions_text, relief=GROOVE, padx=10, pady=10) self.instructions.grid(row=1, column=3, padx=40) # remove button self.remove_button = RedButton(self, text="Deselect circumference", command=self.deselect) self.remove_button.grid(row=2, column=3) # select button self.select_button = YellowButton(self, text="Use this circumference", command=self.select) self.select_button.grid(row=2, column=3) # Selection count self.selected_count = Label(self, textvariable=self.selected_count_text, font=self.controller.important_font) self.selected_count.grid(row=3, column=3, sticky=N) # confirm button self.confirm_button = YellowButton(self, text="CONFIRM", command=self.confirm, state=DISABLED, cursor="arrow") self.confirm_button.grid(row=4, column=3, sticky=SE, padx=20, pady=20) make_columns_responsive(self, ignored=[1, 2]) make_rows_responsive(self, ignored=[0]) def on_show_frame(self, event=None): # fetch all circumferences self.circumferences = render_all_circumferences() # initialize if it's empty if not self.selected_circumferences: # generate selected flags self.selected_circumferences = [False] * len(self.circumferences) # Show the last object we were browsing, or the 1st one if this is a fresh session try: self.show_circumference(self.current_circumference_var.get()) # In case something weird happens except IndexError: print("could not show circumference; resetting") self.reset() self.on_show_frame() def show_circumference(self, index): self.current_circumference_var.set(index) image = self.circumferences[index] if self.responsive_image is not None: self.responsive_image.destroy() self.responsive_image = ResponsiveImage(self, image) self.responsive_image.grid(row=1, column=0, rowspan=4, columnspan=3, sticky=NSEW, pady=20) def update_navigation(self, *args): current = self.current_circumference_var.get() self.circumference_title_var.set("Circumference #" + str(current + 1)) # toggle prev if current == 0: self.prev_button.configure(state=DISABLED, cursor="arrow") else: self.prev_button.configure(state=NORMAL, cursor="hand2") # toggle next if current == len(self.circumferences) - 1: self.next_button.configure(state=DISABLED, cursor="arrow") else: self.next_button.configure(state=NORMAL, cursor="hand2") # Toggle select and remove buttons self.update_select_remove_buttons() def update_buttons(self, *args): self.update_select_remove_buttons() self.update_confirm_button() def update_select_remove_buttons(self): # only if array has been initialized if len(self.selected_circumferences): # selected if self.selected_circumferences[ self.current_circumference_var.get()]: # this one is selected; show remove button self.select_button.grid_remove() self.remove_button.grid() # not selected else: # show select button self.remove_button.grid_remove() self.select_button.grid() # 2 already selected; disable select button if self.selected_count_var.get() == 2: self.select_button.configure(state=DISABLED, cursor="arrow") else: # still can select; re-enable button self.select_button.configure(state=NORMAL, cursor="hand2") def update_confirm_button(self): # Toggle confirm button if self.selected_count_var.get() == 2: self.confirm_button.configure(state=NORMAL, cursor="hand2") else: self.confirm_button.configure(state=DISABLED, cursor="arrow") def update_count_text(self, *args): # update label text self.selected_count_text.set( str(self.selected_count_var.get()) + " of 2 circumferences selected") def deselect(self): # deselect self.selected_circumferences[ self.current_circumference_var.get()] = False # Decrease count self.selected_count_var.set(self.selected_count_var.get() - 1) def select(self): # mark as selected self.selected_circumferences[ self.current_circumference_var.get()] = True # Increase count self.selected_count_var.set(self.selected_count_var.get() + 1) def confirm(self): selected = [] for index, selected_flag in enumerate(self.selected_circumferences): if selected_flag: selected.append(index) # Apply in backend set_final_circumferences(selected) # Show results self.controller.show_frame("RefObjectBSC") def reset(self): # destroy the image container if self.responsive_image is not None: self.responsive_image.destroy() self.responsive_image = None # clear circumferences self.circumferences.clear() self.selected_circumferences.clear() self.selected_count_var.set(0) # Start showing 1st circumference self.current_circumference_var.set(0)
class ResultsBSC(Frame): def __init__(self, parent, controller): Frame.__init__(self, parent) self.controller = controller self.title = "Slice processing results" self.responsive_image = None self.initialize_widgets() self.bind("<<ShowFrame>>", self.on_show_frame) def initialize_widgets(self): # Result image row=0, col=0, columnspan=2 # Save button self.save_button = YellowButton(self, text="Save coordinates", command=self.save, image=self.controller.save_icon, compound=LEFT) self.save_button.grid(row=2, column=0, sticky=E, padx=10, pady=20) # Discard button self.discard_button = RedButton(self, text="DISCARD", command=self.discard) self.discard_button.grid(row=2, column=1, sticky=W, padx=10, pady=20) # min size of buttons row self.grid_rowconfigure(2, minsize=80) make_rows_responsive(self, ignored=[0]) make_columns_responsive(self) def on_show_frame(self, event=None): final_circumferences = translate_coordinates() # create plot figure = Figure(figsize=(5, 5), dpi=100) ax = figure.add_subplot(111) # colors of plot series (outer = red, inner = blue) colors = ("r", "b") for ((contour_x, contour_y), (centroid_x, centroid_y), avg_diameter), color in zip(final_circumferences, colors): # plot the contours ax.plot(contour_x, contour_y, color=color) # plot the centroids ax.plot(centroid_x, centroid_y, color=color, marker="o") # Create a Tk canvas of the plot self.plot = FigureCanvasTkAgg(figure, self) self.plot.show() self.plot.get_tk_widget().grid(row=1, column=1, sticky=NSEW, padx=20) # Show some controls for the figure self.toolbar_container = Frame(self) self.plot_toolbar = NavigationToolbar2TkAgg(self.plot, self.toolbar_container) self.plot_toolbar.update() self.toolbar_container.grid(row=0, column=1, sticky=NSEW, padx=20, pady=20) # original image with both circumferences outlined self.image = get_slice_roi() self.responsive_image = ResponsiveImage(self, self.image, anchor=CENTER) self.responsive_image.grid(row=1, column=0, sticky=NSEW, padx=20, pady=20) def save(self): date = datetime.now().strftime('%Y-%m-%d_%H%M%S') save_path = filedialog.asksaveasfilename(title="Save as", defaultextension=".txt", initialfile="BSC_" + date) # make sure the user didn't cancel the dialog if len(save_path) > 0: if generate_text_file(save_path): # ask to open text file should_open_file = messagebox.askyesno( "Open generated text file?", "The bamboo slice information has been saved in " + save_path + "\n\nWould you like to open the text file now?") # open the text file if should_open_file: try: Popen(save_path, shell=True) except OSError as e: print("Error opening text file:", e) else: messagebox.showerror( "Error generating text file", "Make sure you have access to the selected destination.") def discard(self): result = messagebox.askokcancel("Discard results?", "All progress will be lost.", default="cancel", icon="warning") if result: # reset BSC self.controller.reset_BSC() # go to home screen self.controller.show_frame("Home") def reset(self): # destroy the image container if self.responsive_image is not None: self.responsive_image.destroy() self.responsive_image = None self.image = None # destroy the plot self.plot = None
class ConfigBSC(Frame): def __init__(self, parent, controller): Frame.__init__(self, parent) self.controller = controller self.title = "Select a bamboo slice image" self.initialize_widgets() self.bind("<<ShowFrame>>", self.on_show_frame) def initialize_widgets(self): # Watchers # on image path change self.image_path = StringVar() self.image_path.trace("w", self.on_image_path_change) # responsive image container self.placeholder_image = Image.open("assets/placeholder_image.png") self.responsive_image = ResponsiveImage(self, self.placeholder_image) self.responsive_image.grid(row=0, column=0, rowspan=4) # choose image button self.choose_button = GreenButton(self, text="Choose an image", command=self.load_image) self.choose_button.grid(row=0, column=1, sticky=S) # selected image path self.path_entry = Entry(self, textvariable=self.image_path, state="readonly") self.path_entry.grid(row=1, column=1, sticky=EW, padx=20) # status message in row 2 self.message_var = StringVar() self.message = Label(self, textvariable=self.message_var, font=self.controller.header_font) # begin button self.begin_button = YellowButton(self, text="BEGIN", command=self.begin, image=self.controller.arrow_right, compound=RIGHT) self.begin_button.grid(row=3, column=1, sticky=SE, padx=20, pady=20) # Update widgets self.on_image_path_change() # visited flag self.visit_counter = 0 make_rows_responsive(self) make_columns_responsive(self) def load_image(self): # open a file chooser dialog and allow the user to select a source image temp_path = filedialog.askopenfilename( title="Select an image to process", filetypes=(("JPG", "*.jpg"), ("JPEG", "*.jpeg"), ("PNG", "*.png"), ("TIF", "*.tif"), ("TIFF", "*.tiff"), ("Windows bitmaps", "*.bmp"))) # ensure a file path was selected if len(temp_path) > 0: # disable button before processing self.begin_button.configure(state=DISABLED, cursor="wait") # Process image self.circumferences_found = process_image(temp_path) # update image path, message, and begin button self.image_path.set(temp_path) # image not found if self.circumferences_found is None: # show error message messagebox.showerror( "Could not process image", "The image may have been moved or renamed, or you may not have access to it." ) else: # Not a fresh session if self.visit_counter > 1: # reset the BSC GUI except this frame self.controller.reset_BSC_GUI(ignored=[type(self)]) # make this the 1st visit self.visit_counter = 1 # user image with all detected circumferences outlined image = get_config_image() # make it responsive self.responsive_image.destroy() self.responsive_image = ResponsiveImage(self, image) self.responsive_image.grid(row=0, column=0, rowspan=4, sticky=NSEW, pady=20) def on_image_path_change(self, *args): # image is selected if self.image_path.get(): # show image path self.path_entry.grid() self.choose_button.configure(text="Change image") # error processing image or none found if self.circumferences_found is None or self.circumferences_found == 0: # disable begin button self.begin_button.configure(state=DISABLED, cursor="arrow") # make message red self.message.configure(fg="red") # error processing image if self.circumferences_found is None: self.message_var.set("Error processing selected image") # show placeholder image self.set_placeholder_image() # none found else: self.message_var.set( str(self.circumferences_found) + " circumference(s) found.\n Choose another image.") # some where found else: # enable begin button self.begin_button.configure(state=NORMAL, cursor="hand2") # make message green self.message.configure(fg="#35AD35") # 1 or 2 found if self.circumferences_found <= 2: self.message_var.set( "Bamboo slice detected!\n No need to choose circumferences." ) # more than 2 found else: self.message_var.set( str(self.circumferences_found) + " circumferences found.\n You must choose two of them." ) # show the message self.message.grid(row=2, column=1, padx=20) # not selected else: self.begin_button.configure(state=DISABLED, cursor="arrow") self.path_entry.grid_remove() self.choose_button.configure(text="Choose an image") # hide the message self.message.grid_remove() def begin(self): # Go to pick circumferences if found more than 2 if get_number_original_circumferences() > 2: self.controller.show_frame("PickCircumferencesBSC") # Go to configure scale else: self.controller.show_frame("RefObjectBSC") def on_show_frame(self, event=None): self.visit_counter += 1 def set_placeholder_image(self): self.responsive_image.destroy() self.responsive_image = ResponsiveImage(self, self.placeholder_image) self.responsive_image.grid(row=0, column=0, rowspan=4) def reset(self): # reset to placeholder image self.set_placeholder_image() # Clear image path self.image_path.set("") # visited flag self.visit_counter = 0
class BambooScanner(Tk): def __init__(self): Tk.__init__(self) # START SHARED # Global fonts self.global_font_family = font.Font(family="Segoe UI Emoji") self.common_font = font.Font(family="Segoe UI Emoji", size=14) self.bold_font = font.Font(family="Segoe UI Emoji", size=13, weight="bold") self.header_font = font.Font(family="Segoe UI Emoji", size=16, weight="bold") self.title_font = font.Font(family="Segoe UI Emoji", size=28, weight="bold") self.important_font = font.Font(family="Segoe UI Emoji", size=28, weight="bold", underline=True) # update widget fonts self.option_add("*Font", self.global_font_family) self.option_add("*Button.Font", self.bold_font) self.option_add("*Label.Font", self.common_font) # Common images self.arrow_left = ImageTk.PhotoImage( Image.open("assets/arrow_left.png")) self.arrow_right = ImageTk.PhotoImage( Image.open("assets/arrow_right.png")) self.save_icon = ImageTk.PhotoImage(Image.open("assets/save.png")) # END SHARED # Main page layout: hosts page content and global widgets self.container = Frame(self) # Container fills the entire window self.container.pack(side=TOP, fill=BOTH, expand=True) # Navigation bar self.navbar = Frame(self.container, bg="#35AD35") self.navbar.grid(row=0, columnspan=2, sticky=NSEW) # Home button home_image = Image.open("assets/home.png") # resize home button image home_image = resize_keep_aspect(home_image, max_w=100, max_h=37) home_image = ImageTk.PhotoImage(home_image) self.home_button = Label(self.navbar, image=home_image, cursor="hand2", text="Home") self.home_button.image = home_image self.home_button.grid(row=0, column=0, sticky=NSEW) # bind to click event self.home_button.bind("<Button-1>", self.go_home) self.home_button.bind("<Enter>", self.on_enter_home_btn) self.home_button.bind("<Leave>", self.on_leave_home_btn) # Back button self.back_button = Button(self.navbar, image=self.arrow_left, cursor="hand2", text="Go back", compound=LEFT, command=self.go_back, bg="#35AD35", fg="#FFFFFF", relief=GROOVE, padx=10) self.back_button.grid(row=0, column=1, sticky=NSEW) # Page title self.title_var = StringVar() self.page_title = Label(self.navbar, textvariable=self.title_var, bg="#35AD35", fg="#FFFFFF", font=self.title_font) self.page_title.grid(row=0, column=2, sticky=W + E, padx=10) # Step indicator self.step = StringVar() self.page_step = Label(self.navbar, textvariable=self.step, bg="#35AD35", fg="#FFFFFF") self.page_step.grid(row=0, column=3, sticky=E, padx=10) # only the page title is responsive make_columns_responsive(self.navbar, ignored=[0, 1, 3]) # Bamboo decor on left side of screen bamboo_image = Image.open("assets/bamboo.png") self.bamboo = ResponsiveImage(self.container, bamboo_image, tag="bamboo", anchor=NW) self.bamboo.grid(row=1, column=0, sticky=NSEW) # The class names for both BSC and BPC self.bsc_pages = (ConfigBSC, PickCircumferencesBSC, RefObjectBSC, ResultsBSC) self.bpc_pages = (ConfigBPC, MeasureBPC, ResultsBPC) # Initialize all pages and keep their references accessible self.frames = {} for F in (() + (Home, ) + self.bsc_pages + self.bpc_pages): page_name = F.__name__ frame = F(parent=self.container, controller=self) self.frames[page_name] = frame # Add all frames to the container, on top of each other frame.grid(row=1, column=1, sticky=NSEW) # make the window responsive, except the navbar row make_rows_responsive(self.container, ignored=[0]) make_columns_responsive(self.container) # Start on the home page self.active_frame = None self.show_frame("Home") def go_back(self, event=None): # Check if active frame is from BPC for i, page in enumerate(self.bpc_pages): # find the active page if type(self.active_frame) == page: # first page goes back to Home if i == 0: self.go_home() else: # name of previous frame page_name = self.bpc_pages[i - 1].__name__ self.show_frame(page_name) return # Must be in BSC then for i, page in enumerate(self.bsc_pages): # find the active page if type(self.active_frame) == page: # first page goes back to Home if i == 0: self.go_home() else: # name of previous frame page_name = self.bsc_pages[i - 1].__name__ # Don't go back to pick circumferences if only 2 where detected if page_name == "PickCircumferencesBSC" and get_number_original_circumferences( ) <= 2: self.show_frame("ConfigBSC") else: self.show_frame(page_name) return def go_home(self, event=None): result = messagebox.askokcancel( "Go Home?", "If you leave now, all unsaved progress will be lost.", default="cancel", icon="warning") if result: # Reset the tool we were using # came from BPC if type(self.active_frame) in self.bpc_pages: self.reset_BPC() # came from BSC else: self.reset_BSC() # go home self.show_frame("Home") def on_enter_home_btn(self, event): self.home_button.configure(compound=BOTTOM) def on_leave_home_btn(self, event): self.home_button.configure(compound=NONE) def show_frame(self, page_name): """ Raise a "leave" event on the current frame, a "show" event on the new one, and display it. :param page_name: class name of destination frame """ # If there is an active frame, signal its exit if self.active_frame is not None: self.active_frame.event_generate("<<LeaveFrame>>") # Switch to new active frame self.active_frame = self.frames[page_name] # update page title and step, except for Home page if type(self.active_frame) != Home: self.update_page_title(self.active_frame.title) self.update_page_step() self.active_frame.update() self.active_frame.event_generate("<<ShowFrame>>") self.active_frame.tkraise() def get_frame(self, page_name): """ Get the instance of a page :return: The frame of the specified page """ return self.frames[page_name] def update_page_title(self, title): self.title_var.set(title) def update_page_step(self): # Check if active frame is from BPC for i, page in enumerate(self.bpc_pages, start=1): # find the active page if type(self.active_frame) == page: message = "Pole Characterization\n step " + str( i) + " of " + str(len(self.bpc_pages)) self.step.set(message) return # Active frame is from BSC for i, page in enumerate(self.bsc_pages, start=1): # find the active page if type(self.active_frame) == page: message = "Slice Characterization\n step " + str( i) + " of " + str(len(self.bsc_pages)) self.step.set(message) return def hide_navbar(self): self.navbar.grid_remove() def restore_navbar(self): self.navbar.grid() def reset_BPC_GUI(self, **kwargs): ignored = kwargs.pop("ignored", []) if kwargs: raise TypeError('Unexpected **kwargs: %r' % kwargs) for page in self.bpc_pages: if page not in ignored: name = page.__name__ self.frames[name].reset() def reset_BPC(self): """ Reset all the BPC GUI frames and its backend module """ # reset GUI self.reset_BPC_GUI() # reset backend reset_bpc_backend() def reset_BSC_GUI(self, **kwargs): ignored = kwargs.pop("ignored", []) if kwargs: raise TypeError('Unexpected **kwargs: %r' % kwargs) for page in self.bsc_pages: if page not in ignored: name = page.__name__ self.frames[name].reset() def reset_BSC(self): """ Reset all the BSC GUI frames and its backend module """ # reset GUI self.reset_BSC_GUI() # reset backend reset_bsc_backend()