Example #1
0
    def initialize_widgets(self):
        # Watchers
        # Update captured count
        self.count_str = StringVar()
        self.count_number = IntVar()
        self.count_number.trace("w", self.update_count_label)

        # Status message
        self.status_var = StringVar()
        self.status_message = Label(self, textvariable=self.status_var, font=self.controller.header_font, relief=GROOVE,
                                    padx=10, pady=10)
        self.status_message.grid(row=0, column=0, sticky=EW, padx=40, pady=20)

        # Table headers
        top_headers = ["Sensor #", "Current\nreading", "Last\ncaptured\nvalue", "Last\ndeviation"]
        self.table_headers = VerticalTable(self, rows=1, columns=len(top_headers))
        self.table_headers.update_cells(top_headers)
        self.table_headers.grid(row=1, column=0, sticky=S)

        # column indexes
        self.live_column = 0
        self.captured_column = 1
        self.deviation_column = 2

        # Sensor Data: current readings, last captured, and deviation info
        sensor_headers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "Sensor Z"]
        self.table = HorizontalTable(self, rows=len(sensor_headers), columns=3, header_values=sensor_headers)
        self.table.grid(row=2, column=0, rowspan=2, sticky=N)

        # calibrate button
        self.calibrate_button = GreenButton(self, text="Calibrate Sensors", command=self.calibrate)
        self.calibrate_button.grid(row=0, column=1, pady=20)

        # captured count
        self.captured_count = Label(self, textvariable=self.count_str, font=self.controller.bold_font)
        self.captured_count.grid(row=2, column=1, sticky=S, pady=10)

        # capture button
        self.capture_button = YellowButton(self, text="Capture Measurements", command=self.capture)
        self.capture_button.grid(row=3, column=1, sticky=N)

        # view results button
        self.results_button = GreenButton(self, text="View Results", command=self.view_results,
                                          image=self.controller.arrow_right, compound=RIGHT)
        self.results_button.grid(row=4, column=1, sticky=SE, padx=20, pady=20)

        make_rows_responsive(self)
        make_columns_responsive(self)
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    def initialize_widgets(self):
        # Empty message
        self.empty_message = Label(
            self,
            text="Nothing to see here. Go capture some measurements!",
            font=self.controller.header_font)

        # 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=1, column=0, sticky=SE, padx=10, pady=20)

        # Discard button
        self.discard_button = RedButton(self,
                                        text="DISCARD",
                                        command=self.discard)
        self.discard_button.grid(row=1, column=1, sticky=SW, padx=10, pady=20)

        make_rows_responsive(self)
        make_columns_responsive(self)
Example #5
0
    def initialize_widgets(self):
        # a canvas with scrollbars; the results table goes in it
        h_scrollbar = AutoScrollbar(self, orient=HORIZONTAL)
        h_scrollbar.grid(row=1, column=0, columnspan=2, sticky=EW)
        v_scrollbar = AutoScrollbar(self)
        v_scrollbar.grid(row=0, column=2, sticky=NS)

        self.canvas = Canvas(self,
                             xscrollcommand=h_scrollbar.set,
                             yscrollcommand=v_scrollbar.set)
        h_scrollbar.config(command=self.canvas.xview)
        v_scrollbar.config(command=self.canvas.yview)

        # Empty message
        self.empty_message = Label(
            self,
            text="Nothing to see here. Go capture some measurements!",
            font=self.controller.header_font)

        # 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=S, pady=20)

        # Discard button
        self.discard_button = RedButton(self,
                                        text="DISCARD",
                                        command=self.discard)
        self.discard_button.grid(row=2, column=1, sticky=S, pady=20)

        # responsive except the scrollbars and the buttons
        make_rows_responsive(self, ignored=[1, 2])
        make_columns_responsive(self, ignored=[2])
Example #6
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
Example #7
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)
Example #8
0
    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])
