diff --git a/pyproject.toml b/pyproject.toml index 57072c0..a5eefc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "pymodaq_utils>=5.3.1", "pymodaq_gui>=5.3.0", "pymodaq_data>=5.3.0", + "pymodaq_plugins_utils>=5.3.4", "slmsuite>=0.5.0", ] diff --git a/src/pymodaq_plugins_slmsuite/hardware/slm_controller.py b/src/pymodaq_plugins_slmsuite/hardware/slm_controller.py index 1b43b92..299c44e 100644 --- a/src/pymodaq_plugins_slmsuite/hardware/slm_controller.py +++ b/src/pymodaq_plugins_slmsuite/hardware/slm_controller.py @@ -49,6 +49,18 @@ def shape(self) -> tuple[int, int]: """The shape (height, width) of the SLM in pixels""" return tuple(int(s) for s in self.slm.shape) + @property + def pitch_um(self) -> tuple[float, float]: + """The size (x, y) of the pixels in micrometer, as known by slmsuite (the driver when it can tell, else 8 um)""" + pitch = np.broadcast_to(np.asarray(self.slm.pitch_um, dtype=float), (2,)) + return float(pitch[0]), float(pitch[1]) + + @property + def has_internal_calibration(self) -> bool: + """True if the SLM converts a phase to grey levels with a calibration of its own: a lookup table known by + slmsuite, or the one the Meadowlark SDK loads from the hardware""" + return self.slm_type == 'Meadowlark' or getattr(self.slm, 'lut', None) is not None + @property def bitresolution(self) -> int: """The number of grey levels of the SLM""" diff --git a/src/pymodaq_plugins_slmsuite/resources/daq_move_SlmsuiteBase.py b/src/pymodaq_plugins_slmsuite/resources/daq_move_SlmsuiteBase.py index 6fd8ee7..9c70b18 100644 --- a/src/pymodaq_plugins_slmsuite/resources/daq_move_SlmsuiteBase.py +++ b/src/pymodaq_plugins_slmsuite/resources/daq_move_SlmsuiteBase.py @@ -9,6 +9,8 @@ from pymodaq_utils.utils import ThreadCommand from pymodaq_utils.logger import set_logger, get_module_name +from pymodaq_plugins_utils.hardware.shaper import ModulatorType, shaper_params, fill_shaper_settings + from pymodaq_plugins_slmsuite import config from pymodaq_plugins_slmsuite.hardware.slm_controller import SlmController @@ -50,7 +52,7 @@ class DAQ_Move_SlmsuiteBase(DAQ_Move_base): 'tip': 'Add the wavefront correction loaded in slmsuite (if any) to the displayed phases'}, {'title': 'Input as grey levels:', 'name': 'input_as_grey_levels', 'type': 'bool', 'value': False, 'tip': 'Interpret the received arrays as grey levels instead of phases in radians'}, - ] + ] + shaper_params() params = common_slm_params + comon_parameters_fun(is_multiaxes, _axis_names, epsilon=_epsilons) def ini_attributes(self): @@ -74,6 +76,9 @@ def ini_stage(self, controller=None): height, width = self.controller.shape self.settings.child('info', 'width').setValue(width) self.settings.child('info', 'height').setValue(height) + fill_shaper_settings(self.settings, self.get_slm_type(), width, height, *self.controller.pitch_um, + modulator_type=ModulatorType.PHASE, + has_internal_calibration=self.controller.has_internal_calibration) self.settings.child('info', 'levels').setValue(self.controller.bitresolution) self.current_value = DataActuator(self._title, data=[np.zeros(self.controller.shape)]) diff --git a/tests/test_slmsuite_plugins.py b/tests/test_slmsuite_plugins.py index be6bce4..7676ad9 100644 --- a/tests/test_slmsuite_plugins.py +++ b/tests/test_slmsuite_plugins.py @@ -65,6 +65,49 @@ def test_not_a_dict(self): parse_kwargs('[1, 2]') +def _geometry(plugin): + group = plugin.settings.child('shaper_geometry') + return [group[name] for name in ('width', 'height', 'pixel_size_x', 'pixel_size_y')] + + +def _properties(plugin): + group = plugin.settings.child('shaper_properties') + return [group[name] for name in ('name', 'has_internal_calibration', 'use_internal_calibration')] + + +class TestShaperGeometry: + def test_simulated(self, slm): + width, height, size_x, size_y = _geometry(slm) + assert (width, height) == (WIDTH, HEIGHT) + assert size_x > 0 and size_y > 0 + + def test_pitch_from_slmsuite(self): + controller = SlmController('SimulatedSLM', resolution=(WIDTH, HEIGHT), pitch_um=(5., 6.)) + assert controller.pitch_um == (5., 6.) + controller.close() + + def test_pitch_given_once(self): + controller = SlmController('SimulatedSLM', resolution=(WIDTH, HEIGHT), pitch_um=7.) + assert controller.pitch_um == (7., 7.) + controller.close() + + def test_fake_controller(self, qapp, fake_controller): + plugin = make_actuator(DAQ_Move_SlmsuiteMeadowlark) + assert _geometry(plugin) == [6, 4, 8., 9.] + assert _properties(plugin) == ['Meadowlark', True, True] + plugin.close() + + def test_no_internal_calibration_by_default(self, slm): + assert _properties(slm) == ['SimulatedSLM', False, False] + + def test_lut_is_an_internal_calibration(self): + controller = SlmController('SimulatedSLM', resolution=(WIDTH, HEIGHT)) + assert not controller.has_internal_calibration + controller.slm.lut = np.arange(256) + assert controller.has_internal_calibration + controller.close() + + class TestSimulatedActuator: def test_infos(self, slm): assert slm.settings['info', 'width'] == WIDTH @@ -127,6 +170,8 @@ class FakeController: def __init__(self, slm_type, **kwargs): self.slm_type, self.kwargs = slm_type, kwargs self.shape = (4, 6) + self.pitch_um = (8., 9.) + self.has_internal_calibration = slm_type == 'Meadowlark' self.bitresolution = 256 FakeController.instances.append(self)