Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,15 @@ Infos
=====

The SDK from Holoeye is required to operate those plugins. All plugins can also receive a numpy
array directly to be loaded into the SLM. Working with **SLM Display SDK (Python) < 4.0.0**
array directly to be loaded into the SLM.

Both the **SLM Display SDK (Python) 3** and **4** are supported, and can be installed at the same time. The plugins
find them through environment variables:

* SDK 3: ``HEDS_<version>_PYTHON_MODULES`` (the last one, sorted by name, is used)
* SDK 4 and newer: ``HEDS_<major>_<minor>_PYTHON``, e.g. ``HEDS_4_0_PYTHON`` (the most recent version is used, its
folders ``api/python`` and ``examples`` are added to the python path)

The ``SDK version`` setting of the plugins selects the one to use: ``auto`` (default) uses the SDK 4 if it is
installed (more recent), else the SDK 3. With the SDK 4, the Holoeye preview window is not opened at initialization
(it can fail when authorizations are required), use the ``Show Preview?`` setting to open it.
32 changes: 24 additions & 8 deletions src/pymodaq_plugins_holoeye/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
# call the Holoeye python library for the correct path

import os
import re
from pathlib import Path
import sys
from pathlib import Path
from pymodaq_utils.logger import set_logger # to be imported by other modules.

from pymodaq_utils.utils import get_version
from pymodaq_plugins_holoeye.utils import Config

__version__ = get_version(__package__)
config = Config()


environs = []
for env in os.environ.keys():
if 'HEDS' in env and 'MODULES' in env:
environs.append(env)

environs = sorted(environs)

def _add_to_path(folder: str):
if folder and os.path.isdir(folder) and folder not in sys.path:
sys.path.append(folder)

if 'HEDS_PYTHON_MODULES' in environs: #old stuff without the sdk version
environs.remove('HEDS_PYTHON_MODULES', )

sys.path.append(os.getenv(environs[-1], ''))
# SDK 3: python modules folder given by the HEDS_*_PYTHON_MODULES environment variable
environs = sorted(env for env in os.environ if 'HEDS' in env and 'MODULES' in env)
if 'HEDS_PYTHON_MODULES' in environs: # old stuff without the sdk version
environs.remove('HEDS_PYTHON_MODULES')
if environs:
_add_to_path(os.getenv(environs[-1], ''))

# SDK 4 and newer: root folder given by HEDS_<major>_<minor>_PYTHON (the name changes with the version of the SDK),
# the python libraries are in api/python and examples. The most recent version is used.
_sdk_versions = {}
for env in os.environ:
match = re.fullmatch(r'HEDS_(\d+)_(\d+)_PYTHON', env)
if match and int(match.group(1)) >= 4:
_sdk_versions[(int(match.group(1)), int(match.group(2)))] = env
if _sdk_versions:
_sdk_root = os.getenv(_sdk_versions[max(_sdk_versions)], '')
_add_to_path(os.path.join(_sdk_root, 'api', 'python'))
_add_to_path(os.path.join(_sdk_root, 'examples'))
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

import pymodaq_plugins_holoeye # mandatory if not imported from somewhere else to load holeye module from local install
from holoeye import slmdisplaysdk


from pymodaq.control_modules.move_utility_classes import main
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from typing import Union, List, Dict

import pymodaq_plugins_holoeye # mandatory if not imported from somewhere else to load holeye module from local install
from holoeye import slmdisplaysdk


from pymodaq.control_modules.move_utility_classes import DAQ_Move_base, comon_parameters_fun, main
Expand Down
7 changes: 5 additions & 2 deletions src/pymodaq_plugins_holoeye/hardware/mock_holoeye.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ class SLMInstance:
def __init__(self):
pass

def close(self):
pass

def open(self):
return ErrorCode.NoError.value

def errorString(self, error_code: ErrorCode):
return error_code.name
def errorString(self, error_code):
return ErrorCode(error_code).name

@property
def width_px(self) -> int:
Expand Down
60 changes: 60 additions & 0 deletions src/pymodaq_plugins_holoeye/hardware/sdk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Selection of the Holoeye SLM Display SDK to use: version 3 (``holoeye.slmdisplaysdk``) or version 4 (``HEDS``).

Both SDKs can be installed at the same time (each has its own environment variable, see ``pymodaq_plugins_holoeye``).
They are only imported when requested, and both expose the same minimal interface to the plugins, the one of the
``SLMInstance`` of the SDK 3: ``open``, ``close``, ``errorString``, ``width_px``, ``height_px``, ``showData``,
``showPhasevalues`` and ``utilsSLMPreviewShow``.
"""
import importlib.util
import sys
from typing import List

import pymodaq_plugins_holoeye # adds the SDK folders to the python path

SDK_CHOICES = ['auto', '3', '4']
NO_ERROR = 0


def _is_available(module_name: str) -> bool:
if module_name in sys.modules:
return True
try:
return importlib.util.find_spec(module_name) is not None
except (ImportError, ValueError):
return False


def installed_sdks() -> List[str]:
"""The versions of the SDK that can be imported, the most recent first"""
sdks = []
if _is_available('HEDS') and _is_available('hedslib'):
sdks.append('4')
if _is_available('holoeye.slmdisplaysdk'):
sdks.append('3')
return sdks


def select_sdk(version: str = 'auto') -> str:
"""Return '3' or '4'. With 'auto' the SDK 4 is used when installed (more recent), else the SDK 3"""
version = str(version)
if version not in SDK_CHOICES:
raise ValueError(f'Unknown SDK version {version}, should be one of {SDK_CHOICES}')
sdks = installed_sdks()
if version == 'auto':
if not sdks:
raise ImportError('No Holoeye SLM Display SDK found: install the SDK 3 or 4 and set its environment '
'variable (HEDS_*_PYTHON_MODULES for the SDK 3, HEDS_<major>_<minor>_PYTHON for the SDK 4 and newer)')
return sdks[0]
if version not in sdks:
raise ImportError(f'The Holoeye SLM Display SDK {version} cannot be imported, '
f'installed SDKs: {sdks if sdks else "none"}')
return version


def get_slm_class(version: str = 'auto'):
"""Return the class driving the SLM with the requested SDK"""
if select_sdk(version) == '3':
from holoeye.slmdisplaysdk import SLMInstance
return SLMInstance
from pymodaq_plugins_holoeye.hardware.sdk4 import SLMInstance4
return SLMInstance4
54 changes: 54 additions & 0 deletions src/pymodaq_plugins_holoeye/hardware/sdk4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Adapter of the Holoeye SLM Display SDK 4 (module ``HEDS``) to the interface of the ``SLMInstance`` of the SDK 3"""
import numpy as np