Example #9
0
class ConfigBPC(Frame):
    def __init__(self, parent, controller):
        Frame.__init__(self, parent)
        self.controller = controller
        self.title = "Configuration"
        self.initialize_widgets()

    def initialize_widgets(self):
        # CALIBRATION

        # Ring diameter entry value
        self.ring_diameter_var = StringVar()
        self.ring_diameter_var.trace("w", self.update_begin_button)

        # Calibration object value
        self.calibration_object_var = StringVar()
        self.calibration_object_var.trace("w", self.update_begin_button)

        # Distance to end of rail value
        self.distance_z_var = StringVar()
        self.distance_z_var.trace("w", self.update_begin_button)

        # %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')

        # Calibration settings group
        self.calibration_settings = LabelFrame(
            self,
            text="Calibration Settings",
            fg="grey",
            padx=20,
            pady=20,
            font=self.controller.header_font)
        self.calibration_settings.grid(row=0,
                                       column=0,
                                       sticky=NS + W,
                                       padx=20,
                                       pady=20)

        # Ring diameter
        self.ring_diameter_label = Label(self.calibration_settings,
                                         text="Ring structure diameter",
                                         anchor=SW,
                                         font=self.controller.bold_font)
        self.ring_diameter_label.grid(row=0, column=0, sticky=SW, padx=20)

        self.ring_diameter_entry = EntryWithPlaceholder(
            self.calibration_settings,
            text_var=self.ring_diameter_var,
            placeholder_text="0.00",
            validatecommand=validate_cmd,
            validate="key",
            textvariable=self.ring_diameter_var)
        self.ring_diameter_entry.grid(row=1,
                                      column=0,
                                      sticky=NW,
                                      padx=20,
                                      pady=20)

        # Calibration object
        self.calibration_object_label = Label(self.calibration_settings,
                                              text="Calibration object radius",
                                              font=self.controller.bold_font,
                                              anchor=SW)
        self.calibration_object_label.grid(row=0, column=1, sticky=SW, padx=20)

        self.calibration_object_entry = EntryWithPlaceholder(
            self.calibration_settings,
            text_var=self.calibration_object_var,
            placeholder_text="0.00",
            validatecommand=validate_cmd,
            textvariable=self.calibration_object_var,
            validate="key")
        self.calibration_object_entry.grid(row=1,
                                           column=1,
                                           sticky=NW,
                                           padx=20,
                                           pady=20)

        # Distance to flat surface at end of rail
        self.distance_z_label = Label(self.calibration_settings,
                                      text="Distance to the end of the rail",
                                      anchor=SW,
                                      font=self.controller.bold_font)
        self.distance_z_label.grid(row=2, column=0, sticky=SW, padx=20)

        self.distance_z_entry = EntryWithPlaceholder(
            self.calibration_settings,
            text_var=self.distance_z_var,
            placeholder_text="0.00",
            validate="key",
            validatecommand=validate_cmd,
            textvariable=self.distance_z_var)
        self.distance_z_entry.grid(row=3,
                                   column=0,
                                   sticky=NW,
                                   padx=20,
                                   pady=20)

        # Invalid dimension message
        self.invalid_dimension = Label(
            self.calibration_settings,
            text="Dimension must be between 1 and 28 centimeters",
            fg="red",
            anchor=W)

        # make calibration section responsive
        make_columns_responsive(self.calibration_settings)
        make_rows_responsive(self.calibration_settings)

        # description label
        self.description_label = Label(
            self,
            text="Information about the sample (optional)",
            font=self.controller.bold_font)
        self.description_label.grid(row=1,
                                    column=0,
                                    sticky=SW,
                                    padx=20,
                                    pady=20)

        # Description text area
        self.text_area = ScrollableTextArea(self)
        self.text_area.grid(row=2, column=0, sticky=NW, padx=20)

        # 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=0, sticky=SE, padx=20, pady=20)

        # set placeholders
        self.ring_diameter_entry.set_placeholder()
        self.calibration_object_entry.set_placeholder()
        self.distance_z_entry.set_placeholder()

        make_rows_responsive(self)
        make_columns_responsive(self)

    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_begin_button(self, *args):
        ring_diameter = self.ring_diameter_var.get()
        calibration_obj = self.calibration_object_var.get()
        distance_z = self.distance_z_var.get()

        try:
            ring_diameter_ok = ring_diameter and float(ring_diameter) >= 1.0
            calibration_object_ok = calibration_obj and float(
                calibration_obj) >= 1.0
            distance_z_ok = distance_z and float(distance_z) >= 1.0

            # All entries filled
            if ring_diameter_ok and calibration_object_ok and distance_z_ok:
                self.begin_button.configure(state=NORMAL, cursor="hand2")
            else:
                self.begin_button.configure(state=DISABLED, cursor="arrow")
        except ValueError:
            print("cannot cast values to float")
            self.begin_button.configure(state=DISABLED, cursor="arrow")

    def begin(self):
        # save sample description
        set_sampleDescription(self.text_area.get_text())

        # save calibration settings
        ring_diameter = float(self.ring_diameter_var.get())
        calibration_obj = float(self.calibration_object_var.get())
        distance_z = float(self.distance_z_var.get())
        set_calibration_settings(ringDiameter=ring_diameter,
                                 obj_radius=calibration_obj,
                                 distance_z=distance_z)

        # Show sensors live feed
        self.controller.show_frame("MeasureBPC")

    def reset(self):
        # reset description
        self.text_area.clear_text()
