diff --git a/python/scripts/test_virtual.py b/python/scripts/test_virtual.py index a44c26b89..2629e264f 100644 --- a/python/scripts/test_virtual.py +++ b/python/scripts/test_virtual.py @@ -3,31 +3,148 @@ import subprocess import os import sys import time -sys.path.append(os.path.join(os.getcwd(), 'bin')) -from sls_detector import ExperimentalDetector +import datetime as dt + +sys.path.append(os.path.join(os.getcwd(), "bin")) +from sls_detector import ExperimentalDetector, detectorSettings + +n_detectors = 3 +start_port = 1952 +port_step = 3 + @pytest.fixture(scope="module") def virtual_jf_detectors(request): - print('Setting up virtual detectors') + """ + Fixture that is run once for the module + will launch virtual servers and clean up + after + """ + print("Setting up virtual detectors") + + # Ensure that no detector servers are running subprocess.run(["killall", "jungfrauDetectorServer_virtual"]) - virtual_jf_detectors = [] - virtual_jf_detectors.append(subprocess.Popen('bin/jungfrauDetectorServer_virtual')) - time.sleep(5) + + # Ensure no shared memory exists before tests start + d = ExperimentalDetector() + d.free() + + # Start servers + virtual_jf_detectors = [ + subprocess.Popen( + [ + "bin/jungfrauDetectorServer_virtual", + "--port", + f"{start_port+port_step*i}", + ] + ) + for i in range(n_detectors) + ] + + # Allow server startup to complete + time.sleep(3) + def fin(): print("Cleaning up virtual detectors") + d = ExperimentalDetector() + d.free() subprocess.run(["killall", "jungfrauDetectorServer_virtual"]) - - request.addfinalizer(fin) return virtual_jf_detectors # provide the fixture value +def test_shmid(virtual_jf_detectors): + d = ExperimentalDetector() + assert d.getShmId() == 0 + d.free() + + d = ExperimentalDetector(73) + assert d.getShmId() == 73 + d.free() + + def test_hostname(virtual_jf_detectors): d = ExperimentalDetector() - d.hostname = 'localhost' - assert d.hostname == 'localhost' + d.hostname = "localhost" + assert d.hostname == ["localhost"] + + d.hostname = [f"localhost:{start_port+i*port_step}" for i in range(n_detectors)] + assert d.hostname == ["localhost"] * n_detectors + def test_fwversion(virtual_jf_detectors): d = ExperimentalDetector() - assert d.detectorversion == 0 #Firmware of virtual detector \ No newline at end of file + assert d.detectorversion == 0 # Firmware of virtual detector + assert d.getFirmwareVersion() == [0] * n_detectors + + +def test_len(virtual_jf_detectors): + d = ExperimentalDetector() + assert len(d) == n_detectors + assert d.size() == n_detectors + + +def test_module_geometry(virtual_jf_detectors): + d = ExperimentalDetector() + geo = d.module_geometry + assert geo.x == 1 + assert geo.y == 3 + + +def test_module_size(virtual_jf_detectors): + d = ExperimentalDetector() + geo = d.module_size + assert geo.x == 1024 + assert geo.y == 512 + + +def test_settings(virtual_jf_detectors): + d = ExperimentalDetector() + assert d.settings == detectorSettings.DYNAMICGAIN + + gain_list = [ + detectorSettings.FIXGAIN1, + detectorSettings.FIXGAIN2, + detectorSettings.FORCESWITCHG1, + detectorSettings.FORCESWITCHG2, + detectorSettings.DYNAMICHG0, + detectorSettings.DYNAMICGAIN, + ] + + # Set all viable gain for Jungfrau to make sure nothing is crashing + for gain in gain_list: + d.settings = gain + assert d.settings == gain + + d.setSettings(detectorSettings.FORCESWITCHG1, [1]) + assert d.settings == [ + detectorSettings.DYNAMICGAIN, + detectorSettings.FORCESWITCHG1, + detectorSettings.DYNAMICGAIN, + ] + + d.settings = detectorSettings.DYNAMICGAIN + assert d.settings == detectorSettings.DYNAMICGAIN + +def test_frames(virtual_jf_detectors): + d = ExperimentalDetector() + d.frames = 10 + assert d.frames == 10 + +# def test_triggers(virtual_jf_detectors): + +def test_exptime(virtual_jf_detectors): + d = ExperimentalDetector() + + #default value + assert d.exptime == 1e-5 + + d.exptime = 1.5 + assert d.exptime == 1.5 + + t = dt.timedelta(microseconds=10) + d.exptime = t + assert d.exptime == 10e-6 + + \ No newline at end of file diff --git a/python/sls_detector/__init__.py b/python/sls_detector/__init__.py index dc5202f13..44f4fb3b4 100755 --- a/python/sls_detector/__init__.py +++ b/python/sls_detector/__init__.py @@ -7,3 +7,5 @@ from _sls_detector import DetectorApi import _sls_detector runStatus = _sls_detector.slsDetectorDefs.runStatus +detectorType = _sls_detector.slsDetectorDefs.detectorType +detectorSettings = _sls_detector.slsDetectorDefs.detectorSettings diff --git a/python/sls_detector/experimental.py b/python/sls_detector/experimental.py index 7b7614cf6..a814ef54b 100755 --- a/python/sls_detector/experimental.py +++ b/python/sls_detector/experimental.py @@ -3,9 +3,11 @@ from _sls_detector import slsDetectorDefs runStatus = slsDetectorDefs.runStatus from .utils import element_if_equal, all_equal +from .utils import Geometry, to_geo import datetime as dt from functools import wraps +from collections import namedtuple def freeze(cls): @@ -52,9 +54,15 @@ class ExperimentalDetector(CppDetectorApi): # CONFIGURATION + def __len__(self): + return self.size() + + def free(self): + self.freeSharedMemory() + @property def hostname(self): - return element_if_equal(self.getHostname()) + return self.getHostname() @hostname.setter def hostname(self, hostnames): @@ -69,6 +77,39 @@ class ExperimentalDetector(CppDetectorApi): def detectorversion(self): return element_if_equal(self.getFirmwareVersion()) + @property + def detector_type(self): + return element_if_equal(self.getDetectorType()) + + @property + def module_geometry(self): + return to_geo(self.getModuleGeometry()) + + @property + def module_size(self): + ms = [to_geo(item) for item in self.getModuleSize()] + return element_if_equal(ms) + + @property + def detector_size(self): + return to_geo(self.getDetectorSize()) + + @property + def settings(self): + return element_if_equal(self.getSettings()) + + @settings.setter + def settings(self, value): + self.setSettings(value) + + @property + def frames(self): + return element_if_equal(self.getNumberOfFrames()) + + @frames.setter + def frames(self, n_frames): + self.setNumberOfFrames(n_frames) + # Acq @property def rx_status(self): @@ -178,7 +219,10 @@ class ExperimentalDetector(CppDetectorApi): @exptime.setter def exptime(self, t): - self.setExptime(dt.timedelta(seconds=t)) + if isinstance(t, dt.timedelta): + self.setExptime(t) + else: + self.setExptime(dt.timedelta(seconds=t)) @property def subexptime(self): @@ -187,7 +231,10 @@ class ExperimentalDetector(CppDetectorApi): @subexptime.setter def subexptime(self, t): - self.setSubExptime(dt.timedelta(seconds=t)) + if isinstance(t, dt.timedelta): + self.setSubExptime(t) + else: + self.setSubExptime(dt.timedelta(seconds=t)) @property def period(self): @@ -196,5 +243,8 @@ class ExperimentalDetector(CppDetectorApi): @period.setter def period(self, t): - self.setPeriod(dt.timedelta(seconds=t)) + if isinstance(t, dt.timedelta): + self.setPeriod(t) + else: + self.setPeriod(dt.timedelta(seconds=t)) diff --git a/python/sls_detector/utils.py b/python/sls_detector/utils.py index b4f93fb12..fc0c6537a 100755 --- a/python/sls_detector/utils.py +++ b/python/sls_detector/utils.py @@ -2,7 +2,16 @@ Utility functions that are useful for testing and troubleshooting but not directly used in controlling the detector """ +from collections import namedtuple +import _sls_detector #C++ lib +Geometry = namedtuple('Geometry', ['x', 'y']) + +def to_geo(value): + if isinstance(value, _sls_detector.xy): + return Geometry(x = value.x, y = value.y) + else: + raise ValueError("Can only convert sls_detector.xy") def all_equal(mylist): """If all elements are equal return true otherwise false""" diff --git a/python/src/enums.cpp b/python/src/enums.cpp index 054690c59..87fbdd239 100644 --- a/python/src/enums.cpp +++ b/python/src/enums.cpp @@ -7,6 +7,11 @@ namespace py = pybind11; void init_enums(py::module &m) { py::class_ Defs(m, "slsDetectorDefs"); + py::class_ xy(m, "xy"); + // xy.def(py::init()) + xy.def_readwrite("x", &slsDetectorDefs::xy::x); + xy.def_readwrite("y", &slsDetectorDefs::xy::y); + py::enum_(Defs, "runStatus") .value("IDLE", slsDetectorDefs::runStatus::IDLE) .value("ERROR", slsDetectorDefs::runStatus::ERROR) @@ -16,4 +21,34 @@ void init_enums(py::module &m) { .value("RUNNING", slsDetectorDefs::runStatus::RUNNING) .value("STOPPED", slsDetectorDefs::runStatus::STOPPED) .export_values(); + + py::enum_(Defs, "detectorType") + .value("GENERIC", slsDetectorDefs::detectorType::GENERIC) + .value("EIGER", slsDetectorDefs::detectorType::EIGER) + .value("GOTTHARD", slsDetectorDefs::detectorType::GOTTHARD) + .value("JUNGFRAU", slsDetectorDefs::detectorType::JUNGFRAU) + .value("CHIPTESTBOARD", slsDetectorDefs::detectorType::CHIPTESTBOARD) + .value("MOENCH", slsDetectorDefs::detectorType::MOENCH) + .value("MYTHEN3", slsDetectorDefs::detectorType::MYTHEN3) + .value("GOTTHARD2", slsDetectorDefs::detectorType::GOTTHARD2) + .export_values(); + + py::enum_(Defs, "detectorSettings") + .value("GET_SETTINGS", slsDetectorDefs::detectorSettings::GET_SETTINGS) + .value("STANDARD", slsDetectorDefs::detectorSettings::STANDARD) + .value("FAST", slsDetectorDefs::detectorSettings::FAST) + .value("HIGHGAIN", slsDetectorDefs::detectorSettings::HIGHGAIN) + .value("DYNAMICGAIN", slsDetectorDefs::detectorSettings::DYNAMICGAIN) + .value("LOWGAIN", slsDetectorDefs::detectorSettings::LOWGAIN) + .value("MEDIUMGAIN", slsDetectorDefs::detectorSettings::MEDIUMGAIN) + .value("VERYHIGHGAIN", slsDetectorDefs::detectorSettings::VERYHIGHGAIN) + .value("DYNAMICHG0", slsDetectorDefs::detectorSettings::DYNAMICHG0) + .value("FIXGAIN1", slsDetectorDefs::detectorSettings::FIXGAIN1) + .value("FIXGAIN2", slsDetectorDefs::detectorSettings::FIXGAIN2) + .value("FORCESWITCHG1", slsDetectorDefs::detectorSettings::FORCESWITCHG1) + .value("FORCESWITCHG2", slsDetectorDefs::detectorSettings::FORCESWITCHG2) + .value("VERYLOWGAIN", slsDetectorDefs::detectorSettings::VERYLOWGAIN) + .value("UNDEFINED", slsDetectorDefs::detectorSettings::UNDEFINED) + .value("UNINITIALIZED", slsDetectorDefs::detectorSettings::UNINITIALIZED) + .export_values(); } diff --git a/python/src/experimental.cpp b/python/src/experimental.cpp index f4dfce6cb..d9547b04f 100644 --- a/python/src/experimental.cpp +++ b/python/src/experimental.cpp @@ -10,17 +10,17 @@ namespace py = pybind11; void init_experimental(py::module &m) { using sls::Detector; using sls::Positions; + py::class_ CppDetectorApi(m, "CppDetectorApi"); CppDetectorApi .def(py::init()) // Configuration - .def("free", &Detector::freeSharedMemory) .def("freeSharedMemory", &Detector::freeSharedMemory) .def("loadConfig", &Detector::loadConfig) .def("loadParameters", &Detector::loadParameters) - .def("getHostname", &Detector::getHostname, py::arg() = Positions{}) .def("setHostname", &Detector::setHostname) + .def("getHostname", &Detector::getHostname, py::arg() = Positions{}) .def("getShmId", &Detector::getShmId) .def("getFirmwareVersion", &Detector::getFirmwareVersion, py::arg() = Positions{}) @@ -34,7 +34,27 @@ void init_experimental(py::module &m) { .def("getDetectorType", &Detector::getDetectorType, py::arg() = Positions{}) .def("size", &Detector::size) + .def("getModuleGeometry", &Detector::getModuleGeometry) + .def("getModuleSize", &Detector::getModuleSize, py::arg() = Positions{}) + .def("getDetectorSize", &Detector::getDetectorSize) + .def("setDetectorSize", &Detector::setDetectorSize) + .def("getSettings", &Detector::getSettings, py::arg() = Positions{}) + .def("setSettings", &Detector::setSettings, py::arg(), + py::arg() = Positions{}) + // TODO! Python funcs for callbacks? + + // Acquisition Parameters + .def("getNumberOfFrames", &Detector::getNumberOfFrames) + .def("setNumberOfFrames", &Detector::setNumberOfFrames) + .def("getNumberOfTriggers", &Detector::getNumberOfTriggers) + .def("setNumberOfTriggers", &Detector::setNumberOfTriggers) + .def("setExptime", &Detector::setExptime, py::arg(), + py::arg() = Positions{}) + .def("getExptime", &Detector::getExptime, py::arg() = Positions{}) + .def("setPeriod", &Detector::setPeriod, py::arg(), + py::arg() = Positions{}) + .def("getPeriod", &Detector::getPeriod, py::arg() = Positions{}) // Acq related .def("acquire", &Detector::acquire) @@ -71,12 +91,7 @@ void init_experimental(py::module &m) { py::arg() = Positions{}) // Time - .def("setExptime", &Detector::setExptime, py::arg(), - py::arg() = Positions{}) - .def("getExptime", &Detector::getExptime, py::arg() = Positions{}) - .def("setPeriod", &Detector::setPeriod, py::arg(), - py::arg() = Positions{}) - .def("getPeriod", &Detector::getPeriod, py::arg() = Positions{}) + .def("setSubExptime", &Detector::setSubExptime, py::arg(), py::arg() = Positions{}) .def("getSubExptime", &Detector::getSubExptime,