magscope.ui#
Submodules#
Classes#
A collapsible QGroupBox with the title text as a toggle button to show/hide its content |
|
Simple class for adding '...' to QSplitter handles. |
|
Horizontally combined QLabel and QCheckbox. |
|
Horizontally combined QLabel and QLineedit. |
|
Horizontally combined QLabel, QLineedit, and a second QLabel to show the value. |
|
Horizontally combined QLabel and QLineedit with a QButton to increment/decrement the value on either side. |
|
Custom QLabel that emits a signal when it's resized. |
|
Allow importing, exporting, and editing MagScope configuration values. |
|
Container widget hosting draggable, persistent control panels. |
|
Abstract base class for processes in the MagScope |
Package Contents#
- class magscope.ui.VideoViewer(scale_factor=1.25)[source]#
Bases:
PyQt6.QtWidgets.QGraphicsView- coordinatesChanged: PyQt6.QtCore.pyqtSignal#
- clicked: PyQt6.QtCore.pyqtSignal#
- sceneClicked: PyQt6.QtCore.pyqtSignal#
- _MINIMAP_MARGIN = 12#
- _MINIMAP_MIN_SIZE = 120#
- _MINIMAP_MAX_SIZE = 220#
- _MINIMAP_LABEL_SPACING = 6#
- _MINIMAP_ZOOM_HEIGHT = 26#
- _MINIMAP_BUTTON_SPACING = 6#
- _mouse_start_pos#
- _mouse_start_time = 0.0#
- _zoom = 0#
- scale_factor = 1.25#
- _empty = True#
- scene#
- _image#
- _overlay_entries: list[tuple[PyQt6.QtCore.QRectF, PyQt6.QtCore.QPointF, str, bool, str]] = []#
- _visible_overlay_entries: list[tuple[PyQt6.QtCore.QRectF, str, bool]] | None = None#
- _visible_label_entries: list[tuple[PyQt6.QtCore.QPointF, PyQt6.QtGui.QStaticText, bool]] | None = None#
- _overlay_cache_pixmap#
- _overlay_cache_dirty = True#
- _overlay_cache_size#
- _overlay_cache_device_pixel_ratio = 0.0#
- _static_label_cache: dict[str, PyQt6.QtGui.QStaticText]#
- _label_metrics#
- _label_ascent#
- _marker_x#
- _marker_y#
- _marker_size = 0#
- _minimap_label#
- _minimap_zoom_label#
- _minimap_reset_button#
- _minimap_base#
- _fit_scale = 1.0#
- class magscope.ui.PlotWorker[source]#
Bases:
PyQt6.QtCore.QObject- image_signal#
- limits_signal#
- selected_bead_signal#
- reference_bead_signal#
- stop_signal#
- figure_size_signal#
- time_mode_signal#
- relative_window_signal#
- axes: matplotlib.axes.Axes#
- locks: dict[str, multiprocessing.synchronize.Lock]#
- figure: matplotlib.figure.Figure | None = None#
- canvas: matplotlib.backends.backend_qtagg.FigureCanvasQTAgg#
- _is_running: bool = False#
- plots = []#
- limits: dict[str, tuple[float, float]]#
- selected_bead: int | None = 0#
- reference_bead: int | None = None#
- n_plots: int#
- update_on: bool = True#
- _update_last_time: float#
- fig_width = 5#
- fig_height = 4#
- dpi = 100#
- time_mode = 'absolute'#
- relative_window_seconds: float | None = 300#
- _tracks_snapshot: numpy.ndarray | None = None#
- mutex: PyQt6.QtCore.QMutex#
- figure_size_changed = True#
- add_plot(plot: TimeSeriesPlotBase)[source]#
Used to add plots before the process has started
- class magscope.ui.TimeSeriesPlotBase(buffer_name: str, ylabel: str)[source]#
- buffer: magscope.datatypes.MatrixBuffer#
- buffer_name#
- parent: PlotWorker#
- axes: matplotlib.axes.Axes#
- ylabel#
- set_parent(parent: PlotWorker)[source]#
- class magscope.ui.BeadGraphic(parent: magscope.ui.ui.UIManager, id: int, roi: tuple[int, int, int, int], view_scene)[source]#
Bases:
PyQt6.QtWidgets.QGraphicsRectItem- LABEL_FONT#
- LABEL_COLOR#
- LABEL_OFFSET_X = 10#
- LABEL_OFFSET_Y = 1#
- BORDER_COLOR_DEFAULT = (0, 255, 255, 255)#
- FILL_COLOR_DEFAULT = None#
- BORDER_COLOR_SELECTED = (255, 0, 0, 255)#
- FILL_COLOR_SELECTED = None#
- BORDER_COLOR_REFERENCE = (0, 255, 0, 255)#
- FILL_COLOR_REFERENCE = None#
- HOVER_BORDER_COLOR = (255, 96, 96, 255)#
- DRAG_BORDER_COLOR = (255, 255, 255, 255)#
- IDLE_PEN_WIDTH = 0#
- HOVER_PEN_WIDTH = 2#
- SELECTED_PEN_WIDTH = 2#
- DRAG_PEN_WIDTH = 3#
- CORNER_GRIP_SIZE = 6.0#
- _parent: magscope.ui.ui.UIManager#
- id: int#
- _initializing: bool = True#
- _is_moving: bool = False#
- _is_hovered: bool = False#
- _locked: bool#
- _color_state: str = 'default'#
- _cached_roi: tuple[int, int, int, int] | None = None#
- pen_width = 0#
- label#
- property locked#
- view_scene#
- classmethod label_scene_position_for_roi(roi: tuple[int, int, int, int]) PyQt6.QtCore.QPointF[source]#
- classmethod clamp_roi_to_scene(roi: tuple[int, int, int, int], scene_rect: PyQt6.QtCore.QRectF) tuple[int, int, int, int][source]#
- classmethod move_roi(roi: tuple[int, int, int, int], dx: int, dy: int, scene_rect: PyQt6.QtCore.QRectF) tuple[int, int, int, int][source]#
- set_selection_state(state: str)[source]#
Update the bead overlay color to match selection/reference state.
- class magscope.ui.CollapsibleGroupBox(title='', collapsed=False)[source]#
Bases:
PyQt6.QtWidgets.QGroupBoxA collapsible QGroupBox with the title text as a toggle button to show/hide its content
- title = ''#
- default_collapsed = False#
- _settings_key = '_Group Box Collapsed'#
- toggle_button#
- drag_handle#
- content_area#
- animation#
- collapsed#
- property settings_key: str#
- class magscope.ui.GripSplitter(orientation, name=None, parent=None)[source]#
Bases:
PyQt6.QtWidgets.QSplitterSimple class for adding ‘…’ to QSplitter handles.
- name = None#
- shown_once = False#
- class magscope.ui.LabeledCheckbox(*, label_text: str, widths: tuple[int, int] = (0, 0), default=False, callback: callable = None)[source]#
Bases:
PyQt6.QtWidgets.QWidgetHorizontally combined QLabel and QCheckbox.
- layout#
- label#
- checkbox#
- class magscope.ui.LabeledLineEdit(*, label_text: str, widths: tuple[int, int] = (0, 0), default=None, validator: PyQt6.QtGui.QValidator = None, callback: callable = None)[source]#
Bases:
PyQt6.QtWidgets.QWidgetHorizontally combined QLabel and QLineedit.
- layout#
- label#
- lineedit#
- class magscope.ui.LabeledLineEditWithValue(*, label_text: str, validator: PyQt6.QtGui.QValidator = None, widths: tuple[int, int, int] = (0, 0, 0), default=None, callback: callable = None)[source]#
Bases:
PyQt6.QtWidgets.QWidgetHorizontally combined QLabel, QLineedit, and a second QLabel to show the value.
- layout#
- label#
- lineedit#
- value_label#
- class magscope.ui.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))[source]#
Bases:
PyQt6.QtWidgets.QWidgetHorizontally combined QLabel and QLineedit with a QButton to increment/decrement the value on either side.
- layout#
- name_label#
- left_button#
- lineedit#
- right_button#
- class magscope.ui.ResizableLabel(parent=None)[source]#
Bases:
PyQt6.QtWidgets.QLabelCustom QLabel that emits a signal when it’s resized.
- resized#
- class magscope.ui.AcquisitionPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBase- NO_DIRECTORY_SELECTED_TEXT = 'No save directory selected'#
- acquisition_on_checkbox#
- acquisition_mode_combobox#
- acquisition_dir_on_checkbox#
- acquisition_dir_button#
- acquisition_dir_textedit#
- class magscope.ui.AllanDeviationPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
MatplotlibCleanupMixin,ControlPanelBase- _SETTINGS_GROUP = 'AllanDeviationPanel'#
- refresh_button#
- history_window#
- history_window_hint#
- taus_mode#
- figure#
- canvas#
- axes#
- status_label#
- class magscope.ui.BeadSelectionPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBase- next_bead_id_label#
- reset_id_button#
- roi_size_label#
- clear_button#
- auto_select_button#
- class magscope.ui.CameraPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBase- _last_settings_update: datetime.datetime | None = None#
- settings#
- refresh_button#
- last_update_label#
- class magscope.ui.ControlPanelBase(manager: magscope.ui.ui.UIManager, title: str, collapsed_by_default: bool = False)[source]#
Bases:
PyQt6.QtWidgets.QWidget- manager: magscope.ui.ui.UIManager#
- groupbox: magscope.ui.widgets.CollapsibleGroupBox#
- class magscope.ui.HistogramPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
MatplotlibCleanupMixin,ControlPanelBase- update_interval: float = 1#
- _update_last_time: float = 0#
- enable_checkbox#
- only_beads_checkbox#
- n_bins = 256#
- figure#
- canvas#
- axes#
- class magscope.ui.PlotSettingsPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBase- selected_bead#
- reference_bead#
- limits: dict[str, tuple[PyQt6.QtWidgets.QLineEdit, PyQt6.QtWidgets.QLineEdit]]#
- grid_layout#
- time_mode#
- time_label#
- time_limits_absolute#
- time_relative_window#
- time_inputs_stack#
- beads_in_view_on#
- beads_in_view_count#
- beads_in_view_marker_size#
- class magscope.ui.ScriptPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBase- NO_SCRIPT_SELECTED_TEXT = 'No script loaded'#
- status_prefix = 'Status'#
- status_label#
- step_position_label#
- step_description_label#
- button_layout#
- load_button#
- start_button#
- pause_button#
- filepath_textedit#
- update_status(status: magscope.scripting.ScriptStatus)[source]#
- class magscope.ui.StatusPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBase- dot_count = 0#
- display_rate_status#
- video_processors_status#
- video_buffer_size_status#
- video_buffer_status#
- video_buffer_status_bar#
- video_buffer_purge_label#
- class magscope.ui.TrackingOptionsPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBase- _DEFAULTS: dict[str, Any]#
- _current_options: dict[str, Any]#
- _last_options_update: datetime.datetime | None = None#
- background_combo#
- iterations#
- line_ratio#
- n_local#
- use_fft#
- fft_oversample#
- fft_rmin#
- fft_rmax#
- fft_gaus_factor#
- radial_oversample#
- lookup_n_local#
- status_label#
- _parse_int(widget: magscope.ui.widgets.LabeledLineEditWithValue, fallback: int, *, minimum: int | None = None) int[source]#
- _parse_float(widget: magscope.ui.widgets.LabeledLineEditWithValue, fallback: float, *, minimum: float | None = None) float[source]#
- _set_options(options: dict[str, Any], message: str | None = None, *, populate_inputs: bool = False) None[source]#
- _coerce_int_value(raw: Any, *, name: str, fallback: int, minimum: int | None = None, enforce_odd: bool = False) int[source]#
- class magscope.ui.MagScopeSettingsPanel(manager: magscope.ui.ui.UIManager)[source]#
Bases:
ControlPanelBaseAllow importing, exporting, and editing MagScope configuration values.
- _current_settings#
- _setting_inputs: dict[str, magscope.ui.widgets.LabeledLineEditWithValue]#
- _last_settings_update: datetime.datetime | None = None#
- load_button#
- save_button#
- defaults_button#
- apply_button#
- status_label#
- _collect_settings_from_inputs() magscope.settings.MagScopeSettings | None[source]#
- _push_settings(settings: magscope.settings.MagScopeSettings) None[source]#
- class magscope.ui.Controls(manager: UIManager)[source]#
Bases:
PyQt6.QtWidgets.QWidgetContainer widget hosting draggable, persistent control panels.
- LAYOUT_SETTINGS_GROUP = 'controls/layout'#
- manager#
- panels: dict[str, magscope.ui.controls.ControlPanelBase | PyQt6.QtWidgets.QWidget]#
- _settings#
- _columns_layout#
- _column_scrolls: dict[str, PyQt6.QtWidgets.QScrollArea]#
- _column_prefix = 'column'#
- _column_counter = 1#
- _base_columns#
- _suppress_layout_callback = False#
- layout_manager#
- _add_column_target#
- help_panel#
- reset_panel#
- settings_panel#
- acquisition_panel#
- bead_selection_panel#
- camera_panel#
- histogram_panel#
- tracking_options_panel#
- plot_settings_panel#
- allan_deviation_panel#
- profile_panel#
- script_panel#
- status_panel#
- xy_lock_panel#
- z_lock_panel#
- zlut_panel#
- z_lut_generation_panel#
- property settings#
- _add_column(name: str, *, pinned_ids: Iterable[str] | None = None, index: int | None = None) magscope.ui.panel_layout.ReorderableColumn[source]#
- create_new_column_with_panel(wrapper: magscope.ui.panel_layout.PanelWrapper) None[source]#
- class magscope.ui.UIManager[source]#
Bases:
magscope.processes.ManagerProcessBaseAbstract 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.
- _active_bead_graphic: magscope.ui.widgets.BeadGraphic | None = None#
- _active_bead_id: int | None = None#
- _bead_rois: dict[int, tuple[int, int, int, int]]#
- _pending_bead_add_id: int | None = None#
- _pending_bead_add_roi: tuple[int, int, int, int] | None = None#
- _bead_next_id: int = 0#
- beads_in_view_on = False#
- beads_in_view_count = 1#
- beads_in_view_marker_size = 20#
- central_widgets: list[PyQt6.QtWidgets.QWidget] = []#
- central_layouts: list[PyQt6.QtWidgets.QLayout] = []#
- controls_to_add = []#
- _display_rate_counter: int = 0#
- _display_rate_last_time: float#
- _display_rate_last_rate: float = 0#
- _n_windows: int | None = None#
- plot_worker: magscope.ui.plots.PlotWorker#
- plot_thread: PyQt6.QtCore.QThread#
- plots_widget: PyQt6.QtWidgets.QLabel#
- plots_to_add: list[magscope.ui.plots.TimeSeriesPlotBase] = []#
- qt_app: PyQt6.QtWidgets.QApplication | None = None#
- selected_bead = 0#
- reference_bead: int | None = None#
- _timer: PyQt6.QtCore.QTimer | None = None#
- _timer_video_view: PyQt6.QtCore.QTimer | None = None#
- _video_buffer_last_index: int = 0#
- _video_viewer_need_reset: bool = True#
- video_viewer: magscope.ui.video_viewer.VideoViewer | None = None#
- windows: list[PyQt6.QtWidgets.QMainWindow] = []#
- _suppress_bead_roi_updates: bool = False#
- _last_applied_roi: int | None = None#
- _settings_persistence_warning_shown = False#
- _bead_roi_capacity = 10000#
- _auto_bead_selection_dialog: magscope.ui.auto_bead_selection_dialog.AutoBeadSelectionDialog | None = None#
- _startup_ready_sent = False#
- _zlut_generation_dialog: magscope.ui.controls.ZLUTGenerationDialog | None = None#
- _zlut_generation_phase = 'idle'#
- _zlut_generation_z_axis_min_nm: float | None = None#
- _zlut_generation_z_axis_max_nm: float | None = None#
- _zlut_generation_z_axis_descending = False#
- _zlut_sweep_dataset: magscope.datatypes.ZLUTSweepDataset | None = None#
- _zlut_evaluation_bead_ids: list[int] = []#
- _zlut_evaluation_selected_bead_id: int | None = None#
- _zlut_preview_last_poll: float = 0.0#
- _shutdown_complete = False#
- static _zlut_requested_sweep_edges(z_axis_min_nm: float | None, z_axis_max_nm: float | None, n_steps: int) tuple[float | None, float | None][source]#
- static _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][source]#
- set_settings(settings: magscope.settings.MagScopeSettings)[source]#
Apply new settings and clear beads if the ROI size changed.
- _handle_timer_exception(exc: BaseException) None[source]#
Surface exceptions that occur inside Qt timer callbacks.
- _next_random_bead_roi(rng: numpy.random.Generator, visible_rect: PyQt6.QtCore.QRectF) tuple[int, int, int, int] | None[source]#
- _add_new_bead_batch(rois: Iterable[tuple[int, int, int, int]]) dict[int, tuple[int, int, int, int]][source]#
- _update_bead_highlights(*, old_selected: int | None = None, old_reference: int | None = None)[source]#
- property n_windows#
- property bead_roi_updates_suppressed: bool#
- property bead_next_id: int#
- update_script_status(status: magscope.scripting.ScriptStatus)[source]#
- set_acquisition_mode(mode: magscope.utils.AcquisitionMode)[source]#
- _read_zlut_preview_snapshot(dataset: magscope.datatypes.ZLUTSweepDataset) dict[str, object][source]#
- start_zlut_generation(*, start_nm: float, step_nm: float, stop_nm: float, profiles_per_bead: int) None[source]#
- 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[source]#
- 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[source]#
- update_zlut_generation_evaluation(active: bool, bead_ids: list[int], selected_bead_id: int | None = None) None[source]#