Example #10
0
class ConfigBPC(Frame):

    def __init__(self, parent, controller):
        Frame.__init__(self, parent)
        self.controller = controller
        self.title = "Configuration"
        self.initialize_widgets()

    def initialize_widgets(self):
        # CALIBRATION

        # Ring diameter entry value
        self.ring_diameter_var = StringVar()
        self.ring_diameter_var.trace("w", self.update_begin_button)

        # Calibration object value
        self.calibration_object_var = StringVar()
        self.calibration_object_var.trace("w", self.update_begin_button)

        # Distance to end of rail value
        self.distance_z_var = StringVar()
        self.distance_z_var.trace("w", self.update_begin_button)

        # %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
        # %W = the name of the widget
        validate_cmd = (self.register(self.validate_calibration_settings), '%d', '%P', '%S', '%W')

        # Calibration settings group
        self.calibration_settings = LabelFrame(self, text="Calibration Settings (all measures in centimeters)",
                                               fg="grey", padx=20, pady=20, font=self.controller.header_font)
        self.calibration_settings.grid(row=0, column=0, sticky=NSEW, padx=20, pady=20)

        # Ring diameter
        self.ring_diameter_label = Label(self.calibration_settings, text="Ring structure diameter", anchor=SW,
                                         font=self.controller.bold_font)
        self.ring_diameter_label.grid(row=0, column=0, sticky=SW, padx=20)

        # ring diameter range
        self.range_ring_diameter = Label(self.calibration_settings, text="[Valid range: 10 - 30]", fg="grey", anchor=SW,
                                         font=self.controller.small_font)
        self.range_ring_diameter.grid(row=1, column=0, sticky=NW, padx=20)

        self.ring_diameter_entry = EntryWithPlaceholder(self.calibration_settings, text_var=self.ring_diameter_var,
                                                        placeholder_text="0.00", validatecommand=validate_cmd,
                                                        validate="key", textvariable=self.ring_diameter_var,
                                                        name="ring")
        self.ring_diameter_entry.grid(row=2, column=0, sticky=NW, padx=20, pady=20)

        # Calibration object
        self.calibration_object_label = Label(self.calibration_settings, text="Calibration object diameter",
                                              font=self.controller.bold_font, anchor=SW)
        self.calibration_object_label.grid(row=0, column=1, sticky=SW, padx=20)

        # calibration object diameter range
        self.range_calibration_obj_diameter = Label(self.calibration_settings, text="[Valid range: 2 - 26]", fg="grey",
                                                    anchor=NW, font=self.controller.small_font)
        self.range_calibration_obj_diameter.grid(row=1, column=1, sticky=NW, padx=20)

        self.calibration_object_entry = EntryWithPlaceholder(self.calibration_settings, text_var=self.calibration_object_var,
                                                             placeholder_text="0.00", validatecommand=validate_cmd,
                                                             textvariable=self.calibration_object_var, validate="key",
                                                             name="calibration_obj")
        self.calibration_object_entry.grid(row=2, column=1, sticky=NW, padx=20, pady=20)

        # Distance to flat surface at end of rail
        self.distance_z_label = Label(self.calibration_settings, text="Distance to the end of the rail", anchor=SW,
                                      font = self.controller.bold_font)
        self.distance_z_label.grid(row=3, column=0, sticky=SW, padx=20)

        # z distance range
        self.range_z_distance = Label(self.calibration_settings, text="[Valid range: 15.24 - 645]", fg="grey", anchor=NW,
                                      font=self.controller.small_font)
        self.range_z_distance.grid(row=4, column=0, sticky=NW, padx=20)

        self.distance_z_entry = EntryWithPlaceholder(self.calibration_settings, text_var=self.distance_z_var,
                                                     placeholder_text="0.00", validate="key", name="z_distance",
                                                     validatecommand=validate_cmd, textvariable=self.distance_z_var)
        self.distance_z_entry.grid(row=5, column=0, sticky=NW, padx=20, pady=20)

        # calibration object diameter greater than ring diameter
        self.calibration_obj_greater_ring = Label(self.calibration_settings,
                                                  text="The calibration object's diameter\ncan't be greater than the ring diameter",
                                                  fg="red", anchor=NW)

        # make calibration section responsive
        make_columns_responsive(self.calibration_settings)
        make_rows_responsive(self.calibration_settings)

        # description label
        self.description_label = Label(self, text="Information about the bamboo sample (optional)",
                                       font=self.controller.bold_font)
        self.description_label.grid(row=1, column=0, sticky=SW, padx=20, pady=20)

        # Description text area
        self.text_area = ScrollableTextArea(self)
        self.text_area.grid(row=2, column=0, sticky=NW, padx=20)

        # 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=0, sticky=SE, padx=20, pady=20)

        # set placeholders
        self.ring_diameter_entry.set_placeholder()
        self.calibration_object_entry.set_placeholder()
        self.distance_z_entry.set_placeholder()

        make_rows_responsive(self)
        make_columns_responsive(self)

        # min size of buttons row
        self.grid_rowconfigure(3, minsize=80)


    def validate_calibration_settings(self, action, value_if_allowed, text, widget):
        # only when inserting
        if action == "1":
            if text in "0123456789.":
                try:
                    # name of the widget
                    widget_name = str(widget).split(".")[-1]

                    # ring
                    if widget_name == "ring":
                        # valid range [10,30]
                        if float(value_if_allowed) >= 1.0 and float(value_if_allowed) <= 30.0:
                            return True
                        else:
                            # Make system bell sound
                            self.bell()
                            return False

                    # calibration object    
                    elif widget_name == "calibration_obj":
                        # valid range [2,26]
                        if float(value_if_allowed) >= 1.0 and float(value_if_allowed) <= 26.0:
                            return True
                        else:
                            # Make system bell sound
                            self.bell()
                            return False

                    # z distance entry
                    else:
                        # valid range [15.24, 645]
                        if float(value_if_allowed) >= 1.0 and float(value_if_allowed) <= 645.0:
                            return True
                        else:
                            # Make system bell sound
                            self.bell()
                            return False

                except ValueError:
                    self.bell()
                    return False
            else:
                self.bell()
                return False
        else:
            return True

    def update_begin_button(self, *args):
        ring_diameter = self.ring_diameter_var.get()
        calibration_obj = self.calibration_object_var.get()
        distance_z = self.distance_z_var.get()

        # valid range [10,30]
        ring_diameter_ok = ring_diameter and float(ring_diameter) >= 10.0
        # valid range [2,26]
        calibration_object_ok = calibration_obj and float(calibration_obj) >= 2.0
        # valid range [15.24, 645]
        distance_z_ok = distance_z and float(distance_z) >= 15.24

        # show invalid message when calibration object diameter is greater than ring
        if ring_diameter_ok and calibration_object_ok and (float(ring_diameter) - float(calibration_obj)) > 0:
            self.calibration_obj_greater_ring.grid_forget()
        elif ring_diameter_ok and calibration_object_ok and (float(ring_diameter) - float(calibration_obj)) <= 0:
            self.calibration_obj_greater_ring.grid(row=3, column=1, sticky=NW, padx=20)

        # All entries valid
        if ring_diameter_ok and calibration_object_ok and distance_z_ok and \
                (float(ring_diameter) - float(calibration_obj)) > 0:
            # enable begin button
            self.begin_button.configure(state=NORMAL, cursor="hand2")

        # at least one entry is not valid
        else:
            # disable begin button
            self.begin_button.configure(state=DISABLED, cursor="arrow")

    def begin(self):
        # save sample description
        set_sampleDescription(self.text_area.text.get(1.0, END))

        # save calibration settings
        ring_diameter = float(self.ring_diameter_var.get())
        calibration_obj = float(self.calibration_object_var.get())
        distance_z = float(self.distance_z_var.get())
        set_calibration_settings(ringDiameter=ring_diameter, obj_diameter=calibration_obj, distance_z=distance_z)

        # Show sensors live feed
        self.controller.show_frame("MeasureBPC")

    def reset(self):
        # reset description
        self.text_area.text.delete(1.0, END)
