2
2
from dataclasses import dataclass , field , fields
3
3
from datetime import datetime
4
4
from enum import Enum
5
- from typing import Any , Callable , Dict , List , Optional , Union , Tuple
5
+ from typing import Any , Callable , Dict , Generic , List , Optional , Tuple , TypeVar , Union
6
6
7
7
import numpy as np
8
8
from tickit .core .device import Device , DeviceUpdate
9
9
from tickit .core .typedefs import SimTime
10
10
from typing_extensions import TypedDict
11
11
12
12
from tickit_devices .merlin .acq_header import get_acq_header
13
- from tickit_devices .merlin .commands import ErrorCode
14
-
15
- from typing import Generic , TypeVar
13
+ from tickit_devices .merlin .parameters import (
14
+ AcquisitionType ,
15
+ ChipMode ,
16
+ ColourMode ,
17
+ CommandType ,
18
+ CounterMode ,
19
+ ErrorCode ,
20
+ State ,
21
+ commands ,
22
+ )
16
23
17
24
MAX_THRESHOLD = 100 # keV, assume that a DAC value of 2**9 - 1 represents this energy
18
25
@@ -56,77 +63,6 @@ def get_threshold_kev(self, threshold: int) -> float:
56
63
return value_DAC * MAX_THRESHOLD / max_DAC
57
64
58
65
59
- class GainMode (int , Enum ):
60
- SLGM = 0
61
- LGM = 1
62
- HGM = 2
63
- SHGM = 3
64
-
65
-
66
- class AcquisitionType (str , Enum ):
67
- NORMAL = "Normal"
68
- TH_SCAN = "Th_scan"
69
- CONFIG = "Config"
70
-
71
-
72
- class ChipMode (str , Enum ):
73
- SPM = "SPM"
74
- CSM = "CSM"
75
- CM = "CM"
76
- CSCM = "CSCM"
77
-
78
-
79
- class Trigger (int , Enum ):
80
- POS = 0
81
- NEG = 1
82
- INT = 2
83
-
84
-
85
- class Polarity (str , Enum ):
86
- POS = "Positive"
87
- NEG = "Negative"
88
-
89
-
90
- class State (int , Enum ):
91
- IDLE = 0
92
- BUSY = 1
93
- Standby = 2
94
-
95
-
96
- class ColourMode (int , Enum ):
97
- MONOCHROME = 0
98
- COLOUR = 1
99
-
100
-
101
- class GapFillMode (int , Enum ):
102
- NONE = 0
103
- ZeroFill = 1
104
- Distribute = 2
105
- Interpolate = 3
106
-
107
-
108
- class FileFormat (int , Enum ):
109
- Binary = 0
110
- ASCII = 1
111
-
112
-
113
- class TriggerOut (int , Enum ):
114
- TriggerInTTL = 0
115
- TriggerInLVDS = 1
116
- TriggerInTTLDelayed = 2
117
- TriggerInLVDSDelayed = 3
118
- FollowShutter = 4
119
- OnePerAcqBurst = 5
120
- ShutterAndSensorReadout = 6
121
- Busy = 7
122
-
123
-
124
- class CounterMode (int , Enum ):
125
- Counter0 = 0
126
- Counter1 = 1
127
- Both = 2
128
-
129
-
130
66
@dataclass
131
67
class Chip :
132
68
id : str
@@ -181,14 +117,16 @@ def __init__(
181
117
setter : Optional [Callable [[T ], None ]] = None ,
182
118
):
183
119
self ._value = getter
184
- self .set : Callable [[T ], None ] = setter if setter is not None else self ._set
120
+ self .set : Callable [[T ], None ] = (
121
+ setter if setter is not None else self .default_set
122
+ )
185
123
186
124
def get (self ) -> T :
187
125
if callable (self ._value ):
188
126
return self ._value ()
189
127
return self ._value
190
128
191
- def _set (self , value : T ):
129
+ def default_set (self , value : T ):
192
130
if callable (self ._value ):
193
131
raise RuntimeError ("Can not use default setter with custom getter" )
194
132
self ._value = value
@@ -207,10 +145,17 @@ class MerlinDetector(Device):
207
145
_acq_header_enabled : bool = True
208
146
_current_frame : int = 1
209
147
_current_layer : int = 0
210
- _colour_mode : ColourMode = ColourMode . MONOCHROME
148
+ _colour_mode : ColourMode = commands [ CommandType . SET ][ "COLOURMODE" ]
211
149
_configuration : str = ""
150
+ _detector_status : State = commands [CommandType .GET ]["DETECTORSTATUS" ]
212
151
_images_remaining : int = 0
213
- _gap_time_ns : int = 1_000_000 # 1ms
152
+ _gap_time_ns : int = int (
153
+ (
154
+ commands [CommandType .SET ]["ACQUISITIONPERIOD" ]
155
+ - commands [CommandType .SET ]["ACQUISITIONTIME" ]
156
+ )
157
+ * 1e6
158
+ )
214
159
_last_header : str = ""
215
160
_last_encoded_image : Optional [bytes ] = None
216
161
_last_image_shape : Optional [Tuple [int , int ]] = None
@@ -223,59 +168,15 @@ class MerlinDetector(Device):
223
168
humidity : float = 0.0
224
169
medipix_clock : int = 120
225
170
readout_system : str = "Merlin Quad"
226
- shutter_time_ns : int = 10_000_000 # 10ms
227
- parameters : Dict [str , MerlinParameter [Any ]] = field (
228
- default_factory = lambda : {
229
- "COUNTERDEPTH" : MerlinParameter (12 ),
230
- "CHARGESUMMING" : MerlinParameter (False ),
231
- "CONTINUOUSRW" : MerlinParameter (False ),
232
- "DEADTIMECORRECTION" : MerlinParameter (False ),
233
- "DETECTORSTATUS" : MerlinParameter (State .IDLE ),
234
- "ENABLECOUNTER1" : MerlinParameter (CounterMode .Counter0 ),
235
- "FILECOUNTER" : MerlinParameter (0 ),
236
- "FILEDIRECTORY" : MerlinParameter ("" ),
237
- "FILEENABLE" : MerlinParameter (False ),
238
- "FILEFORMAT" : MerlinParameter (FileFormat .Binary ),
239
- "FILLMODE" : MerlinParameter (GapFillMode .NONE ),
240
- "FILENAME" : MerlinParameter ("" ),
241
- "FLATFIELDCORRECTION" : MerlinParameter (False ),
242
- "FLATFIELDFILE" : MerlinParameter ("None" ),
243
- "GAIN" : MerlinParameter (GainMode .SLGM ),
244
- "HVBIAS" : MerlinParameter (15 ),
245
- "MASKINDATA" : MerlinParameter (False ),
246
- "NUMFRAMESTOACQUIRE" : MerlinParameter (1 ),
247
- "NUMFRAMESPERTRIGGER" : MerlinParameter (1 ),
248
- "OPERATINGENERGY" : MerlinParameter (0 ),
249
- "PIXELMATRIXSAVEFILE" : MerlinParameter ("" ),
250
- "PIXELMATRIXLOADFILE" : MerlinParameter ("" ),
251
- "POLARITY" : MerlinParameter (Polarity .POS ),
252
- "SOFTWAREVERSION" : MerlinParameter ("0.69.0.2" ),
253
- "TEMPERATURE" : MerlinParameter (0.0 ),
254
- "THNUMSTEPS" : MerlinParameter (0 ),
255
- "THSTART" : MerlinParameter (0 ),
256
- "THSTEP" : MerlinParameter (0 ),
257
- "THSCAN" : MerlinParameter (0 ),
258
- "THSTOP" : MerlinParameter (0 ),
259
- "THWINDOWMODE" : MerlinParameter (False ),
260
- "THWINDOWSIZE" : MerlinParameter (0 ),
261
- "TRIGGERSTART" : MerlinParameter (Trigger .INT ),
262
- "TRIGGERSTOP" : MerlinParameter (Trigger .INT ),
263
- "SoftTriggerOutTTL" : MerlinParameter (False ),
264
- "SoftTriggerOutLVDS" : MerlinParameter (False ),
265
- "TriggerInTTLDelay" : MerlinParameter (0 ),
266
- "TriggerInLVDSDelay" : MerlinParameter (0 ),
267
- "TriggerOutTTL" : MerlinParameter (TriggerOut .TriggerInTTL ),
268
- "TriggerOutLVDS" : MerlinParameter (TriggerOut .TriggerInTTL ),
269
- "TriggerOutTTLInvert" : MerlinParameter (False ),
270
- "TriggerOutLVDSInvert" : MerlinParameter (False ),
271
- "TriggerUseDelay" : MerlinParameter (False ),
272
- "TriggerInTTL" : MerlinParameter (False ),
273
- "TriggerInLVDS" : MerlinParameter (False ),
274
- }
275
- )
171
+ shutter_time_ns : int = int (commands [CommandType .SET ]["ACQUISITIONTIME" ] * 1e6 )
172
+ parameters : Dict [str , MerlinParameter [Any ]] = field (default_factory = lambda : {})
276
173
277
174
def initialise (self ):
278
175
"""Create parameters with custom getters/setters"""
176
+ self .parameters ["DETECTORSTATUS" ] = MerlinParameter (
177
+ lambda : self ._detector_status ,
178
+ lambda val : setattr (self , "_detector_status" , val ),
179
+ )
279
180
self .parameters ["THRESHOLD0" ] = MerlinParameter (
280
181
lambda : self .get_threshold (0 ),
281
182
lambda val : self .set_threshold (0 , val ),
@@ -327,6 +228,18 @@ def initialise(self):
327
228
lambda : self ._operating_energy ,
328
229
lambda val : self .set_operating_energy (val ),
329
230
)
231
+ ro_params : Dict [str , MerlinParameter [Any ]] = {
232
+ parameter : MerlinParameter (default_value )
233
+ for parameter , default_value in commands [CommandType .GET ].items ()
234
+ if parameter not in self .parameters
235
+ }
236
+ self .parameters .update (ro_params )
237
+ rw_params : Dict [str , MerlinParameter [Any ]] = {
238
+ parameter : MerlinParameter (default_value )
239
+ for parameter , default_value in commands [CommandType .SET ].items ()
240
+ if parameter not in self .parameters
241
+ }
242
+ self .parameters .update (rw_params )
330
243
331
244
def get (self , parameter : str ):
332
245
return self .parameters [parameter ].get ()
@@ -418,14 +331,16 @@ def THSCAN_cmd(self) -> ErrorCode:
418
331
return ErrorCode .UNDERSTOOD
419
332
420
333
def RESET_cmd (self ) -> ErrorCode :
421
- raise NotImplementedError ("Fix this" )
422
- # TODO: how does this work during acquisition?
423
- # skip = ["chips"]
424
- # for f in fields(self):
425
- # if f.name in skip:
426
- # continue
427
- # setattr(self, f.name, f.default)
428
- # return ErrorCode.UNDERSTOOD
334
+ for ctype in [CommandType .GET , CommandType .SET ]:
335
+ for parameter , default in commands [ctype ].items ():
336
+ try :
337
+ if default is not None :
338
+ self .set_param (parameter , default )
339
+ else :
340
+ print ("No default value set for" , parameter )
341
+ except RuntimeError :
342
+ print ("Could not reset parameter" , parameter )
343
+ return ErrorCode .UNDERSTOOD
429
344
430
345
def ABORT_cmd (self ) -> ErrorCode :
431
346
# TODO: write command
@@ -584,5 +499,4 @@ def get_image(self):
584
499
return message
585
500
586
501
def update (self , time : SimTime , inputs : Inputs ) -> DeviceUpdate [Outputs ]:
587
- # print('TODO: Doing nothing in update, see EigerDevice.update for comparison')
588
502
return DeviceUpdate (self .Outputs (), SimTime (time ))
0 commit comments