3
3
import threading
4
4
from datetime import datetime
5
5
import time
6
- from typing import Tuple
6
+ from typing import Tuple , Any , Optional , Dict
7
+ import numpy as np
7
8
8
- from astropy .io import fits
9
-
10
- from pyobs .interfaces import ICamera , ICameraWindow , ICameraBinning , ICooling
9
+ from pyobs .interfaces import ICamera , IWindow , IBinning , ICooling
11
10
from pyobs .modules .camera .basecamera import BaseCamera
11
+ from pyobs .images import Image
12
12
from pyobs .utils .enums import ExposureStatus
13
- from pyobs_fli .flidriver import *
13
+ from pyobs_fli .flidriver import FliDriver , FliTemperature
14
14
15
15
16
16
log = logging .getLogger (__name__ )
17
17
18
18
19
- class FliCamera (BaseCamera , ICamera , ICameraWindow , ICameraBinning , ICooling ):
19
+ class FliCamera (BaseCamera , ICamera , IWindow , IBinning , ICooling ):
20
20
"""A pyobs module for FLI cameras."""
21
21
__module__ = 'pyobs_fli'
22
22
23
- def __init__ (self , setpoint : float = - 20 , * args , ** kwargs ):
23
+ def __init__ (self , setpoint : float = - 20. , ** kwargs : Any ):
24
24
"""Initializes a new FliCamera.
25
25
26
26
Args:
27
27
setpoint: Cooling temperature setpoint.
28
28
"""
29
- BaseCamera .__init__ (self , * args , * *kwargs )
29
+ BaseCamera .__init__ (self , ** kwargs )
30
30
31
31
# variables
32
- self ._driver = None
33
- self ._temp_setpoint = setpoint
32
+ self ._driver : Optional [ FliDriver ] = None
33
+ self ._temp_setpoint : Optional [ float ] = setpoint
34
34
35
35
# window and binning
36
- self ._window = None
37
- self ._binning = None
36
+ self ._window = ( 0 , 0 , 0 , 0 )
37
+ self ._binning = ( 1 , 1 )
38
38
39
- def open (self ):
39
+ def open (self ) -> None :
40
40
"""Open module."""
41
41
BaseCamera .open (self )
42
42
@@ -58,9 +58,10 @@ def open(self):
58
58
self ._window , self ._binning = self ._driver .get_window_binning ()
59
59
60
60
# set cooling
61
- self .set_cooling (True , self ._temp_setpoint )
61
+ if self ._temp_setpoint is not None :
62
+ self .set_cooling (True , self ._temp_setpoint )
62
63
63
- def close (self ):
64
+ def close (self ) -> None :
64
65
"""Close the module."""
65
66
BaseCamera .close (self )
66
67
@@ -70,31 +71,33 @@ def close(self):
70
71
self ._driver .close ()
71
72
self ._driver = None
72
73
73
- def get_full_frame (self , * args , ** kwargs ) -> Tuple [int , int , int , int ]:
74
+ def get_full_frame (self , ** kwargs : Any ) -> Tuple [int , int , int , int ]:
74
75
"""Returns full size of CCD.
75
76
76
77
Returns:
77
78
Tuple with left, top, width, and height set.
78
79
"""
80
+ if self ._driver is None :
81
+ raise ValueError ('No camera driver.' )
79
82
return self ._driver .get_full_frame ()
80
83
81
- def get_window (self , * args , ** kwargs ) -> Tuple [int , int , int , int ]:
84
+ def get_window (self , ** kwargs : Any ) -> Tuple [int , int , int , int ]:
82
85
"""Returns the camera window.
83
86
84
87
Returns:
85
88
Tuple with left, top, width, and height set.
86
89
"""
87
90
return self ._window
88
91
89
- def get_binning (self , * args , ** kwargs ) -> Tuple [int , int ]:
92
+ def get_binning (self , ** kwargs : Any ) -> Tuple [int , int ]:
90
93
"""Returns the camera binning.
91
94
92
95
Returns:
93
96
Tuple with x and y.
94
97
"""
95
98
return self ._binning
96
99
97
- def set_window (self , left : float , top : float , width : float , height : float , * args , ** kwargs ) :
100
+ def set_window (self , left : int , top : int , width : int , height : int , ** kwargs : Any ) -> None :
98
101
"""Set the camera window.
99
102
100
103
Args:
@@ -109,7 +112,7 @@ def set_window(self, left: float, top: float, width: float, height: float, *args
109
112
self ._window = (left , top , width , height )
110
113
log .info ('Setting window to %dx%d at %d,%d...' , width , height , left , top )
111
114
112
- def set_binning (self , x : int , y : int , * args , ** kwargs ) :
115
+ def set_binning (self , x : int , y : int , ** kwargs : Any ) -> None :
113
116
"""Set the camera binning.
114
117
115
118
Args:
@@ -122,18 +125,25 @@ def set_binning(self, x: int, y: int, *args, **kwargs):
122
125
self ._binning = (x , y )
123
126
log .info ('Setting binning to %dx%d...' , x , y )
124
127
125
- def _expose (self , exposure_time : int , open_shutter : bool , abort_event : threading .Event ) -> fits . PrimaryHDU :
128
+ def _expose (self , exposure_time : float , open_shutter : bool , abort_event : threading .Event ) -> Image :
126
129
"""Actually do the exposure, should be implemented by derived classes.
127
130
128
131
Args:
129
- exposure_time: The requested exposure time in ms .
132
+ exposure_time: The requested exposure time in seconds .
130
133
open_shutter: Whether or not to open the shutter.
131
134
abort_event: Event that gets triggered when exposure should be aborted.
132
135
133
136
Returns:
134
137
The actual image.
138
+
139
+ Raises:
140
+ ValueError: If exposure was not successful.
135
141
"""
136
142
143
+ # check driver
144
+ if self ._driver is None :
145
+ raise ValueError ('No camera driver.' )
146
+
137
147
# set binning
138
148
log .info ("Set binning to %dx%d." , self ._binning [0 ], self ._binning [1 ])
139
149
self ._driver .set_binning (* self ._binning )
@@ -151,11 +161,11 @@ def _expose(self, exposure_time: int, open_shutter: bool, abort_event: threading
151
161
# set some stuff
152
162
self ._change_exposure_status (ExposureStatus .EXPOSING )
153
163
self ._driver .init_exposure (open_shutter )
154
- self ._driver .set_exposure_time (int (exposure_time ))
164
+ self ._driver .set_exposure_time (int (exposure_time * 1000. ))
155
165
156
166
# get date obs
157
167
log .info ('Starting exposure with %s shutter for %.2f seconds...' ,
158
- 'open' if open_shutter else 'closed' , exposure_time / 1000. )
168
+ 'open' if open_shutter else 'closed' , exposure_time )
159
169
date_obs = datetime .utcnow ().strftime ("%Y-%m-%dT%H:%M:%S.%f" )
160
170
161
171
# do exposure
@@ -185,48 +195,50 @@ def _expose(self, exposure_time: int, open_shutter: bool, abort_event: threading
185
195
img [row , :] = self ._driver .grab_row (width )
186
196
187
197
# create FITS image and set header
188
- hdu = fits . PrimaryHDU (img )
189
- hdu .header ['DATE-OBS' ] = (date_obs , 'Date and time of start of exposure' )
190
- hdu .header ['EXPTIME' ] = (exposure_time / 1000. , 'Exposure time [s]' )
191
- hdu .header ['DET-TEMP' ] = (self ._driver .get_temp (FliTemperature .CCD ), 'CCD temperature [C]' )
192
- hdu .header ['DET-COOL' ] = (self ._driver .get_cooler_power (), 'Cooler power [percent]' )
193
- hdu .header ['DET-TSET' ] = (self ._temp_setpoint , 'Cooler setpoint [C]' )
198
+ image = Image (img )
199
+ image .header ['DATE-OBS' ] = (date_obs , 'Date and time of start of exposure' )
200
+ image .header ['EXPTIME' ] = (exposure_time , 'Exposure time [s]' )
201
+ image .header ['DET-TEMP' ] = (self ._driver .get_temp (FliTemperature .CCD ), 'CCD temperature [C]' )
202
+ image .header ['DET-COOL' ] = (self ._driver .get_cooler_power (), 'Cooler power [percent]' )
203
+ image .header ['DET-TSET' ] = (self ._temp_setpoint , 'Cooler setpoint [C]' )
194
204
195
205
# instrument and detector
196
- hdu .header ['INSTRUME' ] = (self ._driver .name , 'Name of instrument' )
206
+ image .header ['INSTRUME' ] = (self ._driver .name , 'Name of instrument' )
197
207
198
208
# binning
199
- hdu .header ['XBINNING' ] = hdu .header ['DET-BIN1' ] = (self ._binning [0 ], 'Binning factor used on X axis' )
200
- hdu .header ['YBINNING' ] = hdu .header ['DET-BIN2' ] = (self ._binning [1 ], 'Binning factor used on Y axis' )
209
+ image .header ['XBINNING' ] = image .header ['DET-BIN1' ] = (self ._binning [0 ], 'Binning factor used on X axis' )
210
+ image .header ['YBINNING' ] = image .header ['DET-BIN2' ] = (self ._binning [1 ], 'Binning factor used on Y axis' )
201
211
202
212
# window
203
- hdu .header ['XORGSUBF' ] = (self ._window [0 ], 'Subframe origin on X axis' )
204
- hdu .header ['YORGSUBF' ] = (self ._window [1 ], 'Subframe origin on Y axis' )
213
+ image .header ['XORGSUBF' ] = (self ._window [0 ], 'Subframe origin on X axis' )
214
+ image .header ['YORGSUBF' ] = (self ._window [1 ], 'Subframe origin on Y axis' )
205
215
206
216
# statistics
207
- hdu .header ['DATAMIN' ] = (float (np .min (img )), 'Minimum data value' )
208
- hdu .header ['DATAMAX' ] = (float (np .max (img )), 'Maximum data value' )
209
- hdu .header ['DATAMEAN' ] = (float (np .mean (img )), 'Mean data value' )
217
+ image .header ['DATAMIN' ] = (float (np .min (img )), 'Minimum data value' )
218
+ image .header ['DATAMAX' ] = (float (np .max (img )), 'Maximum data value' )
219
+ image .header ['DATAMEAN' ] = (float (np .mean (img )), 'Mean data value' )
210
220
211
221
# biassec/trimsec
212
222
full = self ._driver .get_visible_frame ()
213
- self .set_biassec_trimsec (hdu .header , * full )
223
+ self .set_biassec_trimsec (image .header , * full )
214
224
215
225
# return FITS image
216
226
log .info ('Readout finished.' )
217
227
self ._change_exposure_status (ExposureStatus .IDLE )
218
- return hdu
228
+ return image
219
229
220
- def _abort_exposure (self ):
230
+ def _abort_exposure (self ) -> None :
221
231
"""Abort the running exposure. Should be implemented by derived class.
222
232
223
233
Raises:
224
234
ValueError: If an error occured.
225
235
"""
236
+ if self ._driver is None :
237
+ raise ValueError ('No camera driver.' )
226
238
self ._driver .cancel_exposure ()
227
239
self ._camera_status = ExposureStatus .IDLE
228
240
229
- def get_cooling_status (self , * args , ** kwargs ) -> Tuple [bool , float , float ]:
241
+ def get_cooling_status (self , ** kwargs : Any ) -> Tuple [bool , float , float ]:
230
242
"""Returns the current status for the cooling.
231
243
232
244
Returns:
@@ -235,21 +247,25 @@ def get_cooling_status(self, *args, **kwargs) -> Tuple[bool, float, float]:
235
247
SetPoint (float): Setpoint for the cooling in celsius.
236
248
Power (float): Current cooling power in percent or None.
237
249
"""
250
+ if self ._driver is None :
251
+ raise ValueError ('No camera driver.' )
238
252
enabled = self ._temp_setpoint is not None
239
- return enabled , self ._temp_setpoint , self ._driver .get_cooler_power ()
253
+ return enabled , self ._temp_setpoint if self . _temp_setpoint is not None else 99. , self ._driver .get_cooler_power ()
240
254
241
- def get_temperatures (self , * args , ** kwargs ) -> dict :
255
+ def get_temperatures (self , ** kwargs : Any ) -> Dict [ str , float ] :
242
256
"""Returns all temperatures measured by this module.
243
257
244
258
Returns:
245
259
Dict containing temperatures.
246
260
"""
261
+ if self ._driver is None :
262
+ raise ValueError ('No camera driver.' )
247
263
return {
248
264
'CCD' : self ._driver .get_temp (FliTemperature .CCD ),
249
265
'Base' : self ._driver .get_temp (FliTemperature .BASE )
250
266
}
251
267
252
- def set_cooling (self , enabled : bool , setpoint : float , * args , ** kwargs ) :
268
+ def set_cooling (self , enabled : bool , setpoint : float , ** kwargs : Any ) -> None :
253
269
"""Enables/disables cooling and sets setpoint.
254
270
255
271
Args:
@@ -259,6 +275,8 @@ def set_cooling(self, enabled: bool, setpoint: float, *args, **kwargs):
259
275
Raises:
260
276
ValueError: If cooling could not be set.
261
277
"""
278
+ if self ._driver is None :
279
+ raise ValueError ('No camera driver.' )
262
280
263
281
# log
264
282
if enabled :
0 commit comments