Example #11
0
class MeasureBPC(Frame):

    def __init__(self, parent, controller):
        Frame.__init__(self, parent)
        self.controller = controller
        self.title = "Live Sensor Readings (cm)"
        self.initialize_widgets()
        self.bind("<<ShowFrame>>", self.on_show_frame)
        self.bind("<<LeaveFrame>>", self.on_leave_frame)

        # Queue where live feed thread writes data
        self.queue = queue.LifoQueue()

    def add_to_queue(self, data):
        self.queue.put(data)

    def initialize_widgets(self):
        # Watchers
        # Update captured count
        self.count_str = StringVar()
        self.count_number = IntVar()
        self.count_number.trace("w", self.update_count_label)

        # Status message
        self.status_var = StringVar()
        self.status_message = Label(self, textvariable=self.status_var, font=self.controller.header_font, relief=GROOVE,
                                    padx=10, pady=10)
        self.status_message.grid(row=0, column=0, sticky=EW, padx=40, pady=20)

        # Table headers
        top_headers = ["Sensor #", "Current\nreading", "Last\ncaptured\nvalue", "Last\ndeviation"]
        self.table_headers = VerticalTable(self, rows=1, columns=len(top_headers))
        self.table_headers.update_cells(top_headers)
        self.table_headers.grid(row=1, column=0, sticky=S)

        # column indexes
        self.live_column = 0
        self.captured_column = 1
        self.deviation_column = 2

        # Sensor Data: current readings, last captured, and deviation info
        sensor_headers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "Sensor Z"]
        self.table = HorizontalTable(self, rows=len(sensor_headers), columns=3, header_values=sensor_headers)
        self.table.grid(row=2, column=0, rowspan=2, sticky=N)

        # calibrate button
        self.calibrate_button = GreenButton(self, text="Calibrate Sensors", command=self.calibrate)
        self.calibrate_button.grid(row=0, column=1, pady=20)

        # captured count
        self.captured_count = Label(self, textvariable=self.count_str, font=self.controller.bold_font)
        self.captured_count.grid(row=2, column=1, sticky=S, pady=10)

        # capture button
        self.capture_button = YellowButton(self, text="Capture Measurements", command=self.capture)
        self.capture_button.grid(row=3, column=1, sticky=N)

        # view results button
        self.results_button = GreenButton(self, text="View Results", command=self.view_results,
                                          image=self.controller.arrow_right, compound=RIGHT)
        self.results_button.grid(row=4, column=1, sticky=SE, padx=20, pady=20)

        make_rows_responsive(self)
        make_columns_responsive(self)

    def on_show_frame(self, event=None):
        # TODO get count from function
        self.count_number.set(len(saved_measurement))

        # clear live readings from table
        self.table.clear_column(self.live_column)

        # Controls update callback
        self.do_update = True
        # Flag to only update buttons and message when necessary
        self.busy_message_set = False

        # Open port and start reading
        self.run_live_thread()

        # start live GUI
        self.update_live_gui()

    def run_live_thread(self):
        self.live_thread = LiveFeedThread(widget=self)
        self.live_thread.start()

    def update_live_gui(self):
        # No Arduino found alert
        if no_arduino.is_set():
            no_arduino.clear()

            result = messagebox.askretrycancel("Error opening serial port",
                                               "Make sure the Arduino is properly connected, and try again.",
                                               icon="error")
            # Retry
            if result:
                # open new thread
                self.run_live_thread()
            else:
                self.controller.show_frame("ConfigBPC")
                return

        # Arduino disconnected alert
        if disconnected.is_set():
            disconnected.clear()

            result = messagebox.askretrycancel("Error reading from Arduino",
                                               "Make sure the Arduino is properly connected, and try again.",
                                               icon="error")
            # Retry
            if result:
                # open new thread
                self.run_live_thread()
            else:
                self.controller.show_frame("ConfigBPC")
                return

        # Capturing data
        if self.live_thread.capture_now.is_set():
            # only set message once
            if not self.busy_message_set:
                # Show status message
                self.status_var.set("Capturing data...")

                # Disable buttons
                self.disable_buttons()

                # message has been set
                self.busy_message_set = True

            # when data has been captured
            if self.live_thread.capture_done.is_set():
                last_captured = saved_measurement[len(saved_measurement) - 1]

                # update table with new data
                self.table.update_column(self.captured_column, last_captured)

                # clear flags
                self.live_thread.capture_now.clear()
                self.live_thread.capture_done.clear()

                # update captured count label
                self.count_number.set(self.count_number.get() + 1)

                self.update_idletasks()

        # Calibrating sensors
        elif self.live_thread.calibrate_now.is_set():
            # only set message once
            if not self.busy_message_set:
                # Show status message
                self.status_var.set("Calibrating sensors...")

                # Disable buttons
                self.disable_buttons()

                # message has been set
                self.busy_message_set = True

            # when calibration is done
            if self.live_thread.calibration_done.is_set():
                deviations = []

                # exclude the last one; it's the ultrasonic
                for i in range(len(sensorArray) - 1):
                    # IR sensor deviation angle
                    deviations.append(sensorArray[i].devAngle)

                # ultrasonic sensor
                ultrasonic = sensorArray[len(sensorArray) - 1]
                deviations.append(ultrasonic.factor)

                # update table with new data
                self.table.update_column(self.deviation_column, deviations)

                # clear flags
                self.live_thread.calibrate_now.clear()
                self.live_thread.calibration_done.clear()

                self.update_idletasks()

        # Update live feed
        elif self.live_thread.reading_sensors.is_set() and not self.live_thread.capture_now.is_set()\
                and not self.live_thread.calibrate_now.is_set():
            try:
                while True:
                    sensor_readings = self.queue.get_nowait()

                    # Update table with new sensor data
                    self.table.update_column(self.live_column, sensor_readings)

                    # only set message once
                    if self.busy_message_set:
                        # Ready to capture or calibrate
                        self.status_var.set("Ready!")

                        # Restore buttons
                        self.restore_buttons()

                        # no longer busy
                        self.busy_message_set = False

                    self.update_idletasks()

            except queue.Empty:
                pass

        # Sensors are still initializing
        elif not self.live_thread.reading_sensors.is_set() and self.do_update and not self.busy_message_set:
            # Show loading message
            self.status_var.set("Connecting to sensors...")

            # Disable buttons
            self.disable_buttons()

            # message has been set
            self.busy_message_set = True

        # Keep updating until we leave this frame
        if self.do_update:
            self.after(100, self.update_live_gui)

    def restore_buttons(self):
        self.calibrate_button.configure(state=NORMAL, cursor="hand2")
        self.capture_button.configure(state=NORMAL, cursor="hand2")

        # only enable view results if there are any
        if self.count_number.get():
            self.results_button.configure(state=NORMAL, cursor="hand2")

        # leave it disabled, but with a different cursor
        else:
            self.results_button.configure(state=DISABLED, cursor="arrow")

    def disable_buttons(self):
        self.calibrate_button.configure(state=DISABLED, cursor="wait")
        self.capture_button.configure(state=DISABLED, cursor="wait")
        self.results_button.configure(state=DISABLED, cursor="wait")

    def on_leave_frame(self, event=None):
        # kill thread and close serial port
        self.live_thread.kill_thread.set()

        self.do_update = False

    def calibrate(self):
        # only one at a time
        if not self.live_thread.calibrate_now.is_set():
            # clear old values from table
            self.table.clear_column(self.deviation_column)

            # Let the worker thread handle it
            self.live_thread.calibrate_now.set()

    def capture(self):
        # only one at a time
        if not self.live_thread.capture_now.is_set():
            # clear old values from table
            self.table.clear_column(self.captured_column)

            # Let the worker thread handle it
            self.live_thread.capture_now.set()

    def update_count_label(self, *args):
        self.count_str.set(str(self.count_number.get()) + " measurements captured")

    def view_results(self):
        self.controller.show_frame("ResultsBPC")

    def reset(self):
        # reset captured count
        self.count_number.set(0)

        # clear table
        self.table.clear_cells()
