ct2.py 8.71 KB
Newer Older
1
2
3
4
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
Benoit Formet's avatar
Benoit Formet committed
5
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
6
7
8
9
# Distributed under the GNU LGPLv3. See LICENSE for more info.

"""CT2 (P201/C208) bliss acquisition master"""

10
import numpy
11
import gevent.event
12
from collections import deque
13

14
from bliss.common import event
15
from bliss.scanning.chain import AcquisitionMaster
bliss administrator's avatar
bliss administrator committed
16
from bliss.scanning.channel import AcquisitionChannel
17
from bliss.scanning.acquisition.counter import IntegratingCounterAcquisitionSlave
18
19
20
21
22
23
from bliss.controllers.ct2.device import (
    AcqMode,
    AcqStatus,
    StatusSignal,
    PointNbSignal,
    ErrorSignal,
24
    DataSignal,
25
)
26
27
28
29
30
31


class CT2AcquisitionMaster(AcquisitionMaster):

    SoftTrigModes = AcqMode.IntTrigMulti, AcqMode.SoftTrigReadout

32
33
34
    def __init__(
        self,
        device,
35
        ctrl_params=None,
36
37
38
39
40
41
        npoints=1,
        acq_expo_time=1.,
        acq_point_period=None,
        acq_mode=AcqMode.IntTrigMulti,
        prepare_once=True,
        start_once=True,
42
        read_all_triggers=False,
43
    ):
44
        self._connected = False
45
46
        self.acq_expo_time = acq_expo_time
        self.acq_mode = acq_mode
47
        self.acq_point_period = acq_point_period
48
49
50
51
        self.first_trigger = None
        self.status = None
        self.last_point_ready = None
        self.last_error = None
52
53
        self._ready_event = gevent.event.Event()
        self._ready_event.set()
54
55
56
57
        if acq_mode in self.SoftTrigModes:
            trigger_type = self.SOFTWARE
        else:
            trigger_type = self.HARDWARE
58
        self.use_internal_clock = acq_mode == AcqMode.IntTrigSingle
59
60
61
        kwargs = dict(
            npoints=npoints,
            prepare_once=prepare_once,
62
            start_once=start_once,
63
            trigger_type=trigger_type,
64
            ctrl_params=ctrl_params,
65
        )
66
        super().__init__(device, **kwargs)
67

68
69
        self.device.read_all_triggers = read_all_triggers

70
71
72
    def add_counter(self, counter):
        pass

73
74
75
    def __on_event(self, value, signal):
        if signal == StatusSignal:
            self.status = value
76
            if value == AcqStatus.Ready:
77
                self._ready_event.set()
78
79
        elif signal == PointNbSignal:
            self.last_point_ready = value
80
            if value >= 0 and not self.use_internal_clock:
81
                self._ready_event.set()
82
83
84
        elif signal == ErrorSignal:
            self.last_error = value

85
86
87
    def connect(self):
        if self._connected:
            return
88
        event.connect(self.device.server, event.Any, self.__on_event)
89
90
91
92
93
        self._connected = True

    def disconnect(self):
        if not self._connected:
            return
94
        event.disconnect(self.device.server, event.Any, self.__on_event)
95
96
        self._connected = False

97
    def prepare(self):
98
        self.connect()
99
100
101
102
103
        self.first_trigger = True
        device = self.device
        device.acq_mode = self.acq_mode
        device.acq_expo_time = self.acq_expo_time
        device.acq_nb_points = self.npoints
104
        device.acq_point_period = self.acq_point_period
105
        self.device.prepare_acq()
106
107

    def start(self):
108
109
110
111
        if (
            self.parent is None
            or self.trigger_type == AcquisitionMaster.HARDWARE  # top master
        ):
112
            self.trigger()
113
114
115

    def stop(self):
        self.device.stop_acq()
116
        self.disconnect()
117
118

    def trigger(self):
119
        self._ready_event.clear()
120
121
        self.trigger_slaves()

122
123
124
125
126
127
        if self.first_trigger:
            self.first_trigger = False
            self.device.start_acq()
        else:
            self.device.trigger_point()

128
129
130
    def trigger_ready(self):
        return self._ready_event.is_set()

131
    def wait_ready(self):
132
        self._ready_event.wait()
133
134


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class CT2VarTimeAcquisitionMaster(CT2AcquisitionMaster):
    """
    Allow point acquisition time to be variable in step scans.
    """

    def __init__(self, device, acq_expo_time=[], **kwargs):
        kwargs.pop("npoints", None)
        kwargs.pop("prepare_once", None)
        kwargs.pop("start_once", None)

        self._acq_expo_times = acq_expo_time
        super().__init__(
            device, prepare_once=False, start_once=False, npoints=1, **kwargs
        )

