from typing import Union, TYPE_CHECKING, Iterable
from pymodaq_utils.enums import BaseEnum
from pyqtgraph.graphicsItems import InfiniteLine, ROI
from qtpy import QtWidgets
from qtpy.QtCore import QObject, Signal, QRectF
from pymodaq_data.data import DataToExport, DataWithAxes, DataDim, DataDistribution
from pymodaq_gui.plotting.utils.plot_utils import RoiInfo
if TYPE_CHECKING:
from pymodaq_gui.plotting.data_viewers.viewer0D import Viewer0D
from pymodaq_gui.plotting.data_viewers.viewer1D import Viewer1D
from pymodaq_gui.plotting.data_viewers.viewer2D import Viewer2D
from pymodaq_gui.plotting.data_viewers.viewerND import ViewerND
[docs]
class ViewerError(Exception):
...
[docs]
class ViewersEnum(BaseEnum):
"""enum relating a given viewer with data type"""
Viewer0D = 'Data0D'
Viewer1D = 'Data1D'
Viewer2D = 'Data2D'
ViewerND = 'DataND'
ViewerSequential = 'DataSequential'
[docs]
def get_dim(self):
return self.value.split('Data')[1].split('D')[0]
[docs]
def increase_dim(self, ndim: int):
dim = self.get_dim()
if dim != 'N':
dim_as_int = int(dim) + ndim
if dim_as_int > 2:
dim = 'N'
else:
dim = str(dim_as_int)
else:
dim = 'N'
return ViewersEnum[f'Viewer{dim}D']
[docs]
@classmethod
def from_n_axes(cls, n_axes: int):
if n_axes == 0:
return ViewersEnum['Viewer0D']
elif n_axes == 1:
return ViewersEnum['Viewer1D']
elif n_axes == 2:
return ViewersEnum['Viewer2D']
elif n_axes > 2:
return ViewersEnum['ViewerND']
[docs]
@staticmethod
def get_viewers_enum_from_data(dwa: DataWithAxes) -> 'ViewersEnum':
if dwa.dim.name == 'Data0D':
viewer = 'Viewer0D'
elif dwa.dim.name == 'Data1D':
viewer = 'Viewer1D'
elif dwa.dim.name == 'Data2D':
viewer = 'Viewer2D'
else:
if dwa.distribution.name == 'uniform':
if len(dwa.shape) < 3 and dwa.check_axes_linear():
dwa.nav_indexes = ()
if len(dwa.shape) == 1 and dwa.size == 1:
viewer = 'Viewer0D'
elif len(dwa.shape) == 1 and dwa.size > 1:
viewer = 'Viewer1D'
elif len(dwa.shape) == 2:
viewer = 'Viewer2D'
else:
viewer = 'ViewerND'
elif len(dwa.shape) == 1 and not dwa.check_axes_linear():
viewer = 'Viewer1D'
dwa.nav_indexes = ()
else:
viewer = 'ViewerND'
else:
if len(dwa.sig_indexes) == 0:
if len(dwa.get_nav_axes()) == 1:
viewer = 'Viewer1D'
elif len(dwa.get_nav_axes()) == 2:
viewer = 'Viewer2D'
else:
viewer = 'ViewerND'
else:
viewer = 'ViewerND'
return ViewersEnum[viewer]
[docs]
class ViewerBase(QObject):
"""Base Class for data viewers implementing all common functionalities
Parameters
----------
parent: QtWidgets.QWidget
title: str
Attributes
----------
view: QObject
Ui interface of the viewer
data_to_export_signal: Signal[DataToExport]
ROI_changed: Signal
crosshair_dragged: Signal[float, float]
crosshair_clicked: Signal[bool]
sig_double_clicked: Signal[float, float]
status_signal: Signal[str]
"""
data_to_export_signal = Signal(DataToExport)
_data_to_show_signal = Signal(DataWithAxes)
ROI_changed = Signal()
crosshair_dragged = Signal(float, float) # Crosshair position in units of scaled top/right axes
status_signal = Signal(str)
crosshair_clicked = Signal(bool)
sig_double_clicked = Signal(float, float)
ROI_select_signal = Signal(QRectF) # deprecated: use roi_select_signal
roi_select_signal = Signal(RoiInfo)
def __init__(self, parent: QtWidgets.QWidget = None, title=''):
super().__init__()
self.title = title if title != '' else self.__class__.__name__
self._raw_data = None
self.data_to_export: DataToExport = DataToExport(name=self.title)
self.view: Union[Viewer0D, Viewer1D, Viewer2D, ViewerND] = None
if parent is None:
parent = QtWidgets.QWidget()
parent.show()
self.parent = parent
self.parent.setWindowTitle(self.title)
self._display_temporary = False
@property
def has_action(self):
"""Convenience method"""
if hasattr(self.view, 'has_action'):
return self.view.has_action
@property
def is_action_checked(self):
"""Convenience method"""
if hasattr(self.view, 'is_action_checked'):
return self.view.is_action_checked
@property
def is_action_visible(self):
"""Convenience method"""
if hasattr(self.view, 'is_action_visible'):
return self.view.is_action_visible
@property
def set_action_checked(self):
"""Convenience method"""
if hasattr(self.view, 'set_action_checked'):
return self.view.set_action_checked
@property
def set_action_visible(self):
"""Convenience method"""
if hasattr(self.view, 'set_action_visible'):
return self.view.set_action_visible
@property
def get_action(self):
"""Convenience method"""
if hasattr(self.view, 'get_action'):
return self.view.get_action
@property
def toolbar(self):
"""Convenience property"""
if hasattr(self.view, 'toolbar'):
return self.view.toolbar
@property
def viewer_type(self):
"""str: the viewer data type see DATA_TYPES"""
return ViewersEnum[self.__class__.__name__].value
[docs]
def show_data(self, data: DataWithAxes, **kwargs):
"""Entrypoint to display data into the viewer
Parameters
----------
data: data_mod.DataFromPlugins
"""
if len(data.sig_indexes) > 2:
raise ViewerError(f'Ndarray of dim: {len(data.shape)} cannot be plotted using a {self.viewer_type}')
self.data_to_export = DataToExport(name=self.title)
self._raw_data = data
self._display_temporary = False
self._show_data(data, **kwargs)
[docs]
def show_data_temp(self, data: DataWithAxes, **kwargs):
"""Entrypoint to display temporary data into the viewer
No processed data signal is emitted from the viewer
Parameters
----------
data: data_mod.DataFromPlugins
"""
self._display_temporary = True
self.show_data(data, **kwargs)
def _show_data(self, data: DataWithAxes, *args, **kwargs):
"""Specific viewers should implement it"""
raise NotImplementedError
[docs]
def add_attributes_from_view(self):
"""Convenience function to add attributes from the view to self"""
for attribute in self.convenience_attributes:
if hasattr(self.view, attribute):
setattr(self, attribute, getattr(self.view, attribute))
[docs]
def trigger_action(self, action_name: str):
"""Convenience function to trigger programmatically one of the action of the related view"""
if self.has_action(action_name):
self.get_action(action_name).trigger()
[docs]
def activate_roi(self, activate=True):
"""Activate the Roi manager using the corresponding action"""
raise NotImplementedError
[docs]
def setVisible(self, show=True):
"""convenience method to show or hide the parent widget"""
self.parent.setVisible(show)
@property
def roi_target(self) -> Union[InfiniteLine.InfiniteLine, ROI.ROI]:
"""To be implemented if necessary (Viewer1D and above)"""
return None
[docs]
def move_roi_target(self, pos: Iterable[float] = None, **kwargs):
"""move a specific read only ROI at the given position on the viewer"""
...
[docs]
def show_roi_target(self, show=True):
"""Show/Hide a specific read only ROI"""
if self.roi_target is not None:
self.roi_target.setVisible(show)