Example #12
0
class ResultsBPC(Frame):
    def __init__(self, parent, controller):
        Frame.__init__(self, parent)
        self.controller = controller
        self.title = "Review Your Measurements"
        self.captured_data = []
        # TODO Generate headers ?
        self.sensor_headers = [
            "Z (cm)", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
            "S10", "S11", "S12"
        ]
        self.initialize_widgets()
        self.bind("<<ShowFrame>>", self.on_show_frame)
        self.bind("<<LeaveFrame>>", self.on_leave_frame)

    def initialize_widgets(self):
        # Empty message
        self.empty_message = Label(
            self,
            text="Nothing to see here. Go capture some measurements!",
            font=self.controller.header_font)

        # 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=1, column=0, sticky=SE, padx=10, pady=20)

        # Discard button
        self.discard_button = RedButton(self,
                                        text="DISCARD",
                                        command=self.discard)
        self.discard_button.grid(row=1, column=1, sticky=SW, padx=10, pady=20)

        make_rows_responsive(self)
        make_columns_responsive(self)

    def on_show_frame(self, event=None):
        # Enable save button
        self.save_button.configure(state=NORMAL, cursor="hand2")

        print("original", saved_measurement)

        # sort captured measurements by Z value
        sort_ByZeta(saved_measurement)

        print("sorted", saved_measurement)

        # Generate captured measurements table
        self.create_table()

    def create_table(self):
        # Create table if there are any captured measurements
        if saved_measurement:
            # Make a copy of the captured measurements
            self.captured_data = copy.deepcopy(saved_measurement)

            # Place Z as first element
            for column in self.captured_data:
                column.insert(0, column.pop())

            self.table = HorizontalTable(self,
                                         rows=len(self.captured_data[0]),
                                         columns=len(self.captured_data),
                                         header_values=self.sensor_headers,
                                         can_select_columns=True,
                                         button_command=self.delete_z)
            # Set background of top row
            for column in range(len(self.captured_data)):
                self.table.cells[0][column].configure(
                    bg="#5E5E5E", fg="#FFFFFF", font=self.controller.bold_font)
            self.table.headers[0].configure(bg="#5E5E5E", fg="#FFFFFF")
            self.table.grid(row=0, column=0, columnspan=2)

            # load cells with captured measurements
            self.table.update_cells(self.captured_data)

        # No captured measurements
        else:
            # Show an empty message
            self.empty_message.grid(row=0, columnspan=2)

            # disable save button
            self.save_button.configure(state=DISABLED, cursor="arrow")

    def destroy_table(self):
        # Remove checkboxes and table from grid, and destroy them
        self.table.grid_forget()
        self.table.destroy()

    def delete_z(self):
        # get indices to be deleted
        deleted_columns = self.table.get_checked_indices()

        # delete them
        delete_measurement(deleted_columns)

        # Re-create table
        self.destroy_table()
        self.create_table()

    def on_leave_frame(self, event=None):
        # Destroy table if there are any captured measurements
        if saved_measurement:
            # Destroy captured measurements table
            self.destroy_table()
        # Otherwise hide empty message
        else:
            self.empty_message.grid_forget()

    def save(self):
        date = datetime.now().strftime('%Y-%m-%d_%H%M%S')
        save_path = filedialog.asksaveasfilename(title="Save as",
                                                 defaultextension=".txt",
                                                 initialfile="BPC_" + date)

        # make sure the user didn't cancel the dialog
        if len(save_path) > 0:
            if generate_textfile(save_path):
                # all good
                messagebox.showinfo("Success!",
                                    "File was generated successfully.")
                # reset BPC
                self.controller.reset_BPC()
                # 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 captured measurements?",
            "You will lose all the measurements you have captured so far.",
            default="cancel",
            icon="warning")
        if result:
            # reset BPC
            self.controller.reset_BPC()
            # go to home screen
            self.controller.show_frame("Home")

    def reset(self):
        self.captured_data.clear()
