From 3ca624c5d02bccdb4c6e4bade11c2d9f76d177ba Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:22:28 -0500 Subject: [PATCH 1/8] First pass at adding a histogram --- src/navigate/controller/configurator.py | 18 ++++--- src/navigate/controller/controller.py | 26 +++++---- .../controller/sub_controllers/camera_view.py | 36 ++++++++++++- src/navigate/view/main_application_window.py | 26 ++++----- .../main_window_content/acquire_notebook.py | 18 +++++-- .../main_window_content/display_notebook.py | 53 +++++++++++++++++-- src/navigate/view/splash_screen.py | 4 +- .../sub_controllers/test_acquire_bar.py | 2 +- 8 files changed, 138 insertions(+), 45 deletions(-) diff --git a/src/navigate/controller/configurator.py b/src/navigate/controller/configurator.py index 6942e9bca..8231510e1 100644 --- a/src/navigate/controller/configurator.py +++ b/src/navigate/controller/configurator.py @@ -58,7 +58,7 @@ class Configurator: """Navigate Configurator""" - def __init__(self, root, splash_screen): + def __init__(self, root: tk.Tk, splash_screen): """Initiates the configurator application window. Parameters @@ -186,11 +186,12 @@ def set_value(temp_dict, key_list, value): if k.strip() == "": warning_info[hardware_name] = True print( - f"Notice: {hardware_name} has an empty value {ref}! Please double check if it's okay!" + f"Notice: {hardware_name} has an empty value " + f"{ref}! Please double check if it's okay!" ) if k_idx in value_dict: - k = value_dict[k_idx][v] + k = value_dict[k_idx][v] # noqa v = variables[v_idx].get() if v_idx in value_dict: v = value_dict[v_idx][v] @@ -208,7 +209,8 @@ def set_value(temp_dict, key_list, value): except tk._tkinter.TclError: v = "" print( - f"Notice: {hardware_name} has an empty value {k}! Please double check!" + f"Notice: {hardware_name} has an empty value {k}! " + f"Please double check!" ) warning_info[hardware_name] = True set_value(temp_dict, k.split("/"), v) @@ -218,7 +220,9 @@ def set_value(temp_dict, key_list, value): if warning_info: messagebox.showwarning( title="Configuration", - message=f"There are empty value(s) with {', '.join(warning_info.keys())}. Please double check!", + message=f"There are empty value(s) with " + f"{', '.join(warning_info.keys())}" + f". Please double check!", ) def write_to_yaml(self, config, filename): @@ -404,7 +408,7 @@ def build_widgets_value(widgets, value_dict): hardware_ref_name ], ) - except Exception as e: + except Exception: widgets_value = [None] microscope_tab.create_hardware_tab( hardware_type, widgets, hardware_widgets_value=widgets_value @@ -425,7 +429,7 @@ def build_widgets_value(widgets, value_dict): ], ), ] - except: + except Exception: widgets_value = [[None], [None]] microscope_tab.create_hardware_tab( hardware_type, diff --git a/src/navigate/controller/controller.py b/src/navigate/controller/controller.py index 3245e8914..90445dd03 100644 --- a/src/navigate/controller/controller.py +++ b/src/navigate/controller/controller.py @@ -40,7 +40,6 @@ import os import time import platform -import reprlib # Third Party Imports @@ -97,6 +96,7 @@ class Controller: """Navigate Controller""" + def __init__( self, root, @@ -198,8 +198,10 @@ def __init__( verify_waveform_constants(self.manager, self.configuration) total_ram, available_ram = get_ram_info() - logger.info(f"Total RAM: {total_ram / 1024**3:.2f} GB. " - f"Available RAM: {available_ram / 1024**3:.2f} GB.") + logger.info( + f"Total RAM: {total_ram / 1024**3:.2f} GB. " + f"Available RAM: {available_ram / 1024**3:.2f} GB." + ) #: ObjectInSubprocess: Model object in MVC architecture. self.model = ObjectInSubprocess( @@ -225,7 +227,7 @@ def __init__( self.event_listeners = {} #: AcquireBarController: Acquire Bar Sub-Controller. - self.acquire_bar_controller = AcquireBarController(self.view.acqbar, self) + self.acquire_bar_controller = AcquireBarController(self.view.acquire_bar, self) #: ChannelsTabController: Channels Tab Sub-Controller. self.channels_tab_controller = ChannelsTabController( @@ -365,7 +367,7 @@ def update_buffer(self): def update_acquire_control(self): """Update the acquire control based on the current experiment parameters.""" - self.view.acqbar.stop_stage.config( + self.view.acquire_bar.stop_stage.config( command=self.stage_controller.stop_button_handler ) @@ -554,7 +556,7 @@ def refresh(width, height): if width < 1200 or height < 600: return self.view.camera_waveform["width"] = ( - width - self.view.frame_left.winfo_width() - 81 + width - self.view.left_frame.winfo_width() - 81 ) self.view.camera_waveform["height"] = height - 110 @@ -1212,7 +1214,9 @@ def display_images( ) camera_view_controller.microscope_name = microscope_name popup_window.popup.bind("", camera_view_controller.resize) - self.additional_microscopes[microscope_name]["popup_window"] = popup_window + self.additional_microscopes[microscope_name][ + "popup_window" + ] = popup_window self.additional_microscopes[microscope_name][ "camera_view_controller" ] = camera_view_controller @@ -1226,7 +1230,9 @@ def display_images( ), ) - self.additional_microscopes[microscope_name]["show_img_pipe"] = show_img_pipe + self.additional_microscopes[microscope_name][ + "show_img_pipe" + ] = show_img_pipe self.additional_microscopes[microscope_name]["data_buffer"] = data_buffer # start thread @@ -1263,7 +1269,9 @@ def destroy_virtual_microscope(self, microscope_name, destroy_window=True): # destroy the popup window if destroy_window: self.additional_microscopes[microscope_name]["popup_window"].popup.dismiss() - self.additional_microscopes[microscope_name]["camera_view_controller"] = None + self.additional_microscopes[microscope_name][ + "camera_view_controller" + ] = None del self.additional_microscopes[microscope_name] def move_stage(self, pos_dict): diff --git a/src/navigate/controller/sub_controllers/camera_view.py b/src/navigate/controller/sub_controllers/camera_view.py index 654dc8a2c..eb486d26c 100644 --- a/src/navigate/controller/sub_controllers/camera_view.py +++ b/src/navigate/controller/sub_controllers/camera_view.py @@ -59,6 +59,7 @@ p = __name__.split(".")[1] logger = logging.getLogger(p) + class ABaseViewController(metaclass=abc.ABCMeta): """Abstract Base View Controller Class.""" @@ -100,7 +101,7 @@ def __init__(self, view, parent_controller=None): Parameters ---------- - view : tkinter.Frame + view : self.view.camera_waveform.camera_tab The tkinter frame that contains the widgets. parent_controller : Controller The parent controller of the camera view controller. @@ -1036,7 +1037,7 @@ def __init__(self, view, parent_controller=None): Parameters ---------- - view : tkinter.Frame + view : self.view.camera_waveform.camera_tab The tkinter frame that contains the widgets. parent_controller : Controller The parent controller of the camera view controller. @@ -1046,6 +1047,10 @@ def __init__(self, view, parent_controller=None): # SpooledImageLoader: The spooled image loader. self.spooled_images = None + self.histogram_canvas = view.histogram.figure_canvas + self.figure = view.histogram.figure.add_subplot(111) + self.populate_histogram(image=np.random.normal(100, 20, 1000)) + #: dict: The dictionary of image metrics widgets. self.image_metrics = view.image_metrics.get_widgets() @@ -1086,6 +1091,31 @@ def __init__(self, view, parent_controller=None): #: numpy.ndarray: The ilastik mask. self.ilastik_seg_mask = None + def populate_histogram(self, image): + """Populate the histogram.""" + self.figure.clear() + + data = image.flatten() + + self.figure.hist(data, bins=30, color="black") + + # X-axis + xmin, xmax = 0, np.max(data) + self.figure.set_xlim([xmin, xmax]) + self.figure.set_xlabel("", fontsize=9) + + num_ticks = 5 + ticks = np.linspace(xmin, xmax, num_ticks) + self.figure.set_xticks(ticks) + self.figure.set_xticklabels( + [f"{tick:.2f}" for tick in ticks] + ) # Format labels as needed + + # Y-axis + self.figure.set_ylabel("", fontsize=9) + self.figure.set_yticks([]) + self.histogram_canvas.draw() + def try_to_display_image(self, image): """Try to display an image. @@ -1331,6 +1361,8 @@ def display_image(self, image): image : numpy.ndarray Image data. """ + self.populate_histogram(image) + start_time = time.time() self.image = self.flip_image(image) self.max_intensity_history.append(np.max(image)) diff --git a/src/navigate/view/main_application_window.py b/src/navigate/view/main_application_window.py index 57b0e22ef..950da0242 100644 --- a/src/navigate/view/main_application_window.py +++ b/src/navigate/view/main_application_window.py @@ -34,6 +34,7 @@ from tkinter import ttk import logging from pathlib import Path +from typing import Iterable, Dict, Any # Third Party Imports @@ -88,16 +89,16 @@ class MainApp(ttk.Frame): spots 1 & 2 """ - def __init__(self, root, *args, **kwargs): + def __init__(self, root: tk.Tk, *args: Iterable, **kwargs: Dict[str, Any]) -> None: """Initiates the main application window Parameters ---------- root : tk.Tk The main window of the application - *args + *args : iterable Variable length argument list - **kwargs + **kwargs : dict Arbitrary keyword arguments """ @@ -139,30 +140,25 @@ def __init__(self, root, *args, **kwargs): # Left Frame Notebook 1 setup #: ttk.Frame: The left frame of the application - self.frame_left = ttk.Frame(self) + self.left_frame = ttk.Frame(self) # Top right Frame Notebook 2 setup #: ttk.Frame: The top right frame of the application - self.frame_top_right = ttk.Frame(self) - - # Bottom right Frame Notebook 3 setup - #: ttk.Frame: The bottom right frame of the application - self.frame_bottom_right = ttk.Frame(self) + self.right_frame = ttk.Frame(self) # Grid out foundational frames self.grid(column=0, row=0, sticky=tk.NSEW) self.top_frame.grid( row=0, column=0, columnspan=2, sticky=tk.NSEW, padx=3, pady=3 ) - self.frame_left.grid(row=1, column=0, rowspan=2, sticky=tk.NSEW, padx=3, pady=3) - self.frame_top_right.grid(row=1, column=1, sticky=tk.NSEW, padx=3, pady=3) - self.frame_bottom_right.grid(row=2, column=1, sticky=tk.NSEW, padx=3, pady=3) + self.left_frame.grid(row=1, column=0, rowspan=2, sticky=tk.NSEW, padx=3, pady=3) + self.right_frame.grid(row=1, column=1, sticky=tk.NSEW, padx=3, pady=3) #: SettingsNotebook: The settings notebook for the application - self.settings = SettingsNotebook(self.frame_left, self.root) + self.settings = SettingsNotebook(self.left_frame, self.root) #: CameraNotebook: The camera notebook for the application - self.camera_waveform = CameraNotebook(self.frame_top_right, self.root) + self.camera_waveform = CameraNotebook(self.right_frame, self.root) #: AcquireBar: The acquire bar for the application - self.acqbar = AcquireBar(self.top_frame, self.root) + self.acquire_bar = AcquireBar(self.top_frame, self.root) diff --git a/src/navigate/view/main_window_content/acquire_notebook.py b/src/navigate/view/main_window_content/acquire_notebook.py index 362e3251e..8623f93e5 100644 --- a/src/navigate/view/main_window_content/acquire_notebook.py +++ b/src/navigate/view/main_window_content/acquire_notebook.py @@ -33,6 +33,7 @@ import logging import tkinter as tk from tkinter import ttk +from typing import Iterable, Dict, Any # Third Party Imports @@ -51,18 +52,24 @@ class AcquireBar(ttk.Frame): level window """ - def __init__(self, top_frame, root, *args, **kwargs): + def __init__( + self, + top_frame: ttk.Frame, + root: tk.Tk, + *args: Iterable, + **kwargs: Dict[str, Any], + ) -> None: """Initialize Acquire Bar. Parameters ---------- - top_frame : tk.Frame - Frame to place the acquire bar in. + top_frame : ttk.Frame + The frame to place the acquire bar in. root : tk.Tk Root window of the application. - *args + *args: Iterable Variable length argument list. - **kwargs + **kwargs: Dict[str, Any] Arbitrary keyword arguments. """ # Init bar with frame attr @@ -82,6 +89,7 @@ def __init__(self, top_frame, root, *args, **kwargs): # Read Only Pull down menu: continuous, z-stack, single acquisition, projection. #: tk.StringVar: Variable to hold the current option selected self.options = tk.StringVar() + #: ttk.Combobox: Pull down menu to select acquisition type self.pull_down = ttk.Combobox(self, textvariable=self.options) diff --git a/src/navigate/view/main_window_content/display_notebook.py b/src/navigate/view/main_window_content/display_notebook.py index ef3d36a0f..5c16cc598 100644 --- a/src/navigate/view/main_window_content/display_notebook.py +++ b/src/navigate/view/main_window_content/display_notebook.py @@ -52,13 +52,13 @@ class CameraNotebook(DockableNotebook): """This class is the notebook that holds the camera view and waveform settings tabs.""" - def __init__(self, frame_top_right, *args, **kwargs): + def __init__(self, frame, *args, **kwargs): """Init function for the CameraNotebook class. Parameters ---------- - frame_top_right : tk.Frame + frame : tk.Frame The frame that will hold the notebook. *args : tuple Variable length argument list. @@ -66,7 +66,7 @@ def __init__(self, frame_top_right, *args, **kwargs): Arbitrary keyword arguments. """ # Init notebook - DockableNotebook.__init__(self, frame_top_right, *args, **kwargs) + DockableNotebook.__init__(self, frame, *args, **kwargs) # Putting notebook 2 into top right frame self.grid(row=0, column=0) @@ -192,8 +192,10 @@ def __init__(self, cam_wave, *args, **kwargs): self.is_docked = True #: int: The width of the canvas. + self.canvas_width = 512 + #: int: The height of the canvas. - self.canvas_width, self.canvas_height = 512, 512 + self.canvas_height = 512 #: tk.Canvas: The canvas that will hold the camera image. self.canvas = tk.Canvas( @@ -225,6 +227,31 @@ def __init__(self, cam_wave, *args, **kwargs): self.slider.grid(row=3, column=0, sticky=tk.NSEW, padx=5, pady=5) self.slider.grid_remove() + ############### + + # Histogram. + # self.histogram_frame.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) + + #: tk.Canvas: The canvas that will hold the camera image. + # self.histogram_canvas = tk.Canvas( + # self.histogram_frame, + # width=self.canvas_width, + # height=self.canvas_height // 5 + # ) + # self.histogram_canvas.grid(row=0, column=0, sticky=tk.NSEW, padx=5, pady=5) + + #: matplotlib.figure.Figure: The figure that will hold the histogram. + # self.histogram_figure = Figure(figsize=[1, 1], tight_layout=True) + # + # #: FigureCanvasTkAgg: The canvas that will hold the camera image. + # self.histogram_figure_canvas = FigureCanvasTkAgg(self.histogram_figure, + # self.histogram_canvas) + + self.histogram = HistogramFrame(self) + self.histogram.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) + + ########### + #: MetricsFrame: The frame that will hold the camera selection and counts. self.image_metrics = MetricsFrame(self) self.image_metrics.grid(row=1, column=1, sticky=tk.NSEW, padx=5, pady=5) @@ -234,6 +261,24 @@ def __init__(self, cam_wave, *args, **kwargs): self.live_frame.grid(row=2, column=1, sticky=tk.NSEW, padx=5, pady=5) +class HistogramFrame(ttk.Labelframe): + def __init__(self, cam_view, *args, **kwargs): + + text_label = "Intensity Histogram" + ttk.Labelframe.__init__(self, cam_view, text=text_label, *args, **kwargs) + + self.frame = ttk.Frame(self) + self.frame.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) + + self.canvas = tk.Canvas(self.frame, width=512, height=512 // 5) + self.canvas.grid(row=0, column=0, sticky=tk.NSEW, padx=5, pady=5) + + # Matplotlib figure for the histogram + self.figure = Figure(figsize=(3, 1), tight_layout=True) + self.figure_canvas = FigureCanvasTkAgg(self.figure, self.frame) + self.figure_canvas.get_tk_widget().grid(row=0, column=0, sticky=tk.NSEW) + + class RenderFrame(ttk.Labelframe): """This class is the frame that holds the live display functionality.""" diff --git a/src/navigate/view/splash_screen.py b/src/navigate/view/splash_screen.py index fb86c3bb9..bffe788b9 100644 --- a/src/navigate/view/splash_screen.py +++ b/src/navigate/view/splash_screen.py @@ -45,12 +45,12 @@ class SplashScreen(tk.Toplevel): Centered depending upon the host computer being used. """ - def __init__(self, root, image_path, *args, **kargs): + def __init__(self, root: tk.Tk, image_path, *args, **kargs): """Initialize the SplashScreen. Parameters ---------- - root : Tk top-level widget + root : tk.Tk Tkinter GUI instance to which this SplashScreen belongs. image_path : str Path to the image to display on the splash screen. diff --git a/test/controller/sub_controllers/test_acquire_bar.py b/test/controller/sub_controllers/test_acquire_bar.py index d76c70718..1607ca798 100644 --- a/test/controller/sub_controllers/test_acquire_bar.py +++ b/test/controller/sub_controllers/test_acquire_bar.py @@ -59,7 +59,7 @@ def setup_class(self, dummy_controller): c = dummy_controller v = dummy_controller.view - self.acquire_bar_controller = AcquireBarController(v.acqbar, c) + self.acquire_bar_controller = AcquireBarController(v.acquire_bar, c) self.acquire_bar_controller.populate_experiment_values() def test_init(self): From 681cb74bee1235c44f62fc3d76cdb4d038bfcf11 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:41:16 -0500 Subject: [PATCH 2/8] Update display_notebook.py --- .../main_window_content/display_notebook.py | 233 ++++++++++-------- 1 file changed, 137 insertions(+), 96 deletions(-) diff --git a/src/navigate/view/main_window_content/display_notebook.py b/src/navigate/view/main_window_content/display_notebook.py index 5c16cc598..af5e3634d 100644 --- a/src/navigate/view/main_window_content/display_notebook.py +++ b/src/navigate/view/main_window_content/display_notebook.py @@ -34,6 +34,7 @@ import tkinter as tk from tkinter import ttk, Grid import logging +from typing import Iterable, Dict, Any # Third Party Imports from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg @@ -52,17 +53,18 @@ class CameraNotebook(DockableNotebook): """This class is the notebook that holds the camera view and waveform settings tabs.""" - def __init__(self, frame, *args, **kwargs): + def __init__( + self, frame: ttk.Frame, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Init function for the CameraNotebook class. - Parameters ---------- - frame : tk.Frame + frame : ttk.Frame The frame that will hold the notebook. - *args : tuple + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ # Init notebook @@ -74,17 +76,15 @@ def __init__(self, frame, *args, **kwargs): #: CameraTab: The camera tab. self.camera_tab = CameraTab(self) - #: CameraTab: The maximum intensity projection tab. + #: MIPTab: The maximum intensity projection tab. self.mip_tab = MIPTab(self) #: WaveformTab: The waveform settings tab. self.waveform_tab = WaveformTab(self) - # Tab list + # Set tab list tab_list = [self.camera_tab, self.mip_tab, self.waveform_tab] self.set_tablist(tab_list) - - # Adding tabs to self notebook self.add(self.camera_tab, text="Camera", sticky=tk.NSEW) self.add(self.mip_tab, text="MIP", sticky=tk.NSEW) self.add(self.waveform_tab, text="Waveforms", sticky=tk.NSEW) @@ -93,16 +93,18 @@ def __init__(self, frame, *args, **kwargs): class MIPTab(tk.Frame): """MipTab class.""" - def __init__(self, cam_wave, *args, **kwargs): + def __init__( + self, cam_wave: CameraNotebook, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Initialize the MIPTab class. Parameters ---------- - cam_wave : tk.Frame + cam_wave : CameraNotebook The frame that will hold the camera tab. - *args : tuple + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ # Init Frame @@ -121,19 +123,21 @@ def __init__(self, cam_wave, *args, **kwargs): tk.Grid.columnconfigure(self, "all", weight=1) tk.Grid.rowconfigure(self, "all", weight=1) - #: tk.Frame: The frame that will hold the camera image. + #: ttk.Frame: The frame that will hold the camera image. self.cam_image = ttk.Frame(self) self.cam_image.grid(row=0, column=0, rowspan=3, sticky=tk.NSEW) - #: Bool: The popup flag. + #: bool: The popup flag. self.is_popup = False - #: Bool: The docked flag. + #: bool: The docked flag. self.is_docked = True #: int: The width of the canvas. + self.canvas_width = 512 + #: int: The height of the canvas. - self.canvas_width, self.canvas_height = 512, 512 + self.canvas_height = 512 #: tk.Canvas: The canvas that will hold the camera image. self.canvas = tk.Canvas( @@ -144,7 +148,7 @@ def __init__(self, cam_wave, *args, **kwargs): #: matplotlib.figure.Figure: The figure that will hold the camera image. self.matplotlib_figure = Figure(figsize=[6, 6], tight_layout=True) - #: FigureCanvasTkAgg: The canvas that will hold the camera image. + #: FigureCanvasTkAgg: The canvas that will hold the camera image. self.matplotlib_canvas = FigureCanvasTkAgg(self.matplotlib_figure, self.canvas) #: IntensityFrame: The frame that will hold the scale settings/palette color. @@ -159,16 +163,18 @@ def __init__(self, cam_wave, *args, **kwargs): class CameraTab(tk.Frame): """CameraTab class.""" - def __init__(self, cam_wave, *args, **kwargs): + def __init__( + self, cam_wave: CameraNotebook, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Initialize the CameraTab class. Parameters ---------- - cam_wave : tk.Frame + cam_wave : CameraNotebook The frame that will hold the camera tab. - *args : tuple + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ # Init Frame @@ -181,14 +187,14 @@ def __init__(self, cam_wave, *args, **kwargs): tk.Grid.columnconfigure(self, "all", weight=1) tk.Grid.rowconfigure(self, "all", weight=1) - #: tk.Frame: The frame that will hold the camera image. + #: ttk.Frame: The frame that will hold the camera image. self.cam_image = ttk.Frame(self) self.cam_image.grid(row=0, column=0, rowspan=3, sticky=tk.NSEW) - #: Bool: The popup flag. + #: bool: The popup flag. self.is_popup = False - #: Bool: The docked flag. + #: bool: The docked flag. self.is_docked = True #: int: The width of the canvas. @@ -227,31 +233,10 @@ def __init__(self, cam_wave, *args, **kwargs): self.slider.grid(row=3, column=0, sticky=tk.NSEW, padx=5, pady=5) self.slider.grid_remove() - ############### - - # Histogram. - # self.histogram_frame.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) - - #: tk.Canvas: The canvas that will hold the camera image. - # self.histogram_canvas = tk.Canvas( - # self.histogram_frame, - # width=self.canvas_width, - # height=self.canvas_height // 5 - # ) - # self.histogram_canvas.grid(row=0, column=0, sticky=tk.NSEW, padx=5, pady=5) - - #: matplotlib.figure.Figure: The figure that will hold the histogram. - # self.histogram_figure = Figure(figsize=[1, 1], tight_layout=True) - # - # #: FigureCanvasTkAgg: The canvas that will hold the camera image. - # self.histogram_figure_canvas = FigureCanvasTkAgg(self.histogram_figure, - # self.histogram_canvas) - + #: HistogramFrame: The frame that will hold the histogram. self.histogram = HistogramFrame(self) self.histogram.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) - ########### - #: MetricsFrame: The frame that will hold the camera selection and counts. self.image_metrics = MetricsFrame(self) self.image_metrics.grid(row=1, column=1, sticky=tk.NSEW, padx=5, pady=5) @@ -262,19 +247,38 @@ def __init__(self, cam_wave, *args, **kwargs): class HistogramFrame(ttk.Labelframe): - def __init__(self, cam_view, *args, **kwargs): + """This class is the frame that holds the histogram.""" + + def __init__( + self, camera_tab: CameraTab, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: + """Initialize the HistogramFrame class. + + Parameters + ---------- + camera_tab : CameraTab + The frame that will hold the histogram. + *args : Iterable + Variable length argument list. + **kwargs : dict + Arbitrary keyword arguments. + """ text_label = "Intensity Histogram" - ttk.Labelframe.__init__(self, cam_view, text=text_label, *args, **kwargs) + ttk.Labelframe.__init__(self, camera_tab, text=text_label, *args, **kwargs) + #: ttk.Frame: The frame for the histogram. self.frame = ttk.Frame(self) self.frame.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) + #: tk.Canvas: The canvas for the histogram. self.canvas = tk.Canvas(self.frame, width=512, height=512 // 5) self.canvas.grid(row=0, column=0, sticky=tk.NSEW, padx=5, pady=5) - # Matplotlib figure for the histogram + #: matplotlib.figure.Figure: The figure for the histogram. self.figure = Figure(figsize=(3, 1), tight_layout=True) + + #: FigureCanvasTkAgg: The canvas for the histogram. self.figure_canvas = FigureCanvasTkAgg(self.figure, self.frame) self.figure_canvas.get_tk_widget().grid(row=0, column=0, sticky=tk.NSEW) @@ -282,21 +286,23 @@ def __init__(self, cam_view, *args, **kwargs): class RenderFrame(ttk.Labelframe): """This class is the frame that holds the live display functionality.""" - def __init__(self, cam_view, *args, **kwargs): + def __init__( + self, camera_tab: CameraTab, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Initialize the RenderFrame class. Parameters ---------- - cam_view : tk.Frame + camera_tab : CameraTab The frame that will hold the live display functionality. - *args : tuple + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ # Init Frame text_label = "Image Display" - ttk.Labelframe.__init__(self, cam_view, text=text_label, *args, **kwargs) + ttk.Labelframe.__init__(self, camera_tab, text=text_label, *args, **kwargs) # Formatting Grid.columnconfigure(self, "all", weight=1) @@ -323,14 +329,14 @@ def __init__(self, cam_view, *args, **kwargs): self.channel.grid(row=1, column=0) self.channel.state = "readonly" - def get_variables(self): + def get_variables(self) -> Dict[str, Any]: """Function to get the variables. The key is the widget name, value is the variable associated. Returns ------- - variables : dict + variables : Dict[str, Any] The dictionary that holds the variables. """ variables = {} @@ -338,10 +344,15 @@ def get_variables(self): variables[key] = widget.get() return variables - def get_widgets(self): + def get_widgets(self) -> Dict[str, Any]: """Function to get the widgets. The key is the widget name, value is the LabelInput class that has all the data. + + Returns + ------- + widgets : Dict[str, Any] + The dictionary that holds the widgets. """ return self.inputs @@ -349,21 +360,23 @@ def get_widgets(self): class MipRenderFrame(ttk.Labelframe): """This class is the frame that holds the live display functionality.""" - def __init__(self, cam_view, *args, **kwargs): - """Initialize the RenderFrame class. + def __init__( + self, camera_tab: CameraTab, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: + """Initialize the MipRenderFrame class. Parameters ---------- - cam_view : tk.Frame - The frame that will hold the live display functionality. - *args : tuple + camera_tab : CameraTab + The frame that will hold the MIP display functionality. + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ # Init Frame text_label = "Image Display" - ttk.Labelframe.__init__(self, cam_view, text=text_label, *args, **kwargs) + ttk.Labelframe.__init__(self, camera_tab, text=text_label, *args, **kwargs) # Formatting Grid.columnconfigure(self, "all", weight=1) @@ -395,14 +408,14 @@ def __init__(self, cam_view, *args, **kwargs): self.inputs["channel"].grid(row=1, column=0, sticky=tk.EW, padx=3, pady=3) self.columnconfigure(0, weight=1) - def get_variables(self): + def get_variables(self) -> Dict[str, Any]: """Function to get the variables. The key is the widget name, value is the variable associated. Returns ------- - variables : dict + variables : Dict[str, Any] The dictionary that holds the variables. """ variables = {} @@ -414,6 +427,11 @@ def get_widgets(self): """Function to get the widgets. The key is the widget name, value is the LabelInput class that has all the data. + + Returns + ------- + widgets : Dict[str, Any] + The dictionary that holds the widgets. """ return self.inputs @@ -421,21 +439,23 @@ def get_widgets(self): class WaveformTab(tk.Frame): """This class is the frame that holds the waveform tab.""" - def __init__(self, cam_wave, *args, **kwargs): + def __init__( + self, camera_tab: CameraTab, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Initialize the WaveformTab class. Parameters ---------- - cam_wave : tk.Frame + camera_tab : CameraTab The frame that will hold the waveform tab. - *args : tuple + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ # Init Frame - tk.Frame.__init__(self, cam_wave, *args, **kwargs) + tk.Frame.__init__(self, camera_tab, *args, **kwargs) #: int: The index of the tab. self.index = 2 @@ -447,7 +467,7 @@ def __init__(self, cam_wave, *args, **kwargs): tk.Grid.columnconfigure(self, "all", weight=1) tk.Grid.rowconfigure(self, "all", weight=1) - #: tk.Frame: The frame that will hold the waveform plots. + #: ttk.Frame: The frame that will hold the waveform plots. self.waveform_plots = ttk.Frame(self) self.waveform_plots.grid(row=0, column=0, sticky=tk.NSEW) @@ -458,7 +478,7 @@ def __init__(self, cam_wave, *args, **kwargs): self.canvas = FigureCanvasTkAgg(self.fig, master=self.waveform_plots) self.canvas.draw() - #: tk.Frame: The frame that will hold the waveform settings. + #: WaveformSettingsFrame: The frame that will hold the waveform settings. self.waveform_settings = WaveformSettingsFrame(self) self.waveform_settings.grid(row=1, column=0, sticky=tk.NSEW, padx=5, pady=5) @@ -466,21 +486,23 @@ def __init__(self, cam_wave, *args, **kwargs): class WaveformSettingsFrame(ttk.Labelframe): """This class is the frame that holds the waveform settings.""" - def __init__(self, wav_view, *args, **kwargs): + def __init__( + self, waveform_tab: WaveformTab, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Initialize the WaveformSettingsFrame class. Parameters ---------- - wav_view : tk.Frame + waveform_tab : WaveformTab The frame that will hold the waveform settings. - *args : tuple + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ # Init Frame text_label = "Settings" - ttk.Labelframe.__init__(self, wav_view, text=text_label, *args, **kwargs) + ttk.Labelframe.__init__(self, waveform_tab, text=text_label, *args, **kwargs) # Formatting tk.Grid.columnconfigure(self, "all", weight=1) @@ -510,12 +532,12 @@ def __init__(self, wav_view, *args, **kwargs): row=0, column=1, sticky=tk.NSEW, padx=3, pady=3 ) - def get_variables(self): + def get_variables(self) -> Dict[str, Any]: """Function to get the variables. Returns ------- - variables : dict + variables : Dict[str, Any] The dictionary that holds the variables. """ variables = {} @@ -523,28 +545,35 @@ def get_variables(self): variables[key] = widget.get() return variables - def get_widgets(self): - """Function to get the widgets.""" + def get_widgets(self) -> Dict[str, Any]: + """Function to get the widgets. + + Returns + ------- + widgets : Dict[str, Any] + The dictionary that holds the widgets.""" return self.inputs class MetricsFrame(ttk.Labelframe): """This class is the frame that holds the image metrics.""" - def __init__(self, cam_view, *args, **kwargs): + def __init__( + self, camera_tab: CameraTab, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Initialize the MetricsFrame class. Parameters ---------- - cam_view : tk.Frame + camera_tab : CameraTab The frame that will hold the image metrics. - *args : tuple + *args : Iterable Variable length argument list. - **kwargs : dict + **kwargs : Dict[str, Any] Arbitrary keyword arguments. """ text_label = "Image Metrics" - ttk.Labelframe.__init__(self, cam_view, text=text_label, *args, **kwargs) + ttk.Labelframe.__init__(self, camera_tab, text=text_label, *args, **kwargs) tk.Grid.columnconfigure(self, "all", weight=1) tk.Grid.rowconfigure(self, "all", weight=1) @@ -585,7 +614,7 @@ def __init__(self, cam_view, *args, **kwargs): ) self.inputs[self.names[i]].configure(width=5) - def get_variables(self): + def get_variables(self) -> Dict[str, Any]: """This function returns a dictionary of all the variables that are tied to each widget name. @@ -593,7 +622,7 @@ def get_variables(self): Returns ------- - variables : dict + variables : Dict[str, Any] The dictionary that holds the variables. """ variables = {} @@ -601,10 +630,15 @@ def get_variables(self): variables[key] = widget.get() return variables - def get_widgets(self): + def get_widgets(self) -> Dict[str, Any]: """This function returns the dictionary that holds the widgets. The key is the widget name, value is the LabelInput class that has all the data. + + Returns + ------- + widgets : Dict[str, Any] + The dictionary that holds the widgets. """ return self.inputs @@ -612,12 +646,14 @@ def get_widgets(self): class IntensityFrame(ttk.Labelframe): """This class is the frame that holds the intensity controls.""" - def __init__(self, cam_view, *args, **kwargs): + def __init__( + self, camera_tab: CameraTab, *args: Iterable, **kwargs: Dict[str, Any] + ) -> None: """Initialize the IntensityFrame class. Parameters ---------- - cam_view : tk.Frame + camera_tab : CameraTab The frame that will hold the intensity controls. *args : tuple Variable length argument list. @@ -626,7 +662,7 @@ def __init__(self, cam_view, *args, **kwargs): """ # Init Frame text_label = "LUT" - ttk.Labelframe.__init__(self, cam_view, text=text_label, *args, **kwargs) + ttk.Labelframe.__init__(self, camera_tab, text=text_label, *args, **kwargs) # Formatting tk.Grid.columnconfigure(self, "all", weight=1) @@ -718,7 +754,7 @@ def __init__(self, cam_view, *args, **kwargs): pady=3, ) - def get_variables(self): + def get_variables(self) -> Dict[str, Any]: """This function returns a dictionary of all the variables that are tied to each widget name. @@ -734,9 +770,14 @@ def get_variables(self): variables[key] = widget.get() return variables - def get_widgets(self): + def get_widgets(self) -> Dict[str, Any]: """This function returns the dictionary that holds the widgets. The key is the widget name, value is the LabelInput class that has all the data. + + Returns + ------- + widgets : dict + The dictionary that holds the widgets. """ return self.inputs From bcf8b476e09051e6f6e4687d952f086e5979c3fa Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:01:59 -0500 Subject: [PATCH 3/8] Update display_notebook.py --- .../main_window_content/display_notebook.py | 172 ++++-------------- 1 file changed, 36 insertions(+), 136 deletions(-) diff --git a/src/navigate/view/main_window_content/display_notebook.py b/src/navigate/view/main_window_content/display_notebook.py index af5e3634d..565a7d2d9 100644 --- a/src/navigate/view/main_window_content/display_notebook.py +++ b/src/navigate/view/main_window_content/display_notebook.py @@ -49,6 +49,38 @@ logger = logging.getLogger(p) +class CommonMethods: + """This class is a collection of common methods.""" + + def get_variables(self) -> Dict[str, Any]: + """This function returns a dictionary of all the variables that are tied to + each widget name. + + The key is the widget name, value is the variable associated. + + Returns + ------- + variables : dict + The dictionary that holds the variables. + """ + variables = {} + for key, widget in self.inputs.items(): + variables[key] = widget.get() + return variables + + def get_widgets(self) -> Dict[str, Any]: + """This function returns the dictionary that holds the widgets. + + The key is the widget name, value is the LabelInput class that has all the data. + + Returns + ------- + widgets : dict + The dictionary that holds the widgets. + """ + return self.inputs + + class CameraNotebook(DockableNotebook): """This class is the notebook that holds the camera view and waveform settings tabs.""" @@ -329,35 +361,8 @@ def __init__( self.channel.grid(row=1, column=0) self.channel.state = "readonly" - def get_variables(self) -> Dict[str, Any]: - """Function to get the variables. - - The key is the widget name, value is the variable associated. - Returns - ------- - variables : Dict[str, Any] - The dictionary that holds the variables. - """ - variables = {} - for key, widget in self.inputs.items(): - variables[key] = widget.get() - return variables - - def get_widgets(self) -> Dict[str, Any]: - """Function to get the widgets. - - The key is the widget name, value is the LabelInput class that has all the data. - - Returns - ------- - widgets : Dict[str, Any] - The dictionary that holds the widgets. - """ - return self.inputs - - -class MipRenderFrame(ttk.Labelframe): +class MipRenderFrame(ttk.Labelframe, CommonMethods): """This class is the frame that holds the live display functionality.""" def __init__( @@ -408,33 +413,6 @@ def __init__( self.inputs["channel"].grid(row=1, column=0, sticky=tk.EW, padx=3, pady=3) self.columnconfigure(0, weight=1) - def get_variables(self) -> Dict[str, Any]: - """Function to get the variables. - - The key is the widget name, value is the variable associated. - - Returns - ------- - variables : Dict[str, Any] - The dictionary that holds the variables. - """ - variables = {} - for key, widget in self.inputs.items(): - variables[key] = widget.get() - return variables - - def get_widgets(self): - """Function to get the widgets. - - The key is the widget name, value is the LabelInput class that has all the data. - - Returns - ------- - widgets : Dict[str, Any] - The dictionary that holds the widgets. - """ - return self.inputs - class WaveformTab(tk.Frame): """This class is the frame that holds the waveform tab.""" @@ -483,7 +461,7 @@ def __init__( self.waveform_settings.grid(row=1, column=0, sticky=tk.NSEW, padx=5, pady=5) -class WaveformSettingsFrame(ttk.Labelframe): +class WaveformSettingsFrame(ttk.Labelframe, CommonMethods): """This class is the frame that holds the waveform settings.""" def __init__( @@ -532,30 +510,8 @@ def __init__( row=0, column=1, sticky=tk.NSEW, padx=3, pady=3 ) - def get_variables(self) -> Dict[str, Any]: - """Function to get the variables. - - Returns - ------- - variables : Dict[str, Any] - The dictionary that holds the variables. - """ - variables = {} - for key, widget in self.inputs.items(): - variables[key] = widget.get() - return variables - - def get_widgets(self) -> Dict[str, Any]: - """Function to get the widgets. - - Returns - ------- - widgets : Dict[str, Any] - The dictionary that holds the widgets.""" - return self.inputs - -class MetricsFrame(ttk.Labelframe): +class MetricsFrame(ttk.Labelframe, CommonMethods): """This class is the frame that holds the image metrics.""" def __init__( @@ -614,36 +570,8 @@ def __init__( ) self.inputs[self.names[i]].configure(width=5) - def get_variables(self) -> Dict[str, Any]: - """This function returns a dictionary of all the variables that are tied to - each widget name. - - The key is the widget name, value is the variable associated. - - Returns - ------- - variables : Dict[str, Any] - The dictionary that holds the variables. - """ - variables = {} - for key, widget in self.inputs.items(): - variables[key] = widget.get() - return variables - def get_widgets(self) -> Dict[str, Any]: - """This function returns the dictionary that holds the widgets. - - The key is the widget name, value is the LabelInput class that has all the data. - - Returns - ------- - widgets : Dict[str, Any] - The dictionary that holds the widgets. - """ - return self.inputs - - -class IntensityFrame(ttk.Labelframe): +class IntensityFrame(ttk.Labelframe, CommonMethods): """This class is the frame that holds the intensity controls.""" def __init__( @@ -753,31 +681,3 @@ def __init__( padx=3, pady=3, ) - - def get_variables(self) -> Dict[str, Any]: - """This function returns a dictionary of all the variables that are tied to - each widget name. - - The key is the widget name, value is the variable associated. - - Returns - ------- - variables : dict - The dictionary that holds the variables. - """ - variables = {} - for key, widget in self.inputs.items(): - variables[key] = widget.get() - return variables - - def get_widgets(self) -> Dict[str, Any]: - """This function returns the dictionary that holds the widgets. - - The key is the widget name, value is the LabelInput class that has all the data. - - Returns - ------- - widgets : dict - The dictionary that holds the widgets. - """ - return self.inputs From 88db0d474c324d0e14c60c87f92ea4b7cbecc5ba Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:37:06 -0500 Subject: [PATCH 4/8] Move to dedicated controller The log x axis creates some very annoying behaviors with the tick marks... I'm hoping that this is okay now. --- src/navigate/controller/controller.py | 10 +- .../controller/sub_controllers/__init__.py | 1 + .../controller/sub_controllers/camera_view.py | 31 --- .../controller/sub_controllers/histogram.py | 179 ++++++++++++++++++ .../controller/sub_controllers/keystrokes.py | 73 ++++--- 5 files changed, 221 insertions(+), 73 deletions(-) create mode 100644 src/navigate/controller/sub_controllers/histogram.py diff --git a/src/navigate/controller/controller.py b/src/navigate/controller/controller.py index 90445dd03..237feb3f1 100644 --- a/src/navigate/controller/controller.py +++ b/src/navigate/controller/controller.py @@ -63,6 +63,7 @@ FeaturePopupController, MenuController, PluginsController, + HistogramController, # MicroscopePopupController, # AdaptiveOpticsPopupController, ) @@ -244,6 +245,10 @@ def __init__( self.view.camera_waveform.camera_tab, self ) + self.histogram_controller = HistogramController( + self.view.camera_waveform.camera_tab.histogram, self + ) + #: MIPSettingController: MIP Settings Tab Sub-Controller. self.mip_setting_controller = MIPViewController( self.view.camera_waveform.mip_tab, self @@ -1083,13 +1088,16 @@ def capture_image(self, command, mode, *args): ) self.execute("stop_acquire") - # Display the Image in the View + # Display the image and update the histogram self.camera_view_controller.try_to_display_image( image=self.data_buffer[image_id] ) self.mip_setting_controller.try_to_display_image( image=self.data_buffer[image_id] ) + self.histogram_controller.populate_histogram( + image=self.data_buffer[image_id] + ) images_received += 1 # Update progress bar. diff --git a/src/navigate/controller/sub_controllers/__init__.py b/src/navigate/controller/sub_controllers/__init__.py index b8e0adc13..43b4b583d 100644 --- a/src/navigate/controller/sub_controllers/__init__.py +++ b/src/navigate/controller/sub_controllers/__init__.py @@ -18,6 +18,7 @@ from .camera_map import CameraMapSettingPopupController # noqa from .microscope_popup import MicroscopePopupController # noqa from .adaptive_optics import AdaptiveOpticsPopupController # noqa +from .histogram import HistogramController # noqa # from .uninstall_plugin_controller import UninstallPluginController # noqa from .plugins import PluginsController, UninstallPluginController # noqa diff --git a/src/navigate/controller/sub_controllers/camera_view.py b/src/navigate/controller/sub_controllers/camera_view.py index eb486d26c..9399c0b8b 100644 --- a/src/navigate/controller/sub_controllers/camera_view.py +++ b/src/navigate/controller/sub_controllers/camera_view.py @@ -1047,10 +1047,6 @@ def __init__(self, view, parent_controller=None): # SpooledImageLoader: The spooled image loader. self.spooled_images = None - self.histogram_canvas = view.histogram.figure_canvas - self.figure = view.histogram.figure.add_subplot(111) - self.populate_histogram(image=np.random.normal(100, 20, 1000)) - #: dict: The dictionary of image metrics widgets. self.image_metrics = view.image_metrics.get_widgets() @@ -1091,31 +1087,6 @@ def __init__(self, view, parent_controller=None): #: numpy.ndarray: The ilastik mask. self.ilastik_seg_mask = None - def populate_histogram(self, image): - """Populate the histogram.""" - self.figure.clear() - - data = image.flatten() - - self.figure.hist(data, bins=30, color="black") - - # X-axis - xmin, xmax = 0, np.max(data) - self.figure.set_xlim([xmin, xmax]) - self.figure.set_xlabel("", fontsize=9) - - num_ticks = 5 - ticks = np.linspace(xmin, xmax, num_ticks) - self.figure.set_xticks(ticks) - self.figure.set_xticklabels( - [f"{tick:.2f}" for tick in ticks] - ) # Format labels as needed - - # Y-axis - self.figure.set_ylabel("", fontsize=9) - self.figure.set_yticks([]) - self.histogram_canvas.draw() - def try_to_display_image(self, image): """Try to display an image. @@ -1361,8 +1332,6 @@ def display_image(self, image): image : numpy.ndarray Image data. """ - self.populate_histogram(image) - start_time = time.time() self.image = self.flip_image(image) self.max_intensity_history.append(np.max(image)) diff --git a/src/navigate/controller/sub_controllers/histogram.py b/src/navigate/controller/sub_controllers/histogram.py new file mode 100644 index 000000000..86552506f --- /dev/null +++ b/src/navigate/controller/sub_controllers/histogram.py @@ -0,0 +1,179 @@ +# Copyright (c) 2021-2024 The University of Texas Southwestern Medical Center. +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted for academic and research use only (subject to the +# limitations in the disclaimer below) provided that the following conditions are met: + +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. + +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# * Neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. + +# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# Standard Library Imports +import platform +import tkinter as tk + +# Third Party Imports +import numpy as np + +# Local Imports +from navigate.controller.sub_controllers.gui import GUIController + +# Logger Setup +# p = __name__.split(".")[1] +# logger = logging.getLogger(p) + + +class HistogramController(GUIController): + """Histogram controller""" + + def __init__(self, histogram, parent_controller) -> None: + """Initialize the histogram controller + + Parameters + ---------- + histogram : HistogramFrame + Histogram view + parent_controller : MainController + Main controller + """ + + #: HistogramFrame: Histogram view + self.histogram = histogram + + #: MainController: Main controller + self.parent_controller = parent_controller + + #: FigureBase: The histogram figure. + self.figure = self.histogram.figure.add_subplot(111) + + # Event Bindings + widget = self.histogram.figure_canvas.get_tk_widget() + + if platform.system() == "Darwin": + widget.bind("", self.histogram_popup) + else: + widget.bind("", self.histogram_popup) + + # Default axis values + self.x_axis_var = tk.StringVar(value="linear") + self.y_axis_var = tk.StringVar(value="linear") + + #: tk.Menu: Histogram popup menu + self.menu = tk.Menu(widget, tearoff=0) + self.menu.add_radiobutton( + label="Log X", + variable=self.x_axis_var, + value="log", + command=self.update_scale, + ) + self.menu.add_radiobutton( + label="Linear X", + variable=self.x_axis_var, + value="linear", + command=self.update_scale, + ) + self.menu.add_separator() + self.menu.add_radiobutton( + label="Log Y", + variable=self.y_axis_var, + value="log", + command=self.update_scale, + ) + self.menu.add_radiobutton( + label="Linear Y", + variable=self.y_axis_var, + value="linear", + command=self.update_scale, + ) + + #: bool: Logarithmic X-axis + self.log_x = False + + #: bool: Logarithmic Y-axis + self.log_y = False + + self.populate_histogram(image=np.random.normal(100, 20, 1000)) + + def update_scale(self) -> None: + """Update the scale of the histogram""" + self.log_x = self.x_axis_var.get() == "log" + self.log_y = self.y_axis_var.get() == "log" + + def histogram_popup(self, event: tk.Event) -> None: + """Histogram popup menu + + Parameters + ---------- + event : tk.Event + Event + """ + try: + self.menu.tk_popup(event.x_root, event.y_root, 0) + finally: + self.menu.grab_release() + + def populate_histogram(self, image: np.ndarray) -> None: + """Populate the histogram. + + Parameters + ---------- + image : np.ndarray + Image data + """ + data = image.flatten() + self.figure.clear() + self.figure.hist(data, color="black") + + # Limits + std_dev = np.std(data) + xmin, xmax = np.min(data) - std_dev, np.max(data) + std_dev + xmin = 0 if xmin < 0 else xmin + + # Tick marks. + num_ticks = 5 + if self.log_x: + ticks = np.log10(np.logspace(np.log10(xmin), np.log10(xmax), num_ticks)) + self.figure.set_xscale("log", nonpositive="clip", subs=[]) + self.figure.set_xticks(ticks) + self.figure.set_xticklabels( + [f"{10**tick:.2f}" for tick in ticks], fontsize=6 + ) + else: + ticks = np.linspace(xmin, xmax, num_ticks) + self.figure.set_xticks(ticks) + self.figure.set_xticklabels([f"{tick:.2f}" for tick in ticks], fontsize=6) + + self.figure.set_xlim([xmin, xmax]) + self.figure.set_xlabel("", fontsize=4) + + # Y-axis + if self.log_y: + self.figure.set_yscale("log") + + self.figure.set_ylabel("", fontsize=6) + self.figure.set_yticks([]) + + # Draw the figure + + self.histogram.figure_canvas.draw() diff --git a/src/navigate/controller/sub_controllers/keystrokes.py b/src/navigate/controller/sub_controllers/keystrokes.py index d5903c96c..6f3150ea8 100644 --- a/src/navigate/controller/sub_controllers/keystrokes.py +++ b/src/navigate/controller/sub_controllers/keystrokes.py @@ -60,25 +60,9 @@ def __init__(self, main_view, parent_controller): """ super().__init__(main_view, parent_controller) - # References to all sub frames - #: tk.Frame: Camera View - self.camera_view = main_view.camera_waveform.camera_tab # Camera View - - #: tk.Frame: MIP View - self.mip_view = main_view.camera_waveform.mip_tab # MIP View - - # Multiposition Table - #: MultipositionTable: Multiposition Table - self.multi_table = main_view.settings.multiposition_tab.multipoint_list - - # Main view - #: tk.Frame: Main view - self.main_view = main_view.root - #: tk.Notebook: Main tabs self.main_tabs = main_view.settings - # Controllers for all sub frames #: CameraViewController: Camera View Controller self.camera_controller = parent_controller.camera_view_controller @@ -91,52 +75,59 @@ def __init__(self, main_view, parent_controller): #: StageController: Stage Controller self.stage_controller = parent_controller.stage_controller - """Keystrokes for Camera & MIP View""" - # Left Click binding - self.camera_view.canvas.bind("", self.camera_controller.left_click) - self.mip_view.canvas.bind("", self.mip_controller.left_click) - - # MouseWheel Binding + """Keystrokes for Main View""" + #: tk.Tk: Main view + self.main_view = main_view.root self.view.root.bind("", self.view.scroll_frame.mouse_wheel) + self.main_view.bind( + "", self.stage_controller.joystick_button_handler + ) + self.main_view.bind("", self.switch_tab) + self.main_view.bind("", self.switch_tab) + self.main_view.bind("", self.switch_tab) + self.main_view.bind("", self.switch_tab) + self.main_view.bind_all("", self.widget_undo) + self.main_view.bind_all("", self.widget_redo) + + """Keystrokes for Camera View""" + #: CameraTab: Camera View + self.camera_view = main_view.camera_waveform.camera_tab + self.camera_view.canvas.bind("", self.camera_controller.left_click) self.camera_view.canvas.bind( "", self.camera_controller_mouse_wheel_enter ) self.camera_view.canvas.bind( "", self.camera_controller_mouse_wheel_leave ) - self.mip_view.canvas.bind("", self.mip_controller_mouse_wheel_enter) - self.mip_view.canvas.bind("", self.mip_controller_mouse_wheel_leave) - - # Right Click Binding if platform.system() == "Darwin": self.camera_view.canvas.bind( "", self.camera_controller.popup_menu ) - self.mip_view.canvas.bind("", self.mip_controller.popup_menu) else: self.camera_view.canvas.bind( "", self.camera_controller.popup_menu ) + + """Keystrokes for MIP View""" + #: MIPTab: MIP View + self.mip_view = main_view.camera_waveform.mip_tab + self.mip_view.canvas.bind("", self.mip_controller.left_click) + self.mip_view.canvas.bind("", self.mip_controller_mouse_wheel_enter) + self.mip_view.canvas.bind("", self.mip_controller_mouse_wheel_leave) + if platform.system() == "Darwin": + self.mip_view.canvas.bind("", self.mip_controller.popup_menu) + else: self.mip_view.canvas.bind("", self.mip_controller.popup_menu) - """Keystrokes for MultiTable""" + """Keystrokes for Multi-Position Table""" + #: MultipositionTable: Multiposition Table + self.multi_table = main_view.settings.multiposition_tab.multipoint_list.pt + #: MultiPositionTable: Multiposition Table - self.mp_table = self.multi_table.pt - self.mp_table.rowheader.bind( + self.multi_table.rowheader.bind( "", self.multi_controller.handle_double_click ) - """Keystrokes for Main Window""" - self.main_view.bind( - "", self.stage_controller.joystick_button_handler - ) - self.main_view.bind("", self.switch_tab) - self.main_view.bind("", self.switch_tab) - self.main_view.bind("", self.switch_tab) - self.main_view.bind("", self.switch_tab) - self.main_view.bind_all("", self.widget_undo) - self.main_view.bind_all("", self.widget_redo) - def camera_controller_mouse_wheel_enter(self, event): """Mouse wheel binding for camera view From 1735ebcfb1e09bc2333f5d167d16d81f6abab9a8 Mon Sep 17 00:00:00 2001 From: Jinlong_Lin Date: Fri, 11 Oct 2024 20:47:09 -0500 Subject: [PATCH 5/8] Change resize, mainly. Decided I'll simplify the histogram and only plot the values with log y and linear x... --- src/navigate/controller/controller.py | 10 ++-- .../controller/sub_controllers/camera_view.py | 2 +- .../controller/sub_controllers/histogram.py | 58 +++++++++---------- .../main_window_content/display_notebook.py | 2 +- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/navigate/controller/controller.py b/src/navigate/controller/controller.py index 237feb3f1..b90a200ea 100644 --- a/src/navigate/controller/controller.py +++ b/src/navigate/controller/controller.py @@ -558,12 +558,14 @@ def refresh(width, height): height : int Height of the GUI. """ - if width < 1200 or height < 600: + if width < 1300 or height < 800: return self.view.camera_waveform["width"] = ( - width - self.view.left_frame.winfo_width() - 81 - ) - self.view.camera_waveform["height"] = height - 110 + width - self.view.left_frame.winfo_width() - 35 + ) # + self.view.camera_waveform["height"] = height - 117 + + print("camera_waveform height", self.view.camera_waveform["height"]) if event.widget != self.view.scroll_frame: return diff --git a/src/navigate/controller/sub_controllers/camera_view.py b/src/navigate/controller/sub_controllers/camera_view.py index 9399c0b8b..87b881b14 100644 --- a/src/navigate/controller/sub_controllers/camera_view.py +++ b/src/navigate/controller/sub_controllers/camera_view.py @@ -959,7 +959,7 @@ def refresh(self, width, height): if width == self.width and height == self.height: return self.canvas_width = width - self.view.lut.winfo_width() - 24 - self.canvas_height = height - 85 + self.canvas_height = height - 153 self.view.canvas.config(width=self.canvas_width, height=self.canvas_height) self.view.update_idletasks() diff --git a/src/navigate/controller/sub_controllers/histogram.py b/src/navigate/controller/sub_controllers/histogram.py index 86552506f..a7305a23c 100644 --- a/src/navigate/controller/sub_controllers/histogram.py +++ b/src/navigate/controller/sub_controllers/histogram.py @@ -79,39 +79,39 @@ def __init__(self, histogram, parent_controller) -> None: self.x_axis_var = tk.StringVar(value="linear") self.y_axis_var = tk.StringVar(value="linear") - #: tk.Menu: Histogram popup menu - self.menu = tk.Menu(widget, tearoff=0) - self.menu.add_radiobutton( - label="Log X", - variable=self.x_axis_var, - value="log", - command=self.update_scale, - ) - self.menu.add_radiobutton( - label="Linear X", - variable=self.x_axis_var, - value="linear", - command=self.update_scale, - ) - self.menu.add_separator() - self.menu.add_radiobutton( - label="Log Y", - variable=self.y_axis_var, - value="log", - command=self.update_scale, - ) - self.menu.add_radiobutton( - label="Linear Y", - variable=self.y_axis_var, - value="linear", - command=self.update_scale, - ) + # #: tk.Menu: Histogram popup menu + # self.menu = tk.Menu(widget, tearoff=0) + # self.menu.add_radiobutton( + # label="Log X", + # variable=self.x_axis_var, + # value="log", + # command=self.update_scale, + # ) + # self.menu.add_radiobutton( + # label="Linear X", + # variable=self.x_axis_var, + # value="linear", + # command=self.update_scale, + # ) + # self.menu.add_separator() + # self.menu.add_radiobutton( + # label="Log Y", + # variable=self.y_axis_var, + # value="log", + # command=self.update_scale, + # ) + # self.menu.add_radiobutton( + # label="Linear Y", + # variable=self.y_axis_var, + # value="linear", + # command=self.update_scale, + # ) #: bool: Logarithmic X-axis self.log_x = False #: bool: Logarithmic Y-axis - self.log_y = False + self.log_y = True self.populate_histogram(image=np.random.normal(100, 20, 1000)) @@ -143,7 +143,7 @@ def populate_histogram(self, image: np.ndarray) -> None: """ data = image.flatten() self.figure.clear() - self.figure.hist(data, color="black") + self.figure.hist(data, color="black", bins=50) # Limits std_dev = np.std(data) diff --git a/src/navigate/view/main_window_content/display_notebook.py b/src/navigate/view/main_window_content/display_notebook.py index 565a7d2d9..e8e2432a1 100644 --- a/src/navigate/view/main_window_content/display_notebook.py +++ b/src/navigate/view/main_window_content/display_notebook.py @@ -304,7 +304,7 @@ def __init__( self.frame.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) #: tk.Canvas: The canvas for the histogram. - self.canvas = tk.Canvas(self.frame, width=512, height=512 // 5) + self.canvas = tk.Canvas(self.frame, width=512, height=512 // 8) self.canvas.grid(row=0, column=0, sticky=tk.NSEW, padx=5, pady=5) #: matplotlib.figure.Figure: The figure for the histogram. From 13b0cb9176a1cab62b6d2a0340cd1e5e624cf748 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Sun, 13 Oct 2024 08:48:32 -0500 Subject: [PATCH 6/8] Implement CommonMethods for view widgets, fine-tune histogram. May ultimately adjust the position of the histogram to a new tab on the bottom so that it can be built out further in the future. --- .../controller/sub_controllers/acquire_bar.py | 11 +- .../controller/sub_controllers/histogram.py | 118 +++++++++--------- src/navigate/view/custom_widgets/common.py | 84 +++++++++++++ .../main_window_content/display_notebook.py | 37 +----- src/navigate/view/popups/acquire_popup.py | 53 +------- 5 files changed, 155 insertions(+), 148 deletions(-) create mode 100644 src/navigate/view/custom_widgets/common.py diff --git a/src/navigate/controller/sub_controllers/acquire_bar.py b/src/navigate/controller/sub_controllers/acquire_bar.py index 5fc77b29c..917c0fe9b 100644 --- a/src/navigate/controller/sub_controllers/acquire_bar.py +++ b/src/navigate/controller/sub_controllers/acquire_bar.py @@ -44,6 +44,7 @@ p = __name__.split(".")[1] logger = logging.getLogger(p) + class AcquireBarController(GUIController): """Acquire Bar Controller.""" @@ -59,6 +60,9 @@ def __init__(self, view, parent_controller): """ super().__init__(view, parent_controller) + #: AcquirePopUp: Instance of the popup window. + self.acquire_pop = None + #: str: Acquisition image mode. self.mode = "live" @@ -303,8 +307,8 @@ def launch_popup_window(self): self.parent_controller.execute("stop_acquire") elif self.is_save and self.mode != "live": - #: object: Instance of the popup save dialog. self.acquire_pop = AcquirePopUp(self.view) + buttons = self.acquire_pop.get_buttons() widgets = self.acquire_pop.get_widgets() @@ -313,6 +317,9 @@ def launch_popup_window(self): buttons["Done"].config( command=lambda: self.launch_acquisition(self.acquire_pop) ) + self.acquire_pop.popup.bind( + "", lambda e: self.acquire_pop.popup.dismiss() + ) # Configure drop down callbacks, will update save settings when file type is # changed @@ -326,9 +333,7 @@ def launch_popup_window(self): else: self.is_acquiring = True self.view.acquire_btn.configure(state="disabled") - # self.view.pull_down.configure(state="disabled") self.view.pull_down.state(["disabled", "readonly"]) - self.parent_controller.execute("acquire") def update_microscope_mode(self, *args): diff --git a/src/navigate/controller/sub_controllers/histogram.py b/src/navigate/controller/sub_controllers/histogram.py index a7305a23c..e3fbb11e9 100644 --- a/src/navigate/controller/sub_controllers/histogram.py +++ b/src/navigate/controller/sub_controllers/histogram.py @@ -35,9 +35,13 @@ # Third Party Imports import numpy as np +from matplotlib.ticker import FuncFormatter # Local Imports from navigate.controller.sub_controllers.gui import GUIController +from navigate.model.concurrency.concurrency_tools import SharedNDArray +from navigate.view.main_window_content.display_notebook import HistogramFrame + # Logger Setup # p = __name__.split(".")[1] @@ -47,7 +51,7 @@ class HistogramController(GUIController): """Histogram controller""" - def __init__(self, histogram, parent_controller) -> None: + def __init__(self, histogram: HistogramFrame, parent_controller) -> None: """Initialize the histogram controller Parameters @@ -65,7 +69,10 @@ def __init__(self, histogram, parent_controller) -> None: self.parent_controller = parent_controller #: FigureBase: The histogram figure. - self.figure = self.histogram.figure.add_subplot(111) + self.ax = self.histogram.figure.add_axes([0.075, 0.25, 0.88, 0.65]) + self.ax.tick_params( + axis="both", which="both", direction="inout", labelsize=8, reset=True + ) # Event Bindings widget = self.histogram.figure_canvas.get_tk_widget() @@ -77,35 +84,35 @@ def __init__(self, histogram, parent_controller) -> None: # Default axis values self.x_axis_var = tk.StringVar(value="linear") - self.y_axis_var = tk.StringVar(value="linear") - - # #: tk.Menu: Histogram popup menu - # self.menu = tk.Menu(widget, tearoff=0) - # self.menu.add_radiobutton( - # label="Log X", - # variable=self.x_axis_var, - # value="log", - # command=self.update_scale, - # ) - # self.menu.add_radiobutton( - # label="Linear X", - # variable=self.x_axis_var, - # value="linear", - # command=self.update_scale, - # ) - # self.menu.add_separator() - # self.menu.add_radiobutton( - # label="Log Y", - # variable=self.y_axis_var, - # value="log", - # command=self.update_scale, - # ) - # self.menu.add_radiobutton( - # label="Linear Y", - # variable=self.y_axis_var, - # value="linear", - # command=self.update_scale, - # ) + self.y_axis_var = tk.StringVar(value="log") + + #: tk.Menu: Histogram popup menu + self.menu = tk.Menu(widget, tearoff=0) + self.menu.add_radiobutton( + label="Log X", + variable=self.x_axis_var, + value="log", + command=self.update_scale, + ) + self.menu.add_radiobutton( + label="Linear X", + variable=self.x_axis_var, + value="linear", + command=self.update_scale, + ) + self.menu.add_separator() + self.menu.add_radiobutton( + label="Log Y", + variable=self.y_axis_var, + value="log", + command=self.update_scale, + ) + self.menu.add_radiobutton( + label="Linear Y", + variable=self.y_axis_var, + value="linear", + command=self.update_scale, + ) #: bool: Logarithmic X-axis self.log_x = False @@ -113,8 +120,6 @@ def __init__(self, histogram, parent_controller) -> None: #: bool: Logarithmic Y-axis self.log_y = True - self.populate_histogram(image=np.random.normal(100, 20, 1000)) - def update_scale(self) -> None: """Update the scale of the histogram""" self.log_x = self.x_axis_var.get() == "log" @@ -133,47 +138,36 @@ def histogram_popup(self, event: tk.Event) -> None: finally: self.menu.grab_release() - def populate_histogram(self, image: np.ndarray) -> None: + def populate_histogram(self, image: SharedNDArray) -> None: """Populate the histogram. Parameters ---------- - image : np.ndarray + image : SharedNDArray Image data """ data = image.flatten() - self.figure.clear() - self.figure.hist(data, color="black", bins=50) + self.ax.cla() + counts, _, _ = self.ax.hist(data, bins=20, color="black", rwidth=1) - # Limits - std_dev = np.std(data) - xmin, xmax = np.min(data) - std_dev, np.max(data) + std_dev - xmin = 0 if xmin < 0 else xmin - - # Tick marks. - num_ticks = 5 - if self.log_x: - ticks = np.log10(np.logspace(np.log10(xmin), np.log10(xmax), num_ticks)) - self.figure.set_xscale("log", nonpositive="clip", subs=[]) - self.figure.set_xticks(ticks) - self.figure.set_xticklabels( - [f"{10**tick:.2f}" for tick in ticks], fontsize=6 - ) - else: - ticks = np.linspace(xmin, xmax, num_ticks) - self.figure.set_xticks(ticks) - self.figure.set_xticklabels([f"{tick:.2f}" for tick in ticks], fontsize=6) + x_maximum = np.max(data) + np.std(data) + x_minimum = np.min(data) - np.std(data) + x_minimum = 1 if x_minimum < 1 else x_minimum + y_maximum = 10**6 - self.figure.set_xlim([xmin, xmax]) - self.figure.set_xlabel("", fontsize=4) + self.ax.set_xlim(x_minimum, x_maximum) + self.ax.set_ylim(1, y_maximum) - # Y-axis if self.log_y: - self.figure.set_yscale("log") + self.ax.set_yscale("log") - self.figure.set_ylabel("", fontsize=6) - self.figure.set_yticks([]) + if self.log_x: + self.ax.set_xscale("log") - # Draw the figure + self.ax.yaxis.set_major_formatter( + FuncFormatter( + lambda val, pos: f"$10^{{{int(np.log10(val))}}}$" if val > 0 else "" + ) + ) self.histogram.figure_canvas.draw() diff --git a/src/navigate/view/custom_widgets/common.py b/src/navigate/view/custom_widgets/common.py new file mode 100644 index 000000000..ea41cb0bd --- /dev/null +++ b/src/navigate/view/custom_widgets/common.py @@ -0,0 +1,84 @@ +# Copyright (c) 2021-2024 The University of Texas Southwestern Medical Center. +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted for academic and research use only (subject to the +# limitations in the disclaimer below) provided that the following conditions are met: + +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. + +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# * Neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. + +# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY +# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# Standard Library Imports +from typing import Dict, Any +from tkinter import ttk + +# Third Party Imports + +# Local Imports + + +class CommonMethods: + """This class is a collection of common methods for handling variables, widgets, + and buttons. + """ + + def get_variables(self) -> Dict[str, Any]: + """This function returns a dictionary of all the variables that are tied to + each widget name. + + The key is the widget name, value is the variable associated. + + Returns + ------- + variables : dict + The dictionary that holds the variables. + """ + variables = {} + for key, widget in self.inputs.items(): + variables[key] = widget.get() + return variables + + def get_widgets(self) -> Dict[str, Any]: + """This function returns the dictionary that holds the widgets. + + The key is the widget name, value is the LabelInput class that has all the data. + + Returns + ------- + widgets : dict + The dictionary that holds the widgets. + """ + return self.inputs + + def get_buttons(self) -> Dict[str, ttk.Button]: + """Get the buttons of the popup + + This function returns the dictionary that holds the buttons. + The key is the button name, value is the button. + + Returns + ------- + buttons : Dict[str, ttk.Button] + Dictionary of all the buttons + """ + return self.buttons diff --git a/src/navigate/view/main_window_content/display_notebook.py b/src/navigate/view/main_window_content/display_notebook.py index e8e2432a1..c62a3d177 100644 --- a/src/navigate/view/main_window_content/display_notebook.py +++ b/src/navigate/view/main_window_content/display_notebook.py @@ -43,44 +43,13 @@ # Local Imports from navigate.view.custom_widgets.DockableNotebook import DockableNotebook from navigate.view.custom_widgets.LabelInputWidgetFactory import LabelInput +from navigate.view.custom_widgets.common import CommonMethods # Logger Setup p = __name__.split(".")[1] logger = logging.getLogger(p) -class CommonMethods: - """This class is a collection of common methods.""" - - def get_variables(self) -> Dict[str, Any]: - """This function returns a dictionary of all the variables that are tied to - each widget name. - - The key is the widget name, value is the variable associated. - - Returns - ------- - variables : dict - The dictionary that holds the variables. - """ - variables = {} - for key, widget in self.inputs.items(): - variables[key] = widget.get() - return variables - - def get_widgets(self) -> Dict[str, Any]: - """This function returns the dictionary that holds the widgets. - - The key is the widget name, value is the LabelInput class that has all the data. - - Returns - ------- - widgets : dict - The dictionary that holds the widgets. - """ - return self.inputs - - class CameraNotebook(DockableNotebook): """This class is the notebook that holds the camera view and waveform settings tabs.""" @@ -304,11 +273,11 @@ def __init__( self.frame.grid(row=4, column=0, sticky=tk.NSEW, padx=5, pady=5) #: tk.Canvas: The canvas for the histogram. - self.canvas = tk.Canvas(self.frame, width=512, height=512 // 8) + self.canvas = tk.Canvas(self.frame, width=512, height=512 // 6) self.canvas.grid(row=0, column=0, sticky=tk.NSEW, padx=5, pady=5) #: matplotlib.figure.Figure: The figure for the histogram. - self.figure = Figure(figsize=(3, 1), tight_layout=True) + self.figure = Figure(figsize=(3, 1)) #: FigureCanvasTkAgg: The canvas for the histogram. self.figure_canvas = FigureCanvasTkAgg(self.figure, self.frame) diff --git a/src/navigate/view/popups/acquire_popup.py b/src/navigate/view/popups/acquire_popup.py index ba1977557..e5f7dbd12 100644 --- a/src/navigate/view/popups/acquire_popup.py +++ b/src/navigate/view/popups/acquire_popup.py @@ -44,13 +44,14 @@ from navigate.view.custom_widgets.LabelInputWidgetFactory import LabelInput from navigate.view.custom_widgets.validation import ValidatedCombobox from navigate.model.data_sources import FILE_TYPES +from navigate.view.custom_widgets.common import CommonMethods # Logger Setup p = __name__.split(".")[1] logger = logging.getLogger(p) -class AcquirePopUp: +class AcquirePopUp(CommonMethods): """Class creates the popup that is generated when the Acquire button is pressed and Save File checkbox is selected.""" @@ -123,8 +124,6 @@ def __init__(self, root, *args, **kwargs): "File Type", "Notes", ] - max_length = max([len(s) for s in entry_labels]) - # TODO: Make labels have equal spacing. # Loop for each entry and label for i in range(len(entry_names)): @@ -141,7 +140,7 @@ def __init__(self, root, *args, **kwargs): label=entry_labels[i], input_class=ValidatedCombobox, input_var=tk.StringVar(), - label_args={"padding": [0, 0, 30, 0]} + label_args={"padding": [0, 0, 30, 0]}, ) self.inputs[entry_names[i]].set_values(tuple(FILE_TYPES)) self.inputs[entry_names[i]].set("TIFF") @@ -152,7 +151,7 @@ def __init__(self, root, *args, **kwargs): label=entry_labels[i], input_class=ValidatedCombobox, input_var=tk.StringVar(), - label_args={"padding": [0, 0, 36, 0]} + label_args={"padding": [0, 0, 36, 0]}, ) self.inputs[entry_names[i]].set_values( ("BABB", "Water", "CUBIC", "CLARITY", "uDISCO", "eFLASH") @@ -186,47 +185,3 @@ def __init__(self, root, *args, **kwargs): self.buttons["Done"] = ttk.Button(content_frame, text="Acquire Data") self.buttons["Done"].grid(row=10, column=1, padx=(0, 5), sticky=tk.NSEW) - - def get_variables(self): - """Get the variables of the popup - - This function returns a dictionary of all the variables that are tied to each - widget name. - - The key is the widget name, value is the variable associated. - - Returns - ------- - dict - Dictionary of all the variables - """ - variables = {} - for key, widget in self.inputs.items(): - variables[key] = widget.get() - return variables - - def get_widgets(self): - """Get the widgets of the popup - - This function returns the dictionary that holds the input widgets. - The key is the widget name, value is the LabelInput class that has all the data. - - Returns - ------- - dict - Dictionary of all the widgets - """ - return self.inputs - - def get_buttons(self): - """Get the buttons of the popup - - This function returns the dictionary that holds the buttons. - The key is the button name, value is the button. - - Returns - ------- - dict - Dictionary of all the buttons - """ - return self.buttons From 1fd59e06bd79169193b9ebddeb3d9492c6e04197 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:52:50 -0500 Subject: [PATCH 7/8] Revert to original location, numpydoc. --- .../controller/sub_controllers/acquire_bar.py | 57 ++++++++++++------- .../controller/sub_controllers/gui.py | 5 +- .../controller/sub_controllers/histogram.py | 10 ++-- .../controller/sub_controllers/keystrokes.py | 43 +++++++------- src/navigate/view/main_application_window.py | 2 +- 5 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/navigate/controller/sub_controllers/acquire_bar.py b/src/navigate/controller/sub_controllers/acquire_bar.py index 917c0fe9b..d8b6bf568 100644 --- a/src/navigate/controller/sub_controllers/acquire_bar.py +++ b/src/navigate/controller/sub_controllers/acquire_bar.py @@ -32,13 +32,17 @@ # Standard Library Imports import logging +import tkinter as tk from tkinter import messagebox +from typing import Dict, Any, Iterable # Third Party Imports # Local Imports from navigate.controller.sub_controllers.gui import GUIController from navigate.view.popups.acquire_popup import AcquirePopUp +from navigate.view.main_window_content.acquire_notebook import AcquireBar + # Logger Setup p = __name__.split(".")[1] @@ -48,14 +52,14 @@ class AcquireBarController(GUIController): """Acquire Bar Controller.""" - def __init__(self, view, parent_controller): + def __init__(self, view: AcquireBar, parent_controller: Any) -> None: """Initialize the Acquire Bar Controller. Parameters ---------- - view : object + view : AcquireBar Instance of the View. - parent_controller : object + parent_controller : Any Instance of the Main Controller. """ super().__init__(view, parent_controller) @@ -63,6 +67,9 @@ def __init__(self, view, parent_controller): #: AcquirePopUp: Instance of the popup window. self.acquire_pop = None + #: dict: Saving settings. + self.saving_settings = None + #: str: Acquisition image mode. self.mode = "live" @@ -92,7 +99,13 @@ def __init__(self, view, parent_controller): # framerate information. self.framerate = 0 - def progress_bar(self, images_received, microscope_state, mode, stop=False): + def progress_bar( + self, + images_received: int, + microscope_state: Dict[str, Any], + mode: str, + stop=False, + ): """Update progress bars. Parameters @@ -201,12 +214,12 @@ def progress_bar(self, images_received, microscope_state, mode, stop=False): self.update_progress_label(seconds_left=0) self.stop_progress_bar() - def stop_progress_bar(self): + def stop_progress_bar(self) -> None: """Stop moving the continuous progress bar.""" self.view.CurAcq.stop() self.view.OvrAcq.stop() - def update_progress_label(self, seconds_left): + def update_progress_label(self, seconds_left: int) -> None: """Update the progress label in the Acquire Bar. Formatted time is in HH:MM:SS. @@ -222,7 +235,7 @@ def update_progress_label(self, seconds_left): text=f"{int(hours):02}" f":{int(minutes):02}" f":{int(seconds):02}" ) - def set_mode(self, mode): + def set_mode(self, mode: str) -> None: """Set imaging mode. Parameters @@ -241,7 +254,7 @@ def set_mode(self, mode): ] = mode self.show_verbose_info("Image mode is set to", mode) - def get_mode(self): + def get_mode(self) -> str: """Get the current imaging mode. Returns @@ -251,7 +264,7 @@ def get_mode(self): """ return self.mode - def add_mode(self, mode): + def add_mode(self, mode: str) -> None: """Add a new mode to the mode dictionary. Parameters @@ -263,7 +276,7 @@ def add_mode(self, mode): self.mode_dict[mode] = mode self.view.pull_down["values"] = list(self.mode_dict.keys()) - def stop_acquire(self): + def stop_acquire(self) -> None: """Stop the acquisition. Stop the progress bar, set the acquire button back to "Acquire", place pull @@ -275,7 +288,7 @@ def stop_acquire(self): self.view.pull_down.state(["!disabled", "readonly"]) self.is_acquiring = False - def set_save_option(self, is_save): + def set_save_option(self, is_save: bool) -> None: """Set whether the image will be saved. Parameters @@ -289,7 +302,7 @@ def set_save_option(self, is_save): ] = is_save self.show_verbose_info("set save data option:", is_save) - def launch_popup_window(self): + def launch_popup_window(self) -> None: """Launch the Save Dialog Popup Window The popup window should only be launched if the microscope is set to save the @@ -336,7 +349,7 @@ def launch_popup_window(self): self.view.pull_down.state(["disabled", "readonly"]) self.parent_controller.execute("acquire") - def update_microscope_mode(self, *args): + def update_microscope_mode(self, *args: Iterable) -> None: """Gets the state of the pull-down menu and tells the central controller Will additionally call functions to disable and enable widgets based on mode @@ -355,17 +368,17 @@ def update_microscope_mode(self, *args): # Update state status of other widgets in the GUI based on what mode is set self.parent_controller.channels_tab_controller.set_mode("stop") - def update_file_type(self, file_type): - """Updates the file type when the drop down in save dialog is changed. + def update_file_type(self, file_type: tk.StringVar) -> None: + """Updates the file type when the drop-down in save dialog is changed. Parameters ---------- - file_type : str + file_type : tk.StringVar File type. """ self.saving_settings["file_type"] = file_type.get() - def launch_acquisition(self, popup_window): + def launch_acquisition(self, popup_window: AcquirePopUp) -> None: """Launch the Acquisition. Once the popup window has been filled out, we first create the save path using @@ -376,7 +389,7 @@ def launch_acquisition(self, popup_window): Parameters ---------- - popup_window : object + popup_window : AcquirePopUp Instance of the popup save dialog. """ # update saving settings according to user's input @@ -398,14 +411,14 @@ def launch_acquisition(self, popup_window): # tell central controller, save the image/data self.parent_controller.execute("acquire_and_save") - def exit_program(self): + def exit_program(self) -> None: """Exit Button to close the program.""" if messagebox.askyesno("Exit", "Are you sure?"): self.show_verbose_info("Exiting Program") # call the central controller to stop all the threads self.parent_controller.execute("exit") - def populate_experiment_values(self): + def populate_experiment_values(self) -> None: """Populate the experiment values from the config file.""" #: dict: Saving settings. self.saving_settings = self.parent_controller.configuration["experiment"][ @@ -421,13 +434,13 @@ def populate_experiment_values(self): ] self.set_save_option(is_save) - def update_experiment_values(self, popup_window): + def update_experiment_values(self, popup_window: AcquirePopUp) -> None: """Gets the entries from the popup save dialog and overwrites the saving_settings dictionary. Parameters ---------- - popup_window : object + popup_window : AcquirePopUp Instance of the popup save dialog. """ popup_vals = popup_window.get_variables() diff --git a/src/navigate/controller/sub_controllers/gui.py b/src/navigate/controller/sub_controllers/gui.py index 7bf928fa9..81c9f7268 100644 --- a/src/navigate/controller/sub_controllers/gui.py +++ b/src/navigate/controller/sub_controllers/gui.py @@ -32,6 +32,7 @@ # Standard Library Imports import logging +from typing import Any # Third Party Imports @@ -45,12 +46,12 @@ class GUIController: """Base class for GUI controllers""" - def __init__(self, view, parent_controller=None): + def __init__(self, view: Any, parent_controller=None) -> None: """Initializes GUI controller Parameters ---------- - view : tkinter.Tk + view : Any GUI view parent_controller : Controller parent controller diff --git a/src/navigate/controller/sub_controllers/histogram.py b/src/navigate/controller/sub_controllers/histogram.py index e3fbb11e9..a6bc094c1 100644 --- a/src/navigate/controller/sub_controllers/histogram.py +++ b/src/navigate/controller/sub_controllers/histogram.py @@ -32,13 +32,13 @@ # Standard Library Imports import platform import tkinter as tk +from typing import Any # Third Party Imports import numpy as np from matplotlib.ticker import FuncFormatter # Local Imports -from navigate.controller.sub_controllers.gui import GUIController from navigate.model.concurrency.concurrency_tools import SharedNDArray from navigate.view.main_window_content.display_notebook import HistogramFrame @@ -48,18 +48,18 @@ # logger = logging.getLogger(p) -class HistogramController(GUIController): +class HistogramController: """Histogram controller""" - def __init__(self, histogram: HistogramFrame, parent_controller) -> None: + def __init__(self, histogram: HistogramFrame, parent_controller: Any) -> None: """Initialize the histogram controller Parameters ---------- histogram : HistogramFrame Histogram view - parent_controller : MainController - Main controller + parent_controller : Any + Main controller. """ #: HistogramFrame: Histogram view diff --git a/src/navigate/controller/sub_controllers/keystrokes.py b/src/navigate/controller/sub_controllers/keystrokes.py index 6f3150ea8..8d3315def 100644 --- a/src/navigate/controller/sub_controllers/keystrokes.py +++ b/src/navigate/controller/sub_controllers/keystrokes.py @@ -33,12 +33,15 @@ # Standard Library Imports import logging import platform +import tkinter +from typing import Any # Third Party Imports # Local Imports from navigate.controller.sub_controllers.gui import GUIController from navigate.view.custom_widgets.validation import ValidatedEntry, ValidatedSpinbox +from navigate.view.main_application_window import MainApp # Logger Setup p = __name__.split(".")[1] @@ -48,28 +51,28 @@ class KeystrokeController(GUIController): """Keystroke controller""" - def __init__(self, main_view, parent_controller): + def __init__(self, main_view: MainApp, parent_controller: Any) -> None: """Initialize the keystroke controller Parameters ---------- - main_view : MainView + main_view : MainApp Main view - parent_controller : MainController + parent_controller : Any Main controller """ super().__init__(main_view, parent_controller) - #: tk.Notebook: Main tabs + #: SettingsNotebook: Settings Notebook self.main_tabs = main_view.settings #: CameraViewController: Camera View Controller self.camera_controller = parent_controller.camera_view_controller - #: MIPSettingController: MIP Setting Controller + #: MIPViewController: MIP Setting Controller self.mip_controller = parent_controller.mip_setting_controller - #: MultipositionTableController: Multiposition Table Controller + #: MultiPositionController: Multiposition Table Controller self.multi_controller = parent_controller.multiposition_tab_controller #: StageController: Stage Controller @@ -128,12 +131,12 @@ def __init__(self, main_view, parent_controller): "", self.multi_controller.handle_double_click ) - def camera_controller_mouse_wheel_enter(self, event): + def camera_controller_mouse_wheel_enter(self, event: tkinter.Event) -> None: """Mouse wheel binding for camera view Parameters ---------- - event : tkinter event + event : tkinter.Event Mouse wheel event """ self.view.root.unbind("") # get rid of scrollbar mousewheel @@ -149,12 +152,12 @@ def camera_controller_mouse_wheel_enter(self, event): "", self.camera_controller.mouse_wheel ) - def mip_controller_mouse_wheel_enter(self, event): + def mip_controller_mouse_wheel_enter(self, event: tkinter.Event) -> None: """Mouse wheel binding for MIP view Parameters ---------- - event : tkinter event + event : tkinter.Event Mouse wheel event """ self.view.root.unbind("") @@ -164,12 +167,12 @@ def mip_controller_mouse_wheel_enter(self, event): self.mip_view.canvas.bind("", self.mip_controller.mouse_wheel) self.mip_view.canvas.bind("", self.mip_controller.mouse_wheel) - def camera_controller_mouse_wheel_leave(self, event): + def camera_controller_mouse_wheel_leave(self, event: tkinter.Event) -> None: """Mouse wheel binding for camera view Parameters ---------- - event : tkinter event + event : tKinter.Event Mouse wheel event """ @@ -182,12 +185,12 @@ def camera_controller_mouse_wheel_leave(self, event): "", self.view.scroll_frame.mouse_wheel ) # reinstate scrollbar mousewheel - def mip_controller_mouse_wheel_leave(self, event): + def mip_controller_mouse_wheel_leave(self, event: tkinter.Event) -> None: """Mouse wheel binding for MIP view Parameters ---------- - event : tkinter event + event : tkinter.Event Mouse wheel event """ @@ -198,12 +201,12 @@ def mip_controller_mouse_wheel_leave(self, event): self.mip_view.canvas.unbind("") self.view.root.bind("", self.view.scroll_frame.mouse_wheel) - def switch_tab(self, event): + def switch_tab(self, event: tkinter.Event) -> None: """Switches between tabs Parameters ---------- - event : tkinter event + event : tkinter.Event Tab key event """ @@ -211,12 +214,12 @@ def switch_tab(self, event): if (key_val > 0) and (self.main_tabs.index("end") >= key_val): self.main_tabs.select(key_val - 1) - def widget_undo(self, event): + def widget_undo(self, event: tkinter.Event) -> None: """Undo widget changes Parameters ---------- - event : tkinter event + event : tkinter.Event Undo key event """ if isinstance(event.widget, ValidatedEntry) or isinstance( @@ -224,12 +227,12 @@ def widget_undo(self, event): ): # Add all widgets that you want to be able to undo here event.widget.undo(event) - def widget_redo(self, event): + def widget_redo(self, event: tkinter.Event) -> None: """Redo widget changes Parameters ---------- - event : tkinter event + event : tkinter.Event Redo key event """ diff --git a/src/navigate/view/main_application_window.py b/src/navigate/view/main_application_window.py index 950da0242..7e89278d2 100644 --- a/src/navigate/view/main_application_window.py +++ b/src/navigate/view/main_application_window.py @@ -63,7 +63,7 @@ class MainApp(ttk.Frame): Finally, it uses the notebook classes to put them into the respective frames on the tk.Grid. Each of the notebook classes includes tab classes and inits those etc. - The second parameter in each classes __init__ function is the parent. + The second parameter in each class __init__ function is the parent. I used the name of the parent so that it would be easier to keep track of inheritances. From 047a84df93a85c2185cad63dc5cfb82c82c32fc5 Mon Sep 17 00:00:00 2001 From: Annie Wang Date: Wed, 16 Oct 2024 09:33:43 -0700 Subject: [PATCH 8/8] minor tweaks --- src/navigate/controller/sub_controllers/camera_view.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/navigate/controller/sub_controllers/camera_view.py b/src/navigate/controller/sub_controllers/camera_view.py index 87b881b14..8f48d40e0 100644 --- a/src/navigate/controller/sub_controllers/camera_view.py +++ b/src/navigate/controller/sub_controllers/camera_view.py @@ -101,7 +101,7 @@ def __init__(self, view, parent_controller=None): Parameters ---------- - view : self.view.camera_waveform.camera_tab + view : tkinter.Frame The tkinter frame that contains the widgets. parent_controller : Controller The parent controller of the camera view controller. @@ -1037,8 +1037,8 @@ def __init__(self, view, parent_controller=None): Parameters ---------- - view : self.view.camera_waveform.camera_tab - The tkinter frame that contains the widgets. + view : CameraTab + The Camera tkinter frame that contains the widgets. parent_controller : Controller The parent controller of the camera view controller. """ @@ -1390,8 +1390,8 @@ def __init__(self, view, parent_controller=None): Parameters ---------- - view : tkinter.Frame - The tkinter frame that contains the widgets. + view : MIPTab + The MIP tkinter frame that contains the widgets. parent_controller : Controller The parent controller of the camera view controller. """