mosca-client 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
File without changes
File without changes
@@ -0,0 +1,116 @@
1
+ import time
2
+
3
+
4
+ from ..mca import McaBase
5
+
6
+
7
+ # from .base import McaController, TriggerMode
8
+
9
+
10
+ class Dante(McaBase):
11
+ """XGLab's Dante mosca 2 client."""
12
+
13
+ # Bounds for config_file's wait loop, in seconds.
14
+ CONFIG_LOAD_START_WAIT = 5.
15
+ CONFIG_LOAD_TIMEOUT = 60.
16
+
17
+ def __init__(self, *args, **kwargs):
18
+ super().__init__(*args, **kwargs)
19
+
20
+ def __info__(self) -> str:
21
+ """
22
+ Add specific information for XIA controllers.
23
+ """
24
+ txt = super().__info__()
25
+ txt += f"\n * config file: {self.config_file}\n"
26
+ txt += f" * gating mode: {self.gating_mode}\n"
27
+ return txt
28
+
29
+ # def _apply_settings(self, config_file=None, **kwargs):
30
+ # if config_file is not None:
31
+ # self.config_file = config_file
32
+ # return super()._apply_settings(**kwargs)
33
+
34
+ def stats_mapping(self):
35
+ return {
36
+ "realtime": "elapsed_time",
37
+ "trigger_livetime": "trigger_livetime",
38
+ "icr": "icr",
39
+ "ocr": "ocr",
40
+ }
41
+
42
+ @property
43
+ def gating_mode(self):
44
+ return self.proxy.gating_mode
45
+
46
+ def available_configurations(self):
47
+ return self.proxy.available_configurations
48
+
49
+ def _load_configuration(self, configuration: str | None = None) -> None:
50
+ proxy = self.proxy
51
+ print(f"Setting config file to {configuration}. This may take a while.")
52
+ proxy.set_configuration_file_async(configuration)
53
+
54
+ # Wait for the server to actually start processing the request
55
+ # (mca_status flips to UNINITIALIZED) instead of blindly sleeping
56
+ # for a fixed amount of time -- avoids racing against slow thread
57
+ # scheduling on the server side, which could otherwise let a stale
58
+ # prior status (e.g. READY) slip past the checks below undetected.
59
+ start_deadline = time.time() + self.CONFIG_LOAD_START_WAIT
60
+ while proxy.mca_status != "UNINITIALIZED" and time.time() < start_deadline:
61
+ time.sleep(0.1)
62
+
63
+ if proxy.mca_status != "UNINITIALIZED":
64
+ raise RuntimeError(
65
+ f"Server did not start processing config [{configuration}] "
66
+ f"(mca_status still {proxy.mca_status!r} after "
67
+ f"{self.CONFIG_LOAD_START_WAIT}s)."
68
+ )
69
+
70
+ deadline = time.time() + self.CONFIG_LOAD_TIMEOUT
71
+ spin = ["\\", "-", "/", "-"]
72
+ i = 0
73
+ while proxy.mca_status == "UNINITIALIZED":
74
+ if time.time() > deadline:
75
+ raise RuntimeError(
76
+ f"Timed out waiting for config [{configuration}] to load "
77
+ f"(still UNINITIALIZED after {self.CONFIG_LOAD_TIMEOUT}s)."
78
+ )
79
+ init_msg = proxy.init_message
80
+ print(f"\033[KIn progress [{spin[i%4]}] {init_msg} ", end="\r")
81
+ time.sleep(0.3)
82
+ i = (i + 1) % 4
83
+
84
+ init_msg = proxy.init_message
85
+ print(f"\033[K{init_msg}")
86
+ if proxy.mca_status != "READY":
87
+ raise RuntimeError(
88
+ f"Failed to load config [{configuration}]. Please check server logs."
89
+ )
90
+ return self.current_configuration()
91
+
92
+ def current_configuration(self):
93
+ return self.proxy.configuration_file
94
+
95
+ @property
96
+ def config_files_list(self):
97
+ # legacy code
98
+ return self.available_configurations()
99
+
100
+ @property
101
+ def config_file(self):
102
+ # legacy code
103
+ return self.current_configuration()
104
+
105
+ @config_file.setter
106
+ def config_file(self, config_file):
107
+ # legacy code
108
+ self.load_configuration(config_file)
109
+
110
+ @property
111
+ def config_dir(self):
112
+ # legacy code
113
+ return self.proxy.configuration_directory
114
+
115
+
116
+ Mca = Dante
@@ -0,0 +1,8 @@
1
+ from .dante import Dante
2
+
3
+
4
+ class DantePlus(Dante):
5
+ pass
6
+
7
+
8
+ Mca = DantePlus
@@ -0,0 +1,9 @@
1
+ from ..mca import McaBase
2
+ from .simulation import SimulatorMixin
3
+
4
+
5
+ class Dummy(SimulatorMixin, McaBase):
6
+ pass
7
+
8
+
9
+ Mca = Dummy
@@ -0,0 +1,71 @@
1
+ import numpy as np
2
+
3
+ from .xia import Xia
4
+
5
+
6
+ class FalconX(Xia):
7
+ def __info__(self) -> str:
8
+ txt = super().__info__()
9
+ txt += f" * mca_refresh: {self.proxy.mca_refresh:.4f} s\n"
10
+ return txt
11
+
12
+ def scan_metadata(self) -> dict:
13
+ metadata = super().scan_metadata()
14
+ metadata["mca_refresh"] = self.proxy.mca_refresh
15
+ return metadata
16
+
17
+ def filter_parameters(self,
18
+ acq_realtime=None,
19
+ block_size=None,
20
+ trigger_mode=None,
21
+ mca_refresh=None,
22
+ **kwargs):
23
+ """ Computes mca_refresh and block_size if not provided.
24
+ * mca_refresh: 0.01 <= acq_realtime or 0.1 <= 0.1
25
+ * block_size: 1 <= 1 / acq_realtime <= 1020
26
+ Attention: block_size is not changed if acq_realtime
27
+ is None or nul!
28
+ """
29
+
30
+ params = super().filter_parameters(**kwargs)
31
+
32
+ acq_realtime = params.get("acq_realtime", acq_realtime)
33
+ block_size = params.get("block_size", block_size)
34
+ trigger_mode = params.get("trigger_mode", trigger_mode)
35
+ mca_refresh = params.get("mca_refresh", mca_refresh)
36
+
37
+ if trigger_mode == "INT_TRIG_READOUT":
38
+ params["acq_nb_points"] = 1
39
+
40
+ if mca_refresh is None:
41
+ mca_refresh = np.clip(acq_realtime or 0.1,
42
+ 0.01, 0.1)
43
+ params["mca_refresh"] = float(mca_refresh)
44
+
45
+ if block_size is None and acq_realtime:
46
+ if trigger_mode in ("GATE", "EXT_TRIG"):
47
+ # aiming for a buffer swap every second
48
+ block_size = np.clip(
49
+ 1. / acq_realtime,
50
+ 1, 1020)
51
+ params["block_size"] = int(block_size)
52
+
53
+ return params
54
+
55
+ def set_parameters(self,
56
+ mca_refresh=None,
57
+ **kwargs) -> None:
58
+ super().set_parameters(**kwargs)
59
+ if mca_refresh is not None:
60
+ self.proxy.mca_refresh = mca_refresh
61
+
62
+ @property
63
+ def mca_refresh(self) -> float:
64
+ return self.proxy.mca_refresh
65
+
66
+ @mca_refresh.setter
67
+ def mca_refresh(self, value: float) -> None:
68
+ self.proxy.mca_refresh = value
69
+
70
+
71
+ Mca = FalconX
@@ -0,0 +1,58 @@
1
+ from tango import DeviceProxy
2
+
3
+ from ..utils import is_ds_exported, Callback
4
+
5
+
6
+ class Generator:
7
+ def __init__(self, name, tg_url):
8
+ self._name = name
9
+
10
+ self._tg_url = tg_url
11
+ self._proxy = None
12
+
13
+ @property
14
+ def proxy(self):
15
+ if self._proxy is None:
16
+ if not is_ds_exported(self._tg_url):
17
+ raise RuntimeError(f"Server {self._tg_url} doesn't seem to be running.")
18
+ self._proxy = DeviceProxy(self._tg_url)
19
+ return self._proxy
20
+
21
+ # TODO: put this in bliss
22
+ def __info__(self):
23
+ return f"Generator {self.__class__.__name__} ({self.proxy.name()})"
24
+
25
+ @property
26
+ def acq_nb_points(self):
27
+ return self.proxy.acq_nb_points
28
+
29
+ @acq_nb_points.setter
30
+ def acq_nb_points(self, acq_nb_points):
31
+ self.proxy.acq_nb_points = acq_nb_points
32
+
33
+ @property
34
+ def trigger_mode(self):
35
+ return self.proxy.trigger_mode
36
+
37
+ @trigger_mode.setter
38
+ def trigger_mode(self, trigger_mode):
39
+ self.proxy.trigger_mode = trigger_mode
40
+
41
+ @property
42
+ def acq_realtime(self):
43
+ return self.proxy.acq_realtime
44
+
45
+ @acq_realtime.setter
46
+ def acq_realtime(self, acq_realtime):
47
+ self.proxy.acq_realtime = acq_realtime
48
+
49
+ @property
50
+ def use_external_trigger(self):
51
+ return self.proxy.use_external_trigger
52
+
53
+ @use_external_trigger.setter
54
+ def use_external_trigger(self, use_external_trigger):
55
+ self.proxy.use_external_trigger = use_external_trigger
56
+
57
+ def hw_trig(self):
58
+ self.proxy.hw_trig()
@@ -0,0 +1,9 @@
1
+ from .dante import Dante
2
+ from .simulation import SimulatorMixin
3
+
4
+
5
+ class SimDante(SimulatorMixin, Dante):
6
+ pass
7
+
8
+
9
+ Mca = SimDante
@@ -0,0 +1,9 @@
1
+ from .falconx import FalconX
2
+ from .simulation import SimulatorMixin
3
+
4
+
5
+ class SimFalconX(SimulatorMixin, FalconX):
6
+ pass
7
+
8
+
9
+ Mca = SimFalconX
@@ -0,0 +1,18 @@
1
+ from tango import DeviceProxy
2
+
3
+
4
+ class SimulatorMixin:
5
+ def __init__(self, *args, **kwargs):
6
+ self._generator = None
7
+ super().__init__(*args, **kwargs)
8
+
9
+ def generator(self):
10
+ if self._generator is None:
11
+ # keep everything before "family/instance" as-is, so this
12
+ # also works with a fully-qualified tango://host:port/... URL
13
+ parts = self.tg_url.split("/")
14
+ prefix = "/".join(parts[:-2])
15
+ instance = parts[-1]
16
+ tg_name = f"{prefix}/generator/{instance}"
17
+ self._generator = DeviceProxy(tg_name)
18
+ return self._generator
@@ -0,0 +1,66 @@
1
+ from ..mca import McaBase
2
+ from ..utils import ChangeTangoTimeout
3
+
4
+
5
+ class Xia(McaBase):
6
+
7
+ def __init__(self, *args, **kwargs):
8
+ super().__init__(*args, **kwargs)
9
+
10
+ def stats_mapping(self):
11
+ return {
12
+ # MOSCA : NxWriter
13
+ "output": "events",
14
+ "icr": "icr",
15
+ "ocr": "ocr",
16
+ "livetime": "trigger_livetime",
17
+ "deadtime": "deadtime",
18
+ "realtime": "realtime",
19
+ "triggers": "triggers",
20
+ "livetime_events": "energy_livetime",
21
+ "deadtime_correction": "deadtime_correction",
22
+ }
23
+
24
+ # def _apply_settings(self, ini_file=None, **kwargs):
25
+ # if ini_file:
26
+ # self.configuration_file = ini_file
27
+ # return super()._apply_settings(**kwargs)
28
+
29
+ def __info__(self) -> str:
30
+ txt = super().__info__()
31
+ txt += f"\n * ini file: {self.proxy.ini_file}\n"
32
+ txt += f" * gate ignore: {self.proxy.gate_ignore}\n"
33
+ txt += f" * pix per buf: {self.proxy.pixels_per_buffer}\n"
34
+ txt += f" * preset type: {self.proxy.preset_type}\n"
35
+ txt += f" * preset value: {self.proxy.preset_value}\n"
36
+ return txt
37
+
38
+ @property
39
+ def configuration_file(self) -> str:
40
+ return self.current_configuration()
41
+
42
+ @configuration_file.setter
43
+ def configuration_file(self, fname: str) -> None:
44
+ self.load_configuration(fname)
45
+
46
+ @property
47
+ def configuration_dir(self) -> str:
48
+ return self.proxy.ini_dir
49
+
50
+ def available_configurations(self) -> list[str]:
51
+ return self.proxy.available_ini_files()
52
+
53
+ def _load_configuration(self, configuration: str | None = None) -> None:
54
+ try:
55
+ with ChangeTangoTimeout(self.proxy, 10):
56
+ print(f"Loading {configuration}...")
57
+ if configuration is None:
58
+ configuration = ""
59
+ self.proxy.ini_file = configuration
60
+ except Exception as ex:
61
+ # TODO: report server msg
62
+ raise RuntimeError(f"Loading configuration file '{configuration}' has failed.") from ex
63
+ return self.current_configuration()
64
+
65
+ def current_configuration(self):
66
+ return self.proxy.ini_file
@@ -0,0 +1,96 @@
1
+ from tango import DeviceProxy
2
+
3
+ from .utils import is_ds_exported, Callback
4
+
5
+
6
+ class McaBase:
7
+
8
+ def __init__(self, name, tg_url, on_change_cb=None, **kwargs):
9
+ self._name = name
10
+
11
+ # settings_name = f"mosca:{name}:{self.__class__.__name__}"
12
+ # self._settings2 = HashObjSetting(settings_name)
13
+ self._settings = kwargs.get("settings", {})
14
+
15
+ self._add_setting("configuration", None)
16
+
17
+ self._tg_url = tg_url
18
+ self._proxy = None
19
+ self._on_change_cb = Callback(on_change_cb, blocked=True)
20
+
21
+ @property
22
+ def mca_status(self):
23
+ return self.proxy.mca_status
24
+
25
+ @property
26
+ def tg_url(self):
27
+ return self._tg_url
28
+
29
+ def stats_mapping(self):
30
+ return {}
31
+
32
+ def changed(self):
33
+ self._on_change_cb()
34
+
35
+ def _add_setting(self, key, value=None):
36
+ if key not in self._settings:
37
+ self._settings[key] = value
38
+
39
+ def _set_settings(self, **kwargs):
40
+ for key, value in kwargs.items():
41
+ self._settings[key] = value
42
+
43
+ def initialize(self, load_configuration=True):
44
+ self._on_change_cb.blocked = True
45
+ # iterating on KEYS, because ITEMS doesn't work (returns #ERR)
46
+ settings = {key: (self._settings[key] if load_configuration or key != "configuration" else None)
47
+ for key in self._settings.keys()}
48
+ try:
49
+ self._apply_settings(**settings)
50
+ finally:
51
+ self._on_change_cb.blocked = False
52
+
53
+ def _apply_settings(self, configuration=None, **kwargs):
54
+ if configuration is not None:
55
+ self.load_configuration(configuration)
56
+
57
+ @property
58
+ def proxy(self):
59
+ if self._proxy is None:
60
+ if not is_ds_exported(self._tg_url):
61
+ raise RuntimeError(f"Server {self._tg_url} doesn't seem to be running.")
62
+ self._proxy = DeviceProxy(self._tg_url)
63
+ return self._proxy
64
+
65
+ # TODO: put this in bliss
66
+ def __info__(self):
67
+ return f"MCA {self.__class__.__name__} ({self.proxy.name()})"
68
+
69
+ def load_configuration(self, *args, **kwargs):
70
+ reply = None
71
+ try:
72
+ reply = self._load_configuration(*args, **kwargs)
73
+ except NotImplementedError:
74
+ print(f"MCA doesn't implement load_configuration.")
75
+ else:
76
+ self._set_settings(configuration=reply)
77
+ self.initialize(load_configuration=False)
78
+ self.changed()
79
+ return reply
80
+
81
+ # To be overridden, if necessary
82
+
83
+ def _load_configuration(self, *args, **kwargs):
84
+ raise NotImplementedError
85
+
86
+ def filter_parameters(self, **kwargs):
87
+ return {}
88
+
89
+ def set_parameters(self, **kwargs):
90
+ pass
91
+
92
+ def available_configurations(self, *args, **kwargs):
93
+ return []
94
+
95
+ def current_configuration(self, *args, **kwargs):
96
+ return "N/A"