Example #13
0
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
Example #14
0
class ResultsBPC(Frame):
    def __init__(self, parent, controller):
        Frame.__init__(self, parent)
        self.controller = controller
        self.title = "Review saved measurements"
        self.captured_data = []
        # TODO Generate headers ?
        self.sensor_headers = [
            "Z (cm)", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
            "S10", "S11", "S12"
        ]
        self.initialize_widgets()
        self.bind("<<ShowFrame>>", self.on_show_frame)
        self.bind("<<LeaveFrame>>", self.on_leave_frame)

    def initialize_widgets(self):
        # a canvas with scrollbars; the results table goes in it
        h_scrollbar = AutoScrollbar(self, orient=HORIZONTAL)
        h_scrollbar.grid(row=1, column=0, columnspan=2, sticky=EW)
        v_scrollbar = AutoScrollbar(self)
        v_scrollbar.grid(row=0, column=2, sticky=NS)

        self.canvas = Canvas(self,
                             xscrollcommand=h_scrollbar.set,
                             yscrollcommand=v_scrollbar.set)
        h_scrollbar.config(command=self.canvas.xview)
        v_scrollbar.config(command=self.canvas.yview)

        # Empty message
        self.empty_message = Label(
            self,
            text="Nothing to see here. Go capture some measurements!",
            font=self.controller.header_font)

        # 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=S, pady=20)

        # Discard button
        self.discard_button = RedButton(self,
                                        text="DISCARD",
                                        command=self.discard)
        self.discard_button.grid(row=2, column=1, sticky=S, pady=20)

        # responsive except the scrollbars and the buttons
        make_rows_responsive(self, ignored=[1, 2])
        make_columns_responsive(self, ignored=[2])

    def on_show_frame(self, event=None):
        # restore canvas in grid
        self.canvas.grid(row=0, column=0, sticky=NSEW, columnspan=2, pady=20)

        # Enable save button
        self.save_button.configure(state=NORMAL, cursor="hand2")

        # sort captured measurements by Z value
        sort_ByZeta(saved_measurement)

        # Generate captured measurements table
        self.create_table()

    def create_table(self):
        # Create table if there are any captured measurements
        if saved_measurement:
            # Make a copy of the captured measurements
            self.captured_data = copy.deepcopy(saved_measurement)

            # Place Z as first element
            for column in self.captured_data:
                column.insert(0, column.pop())

            # the results table
            self.table = HorizontalTable(self.canvas,
                                         rows=len(self.captured_data[0]),
                                         columns=len(self.captured_data),
                                         header_values=self.sensor_headers,
                                         can_select_columns=True,
                                         button_command=self.delete_z)
            # Set background of top row
            for column in range(len(self.captured_data)):
                self.table.cells[0][column].configure(
                    bg="#5E5E5E", fg="#FFFFFF", font=self.controller.bold_font)
            self.table.headers[0].configure(bg="#5E5E5E", fg="#FFFFFF")

            # load cells with captured measurements
            self.table.update_cells(self.captured_data)

            # place the table inside the canvas
            self.canvas.create_window(0, 0, anchor=NW, window=self.table)

            # wait for the canvas to create table
            self.table.update_idletasks()

            # update scroll region of table
            self.canvas.config(scrollregion=self.canvas.bbox("all"))

        # No captured measurements
        else:
            # Hide canvas
            self.canvas.grid_forget()

            # Show an empty message
            self.empty_message.grid(row=0, columnspan=2)

            # disable save button
            self.save_button.configure(state=DISABLED, cursor="arrow")

    def destroy_table(self):
        # Remove table from canvas
        self.canvas.delete("all")
        # Destroy the table
        self.table.destroy()

    def delete_z(self):
        # get indices to be deleted
        deleted_columns = self.table.get_checked_indices()

        # delete them
        delete_measurement(deleted_columns)

        # Re-create table
        self.destroy_table()
        self.create_table()

    def on_leave_frame(self, event=None):
        # Destroy table if there are any captured measurements
        if saved_measurement:
            self.destroy_table()

        # Otherwise hide empty message
        else:
            self.empty_message.grid_forget()

    def save(self):
        date = datetime.now().strftime('%Y-%m-%d_%H%M%S')
        save_path = filedialog.asksaveasfilename(title="Save as",
                                                 defaultextension=".txt",
                                                 initialfile="BPC_" + 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?",
                    "Your measurements have 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 captured measurements?",
            "You will lose all the measurements you have captured so far.",
            default="cancel",
            icon="warning")
        if result:
            # reset BPC
            self.controller.reset_BPC()
            # go to home screen
            self.controller.show_frame("Home")

    def reset(self):
        self.captured_data.clear()
    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("")
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)
Example #17
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
Example #18
0
    def initialize_widgets(self):
        # CALIBRATION

        # Ring diameter entry value
        self.ring_diameter_var = StringVar()
        self.ring_diameter_var.trace("w", self.update_begin_button)

        # Calibration object value
        self.calibration_object_var = StringVar()
        self.calibration_object_var.trace("w", self.update_begin_button)

        # Distance to end of rail value
        self.distance_z_var = StringVar()
        self.distance_z_var.trace("w", self.update_begin_button)

        # %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')

        # Calibration settings group
        self.calibration_settings = LabelFrame(
            self,
            text="Calibration Settings",
            fg="grey",
            padx=20,
            pady=20,
            font=self.controller.header_font)
        self.calibration_settings.grid(row=0,
                                       column=0,
                                       sticky=NS + W,
                                       padx=20,
                                       pady=20)

        # Ring diameter
        self.ring_diameter_label = Label(self.calibration_settings,
                                         text="Ring structure diameter",
                                         anchor=SW,
                                         font=self.controller.bold_font)
        self.ring_diameter_label.grid(row=0, column=0, sticky=SW, padx=20)

        self.ring_diameter_entry = EntryWithPlaceholder(
            self.calibration_settings,
            text_var=self.ring_diameter_var,
            placeholder_text="0.00",
            validatecommand=validate_cmd,
            validate="key",
            textvariable=self.ring_diameter_var)
        self.ring_diameter_entry.grid(row=1,
                                      column=0,
                                      sticky=NW,
                                      padx=20,
                                      pady=20)

        # Calibration object
        self.calibration_object_label = Label(self.calibration_settings,
                                              text="Calibration object radius",
                                              font=self.controller.bold_font,
                                              anchor=SW)
        self.calibration_object_label.grid(row=0, column=1, sticky=SW, padx=20)

        self.calibration_object_entry = EntryWithPlaceholder(
            self.calibration_settings,
            text_var=self.calibration_object_var,
            placeholder_text="0.00",
            validatecommand=validate_cmd,
            textvariable=self.calibration_object_var,
            validate="key")
        self.calibration_object_entry.grid(row=1,
                                           column=1,
                                           sticky=NW,
                                           padx=20,
                                           pady=20)

        # Distance to flat surface at end of rail
        self.distance_z_label = Label(self.calibration_settings,
                                      text="Distance to the end of the rail",
                                      anchor=SW,
                                      font=self.controller.bold_font)
        self.distance_z_label.grid(row=2, column=0, sticky=SW, padx=20)

        self.distance_z_entry = EntryWithPlaceholder(
            self.calibration_settings,
            text_var=self.distance_z_var,
            placeholder_text="0.00",
            validate="key",
            validatecommand=validate_cmd,
            textvariable=self.distance_z_var)
        self.distance_z_entry.grid(row=3,
                                   column=0,
                                   sticky=NW,
                                   padx=20,
                                   pady=20)

        # Invalid dimension message
        self.invalid_dimension = Label(
            self.calibration_settings,
            text="Dimension must be between 1 and 28 centimeters",
            fg="red",
            anchor=W)

        # make calibration section responsive
        make_columns_responsive(self.calibration_settings)
        make_rows_responsive(self.calibration_settings)

        # description label
        self.description_label = Label(
            self,
            text="Information about the sample (optional)",
            font=self.controller.bold_font)
        self.description_label.grid(row=1,
                                    column=0,
                                    sticky=SW,
                                    padx=20,
                                    pady=20)

        # Description text area
        self.text_area = ScrollableTextArea(self)
        self.text_area.grid(row=2, column=0, sticky=NW, padx=20)

        # 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=0, sticky=SE, padx=20, pady=20)

        # set placeholders
        self.ring_diameter_entry.set_placeholder()
        self.calibration_object_entry.set_placeholder()
        self.distance_z_entry.set_placeholder()

        make_rows_responsive(self)
        make_columns_responsive(self)
