magscope.ui
===========

.. py:module:: magscope.ui


Submodules
----------

.. toctree::
   :maxdepth: 1

   /api/magscope/ui/auto_bead_selection_dialog/index
   /api/magscope/ui/controls/index
   /api/magscope/ui/panel_layout/index
   /api/magscope/ui/plots/index
   /api/magscope/ui/search/index
   /api/magscope/ui/theme/index
   /api/magscope/ui/ui/index
   /api/magscope/ui/video_viewer/index
   /api/magscope/ui/widgets/index


Classes
-------

.. autoapisummary::

   magscope.ui.VideoViewer
   magscope.ui.PlotWorker
   magscope.ui.TimeSeriesPlotBase
   magscope.ui.BeadGraphic
   magscope.ui.CollapsibleGroupBox
   magscope.ui.GripSplitter
   magscope.ui.LabeledCheckbox
   magscope.ui.LabeledLineEdit
   magscope.ui.LabeledLineEditWithValue
   magscope.ui.LabeledStepperLineEdit
   magscope.ui.ResizableLabel
   magscope.ui.AcquisitionPanel
   magscope.ui.AllanDeviationPanel
   magscope.ui.BeadSelectionPanel
   magscope.ui.CameraPanel
   magscope.ui.ControlPanelBase
   magscope.ui.HistogramPanel
   magscope.ui.PlotSettingsPanel
   magscope.ui.PreferencesDialog
   magscope.ui.ScriptPanel
   magscope.ui.StatusPanel
   magscope.ui.TrackingOptionsPanel
   magscope.ui.MagScopeSettingsPanel
   magscope.ui.Controls
   magscope.ui.UIManager


Package Contents
----------------

.. py:class:: VideoViewer(scale_factor=1.25)

   Bases: :py:obj:`PyQt6.QtWidgets.QGraphicsView`


   .. py:attribute:: coordinatesChanged
      :type:  PyQt6.QtCore.pyqtSignal


   .. py:attribute:: clicked
      :type:  PyQt6.QtCore.pyqtSignal


   .. py:attribute:: sceneClicked
      :type:  PyQt6.QtCore.pyqtSignal


   .. py:attribute:: _MINIMAP_MARGIN
      :value: 12



   .. py:attribute:: _MINIMAP_MIN_SIZE
      :value: 120



   .. py:attribute:: _MINIMAP_MAX_SIZE
      :value: 220



   .. py:attribute:: _MINIMAP_LABEL_SPACING
      :value: 6



   .. py:attribute:: _MINIMAP_ZOOM_HEIGHT
      :value: 26



   .. py:attribute:: _MINIMAP_BUTTON_SPACING
      :value: 6



   .. py:attribute:: _mouse_start_pos


   .. py:attribute:: _mouse_start_time
      :value: 0.0



   .. py:attribute:: _zoom
      :value: 0



   .. py:attribute:: scale_factor
      :value: 1.25



   .. py:attribute:: _empty
      :value: True



   .. py:attribute:: scene


   .. py:attribute:: _image


   .. py:attribute:: _overlay_entries
      :type:  list[tuple[PyQt6.QtCore.QRectF, PyQt6.QtCore.QPointF, str, bool, str]]
      :value: []



   .. py:attribute:: _visible_overlay_entries
      :type:  list[tuple[PyQt6.QtCore.QRectF, str, bool]] | None
      :value: None



   .. py:attribute:: _visible_label_entries
      :type:  list[tuple[PyQt6.QtCore.QPointF, PyQt6.QtGui.QStaticText, bool]] | None
      :value: None



   .. py:attribute:: _overlay_cache_pixmap


   .. py:attribute:: _overlay_cache_dirty
      :value: True



   .. py:attribute:: _overlay_cache_size


   .. py:attribute:: _overlay_cache_device_pixel_ratio
      :value: 0.0



   .. py:attribute:: _static_label_cache
      :type:  dict[str, PyQt6.QtGui.QStaticText]


   .. py:attribute:: _label_metrics


   .. py:attribute:: _label_ascent


   .. py:attribute:: _marker_x


   .. py:attribute:: _marker_y


   .. py:attribute:: _marker_size
      :value: 0



   .. py:attribute:: _minimap_label


   .. py:attribute:: _minimap_zoom_label


   .. py:attribute:: _minimap_reset_button


   .. py:attribute:: _minimap_base


   .. py:attribute:: _fit_scale
      :value: 1.0



   .. py:method:: set_bead_overlay(bead_rois: dict[int, tuple[int, int, int, int]], active_bead_id: int | None, selected_bead_id: int | None, reference_bead_id: int | None, label_overrides: dict[int, str] | None = None, state_overrides: dict[int, str] | None = None) -> None


   .. py:method:: _invalidate_overlay_view_cache() -> None


   .. py:method:: _get_static_label(label_text: str) -> PyQt6.QtGui.QStaticText


   .. py:method:: _rebuild_overlay_view_cache() -> None


   .. py:method:: _rebuild_overlay_cache_pixmap() -> None


   .. py:method:: _ensure_overlay_cache_pixmap() -> None


   .. py:method:: plot(x, y, size)


   .. py:method:: clear_crosshairs()


   .. py:method:: set_image_to_default()


   .. py:method:: has_image()


   .. py:method:: image_scene_rect() -> PyQt6.QtCore.QRectF


   .. py:method:: reset_view(scale=1)


   .. py:method:: clear_image()


   .. py:method:: set_pixmap(pixmap)


   .. py:method:: zoom_level()


   .. py:method:: zoom(step)


   .. py:method:: wheelEvent(event)


   .. py:method:: resizeEvent(event)


   .. py:method:: toggle_drag_mode()


   .. py:method:: update_coordinates(pos=None)


   .. py:method:: mouseMoveEvent(event)


   .. py:method:: leaveEvent(event)


   .. py:method:: mousePressEvent(event)


   .. py:method:: mouseReleaseEvent(event)


   .. py:method:: scrollContentsBy(dx, dy)


   .. py:method:: _refresh_minimap()


   .. py:method:: _layout_minimap()


   .. py:method:: _compute_highlight_rect(scaled_size, offset_x, offset_y)


   .. py:method:: _current_zoom_percent()


   .. py:method:: drawForeground(painter, rect)


.. py:class:: PlotWorker

   Bases: :py:obj:`PyQt6.QtCore.QObject`


   .. py:attribute:: image_signal


   .. py:attribute:: limits_signal


   .. py:attribute:: selected_bead_signal


   .. py:attribute:: reference_bead_signal


   .. py:attribute:: stop_signal


   .. py:attribute:: figure_size_signal


   .. py:attribute:: time_mode_signal


   .. py:attribute:: relative_window_signal


   .. py:attribute:: axes
      :type:  matplotlib.axes.Axes


   .. py:attribute:: locks
      :type:  dict[str, multiprocessing.synchronize.Lock]


   .. py:attribute:: figure
      :type:  matplotlib.figure.Figure | None
      :value: None



   .. py:attribute:: canvas
      :type:  matplotlib.backends.backend_qtagg.FigureCanvasQTAgg


   .. py:attribute:: _is_running
      :type:  bool
      :value: False



   .. py:attribute:: plots
      :value: []



   .. py:attribute:: limits
      :type:  dict[str, tuple[float, float]]


   .. py:attribute:: selected_bead
      :type:  int | None
      :value: 0



   .. py:attribute:: reference_bead
      :type:  int | None
      :value: None



   .. py:attribute:: n_plots
      :type:  int


   .. py:attribute:: update_on
      :type:  bool
      :value: True



   .. py:attribute:: _update_last_time
      :type:  float


   .. py:attribute:: fig_width
      :value: 5



   .. py:attribute:: fig_height
      :value: 4



   .. py:attribute:: dpi
      :value: 100



   .. py:attribute:: device_pixel_ratio
      :value: 1.0



   .. py:attribute:: time_mode
      :value: 'absolute'



   .. py:attribute:: relative_window_seconds
      :type:  float | None
      :value: 300



   .. py:attribute:: _tracks_snapshot
      :type:  numpy.ndarray | None
      :value: None



   .. py:attribute:: mutex
      :type:  PyQt6.QtCore.QMutex


   .. py:attribute:: figure_size_changed
      :value: True



   .. py:method:: setup()


   .. py:method:: run()


   .. py:method:: do_main_loop()


   .. py:method:: add_plot(plot: TimeSeriesPlotBase)

      Used to add plots before the process has started



   .. py:method:: _set_limits(limits: dict[str, list[float, float]])


   .. py:method:: _set_selected_bead(bead: int)


   .. py:method:: _set_reference_bead(bead: int | None)


   .. py:method:: set_locks(locks: dict[str, multiprocessing.synchronize.Lock])


   .. py:method:: _stop()


   .. py:method:: dispose() -> None


   .. py:method:: _update_figure_size(width: int, height: int, device_pixel_ratio: float)

      Slot: update figure size based on QLabel dimensions.



   .. py:method:: _recreate_figure_if_needed()

      Recreate figure and canvas if size changed.



   .. py:method:: _set_time_mode(time_mode: str)


   .. py:method:: _set_relative_window(window_seconds: float | None)


   .. py:method:: _apply_time_axis_format()