bliss administrator's avatar
bliss administrator committed
150
151
152
        self._channel_time = AcquisitionChannel("acq_time", numpy.double, (), unit="s")
        self.channels.extend((self._channel_time,))

153
154
155
156
157
158
    def __iter__(self):
        npoints = len(self._acq_expo_times)
        for expo_time in self._acq_expo_times:
            self.acq_expo_time = expo_time
            yield self

bliss administrator's avatar
bliss administrator committed
159
160
161
162
    def prepare(self):
        self._channel_time.emit(self.acq_expo_time)
        super().prepare()

163
164
165
166
167
168
169
170
    def start(self):
        if (
            self.parent is None
            or self.trigger_type == AcquisitionMaster.HARDWARE  # top master
        ):
            self.trigger()


171
class CT2CounterAcquisitionSlave(IntegratingCounterAcquisitionSlave):
172
173
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
174
        self.__buffer = deque()
175
        self.__buffer_event = gevent.event.Event()
176
        self._event_connected = False
177

178
179
180
    def prepare_device(self):
        channels = []
        counter_indexes = {}
181
        ctrl = self.device._master_controller
182
183
184
185
186
187
        in_channels = ctrl.INPUT_CHANNELS
        timer_counter = ctrl.internal_timer_counter
        point_nb_counter = ctrl.internal_point_nb_counter
        channel_counters = dict(
            [(counter.channel, counter) for counter in self._counters]
        )
188
        cntname2channel = {cnt.name: cnt.channel for cnt in self._counters}
189
190
191
192
193
194
195
196
197
198
199
        for i, channel in enumerate(sorted(channel_counters)):
            counter = channel_counters[channel]
            if channel in in_channels:
                channels.append(channel)
            elif channel == timer_counter:
                i = -2
                counter.timer_freq = ctrl.timer_freq
            elif channel == point_nb_counter:
                i = -1
            counter_indexes[counter] = i
        ctrl.acq_channels = channels
200
201
202
203
204

        def _sort(chan):
            return cntname2channel[chan.short_name]

        self.channels.sort(key=_sort)
205
        # counter_indexes dict<counter: index in data array>
206
207
208
209
210
211
212
213
214
215
        def _counters_sort(val):
            k, v = val
            if v >= 0:
                return v
            else:
                return len(counter_indexes) - v + 1

        self.device.counter_indexes = dict(
            sorted(counter_indexes.items(), key=_counters_sort)
        )
216
217
218
219
        # a hack here: since this prepare is called AFTER the
        # CT2AcquisitionMaster prepare, we do a "second" prepare
        # here after the acq_channels have been configured
        ctrl.prepare_acq()
220
221

    def start_device(self):
222
223
        # Connect only at scan startup.
        if not self._event_connected:
224
225
226
            event.connect(
                self.device._master_controller.server, DataSignal, self.rx_data
            )
227
            self._event_connected = True
228
229

    def stop_device(self):
230
231
232
        event.disconnect(
            self.device._master_controller.server, DataSignal, self.rx_data
        )
233
234
235

    def stop(self):
        self._stop_flag = True
236
        self.__buffer_event.set()
237
        self.stop_device()
238
239
240
241
242
243
244
245
246
247
248
249

    def rx_data(self, data, signal):
        self.__buffer.extend(data)
        self.__buffer_event.set()

    def reading(self):
        from_index = 0
        while (
            not self.npoints or self._nb_acq_points < self.npoints
        ) and not self._stop_flag:
            self.__buffer_event.wait()
            self.__buffer_event.clear()
250
251
            data = numpy.array(self.__buffer, dtype=numpy.uint32)
            self.__buffer.clear()
252
253
254
            if not data.size:
                continue
            data_len = len(data)
255
256
257

            # in read all triggers mode: replace 1st point value (at 0) by 2nd
            # point value if available.
258
            if self.device._master_controller.read_all_triggers:
259
260
261
262
263
264
265
266
267
268
269
270
271
272
                if from_index == 0:
                    if data_len > 1:
                        data[0] = data[1]
                        from_index += data_len
                        self._nb_acq_points += data_len
                        self._emit_new_data(data.T)
                else:
                    from_index += data_len
                    self._nb_acq_points += data_len
                    self._emit_new_data(data.T)
            else:
                from_index += data_len
                self._nb_acq_points += data_len
                self._emit_new_data(data.T)
273
            gevent.sleep(0)
274

275
        # finally
276
        data = numpy.array(self.__buffer, dtype=numpy.uint32)
277
        self.__buffer.clear()
278
279
        if data.size:
            self._emit_new_data(data.T)
280
281
282
283
284

    def _emit_new_data(self, data):
        super()._emit_new_data(
            [c.convert(v) for c, v in zip(self.device.counter_indexes, data)]
        )