Example #19
0
    def initialize_widgets(self):
        # CALIBRATION

        # Ring diameter entry value
        self.ring_diameter_var = StringVar()
        self.ring_diameter_var.trace("w", self.update_begin_button)

        # Calibration object value
        self.calibration_object_var = StringVar()
        self.calibration_object_var.trace("w", self.update_begin_button)

        # Distance to end of rail value
        self.distance_z_var = StringVar()
        self.distance_z_var.trace("w", self.update_begin_button)

        # %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
        # %W = the name of the widget
        validate_cmd = (self.register(self.validate_calibration_settings), '%d', '%P', '%S', '%W')

        # Calibration settings group
        self.calibration_settings = LabelFrame(self, text="Calibration Settings (all measures in centimeters)",
                                               fg="grey", padx=20, pady=20, font=self.controller.header_font)
        self.calibration_settings.grid(row=0, column=0, sticky=NSEW, padx=20, pady=20)

        # Ring diameter
        self.ring_diameter_label = Label(self.calibration_settings, text="Ring structure diameter", anchor=SW,
                                         font=self.controller.bold_font)
        self.ring_diameter_label.grid(row=0, column=0, sticky=SW, padx=20)

        # ring diameter range
        self.range_ring_diameter = Label(self.calibration_settings, text="[Valid range: 10 - 30]", fg="grey", anchor=SW,
                                         font=self.controller.small_font)
        self.range_ring_diameter.grid(row=1, column=0, sticky=NW, padx=20)

        self.ring_diameter_entry = EntryWithPlaceholder(self.calibration_settings, text_var=self.ring_diameter_var,
                                                        placeholder_text="0.00", validatecommand=validate_cmd,
                                                        validate="key", textvariable=self.ring_diameter_var,
                                                        name="ring")
        self.ring_diameter_entry.grid(row=2, column=0, sticky=NW, padx=20, pady=20)

        # Calibration object
        self.calibration_object_label = Label(self.calibration_settings, text="Calibration object diameter",
                                              font=self.controller.bold_font, anchor=SW)
        self.calibration_object_label.grid(row=0, column=1, sticky=SW, padx=20)

        # calibration object diameter range
        self.range_calibration_obj_diameter = Label(self.calibration_settings, text="[Valid range: 2 - 26]", fg="grey",
                                                    anchor=NW, font=self.controller.small_font)
        self.range_calibration_obj_diameter.grid(row=1, column=1, sticky=NW, padx=20)

        self.calibration_object_entry = EntryWithPlaceholder(self.calibration_settings, text_var=self.calibration_object_var,
                                                             placeholder_text="0.00", validatecommand=validate_cmd,
                                                             textvariable=self.calibration_object_var, validate="key",
                                                             name="calibration_obj")
        self.calibration_object_entry.grid(row=2, column=1, sticky=NW, padx=20, pady=20)

        # Distance to flat surface at end of rail
        self.distance_z_label = Label(self.calibration_settings, text="Distance to the end of the rail", anchor=SW,
                                      font = self.controller.bold_font)
        self.distance_z_label.grid(row=3, column=0, sticky=SW, padx=20)

        # z distance range
        self.range_z_distance = Label(self.calibration_settings, text="[Valid range: 15.24 - 645]", fg="grey", anchor=NW,
                                      font=self.controller.small_font)
        self.range_z_distance.grid(row=4, column=0, sticky=NW, padx=20)

        self.distance_z_entry = EntryWithPlaceholder(self.calibration_settings, text_var=self.distance_z_var,
                                                     placeholder_text="0.00", validate="key", name="z_distance",
                                                     validatecommand=validate_cmd, textvariable=self.distance_z_var)
        self.distance_z_entry.grid(row=5, column=0, sticky=NW, padx=20, pady=20)

        # calibration object diameter greater than ring diameter
        self.calibration_obj_greater_ring = Label(self.calibration_settings,
                                                  text="The calibration object's diameter\ncan't be greater than the ring diameter",
                                                  fg="red", anchor=NW)

        # make calibration section responsive
        make_columns_responsive(self.calibration_settings)
        make_rows_responsive(self.calibration_settings)

        # description label
        self.description_label = Label(self, text="Information about the bamboo sample (optional)",
                                       font=self.controller.bold_font)
        self.description_label.grid(row=1, column=0, sticky=SW, padx=20, pady=20)

        # Description text area
        self.text_area = ScrollableTextArea(self)
        self.text_area.grid(row=2, column=0, sticky=NW, padx=20)

        # 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=0, sticky=SE, padx=20, pady=20)

        # set placeholders
        self.ring_diameter_entry.set_placeholder()
        self.calibration_object_entry.set_placeholder()
        self.distance_z_entry.set_placeholder()

        make_rows_responsive(self)
        make_columns_responsive(self)

        # min size of buttons row
        self.grid_rowconfigure(3, minsize=80)