Exemple #1
0
    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)
Exemple #2
0
    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 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 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)
Exemple #5
0
    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 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)
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)
Exemple #8
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
Exemple #9
0
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)
Exemple #10
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
 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)
Exemple #13
0
    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")
Exemple #14
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()