import dataclasses
from pyqtgraph.parametertree.parameterTypes import SimpleParameter
from typing import Union
import numpy as np
from qtpy import QtCore, QtGui
from pyqtgraph.parametertree.Parameter import registerParameterType
from pymodaq_gui.parameter.pymodaq_ptypes import GroupParameter
from pymodaq_utils.config import GlobalConfig as Config
from pymodaq_data.plotting.utils import PlotColors
from pymodaq_data.post_treatment.process_to_scalar import DataProcessorFactory
from pymodaq_gui.plotting.items.roi import ROIDim, ROI, ROIFactory, LinearROI, mkColor, roi_format, plot_colors
from pymodaq_gui.utils.widget_sync import WidgetSync, SyncMode
from pymodaq_gui.parameter.ioxml import optional_int_options, optional_str_options
optional_int_options['index'] = None
optional_str_options['dim'] = None
optional_str_options['descriptor'] = None
data_processors = DataProcessorFactory()
ROI2D_TYPES = ROIFactory.get_descriptors_from_dimensionality(ROIDim.ROI2D)
config = Config()
[docs]
@dataclasses.dataclass
class ROIParameterOptions:
color: Union[QtGui.QColor, tuple[int, int, int]] = plot_colors[0]
zlevel: int = 1
x: float = 0.
y: float = 0.
width: float = 10.
height: float = 10.
angle: float = 0.
[docs]
class RoiParameter(GroupParameter):
def __init__(self, dim: ROIDim, descriptor: str, index: int = 0, **kwargs):
kwargs.pop('type', None)
kwargs.pop('name', None)
removable = kwargs.pop('removable', True)
context = kwargs.pop('context', ['Copy'])
super().__init__(name=roi_format(index), type='roi_group',
removable=removable, context=context, **kwargs)
self.opts['dim'] = dim
self.opts['descriptor'] = descriptor
self.opts['index'] = index
self.roi_dim = dim
self.index = index
self.descriptor = descriptor
if len(self.children()) == 0:
if dim == ROIDim.ROI1D:
self.addChildren(self.make_ROIParam1D(descriptor, index))
elif dim == ROIDim.ROI2D:
self.addChildren(self.make_ROIParam2D(descriptor, index))
[docs]
def to_options(self) -> ROIParameterOptions:
options = ROIParameterOptions(
color=self['color'],
zlevel=self['zlevel'],
x=self['position', 'x'],
y=self['position', 'y'],
)
if self.roi_dim == ROIDim.ROI2D:
options.width = self['size', 'width']
options.height = self['size', 'height']
options.angle = self['angle']
return options
[docs]
@staticmethod
def makeChannelsParam(dim=ROIDim.ROI2D):
if dim == ROIDim.ROI2D:
child = [{'title': 'Use channel', 'name': 'use_channel', 'type': 'itemselect', 'checkbox': True,
'value': dict(all_items=['red', 'green', 'blue'],
selected=['red']),
}]
else:
child = [{'title': 'Use channel', 'name': 'use_channel', 'type': 'itemselect', 'checkbox': True}]
return child
[docs]
@staticmethod
def makeDisplayParam(index: int = 0, roi_dim=ROIDim.ROI1D):
color = list(np.roll(plot_colors, index)[0])
color = mkColor(color)
if roi_dim == ROIDim.ROI1D:
color.setAlpha(50)
return [{'title': 'Color', 'name': 'color', 'type': 'color', 'value': color},
{'name': 'zlevel', 'title': 'Z-level', 'type': 'int', 'expanded': False, 'value': 10}]
[docs]
@staticmethod
def makeMathParam(dim: ROIDim = ROIDim.ROI2D):
return [{'title': 'Math type:', 'name': 'math_function', 'type': 'list',
'limits': data_processors.functions_filtered(dim.map_to_datadim())}]
[docs]
@staticmethod
def make_ROIParam2D(descriptor: str, index: int):
children = []
children.extend([{'title': 'Type', 'name': 'roi_type', 'type': 'list', 'value': descriptor,
'limits': ROI2D_TYPES, 'readonly': False}])
children.append({'title': 'Process data', 'name': 'process_data', 'type': 'led_push',
'value': config.get(('utils', 'plotting', 'process_roi'), True)})
children.extend(RoiParameter.makeChannelsParam(ROIDim.ROI2D))
children.extend(RoiParameter.makeMathParam(ROIDim.ROI2D))
children.extend(RoiParameter.makeDisplayParam(index, ROIDim.ROI2D))
children.extend([{'title': 'Center', 'name': 'position', 'type': 'group', 'expanded': False, 'children': [
{'name': 'x', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6},
{'name': 'y', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6},
]}])
children.extend([
{'name': 'size', 'type': 'group', 'expanded': False, 'children': [
{'name': 'width', 'type': 'float', 'value': 10, 'step': 1, 'decimals': 6},
{'name': 'height', 'type': 'float', 'value': 10, 'step': 1, 'decimals': 6},
]},
{'name': 'angle', 'type': 'float', 'value': 0, 'step': 1}])
return children
[docs]
@staticmethod
def make_ROIParam1D(descriptor: str, index: int):
children = []
children.append({'title': 'Process data', 'name': 'process_data', 'type': 'led_push',
'value': config.get(('utils', 'plotting', 'process_roi'), True)})
children.extend(RoiParameter.makeChannelsParam(ROIDim.ROI1D))
children.extend(RoiParameter.makeMathParam(ROIDim.ROI1D))
children.extend(RoiParameter.makeDisplayParam(index=index, roi_dim=ROIDim.ROI1D))
children.extend([{'title': 'Position', 'name': 'position', 'type': 'group', 'children': [
{'title': 'Left', 'name': 'x', 'type': 'float', 'value': 0, 'step': 1},
{'title': 'Right', 'name': 'y', 'type': 'float', 'value': 10, 'step': 1},
]}])
return children
[docs]
def roi_from_param(self, index: int) -> ROI:
options = self.to_options()
if self.roi_dim == ROIDim.ROI1D:
return self.make_ROI(index, self.roi_dim, self.descriptor,
pos=(options.x, options.y),
color=options.color)
else:
return self.make_ROI(index, self.roi_dim, self.descriptor,
pos=(options.x, options.y),
size=(options.width, options.height),
color=options.color,
angle=options.angle,
)
[docs]
def make_ROI(self, index, roi_type: ROIDim, roi_descriptor: str,
pos=(0., 0.), size=(10., 10.), **kwargs) -> ROI:
newindex = index
if roi_type == ROIDim.ROI1D:
roi = self.make_ROI1D(newindex, pos, **kwargs)
elif roi_type == ROIDim.ROI2D:
roi = self.make_ROI2D(roi_descriptor, newindex, pos, size, **kwargs)
return roi
[docs]
def make_ROI1D(self, index, pos, compute=True, **kwargs) -> ROI:
"""Convenience function to make custom ROI_1D
Args:
index (int): Current index of ROI
pos: Initial position of ROI
Returns:
roi: LinearROI
"""
roi = ROIFactory.create(ROIDim.ROI1D,
ROIFactory.get_descriptors_from_dimensionality(ROIDim.ROI1D)[0],
index=index, pos=pos, compute=compute, **kwargs)
# roi.setZValue(-10)
#roi.setOpacity(0.2)
return roi
[docs]
def make_ROI2D(self, descriptor: str, index, pos, size, compute=True, **kwargs) -> ROI:
"""Convenience function to make custom ROI_2D
Args:
descriptor (str): name of 2D ROI
index (int): Current index of ROI
pos: Initial position of ROI
size: Initial size of ROI
Returns:
roi: pg.ROI
"""
return ROIFactory.create(ROIDim.ROI2D, descriptor,
index=index, pos=pos,
size=size, name=roi_format(index),
compute=compute, **kwargs)
registerParameterType('roi_group', RoiParameter, override=True)
[docs]
class ROISync(QtCore.QObject):
def __init__(self, roi_dim: ROIDim,
roi_options=ROIParameterOptions()):
super().__init__()
self.roi_dim = roi_dim
initial_value = {
'color': roi_options.color,
'zlevel': roi_options.zlevel,
'x': roi_options.x,
'y': roi_options.y,
}
if self.roi_dim == ROIDim.ROI2D:
initial_value.update({
'width': roi_options.width,
'height': roi_options.height,
'angle': roi_options.angle
},
)
self.entries_sync = WidgetSync(
initial_value=initial_value,
validator=self.validator)
[docs]
@classmethod
def sync_from_param(cls, param: RoiParameter):
return cls(param.roi_dim, roi_options=param.to_options())
[docs]
def validator(self, value: dict) -> dict:
for key in value:
if isinstance(value[key], (ROI, LinearROI)):
if key in ('x', 'y') and self.roi_dim == ROIDim.ROI2D:
if key == 'x':
value[key] = getattr(value[key], 'center')()[0]
else:
value[key] = getattr(value[key], 'center')()[1]
else:
value[key] = getattr(value[key], key)()
elif isinstance(value[key], QtGui.QColor) and self.roi_dim == ROIDim.ROI1D:
value[key].setAlphaF(0.3)
return value
[docs]
def sync_entries_with(self, roi: ROI, roi_parameter: RoiParameter):
roi_property_map = {
'color': {
'setter': roi.set_color,
'mode': SyncMode.FROM_SYNC,
},
'zlevel': {
'setter': roi.setZValue,
'mode': SyncMode.FROM_SYNC,
},
}
parameter_property_map = {
'color': {'param': roi_parameter.child('color')},
'zlevel': {'param': roi_parameter.child('zlevel')},
'x': {'param': roi_parameter.child('position', 'x')},
'y': {'param': roi_parameter.child('position', 'y')},
}
if self.roi_dim == ROIDim.ROI1D:
roi_property_map.update({
'x': {
'signal': roi.sigRegionChangeFinished,
'getter': roi.x,
'setter': roi.set_x,
'mode': SyncMode.BIDIRECTIONAL,
},
'y': {
'signal': roi.sigRegionChangeFinished,
'getter': roi.y,
'setter': roi.set_y,
'mode': SyncMode.BIDIRECTIONAL,
},})
if self.roi_dim == ROIDim.ROI2D:
parameter_property_map.update({
'width': {'param': roi_parameter.child('size', 'width')},
'height': {'param': roi_parameter.child('size', 'height')},
'angle': {'param': roi_parameter.child('angle')},
},
)
roi_property_map.update({
'x': {
'signal': roi.sigRegionChangeFinished,
'getter': lambda: roi.center()[0],
'setter': lambda cx: roi.set_center((cx, roi.center()[1])),
'mode': SyncMode.BIDIRECTIONAL,
},
'y': {
'signal': roi.sigRegionChangeFinished,
'getter': lambda: roi.center()[1],
'setter': lambda cy: roi.set_center((roi.center()[0], cy)),
'mode': SyncMode.BIDIRECTIONAL,
},
'width': {
'signal': roi.sigRegionChangeFinished,
'getter': roi.width,
'setter': roi.set_width,
'mode': SyncMode.BIDIRECTIONAL,
},
'height': {
'signal': roi.sigRegionChangeFinished,
'getter': roi.height,
'setter': roi.set_height,
'mode': SyncMode.BIDIRECTIONAL,
},
'angle': {
'signal': roi.sigRegionChangeFinished,
'getter': roi.angle,
'setter': lambda angle: roi.setAngle(angle, center=(0.5, 0.5)),
'mode': SyncMode.BIDIRECTIONAL,
},
})
self.entries_sync.bind_properties(
roi,
property_map=roi_property_map
)
self.entries_sync.bind_parameter(
roi_parameter,
property_map=parameter_property_map
)
if __name__ == '__main__':
import sys
from qtpy import QtWidgets
from pymodaq_gui.utils.utils import mkQApp
from pymodaq_gui.parameter import ParameterTree
app = mkQApp('RoiSync')
from pymodaq_gui.plotting.data_viewers.viewer2D import Viewer2D, generate_uniform_data
widget_viewer = QtWidgets.QWidget()
viewer = Viewer2D(widget_viewer)
im = viewer.view.image_widget
sync_2D = ROISync(ROIDim.ROI2D)
sync_1D = ROISync(ROIDim.ROI1D)
tree_2D = ParameterTree()
tree_1D = ParameterTree()
roi_2D = ROIFactory.create(ROIDim.ROI2D, 'EllipseROI')
roi_2D_param = RoiParameter(ROIDim.ROI2D, 'EllipseROI')
roi_1D = ROIFactory.create(ROIDim.ROI1D, 'LinearROI')
roi_1D_param = RoiParameter(ROIDim.ROI1D, 'LinearROI', index=2)
tree_2D.setParameters(roi_2D_param)
tree_1D.setParameters(roi_1D_param)
dwa = generate_uniform_data()
viewer.show_data(dwa)
im.plotItem.addItem(roi_2D)
im.plotItem.addItem(roi_1D)
widget_viewer.show()
tree_2D.show()
tree_1D.show()
sync_2D.sync_entries_with(roi_2D, roi_2D_param)
sync_1D.sync_entries_with(roi_1D, roi_1D_param)
sync_1D.entries_sync.set_value(sync_1D.entries_sync.value, emit=True)
sync_2D.entries_sync.set_value(sync_2D.entries_sync.value, emit=True)
sys.exit(app.exec())