.. py:class:: TimeSeriesPlotBase(buffer_name: str, ylabel: str)

   .. py:attribute:: buffer
      :type:  magscope.datatypes.MatrixBuffer


   .. py:attribute:: buffer_name


   .. py:attribute:: parent
      :type:  PlotWorker


   .. py:attribute:: axes
      :type:  matplotlib.axes.Axes


   .. py:attribute:: ylabel


   .. py:method:: setup()

      Called after the parent process is started



   .. py:method:: set_parent(parent: PlotWorker)


   .. py:method:: set_axes(axes: matplotlib.axes.Axes)


   .. py:method:: update()
      :abstractmethod:



.. py:class:: BeadGraphic(parent: magscope.ui.ui.UIManager, id: int, roi: tuple[int, int, int, int], view_scene)

   Bases: :py:obj:`PyQt6.QtWidgets.QGraphicsRectItem`


   .. py:attribute:: LABEL_FONT


   .. py:attribute:: LABEL_COLOR


   .. py:attribute:: LABEL_OFFSET_X
      :value: 10



   .. py:attribute:: LABEL_OFFSET_Y
      :value: 1



   .. py:attribute:: BORDER_COLOR_DEFAULT
      :value: (0, 255, 255, 255)



   .. py:attribute:: FILL_COLOR_DEFAULT
      :value: None



   .. py:attribute:: BORDER_COLOR_SELECTED
      :value: (255, 0, 0, 255)



   .. py:attribute:: FILL_COLOR_SELECTED
      :value: None



   .. py:attribute:: BORDER_COLOR_REFERENCE
      :value: (0, 255, 0, 255)



   .. py:attribute:: FILL_COLOR_REFERENCE
      :value: None



   .. py:attribute:: HOVER_BORDER_COLOR
      :value: (255, 96, 96, 255)



   .. py:attribute:: DRAG_BORDER_COLOR
      :value: (255, 255, 255, 255)



   .. py:attribute:: IDLE_PEN_WIDTH
      :value: 0



   .. py:attribute:: HOVER_PEN_WIDTH
      :value: 2



   .. py:attribute:: SELECTED_PEN_WIDTH
      :value: 2



   .. py:attribute:: DRAG_PEN_WIDTH
      :value: 3



   .. py:attribute:: CORNER_GRIP_SIZE
      :value: 6.0



   .. py:attribute:: _shared_pens
      :type:  dict[str, PyQt6.QtGui.QPen] | None
      :value: None



   .. py:attribute:: _shared_brushes
      :type:  dict[str, PyQt6.QtGui.QBrush] | None
      :value: None



   .. py:attribute:: _parent
      :type:  magscope.ui.ui.UIManager


   .. py:attribute:: id
      :type:  int


   .. py:attribute:: _initializing
      :type:  bool
      :value: True



   .. py:attribute:: _is_moving
      :type:  bool
      :value: False



   .. py:attribute:: _is_hovered
      :type:  bool
      :value: False



   .. py:attribute:: _locked
      :type:  bool


   .. py:attribute:: _color_state
      :type:  str
      :value: 'default'



   .. py:attribute:: _cached_roi
      :type:  tuple[int, int, int, int] | None
      :value: None



   .. py:attribute:: pen_width
      :value: 0



   .. py:attribute:: label


   .. py:property:: locked


   .. py:attribute:: view_scene


   .. py:method:: remove()


   .. py:method:: roi_from_center(x: float, y: float, width: float) -> tuple[int, int, int, int]
      :classmethod:



   .. py:method:: label_scene_position_for_roi(roi: tuple[int, int, int, int]) -> PyQt6.QtCore.QPointF
      :classmethod:



   .. py:method:: clamp_roi_to_scene(roi: tuple[int, int, int, int], scene_rect: PyQt6.QtCore.QRectF) -> tuple[int, int, int, int]
      :classmethod:



   .. py:method:: move_roi(roi: tuple[int, int, int, int], dx: int, dy: int, scene_rect: PyQt6.QtCore.QRectF) -> tuple[int, int, int, int]
      :classmethod:



   .. py:method:: set_selection_state(state: str)

      Update the bead overlay color to match selection/reference state.



   .. py:method:: _ensure_shared_pens_and_brushes() -> None
      :classmethod:



   .. py:method:: _create_pen(color: tuple[int, int, int, int]) -> PyQt6.QtGui.QPen
      :staticmethod:



   .. py:method:: _create_brush(color: tuple[int, int, int, int] | None) -> PyQt6.QtGui.QBrush
      :staticmethod:



   .. py:method:: _apply_color()


   .. py:method:: _current_visual_state() -> str


   .. py:method:: _current_border_color() -> PyQt6.QtGui.QColor


   .. py:method:: _current_pen_width() -> int


   .. py:method:: _update_cursor() -> None


   .. py:method:: _corner_grip_rects() -> list[PyQt6.QtCore.QRectF]


   .. py:method:: _paint_rect() -> PyQt6.QtCore.QRectF


   .. py:method:: _current_scene_rect() -> PyQt6.QtCore.QRectF


   .. py:method:: paint(painter, option, widget=None)


   .. py:method:: move(dx, dy)


   .. py:method:: validate_move(value)

      Prevents the graphic from moving outside the scene border



   .. py:method:: get_label_scene_position() -> PyQt6.QtCore.QPointF


   .. py:method:: set_roi_bounds(roi: tuple[int, int, int, int]) -> None


   .. py:method:: itemChange(change, value)


   .. py:method:: hoverEnterEvent(event)


   .. py:method:: hoverLeaveEvent(event)


   .. py:method:: mousePressEvent(event)


   .. py:method:: mouseReleaseEvent(event)


   .. py:method:: on_move_completed()


   .. py:method:: get_roi_bounds() -> tuple[int, int, int, int]


   .. py:method:: _update_cached_roi()


.. py:class:: CollapsibleGroupBox(title='', collapsed=False, *, collapsible: bool = True)

   Bases: :py:obj:`PyQt6.QtWidgets.QGroupBox`


   A titled QGroupBox that can optionally collapse its content.


   .. py:attribute:: _DEFAULT_BORDER_COLOR
      :value: 'palette(mid)'



   .. py:attribute:: _DEFAULT_BORDER_WIDTH
      :value: 1



   .. py:attribute:: title
      :value: ''



   .. py:attribute:: collapsible
      :value: True



   .. py:attribute:: default_collapsed
      :value: False



   .. py:attribute:: _border_color
      :value: 'palette(mid)'



   .. py:attribute:: _border_width
      :value: 1



   .. py:attribute:: _settings_key
      :value: '_Group Box Collapsed'



   .. py:attribute:: toggle_button


   .. py:attribute:: drag_handle


   .. py:attribute:: content_area


   .. py:attribute:: animation


   .. py:attribute:: collapsed


   .. py:method:: set_highlight_border(color_name: str | None) -> None


   .. py:method:: _apply_panel_style() -> None


   .. py:method:: _animation_finished() -> None


   .. py:property:: settings_key
      :type: str



   .. py:method:: toggle(checked)


   .. py:method:: reset_to_default() -> None


   .. py:method:: _apply_collapsed_state(collapsed: bool, *, animate: bool, persist: bool) -> None


   .. py:method:: setContentLayout(content_layout)


   .. py:method:: _get_toggle_text(title, expanded, *, collapsible: bool = True)
      :staticmethod:



.. py:class:: GripSplitter(orientation, name=None, parent=None)

   Bases: :py:obj:`PyQt6.QtWidgets.QSplitter`


   Simple class for adding '...' to QSplitter handles.


   .. py:attribute:: name
      :value: None



   .. py:attribute:: shown_once
      :value: False



   .. py:method:: showEvent(e)


   .. py:method:: createHandle()


   .. py:method:: handle_released()


.. py:class:: LabeledCheckbox(*, label_text: str, widths: tuple[int, int] = (0, 0), default=False, callback: callable = None)

   Bases: :py:obj:`PyQt6.QtWidgets.QWidget`


   Horizontally combined QLabel and QCheckbox.


   .. py:attribute:: layout


   .. py:attribute:: label


   .. py:attribute:: checkbox