NO_ERROR = 0


class SLMInstance4:
"""Same methods as the ``SLMInstance`` of the SDK 3, used by the plugins, but based on the SDK 4"""

def __init__(self):
import HEDS
from hedslib import heds_types
self._heds = HEDS
self._types = heds_types
self._slm = None
self.width_px = 0
self.height_px = 0
self.pixelsize_um = 0.

def open(self, preview: bool = False):
"""Open the SLM window. The preview window is not opened by default (it may require authorizations)"""
error = self._heds.SDK.Init(4, 0)
if error != self._types.HEDSERR_NoError:
return error
self._slm = self._heds.SLM.Init(preselect='', openPreview=preview)
error = self._slm.errorCode()
if error != self._types.HEDSERR_NoError:
return error
self.width_px = self._slm.width_px()
self.height_px = self._slm.height_px()
self.pixelsize_um = self._slm.pixelsize_um()
return NO_ERROR

def errorString(self, error):
return self._heds.SDK.ErrorString(error)

def close(self):
if self._slm is not None:
self._slm.window().close()
self._slm = None

def showData(self, data):
"""Show 8 bits grey levels"""
grey = np.ascontiguousarray(np.clip(np.rint(np.asarray(data, dtype=float)), 0, 255).astype(np.uint8))
return self._slm.showImageData(grey)

def showPhasevalues(self, data):
"""Show phase values in radians, wrapped in [0, 2pi["""
phase = np.ascontiguousarray(np.mod(np.asarray(data, dtype=np.float32), 2 * np.pi))
return self._slm.showPhaseData(phase, phase_unit=2 * np.pi)

def utilsSLMPreviewShow(self, show=True):
preview = self._slm.preview()
return preview.open() if show else preview.close()
18 changes: 13 additions & 5 deletions src/pymodaq_plugins_holoeye/resources/daq_move_HoloeyeBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
from pymodaq_utils.logger import set_logger, get_module_name
from pymodaq_utils.math_utils import wrap

from holoeye.slmdisplaysdk import SLMInstance, ErrorCode
from pymodaq_plugins_holoeye.hardware.sdk import SDK_CHOICES, NO_ERROR, get_slm_class

logger = set_logger(get_module_name(__file__))
config = HoloConfig()


class DAQ_Move_HoloeyeBase(DAQ_Move_base):

controller_class = SLMInstance
controller_class = None # if None: the class of the SDK selected in the settings
shaping_type: str = None
shaping_settings: List = []

Expand All @@ -39,6 +39,8 @@ class DAQ_Move_HoloeyeBase(DAQ_Move_base):
{'title': 'Width:', 'name': 'width', 'type': 'int', 'value': 0, 'readonly': True},
{'title': 'Height:', 'name': 'height', 'type': 'int', 'value': 0, 'readonly': True},
]},
{'title': 'SDK version:', 'name': 'sdk', 'type': 'list', 'limits': SDK_CHOICES, 'value': 'auto',
'tip': 'Holoeye SLM Display SDK to use, auto: the SDK 4 if installed, else the SDK 3'},
{'title': 'Show Preview?:', 'name': 'show_preview', 'type': 'bool', 'value': False},
{'title': 'Shaping type:', 'name': 'shaping_type', 'type': 'str', 'value': ''},
{'title': 'shaping options:', 'name': 'options', 'type': 'group', 'visible': True,
Expand Down Expand Up @@ -81,14 +83,14 @@ def ini_stage(self, controller=None):
daq_utils.ThreadCommand
"""
if self.is_master:
self.controller = self.controller_class()
self.controller = self.create_controller()
else:
self.controller = controller


if self.is_master:
error = self.controller.open()
if error != ErrorCode.NoError:
if error != NO_ERROR:
raise IOError(f'SLM Error: {self.controller.errorString(error)}')

data_width = self.controller.width_px
Expand All @@ -103,12 +105,18 @@ def ini_stage(self, controller=None):
initialized = True
return info, initialized

def create_controller(self):
controller_class = self.controller_class
if controller_class is None:
controller_class = get_slm_class(self.settings['sdk'])
return controller_class()

def commit_settings(self, param):
"""Apply settings modification to the SLM

To be implemented in real implementations
"""
if param.name() == 'show_preview':
if param.name() == 'show_preview' and self.controller is not None:
self.controller.utilsSLMPreviewShow(param.value())

@property
Expand Down
Loading
Loading