.. py:class:: LabeledLineEdit(*, label_text: str, widths: tuple[int, int] = (0, 0), default=None, validator: PyQt6.QtGui.QValidator = None, callback: callable = None)

   Bases: :py:obj:`PyQt6.QtWidgets.QWidget`


   Horizontally combined QLabel and QLineedit.


   .. py:attribute:: layout


   .. py:attribute:: label


   .. py:attribute:: lineedit


.. py:class:: LabeledLineEditWithValue(*, label_text: str, validator: PyQt6.QtGui.QValidator = None, widths: tuple[int, int, int] = (0, 0, 0), default=None, callback: callable = None)

   Bases: :py:obj:`PyQt6.QtWidgets.QWidget`


   Horizontally combined QLabel, QLineedit, and a second QLabel to show the value.


   .. py:attribute:: layout


   .. py:attribute:: label


   .. py:attribute:: lineedit


   .. py:attribute:: value_label


.. py:class:: LabeledStepperLineEdit(*, label_text: str, left_button_text: str, right_button_text: str, widths: tuple[int, int, int, int] = (0, 0, 0, 0), default=None, validator: PyQt6.QtGui.QValidator = None, callbacks: tuple[callable, callable, callable] = (None, None, None))

   Bases: :py:obj:`PyQt6.QtWidgets.QWidget`


   Horizontally combined QLabel and QLineedit with a QButton to increment/decrement the value on either side.


   .. py:attribute:: layout


   .. py:attribute:: name_label


   .. py:attribute:: left_button


   .. py:attribute:: lineedit


   .. py:attribute:: right_button


.. py:class:: ResizableLabel(parent=None, *, ignore_pixmap_size_hint: bool = False)

   Bases: :py:obj:`PyQt6.QtWidgets.QLabel`


   Custom QLabel that emits a signal when it's resized.


   .. py:attribute:: resized


   .. py:attribute:: _ignore_pixmap_size_hint
      :value: False



   .. py:method:: sizeHint() -> PyQt6.QtCore.QSize


   .. py:method:: minimumSizeHint() -> PyQt6.QtCore.QSize


   .. py:method:: resizeEvent(event)

      Override resize event to emit signal with new dimensions.



.. py:class:: AcquisitionPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`ControlPanelBase`


   .. py:attribute:: NO_DIRECTORY_SELECTED_TEXT
      :value: 'No save folder selected'



   .. py:attribute:: acquisition_on_checkbox


   .. py:attribute:: acquisition_mode_combobox


   .. py:attribute:: acquisition_dir_on_checkbox


   .. py:attribute:: acquisition_dir_textedit


   .. py:attribute:: acquisition_dir_button


   .. py:method:: set_acquisition_dir_text(path: str | None) -> None


   .. py:method:: callback_acquisition_on()


   .. py:method:: callback_acquisition_dir_on()


   .. py:method:: callback_acquisition_mode()


   .. py:method:: callback_acquisition_dir()


   .. py:method:: update_save_highlight(should_save: bool) -> None


   .. py:method:: search_targets() -> list[magscope.ui.search.SearchTarget]


.. py:class:: AllanDeviationPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`MatplotlibCleanupMixin`, :py:obj:`ControlPanelBase`


   .. py:attribute:: _SETTINGS_GROUP
      :value: 'AllanDeviationPanel'



   .. py:attribute:: refresh_button


   .. py:attribute:: history_window


   .. py:attribute:: history_window_hint


   .. py:attribute:: taus_mode


   .. py:attribute:: figure


   .. py:attribute:: canvas


   .. py:attribute:: axes


   .. py:attribute:: status_label


   .. py:method:: _settings() -> PyQt6.QtCore.QSettings


   .. py:method:: _setting_key(name: str) -> str


   .. py:method:: _load_setting(name: str, default: str) -> str


   .. py:method:: _persist_controls() -> None


   .. py:method:: _configure_axes() -> None


   .. py:method:: refresh_plot() -> None


   .. py:method:: clear(message: str = 'Click Refresh to compute Allan deviation') -> None


   .. py:method:: _parse_window_seconds(value: str) -> float | None
      :staticmethod:



   .. py:method:: _estimate_sampling_rate(timestamps: numpy.ndarray) -> float | None
      :staticmethod:



   .. py:method:: _apply_history_window(timestamps: numpy.ndarray, values: numpy.ndarray, window_seconds: float) -> tuple[numpy.ndarray, numpy.ndarray]
      :staticmethod:



   .. py:method:: _extract_axis_series(tracks: numpy.ndarray, *, axis_name: str, selected_bead: int | None, reference_bead: int | None) -> tuple[numpy.ndarray, numpy.ndarray]
      :staticmethod:



.. py:class:: BeadSelectionPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`ControlPanelBase`


   .. py:attribute:: next_bead_id_label


   .. py:attribute:: reset_id_button


   .. py:attribute:: roi_size_label


   .. py:attribute:: clear_button


   .. py:method:: search_targets() -> list[magscope.ui.search.SearchTarget]


   .. py:method:: update_next_bead_id_label(next_bead_id: int) -> None


.. py:class:: CameraPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`ControlPanelBase`


   .. py:attribute:: _last_settings_update
      :type:  datetime.datetime | None
      :value: None



   .. py:attribute:: settings


   .. py:attribute:: refresh_button


   .. py:attribute:: last_update_label


   .. py:method:: callback_refresh()


   .. py:method:: callback_set_camera_setting(name)


   .. py:method:: update_camera_setting(name: str, value: str)


   .. py:method:: _format_last_update_text() -> str


.. py:class:: ControlPanelBase(manager: magscope.ui.ui.UIManager, title: str, collapsed_by_default: bool = False, collapsible: bool = False)

   Bases: :py:obj:`PyQt6.QtWidgets.QWidget`


   .. py:attribute:: manager
      :type:  magscope.ui.ui.UIManager


   .. py:attribute:: groupbox
      :type:  magscope.ui.widgets.CollapsibleGroupBox | None
      :value: None



   .. py:attribute:: _content_layout
      :type:  PyQt6.QtWidgets.QBoxLayout | None
      :value: None



   .. py:method:: set_title(text: str) -> None


   .. py:method:: setLayout(layout: PyQt6.QtWidgets.QBoxLayout) -> None


   .. py:method:: layout() -> PyQt6.QtWidgets.QBoxLayout


   .. py:method:: set_highlighted(enabled: bool) -> None


.. py:class:: HistogramPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`MatplotlibCleanupMixin`, :py:obj:`ControlPanelBase`


   .. py:attribute:: update_interval
      :type:  float
      :value: 1



   .. py:attribute:: _update_last_time
      :type:  float
      :value: 0



   .. py:attribute:: enable_checkbox


   .. py:attribute:: only_beads_checkbox


   .. py:attribute:: n_bins
      :value: 256



   .. py:attribute:: figure


   .. py:attribute:: canvas


   .. py:attribute:: axes


   .. py:method:: enabled_callback(enabled: bool) -> None


   .. py:method:: _groupbox_toggled(expanded: bool) -> None


   .. py:method:: _apply_enabled_state(enabled: bool) -> None


   .. py:method:: update_plot(data)


   .. py:method:: clear()


.. py:class:: PlotSettingsPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`ControlPanelBase`


   .. py:attribute:: selected_bead


   .. py:attribute:: reference_bead


   .. py:attribute:: limits
      :type:  dict[str, tuple[PyQt6.QtWidgets.QLineEdit, PyQt6.QtWidgets.QLineEdit]]


   .. py:attribute:: grid_layout


   .. py:attribute:: time_mode


   .. py:attribute:: time_label


   .. py:attribute:: time_limits_absolute


   .. py:attribute:: time_relative_window


   .. py:attribute:: time_inputs_stack


   .. py:attribute:: beads_in_view_on


   .. py:attribute:: beads_in_view_count


   .. py:attribute:: beads_in_view_marker_size


   .. py:method:: search_targets() -> list[magscope.ui.search.SearchTarget]


   .. py:method:: selected_bead_callback(value)


   .. py:method:: reference_bead_callback(value)


   .. py:method:: time_mode_callback(value: str)


   .. py:method:: relative_time_window_callback(_value)


   .. py:method:: limits_callback(_)


   .. py:method:: beads_in_view_on_callback()


   .. py:method:: beads_in_view_count_callback()


   .. py:method:: beads_in_view_marker_size_callback()


.. py:class:: PreferencesDialog(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`PyQt6.QtWidgets.QDialog`


   Modal dialog for global MagScope preferences.


   .. py:attribute:: _SIDEBAR_SECTIONS
      :type:  tuple[tuple[str, str], Ellipsis]
      :value: (('tune', 'MagScope'), ('ads_click', 'Tracking'), ('palette', 'Appearance/Layout'))



   .. py:attribute:: manager


   .. py:attribute:: load_preferences_button


   .. py:attribute:: save_preferences_button


   .. py:attribute:: reset_all_preferences_button


   .. py:attribute:: preferences_file_status


   .. py:attribute:: settings_panel


   .. py:attribute:: settings_scroll


   .. py:attribute:: tracking_options_panel


   .. py:attribute:: tracking_scroll


   .. py:attribute:: appearance_layout_tab


   .. py:attribute:: appearance_scroll


   .. py:attribute:: stack


   .. py:attribute:: reset_section_button


   .. py:attribute:: sidebar


   .. py:method:: _sidebar_selection_background(accent: str) -> str
      :staticmethod:



   .. py:method:: _apply_preferences_style(accent: str) -> None


   .. py:method:: _refresh_sidebar_icons(accent: str | None = None) -> None


   .. py:method:: _on_load_preferences_clicked() -> None


   .. py:method:: _on_save_preferences_clicked() -> None


   .. py:method:: _on_reset_all_preferences_clicked() -> None


   .. py:method:: _set_preferences_file_status(message: str) -> None


   .. py:method:: _create_appearance_layout_tab() -> PyQt6.QtWidgets.QWidget


   .. py:method:: _choose_accent_color() -> None


   .. py:method:: _update_accent_color_swatch(color: str) -> None


   .. py:method:: _apply_accent_color_setting() -> None


   .. py:method:: _apply_live_plot_progress_indicator_setting(checked: bool) -> None


   .. py:method:: _scrollable_tab(widget: PyQt6.QtWidgets.QWidget) -> PyQt6.QtWidgets.QScrollArea


   .. py:method:: _make_material_symbol_icon(font: PyQt6.QtGui.QFont, text: str, color: str = '#888888', size: int = 16) -> PyQt6.QtGui.QIcon
      :staticmethod:



   .. py:method:: _on_sidebar_selection(index: int) -> None


   .. py:method:: _on_reset_current_section() -> None


   .. py:method:: _stack_index_for_scroll(scroll: PyQt6.QtWidgets.QScrollArea) -> int


   .. py:method:: reveal_setting(setting_key: str) -> None


   .. py:method:: reveal_magscope_setting(setting_key: str) -> None


   .. py:method:: reveal_tracking_option(widget_attr: str) -> None


   .. py:method:: reveal_widget(tab_name: str, widget_attr: str) -> None


   .. py:method:: _reveal_widget(scroll: PyQt6.QtWidgets.QScrollArea, widget: PyQt6.QtWidgets.QWidget) -> None


   .. py:method:: _on_reset_appearance_tab_clicked() -> None


   .. py:method:: _reset_appearance_layout(*, reset_accent: bool) -> None


.. py:class:: ScriptPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`ControlPanelBase`


   .. py:attribute:: NO_SCRIPT_SELECTED_TEXT
      :value: 'No script loaded'



   .. py:attribute:: status_prefix
      :value: 'Status'



   .. py:attribute:: status_label


   .. py:attribute:: step_position_label


   .. py:attribute:: step_description_label


   .. py:attribute:: button_layout


   .. py:attribute:: load_button


   .. py:attribute:: start_button


   .. py:attribute:: pause_button


   .. py:attribute:: filepath_textedit


   .. py:method:: update_status(status: magscope.scripting.ScriptStatus)


   .. py:method:: update_step(current_step: int | None, total_steps: int, description: str | None)


   .. py:method:: callback_load()


   .. py:method:: callback_start()


   .. py:method:: callback_pause()


.. py:class:: StatusPanel(manager: magscope.ui.ui.UIManager)

   Bases: :py:obj:`ControlPanelBase`


   .. py:attribute:: dot_count
      :value: 0



   .. py:attribute:: display_rate_status


   .. py:attribute:: video_processors_status


   .. py:attribute:: video_buffer_size_status


   .. py:attribute:: video_buffer_status


   .. py:attribute:: video_buffer_status_bar


   .. py:attribute:: video_buffer_purge_label


   .. py:method:: update_display_rate(text)


   .. py:method:: update_video_processors_status(status_text: str)


   .. py:method:: update_video_buffer_status(status_text: str)


   .. py:method:: _update_video_buffer_size_label() -> None


   .. py:method:: update_video_buffer_purge(timestamp: float)


.. py:class:: TrackingOptionsPanel(manager: magscope.ui.ui.UIManager, *, collapsible: bool = True)

   Bases: :py:obj:`ControlPanelBase`


   .. py:attribute:: _current_options
      :type:  dict[str, Any]


   .. py:attribute:: _updating_fields
      :value: False



   .. py:attribute:: background_combo


   .. py:attribute:: iterations


   .. py:attribute:: line_ratio


   .. py:attribute:: n_local


   .. py:attribute:: use_fft


   .. py:attribute:: fft_oversample


   .. py:attribute:: fft_rmin


   .. py:attribute:: fft_rmax


   .. py:attribute:: fft_gaus_factor


   .. py:attribute:: radial_oversample


   .. py:attribute:: lookup_n_local


   .. py:method:: _build_preferences_group(title: str, parent: PyQt6.QtWidgets.QWidget) -> tuple[PyQt6.QtWidgets.QWidget, PyQt6.QtWidgets.QVBoxLayout]
      :staticmethod:



   .. py:method:: _configure_lineedit_row(widget: magscope.ui.widgets.LabeledLineEditWithValue) -> None
      :staticmethod:



   .. py:method:: _configure_checkbox_row(widget: magscope.ui.widgets.LabeledCheckbox) -> None
      :staticmethod:



   .. py:method:: search_targets() -> list[magscope.ui.search.SearchTarget]
      :staticmethod:



   .. py:method:: _option_line_edits() -> tuple[magscope.ui.widgets.LabeledLineEditWithValue, Ellipsis]


   .. py:method:: _update_value_labels() -> None


   .. py:method:: _sync_fft_enabled_state() -> None


   .. py:method:: _use_fft_changed(value: bool) -> None


   .. py:method:: _set_options(options: dict[str, Any], message: str | None = None, *, populate_inputs: bool = False) -> None


   .. py:method:: _populate_inputs_from_options() -> None


   .. py:method:: _load_options_from_mapping(loaded: Any) -> dict[str, Any]


   .. py:method:: _options_from_inputs() -> dict[str, Any]


   .. py:method:: _apply_options_from_inputs() -> None


   .. py:method:: reset_defaults() -> None


.. py:class:: MagScopeSettingsPanel(manager: magscope.ui.ui.UIManager, *, collapsible: bool = True, file_status_label: PyQt6.QtWidgets.QLabel | None = None)

   Bases: :py:obj:`PyQt6.QtWidgets.QWidget`


   Allow importing, exporting, and editing MagScope configuration values.


   .. py:attribute:: _SETTING_GROUPS
      :type:  tuple[tuple[str, tuple[str, Ellipsis]], Ellipsis]
      :value: (('Imaging', ('ROI', 'magnification')), ('Data Buffers', ('tracks max datapoints', 'video buffer...



   .. py:attribute:: manager


   .. py:attribute:: _current_settings


   .. py:attribute:: _setting_inputs
      :type:  dict[str, PyQt6.QtWidgets.QLineEdit]


   .. py:attribute:: _setting_value_labels
      :type:  dict[str, PyQt6.QtWidgets.QLabel]


   .. py:method:: search_targets() -> list[magscope.ui.search.SearchTarget]
      :staticmethod:



   .. py:method:: _show_error(message: str) -> None


   .. py:method:: _push_settings(settings: magscope.settings.MagScopeSettings) -> None


   .. py:method:: _refresh_fields() -> None


   .. py:method:: _apply_setting_from_input(key: str) -> None


   .. py:method:: reset_defaults() -> None


   .. py:method:: _build_setting_group(title: str, keys: tuple[str, Ellipsis]) -> PyQt6.QtWidgets.QWidget


   .. py:method:: _update_saved_label_for_input(key: str) -> None


.. py:class:: Controls(manager: UIManager)

   Bases: :py:obj:`PyQt6.QtWidgets.QWidget`


   Adaptive workflow controls with movable tabs and responsive columns.


   .. py:attribute:: LAYOUT_SETTINGS_GROUP
      :value: 'controls/layout'



   .. py:attribute:: WORKFLOW_COLUMNS_SETTINGS_KEY
      :value: 'controls/workflow_columns'



   .. py:attribute:: MIN_COLUMN_WIDTH
      :value: 360



   .. py:attribute:: MAX_COLUMNS
      :value: 4



   .. py:attribute:: WORKFLOW_ORDER
      :value: ['Run', 'Analysis', 'Locking', 'Motors']



   .. py:attribute:: WORKFLOW_TAB_ALIASES


   .. py:attribute:: DEFAULT_LAYOUTS


   .. py:attribute:: manager


   .. py:attribute:: panels
      :type:  dict[str, magscope.ui.controls.ControlPanelBase | PyQt6.QtWidgets.QWidget]


   .. py:attribute:: _settings


   .. py:attribute:: _tab_widgets
      :type:  list[WorkflowTabWidget]
      :value: []



   .. py:attribute:: _tab_pages
      :type:  dict[str, PyQt6.QtWidgets.QScrollArea]


   .. py:attribute:: _tab_content_layouts
      :type:  dict[str, PyQt6.QtWidgets.QVBoxLayout]


   .. py:attribute:: _panel_to_tab
      :type:  dict[str, str]


   .. py:attribute:: _current_column_count
      :value: 0



   .. py:attribute:: _loading_layout
      :value: False



   .. py:attribute:: _columns_layout


   .. py:property:: settings


   .. py:method:: _create_standard_panels() -> None


   .. py:method:: _create_workflow_pages() -> None


   .. py:method:: _populate_workflow_pages() -> None


   .. py:method:: _desired_column_count() -> int


   .. py:method:: _default_layout_for_count(count: int) -> list[list[str]]


   .. py:method:: _load_saved_layout() -> list[list[str]] | None


   .. py:method:: _save_workflow_layout() -> None


   .. py:method:: _current_workflow_layout() -> list[list[str]]


   .. py:method:: _canonical_workflow_tab_id(tab_id: Any) -> str
      :classmethod:



   .. py:method:: _layout_for_column_count(count: int) -> list[list[str]]


   .. py:method:: _expand_layout_to_count(columns: list[list[str]], count: int) -> list[list[str]]


   .. py:method:: _fill_empty_columns(columns: list[list[str]], preferred_layout: list[list[str]]) -> list[list[str]]


   .. py:method:: _tabs_for_empty_column(columns: list[list[str]], preferred_layout: list[list[str]], empty_index: int) -> list[str]


   .. py:method:: _clear_tab_widgets() -> None


   .. py:method:: _apply_workflow_layout(layout: list[list[str]], *, save: bool) -> None


   .. py:method:: _sync_column_count_to_width() -> None


   .. py:method:: move_workflow_tab(tab_id: str, target_column: int, target_index: int) -> None


   .. py:method:: export_preferences() -> dict[str, Any]


   .. py:method:: import_preferences(preferences: Mapping[str, Any]) -> None


   .. py:method:: validate_preferences(preferences: Mapping[str, Any]) -> None


   .. py:method:: _normalise_workflow_layout(raw_layout: Any) -> list[list[str]]


   .. py:method:: reveal_panel(panel_id: str) -> None


   .. py:method:: _reveal_legacy_panel(panel_id: str) -> None


   .. py:method:: reset_to_defaults() -> None


   .. py:method:: resizeEvent(event)


.. py:class:: UIManager

   Bases: :py:obj:`magscope.processes.ManagerProcessBase`


   Abstract base class for processes in the MagScope

   Subclass requirements:
   * Each subclass should have a unique name.
   * There should only be one instance of each subclass (singleton).
   * The class name is used for consistent inter-process identification.


   .. py:attribute:: _material_symbols_font_family
      :type:  str | None
      :value: None



   .. py:attribute:: VIEWER_LAYOUT_STATE_VERSION
      :value: 1



   .. py:attribute:: VIEWER_GEOMETRY_SETTINGS_KEY
      :value: 'viewer/main_window_geometry'



   .. py:attribute:: VIEWER_DOCK_STATE_SETTINGS_KEY
      :value: 'viewer/dock_state'



   .. py:attribute:: _ZLUT_TRACKING_ACQUISITION_MODES


   .. py:attribute:: _active_bead_graphic
      :type:  magscope.ui.widgets.BeadGraphic | None
      :value: None



   .. py:attribute:: _active_bead_id
      :type:  int | None
      :value: None



   .. py:attribute:: _bead_rois
      :type:  dict[int, tuple[int, int, int, int]]


   .. py:attribute:: _pending_bead_add_id
      :type:  int | None
      :value: None



   .. py:attribute:: _pending_bead_add_roi
      :type:  tuple[int, int, int, int] | None
      :value: None



   .. py:attribute:: _bead_next_id
      :type:  int
      :value: 0



   .. py:attribute:: beads_in_view_on
      :value: False



   .. py:attribute:: beads_in_view_count
      :value: 1



   .. py:attribute:: beads_in_view_marker_size
      :value: 20



   .. py:attribute:: central_widgets
      :type:  list[PyQt6.QtWidgets.QWidget]
      :value: []



   .. py:attribute:: central_layouts
      :type:  list[PyQt6.QtWidgets.QLayout]
      :value: []



   .. py:attribute:: controls
      :type:  Controls | None
      :value: None



   .. py:attribute:: controls_to_add
      :value: []



   .. py:attribute:: camera_dock
      :type:  PyQt6.QtWidgets.QDockWidget | None
      :value: None



   .. py:attribute:: camera_dock_title_bar
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: camera_dock_header
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: bead_toolbar
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: bead_instructions_button
      :type:  PyQt6.QtWidgets.QPushButton | None
      :value: None



   .. py:attribute:: bead_roi_size_label
      :type:  PyQt6.QtWidgets.QLabel | None
      :value: None



   .. py:attribute:: bead_total_count_label
      :type:  PyQt6.QtWidgets.QLabel | None
      :value: None



   .. py:attribute:: bead_next_id_label
      :type:  PyQt6.QtWidgets.QLabel | None
      :value: None



   .. py:attribute:: bead_reassign_ids_button
      :type:  PyQt6.QtWidgets.QPushButton | None
      :value: None



   .. py:attribute:: bead_remove_all_button
      :type:  PyQt6.QtWidgets.QPushButton | None
      :value: None



   .. py:attribute:: _display_rate_counter
      :type:  int
      :value: 0



   .. py:attribute:: _display_rate_last_time
      :type:  float


   .. py:attribute:: _display_rate_last_rate
      :type:  float
      :value: 0



   .. py:attribute:: plot_worker
      :type:  magscope.ui.plots.PlotWorker


   .. py:attribute:: plot_thread
      :type:  PyQt6.QtCore.QThread


   .. py:attribute:: plots_widget
      :type:  PyQt6.QtWidgets.QLabel


   .. py:attribute:: plots_dock
      :type:  PyQt6.QtWidgets.QDockWidget | None
      :value: None



   .. py:attribute:: plots_dock_title_bar
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: plots_dock_header
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: plots_to_add
      :type:  list[magscope.ui.plots.TimeSeriesPlotBase]
      :value: []



   .. py:attribute:: plots_progress_indicator
      :type:  LivePlotProgressIndicator | None
      :value: None



   .. py:attribute:: _plot_progress_timer
      :type:  PyQt6.QtCore.QTimer | None
      :value: None



   .. py:attribute:: _plot_progress_started_at
      :type:  float | None
      :value: None



   .. py:attribute:: _plot_progress_last_image_time
      :type:  float | None
      :value: None



   .. py:attribute:: _plot_progress_interval_seconds
      :value: 1.0



   .. py:attribute:: _preferences_dialog
      :type:  magscope.ui.controls.PreferencesDialog | None
      :value: None



   .. py:attribute:: qt_app
      :type:  PyQt6.QtWidgets.QApplication | None
      :value: None



   .. py:attribute:: selected_bead
      :value: 0



   .. py:attribute:: reference_bead
      :type:  int | None
      :value: None



   .. py:attribute:: _timer
      :type:  PyQt6.QtCore.QTimer | None
      :value: None



   .. py:attribute:: _timer_video_view
      :type:  PyQt6.QtCore.QTimer | None
      :value: None



   .. py:attribute:: _video_buffer_last_index
      :type:  int
      :value: 0



   .. py:attribute:: _video_viewer_need_reset
      :type:  bool
      :value: True



   .. py:attribute:: video_viewer
      :type:  magscope.ui.video_viewer.VideoViewer | None
      :value: None



   .. py:attribute:: windows
      :type:  list[PyQt6.QtWidgets.QMainWindow]
      :value: []



   .. py:attribute:: _suppress_bead_roi_updates
      :type:  bool
      :value: False



   .. py:attribute:: _last_applied_roi
      :type:  int | None
      :value: None



   .. py:attribute:: _settings_persistence_warning_shown
      :value: False



   .. py:attribute:: _bead_roi_capacity
      :value: 10000



   .. py:attribute:: _auto_bead_selection_dialog
      :type:  magscope.ui.auto_bead_selection_dialog.AutoBeadSelectionDialog | None
      :value: None



   .. py:attribute:: _startup_ready_sent
      :value: False



   .. py:attribute:: _zlut_generation_dialog
      :type:  magscope.ui.controls.ZLUTGenerationDialog | None
      :value: None



   .. py:attribute:: _zlut_generation_setup_dialog
      :type:  magscope.ui.controls.ZLUTGenerationSetupDialog | None
      :value: None



   .. py:attribute:: _current_zlut_dialog
      :type:  magscope.ui.controls.CurrentZLUTDialog | None
      :value: None



   .. py:attribute:: _current_zlut_filepath
      :type:  str | None
      :value: None



   .. py:attribute:: _current_zlut_metadata
      :type:  dict[str, float | int | None]


   .. py:attribute:: _zlut_generation_phase
      :value: 'idle'



   .. py:attribute:: _zlut_generation_z_axis_min_nm
      :type:  float | None
      :value: None



   .. py:attribute:: _zlut_generation_z_axis_max_nm
      :type:  float | None
      :value: None



   .. py:attribute:: _zlut_generation_z_axis_descending
      :value: False



   .. py:attribute:: _zlut_sweep_dataset
      :type:  magscope.datatypes.ZLUTSweepDataset | None
      :value: None



   .. py:attribute:: _zlut_evaluation_bead_ids
      :type:  list[int]
      :value: []



   .. py:attribute:: _zlut_evaluation_selected_bead_id
      :type:  int | None
      :value: None



   .. py:attribute:: _zlut_preview_last_poll
      :type:  float
      :value: 0.0



   .. py:attribute:: _shutdown_complete
      :value: False



   .. py:attribute:: _search_box
      :type:  PyQt6.QtWidgets.QLineEdit | None
      :value: None



   .. py:attribute:: _search_container
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: _search_toggle_button
      :type:  PyQt6.QtWidgets.QToolButton | None
      :value: None



   .. py:attribute:: _search_popup
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: _search_popup_box
      :type:  PyQt6.QtWidgets.QLineEdit | None
      :value: None



   .. py:attribute:: _search_popup_escape_shortcut
      :type:  PyQt6.QtGui.QShortcut | None
      :value: None



   .. py:attribute:: _search_status_label
      :type:  PyQt6.QtWidgets.QLabel | None
      :value: None



   .. py:attribute:: _search_status_timer
      :type:  PyQt6.QtCore.QTimer | None
      :value: None



   .. py:attribute:: _menu_row
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: _menu_bar
      :type:  PyQt6.QtWidgets.QMenuBar | None
      :value: None



   .. py:attribute:: _top_bar_menu_controls
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: _top_bar_menu_buttons
      :type:  dict[str, _TopBarActionButton]


   .. py:attribute:: _top_bar_action_buttons
      :type:  dict[str, _TopBarActionButton]


   .. py:attribute:: _window_controls
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: _minimize_button
      :type:  PyQt6.QtWidgets.QToolButton | None
      :value: None



   .. py:attribute:: _maximize_restore_button
      :type:  PyQt6.QtWidgets.QToolButton | None
      :value: None



   .. py:attribute:: _close_button
      :type:  PyQt6.QtWidgets.QToolButton | None
      :value: None



   .. py:attribute:: _caption_button_state_filter
      :type:  _CaptionButtonStateFilter | None
      :value: None



   .. py:attribute:: _top_bar_compact_filter
      :type:  _TopBarCompactModeFilter | None
      :value: None



   .. py:attribute:: _top_bar
      :type:  _UnifiedTopBar | None
      :value: None



   .. py:attribute:: _help_menu_action
      :type:  PyQt6.QtGui.QAction | None
      :value: None



   .. py:attribute:: _window_icon_label
      :type:  PyQt6.QtWidgets.QLabel | None
      :value: None



   .. py:attribute:: _window_title_label
      :type:  PyQt6.QtWidgets.QLabel | None
      :value: None



   .. py:attribute:: _title_bar_safe_area_spacer
      :type:  PyQt6.QtWidgets.QWidget | None
      :value: None



   .. py:attribute:: _title_bar_safe_area_window_handle
      :type:  Any
      :value: None



   .. py:attribute:: _layout_menu
      :type:  PyQt6.QtWidgets.QMenu | None
      :value: None



   .. py:attribute:: _auto_bead_selection_action
      :type:  PyQt6.QtGui.QAction | None
      :value: None



   .. py:attribute:: _zlut_menu
      :type:  PyQt6.QtWidgets.QMenu | None
      :value: None



   .. py:attribute:: _new_zlut_action
      :type:  PyQt6.QtGui.QAction | None
      :value: None



   .. py:attribute:: _load_zlut_action
      :type:  PyQt6.QtGui.QAction | None
      :value: None



   .. py:attribute:: _unload_zlut_action
      :type:  PyQt6.QtGui.QAction | None
      :value: None



   .. py:attribute:: _show_current_zlut_action
      :type:  PyQt6.QtGui.QAction | None
      :value: None



   .. py:attribute:: _menus
      :type:  dict[str, PyQt6.QtWidgets.QMenu]


   .. py:attribute:: _search_shortcuts
      :type:  list[PyQt6.QtGui.QShortcut]
      :value: []



   .. py:attribute:: _search_registry


   .. py:attribute:: _search_highlighter


   .. py:method:: _material_symbols_font(point_size: int = 18) -> PyQt6.QtGui.QFont
      :classmethod:



   .. py:method:: _material_symbols_filled_stylesheet() -> str
      :staticmethod:



   .. py:method:: _configure_unified_top_bar_window(window: PyQt6.QtWidgets.QMainWindow) -> None
      :staticmethod:



   .. py:method:: _ensure_unified_top_menu_bar(window: PyQt6.QtWidgets.QMainWindow) -> _UnifiedTopMenuBar
      :staticmethod:



   .. py:method:: _viewer_dock_separator_stylesheet() -> str
      :staticmethod:



   .. py:method:: _refresh_plot_progress_indicator() -> None


   .. py:method:: _live_plot_progress_indicator_enabled() -> bool


   .. py:method:: _apply_live_plot_progress_indicator_enabled() -> None


   .. py:method:: _install_viewer_dock_separator_hover_delay(window: PyQt6.QtWidgets.QMainWindow) -> None
      :staticmethod:



   .. py:method:: _apply_viewer_dock_separator_style(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _zlut_requested_sweep_edges(z_axis_min_nm: float | None, z_axis_max_nm: float | None, n_steps: int) -> tuple[float | None, float | None]
      :staticmethod:



   .. py:method:: _build_zlut_preview_payload(preview_snapshot: dict[str, object], *, z_axis_min_nm: float | None, z_axis_max_nm: float | None, z_axis_descending: bool) -> dict[str, object]
      :staticmethod:



   .. py:method:: setup()


   .. py:method:: _notify_startup_ready() -> None


   .. py:method:: set_settings(settings: magscope.settings.MagScopeSettings)

      Apply new settings and clear beads if the ROI size changed.



   .. py:method:: _current_accent_color() -> str


   .. py:method:: _apply_accent_color(color: str) -> None


   .. py:method:: _show_settings_persistence_warning_if_needed() -> None


   .. py:method:: _show_settings_persistence_warning() -> None


   .. py:method:: update_plot_figure_size(w, h)


   .. py:method:: _set_plot_image(img: PyQt6.QtGui.QImage) -> None


   .. py:method:: _create_live_plot_progress_indicator() -> LivePlotProgressIndicator


   .. py:method:: _ensure_plot_progress_timer() -> None


   .. py:method:: _reset_plot_progress() -> None


   .. py:method:: _update_plot_progress() -> None


   .. py:method:: _disconnect_signal(signal, callback) -> None
      :staticmethod:



   .. py:method:: _stop_timer(timer: PyQt6.QtCore.QTimer | None) -> None
      :staticmethod:



   .. py:method:: _close_widget(widget: PyQt6.QtWidgets.QWidget | None) -> None
      :staticmethod:



   .. py:method:: _shutdown_plot_worker() -> None


   .. py:method:: quit()

      Shutdown the process (and ask the other processes to quit too).



   .. py:method:: do_main_loop()


   .. py:method:: _handle_timer_exception(exc: BaseException) -> None

      Surface exceptions that occur inside Qt timer callbacks.



   .. py:method:: _run_safe(callback: Callable[[], None]) -> None


   .. py:method:: _main_loop_tick() -> None


   .. py:method:: _update_view_and_hist_tick() -> None


   .. py:method:: set_selected_bead(bead: int)


   .. py:method:: set_live_profile_monitor_enabled(enabled: bool) -> None


   .. py:method:: set_reference_bead(bead: int | None)


   .. py:method:: _sync_plot_settings_selected_bead(bead: int) -> None


   .. py:method:: _sync_plot_settings_reference_bead(bead: int | None) -> None


   .. py:method:: _normalize_bead_id(bead: int | None) -> int | None


   .. py:method:: _get_bead_highlight_state(bead_id: int) -> str


   .. py:method:: _refresh_bead_overlay() -> None


   .. py:method:: _update_auto_bead_selection_action_state() -> None


   .. py:method:: _can_start_auto_bead_selection() -> bool


   .. py:method:: _snapshot_recent_image() -> numpy.ndarray | None


   .. py:method:: _current_image_display_scale() -> int


   .. py:method:: _current_scene_rect() -> PyQt6.QtCore.QRectF


   .. py:method:: _current_visible_scene_rect() -> PyQt6.QtCore.QRectF


   .. py:method:: _next_random_bead_roi(rng: numpy.random.Generator, visible_rect: PyQt6.QtCore.QRectF) -> tuple[int, int, int, int] | None


   .. py:method:: _set_active_bead(bead_id: int | None) -> None


   .. py:method:: on_active_bead_move_completed(bead_id: int, roi: tuple[int, int, int, int]) -> None


   .. py:method:: add_random_beads(count: int, seed: int | None = None) -> None


   .. py:method:: _add_new_bead_batch(rois: Iterable[tuple[int, int, int, int]]) -> dict[int, tuple[int, int, int, int]]


   .. py:method:: _hit_test_bead(pos: PyQt6.QtCore.QPoint) -> int | None


   .. py:method:: _update_bead_highlight(bead_id: int) -> None


   .. py:method:: _update_bead_highlights(*, old_selected: int | None = None, old_reference: int | None = None)


   .. py:method:: _clear_live_profile_buffer() -> None


   .. py:property:: n_windows


   .. py:property:: bead_roi_updates_suppressed
      :type: bool



   .. py:property:: bead_next_id
      :type: int



   .. py:method:: _broadcast_bead_roi_update() -> None


   .. py:method:: _write_bead_rois_to_buffer(bead_rois: dict[int, tuple[int, int, int, int]]) -> None


   .. py:method:: create_central_widgets()


   .. py:method:: _create_viewer_docks(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _create_viewer_dock_title_bar(dock: PyQt6.QtWidgets.QDockWidget, title: str) -> PyQt6.QtWidgets.QWidget


   .. py:method:: _create_viewer_dock_content(dock: PyQt6.QtWidgets.QDockWidget, viewer: PyQt6.QtWidgets.QWidget, toolbar: PyQt6.QtWidgets.QWidget | None = None, viewer_margins: tuple[int, int, int, int] | None = None, viewer_overlay: PyQt6.QtWidgets.QWidget | None = None) -> tuple[PyQt6.QtWidgets.QWidget, PyQt6.QtWidgets.QWidget]


   .. py:method:: _create_live_bead_toolbar() -> PyQt6.QtWidgets.QWidget


   .. py:method:: show_bead_selection_instructions() -> None


   .. py:method:: _dock_viewer_pane(dock: PyQt6.QtWidgets.QDockWidget) -> None


   .. py:method:: _set_viewer_dock_header_visible(dock: PyQt6.QtWidgets.QDockWidget, visible: bool) -> None


   .. py:method:: _set_viewer_dock_title_bar_visible(dock: PyQt6.QtWidgets.QDockWidget, visible: bool) -> None


   .. py:method:: _handle_viewer_dock_top_level_changed(dock: PyQt6.QtWidgets.QDockWidget, floating: bool) -> None


   .. py:method:: _schedule_floating_dock_window_configuration(dock: PyQt6.QtWidgets.QDockWidget, floating: bool) -> None


   .. py:method:: _configure_floating_dock_window(dock: PyQt6.QtWidgets.QDockWidget, floating: bool) -> None


   .. py:method:: _create_view_menu(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _create_tools_menu(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _create_zlut_menu(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _update_zlut_menu_action_state() -> None


   .. py:method:: _register_menu(name: str, menu: PyQt6.QtWidgets.QMenu) -> None


   .. py:method:: _create_preferences_menu_action(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _create_help_menu_action(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _title_bar_right_safe_area_width(window: PyQt6.QtWidgets.QMainWindow) -> int
      :staticmethod:



   .. py:method:: _update_title_bar_safe_area_spacing(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _install_title_bar_safe_area_updates(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _apply_top_bar_style(menu_container: PyQt6.QtWidgets.QWidget, menu_bar_vertical_padding: int) -> None
      :staticmethod:



   .. py:method:: _create_top_bar_menu_controls(menu_bar: PyQt6.QtWidgets.QMenuBar, parent: PyQt6.QtWidgets.QWidget, target_height: int) -> PyQt6.QtWidgets.QWidget


   .. py:method:: _create_caption_button(parent: PyQt6.QtWidgets.QWidget, object_name: str, icon_text: str, tooltip: str, callback: Callable[[], None], target_height: int, role: str) -> PyQt6.QtWidgets.QToolButton


   .. py:method:: _create_main_window_controls(window: PyQt6.QtWidgets.QMainWindow, parent: PyQt6.QtWidgets.QWidget, target_height: int) -> PyQt6.QtWidgets.QWidget


   .. py:method:: _toggle_main_window_maximized(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _create_search_menu_widget(window: PyQt6.QtWidgets.QMainWindow) -> None


   .. py:method:: _connect_menu_search_box(search_box: PyQt6.QtWidgets.QLineEdit) -> None


   .. py:method:: _create_search_popup(window: PyQt6.QtWidgets.QMainWindow, target_height: int) -> tuple[PyQt6.QtWidgets.QWidget, PyQt6.QtWidgets.QLineEdit]


   .. py:method:: _handle_search_box_text_edited(search_box: PyQt6.QtWidgets.QLineEdit, model: PyQt6.QtCore.QStringListModel, completer: PyQt6.QtWidgets.QCompleter, text: str) -> None


   .. py:method:: _guide_to_search_result_from_box(search_box: PyQt6.QtWidgets.QLineEdit) -> None


   .. py:method:: _guide_to_search_result_from_text(text: str) -> None


   .. py:method:: _sync_search_boxes(text: str, source: PyQt6.QtWidgets.QLineEdit | None = None) -> None


   .. py:method:: _show_search_popup() -> None


   .. py:method:: _hide_search_popup() -> None


   .. py:method:: _clear_search_popup_box() -> None


   .. py:method:: _focus_menu_search() -> None


   .. py:method:: _search_container_horizontal_margins() -> int


   .. py:method:: _search_inline_total_width(search_box_width: int) -> int


   .. py:method:: _search_collapsed_total_width() -> int


   .. py:method:: _widget_width_hint(widget: PyQt6.QtWidgets.QWidget | None) -> int
      :staticmethod:



   .. py:method:: _set_menu_search_compact_state(collapsed: bool, search_box_width: int) -> None


   .. py:method:: _set_top_bar_menu_icon_mode(icon_only: bool) -> None


   .. py:method:: _update_top_bar_compact_mode() -> None


   .. py:method:: _refresh_search_registry() -> None


   .. py:method:: _ensure_search_registry() -> None


   .. py:method:: _menu_search_targets() -> list[magscope.ui.search.SearchTarget]


   .. py:method:: _generic_panel_search_targets() -> list[magscope.ui.search.SearchTarget]


   .. py:method:: _core_control_search_targets() -> list[magscope.ui.search.SearchTarget]


   .. py:method:: _normalize_search_text(text: str) -> str
      :staticmethod:



   .. py:method:: _find_search_target(text: str) -> magscope.ui.search.SearchTarget | None


   .. py:method:: _find_exact_search_target(text: str) -> magscope.ui.search.SearchTarget | None


   .. py:method:: _search_completion_labels(text: str) -> list[str]


   .. py:method:: _update_search_completion_model(model: PyQt6.QtCore.QStringListModel, completer: PyQt6.QtWidgets.QCompleter, text: str) -> None


   .. py:method:: _guide_to_search_result(text: str) -> bool


   .. py:method:: _guide_to_target(target: magscope.ui.search.SearchTarget) -> None


   .. py:method:: _reveal_search_target(target: magscope.ui.search.SearchTarget) -> None


   .. py:method:: _search_target_widget(target: magscope.ui.search.PanelControlTarget) -> PyQt6.QtWidgets.QWidget | None


   .. py:method:: _reveal_preference_setting(target: magscope.ui.search.PreferencesSettingTarget) -> None


   .. py:method:: _reveal_preference_widget(target: magscope.ui.search.PreferencesWidgetTarget) -> None


   .. py:method:: _reveal_menu_action(target: magscope.ui.search.MenuActionTarget) -> None


   .. py:method:: _highlight_search_widget(widget: PyQt6.QtWidgets.QWidget) -> None


   .. py:method:: _clear_search_highlight(widget: PyQt6.QtWidgets.QWidget) -> None


   .. py:method:: _install_search_shortcuts(window: PyQt6.QtWidgets.QMainWindow, search_box: PyQt6.QtWidgets.QLineEdit) -> None


   .. py:method:: _focus_search_box(search_box: PyQt6.QtWidgets.QLineEdit) -> None


   .. py:method:: _clear_search_box(search_box: PyQt6.QtWidgets.QLineEdit) -> None


   .. py:method:: _set_search_status(text: str) -> None


   .. py:method:: _clear_search_status_label_ref() -> None


   .. py:method:: _show_preferences_dialog() -> None


   .. py:method:: _dock_all_viewers() -> None


   .. py:method:: _viewer_layout_settings() -> PyQt6.QtCore.QSettings


   .. py:method:: _save_viewer_layout() -> None


   .. py:method:: _restore_viewer_layout() -> bool


   .. py:method:: _clear_viewer_layout() -> None


   .. py:method:: _encode_qbytearray(value: PyQt6.QtCore.QByteArray) -> str
      :staticmethod:



   .. py:method:: _decode_qbytearray(value: str) -> PyQt6.QtCore.QByteArray
      :staticmethod:



   .. py:method:: export_appearance_layout_preferences() -> dict[str, Any]


   .. py:method:: import_appearance_layout_preferences(preferences: Mapping[str, Any]) -> None


   .. py:method:: validate_appearance_layout_preferences(preferences: Mapping[str, Any]) -> None


   .. py:method:: reset_appearance_layout_preferences() -> None


   .. py:method:: _sync_viewer_dock_headers() -> None


   .. py:method:: _apply_default_viewer_layout() -> None


   .. py:method:: _reset_viewer_layout() -> None


   .. py:method:: update_view_coords()


   .. py:method:: _update_view_and_hist()


   .. py:method:: callback_view_clicked(pos: PyQt6.QtCore.QPoint, button=Qt.MouseButton.LeftButton)


   .. py:method:: refresh_bead_rois()


   .. py:method:: update_bead_rois()


   .. py:method:: _add_bead_roi(bead_id: int, roi: tuple[int, int, int, int]) -> None


   .. py:method:: _update_bead_roi(bead_id: int, roi: tuple[int, int, int, int]) -> None


   .. py:method:: _update_multiple_bead_rois(bead_rois: dict[int, tuple[int, int, int, int]]) -> None


   .. py:method:: _remove_bead_roi(bead_id: int) -> None


   .. py:method:: move_beads(moves: list[tuple[int, int, int]])


   .. py:method:: add_bead(pos: PyQt6.QtCore.QPoint)


   .. py:method:: start_auto_bead_selection() -> None


   .. py:method:: _apply_auto_bead_selection(rois: list[tuple[int, int, int, int]]) -> None


   .. py:method:: _show_auto_bead_selection_capacity_warning(skipped_count: int) -> None


   .. py:method:: _on_auto_bead_selection_dialog_finished(_result: int) -> None


   .. py:method:: remove_bead(id: int)


   .. py:method:: clear_beads()


   .. py:method:: reset_bead_ids()


   .. py:method:: _update_roi_labels(roi: int) -> None


   .. py:method:: _update_next_bead_id_label() -> None


   .. py:method:: _update_bead_roi_size_label(roi: int | None = None) -> None


   .. py:method:: _update_bead_count_label() -> None


   .. py:method:: _update_live_bead_toolbar_labels() -> None


   .. py:method:: _clear_pending_bead_add() -> None


   .. py:method:: _calculate_next_bead_id() -> int


   .. py:method:: update_video_processors_status()


   .. py:method:: update_video_buffer_status()


   .. py:method:: _update_display_rate()


   .. py:method:: _update_beads_in_view()


   .. py:method:: update_camera_setting(name: str, value: str)


   .. py:method:: update_video_buffer_purge(t: float)


   .. py:method:: update_script_status(status: magscope.scripting.ScriptStatus)


   .. py:method:: update_script_step(current_step: int | None, total_steps: int, description: str | None)


   .. py:method:: print(text: str, details: str | None = None)


   .. py:method:: show_error(text: str, details: str | None = None)


   .. py:method:: show_warning(text: str, details: str | None = None)


   .. py:method:: set_acquisition_on(value: bool)


   .. py:method:: set_acquisition_dir(value: str | None)


   .. py:method:: set_acquisition_dir_on(value: bool)


   .. py:method:: set_acquisition_mode(mode: magscope.utils.AcquisitionMode)


   .. py:method:: update_xy_lock_enabled(value: bool)


   .. py:method:: update_xy_lock_interval(value: float)


   .. py:method:: update_xy_lock_max(value: float)


   .. py:method:: update_xy_lock_window(value: int)


   .. py:method:: update_z_lock_enabled(value: bool)


   .. py:method:: update_z_lock_bead(value: int)


   .. py:method:: update_z_lock_target(value: float)


   .. py:method:: update_z_lock_interval(value: float)


   .. py:method:: update_z_lock_max(value: float)


   .. py:method:: update_z_lock_window(value: int)


   .. py:method:: request_zlut_file(filepath: str) -> None


   .. py:method:: clear_zlut() -> None


   .. py:method:: unload_zlut() -> None


   .. py:method:: load_zlut_file_dialog() -> None


   .. py:method:: show_current_zlut_dialog() -> None


   .. py:method:: _clear_current_zlut_dialog_ref() -> None


   .. py:method:: show_new_zlut_dialog() -> None


   .. py:method:: _zlut_generation_preflight_error() -> str | None


   .. py:method:: _registered_focus_motor_names() -> list[str]


   .. py:method:: _set_current_zlut(*, filepath: str | None, z_min: float | None = None, z_max: float | None = None, step_size: float | None = None, profile_length: int | None = None) -> None


   .. py:method:: _update_current_zlut_dialog() -> None


   .. py:method:: _clear_zlut_generation_preview(message: str = 'Waiting for Z-LUT sweep data...') -> None


   .. py:method:: _read_zlut_preview_snapshot(dataset: magscope.datatypes.ZLUTSweepDataset) -> dict[str, object]


   .. py:method:: show_zlut_generation_dialog() -> None


   .. py:method:: start_zlut_generation(*, start_nm: float, step_nm: float, stop_nm: float, profiles_per_bead: int) -> None


   .. py:method:: cancel_zlut_generation() -> None


   .. py:method:: discard_generated_zlut_evaluation() -> None


   .. py:method:: select_generated_zlut_bead(bead_id: int) -> None


   .. py:method:: save_generated_zlut(bead_id: int, load_after_save: bool = True) -> None


   .. py:method:: request_profile_length() -> None


   .. py:method:: update_zlut_metadata(filepath: str | None = None, z_min: float | None = None, z_max: float | None = None, step_size: float | None = None, profile_length: int | None = None) -> None


   .. py:method:: report_profile_length(profile_length: int | None = None) -> None


   .. py:method:: update_zlut_generation_state(status: str, detail: str | None = None, running: bool = False, can_cancel: bool = False, phase: str = 'idle', z_axis_min_nm: float | None = None, z_axis_max_nm: float | None = None, z_axis_descending: bool = False) -> None


   .. py:method:: update_zlut_generation_evaluation(active: bool, bead_ids: list[int], selected_bead_id: int | None = None) -> None


   .. py:method:: update_zlut_generation_progress(current_step: int, total_steps: int, capture_count: int, capture_capacity: int, motor_z_value: float | None = None) -> None


   .. py:method:: _handle_zlut_dialog_destroyed() -> None


   .. py:method:: _detach_zlut_sweep_dataset() -> None


   .. py:method:: _update_zlut_generation_dialog() -> None


   .. py:method:: _refresh_zlut_preview_from_dataset() -> None


