Skip to content

Commit b9e834e

Browse files
authored
Merge pull request #6 from pyobs/develop
v0.14
2 parents 7e58919 + d1f51df commit b9e834e

File tree

4 files changed

+88
-65
lines changed

4 files changed

+88
-65
lines changed

pyobs_fli/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
__title__ = 'FLI camera modules'
55

66
from .flicamera import FliCamera
7+
8+
9+
__all__ = ['FliCamera']

pyobs_fli/flicamera.py

+63-45
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,40 @@
33
import threading
44
from datetime import datetime
55
import time
6-
from typing import Tuple
6+
from typing import Tuple, Any, Optional, Dict
7+
import numpy as np
78

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
1110
from pyobs.modules.camera.basecamera import BaseCamera
11+
from pyobs.images import Image
1212
from pyobs.utils.enums import ExposureStatus
13-
from pyobs_fli.flidriver import *
13+
from pyobs_fli.flidriver import FliDriver, FliTemperature
1414

1515

1616
log = logging.getLogger(__name__)
1717

1818

19-
class FliCamera(BaseCamera, ICamera, ICameraWindow, ICameraBinning, ICooling):
19+
class FliCamera(BaseCamera, ICamera, IWindow, IBinning, ICooling):
2020
"""A pyobs module for FLI cameras."""
2121
__module__ = 'pyobs_fli'
2222

23-
def __init__(self, setpoint: float = -20, *args, **kwargs):
23+
def __init__(self, setpoint: float = -20., **kwargs: Any):
2424
"""Initializes a new FliCamera.
2525
2626
Args:
2727
setpoint: Cooling temperature setpoint.
2828
"""
29-
BaseCamera.__init__(self, *args, **kwargs)
29+
BaseCamera.__init__(self, **kwargs)
3030

3131
# variables
32-
self._driver = None
33-
self._temp_setpoint = setpoint
32+
self._driver: Optional[FliDriver] = None
33+
self._temp_setpoint: Optional[float] = setpoint
3434

3535
# window and binning
36-
self._window = None
37-
self._binning = None
36+
self._window = (0, 0, 0, 0)
37+
self._binning = (1, 1)
3838

39-
def open(self):
39+
def open(self) -> None:
4040
"""Open module."""
4141
BaseCamera.open(self)
4242

@@ -58,9 +58,10 @@ def open(self):
5858
self._window, self._binning = self._driver.get_window_binning()
5959

6060
# 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)
6263

63-
def close(self):
64+
def close(self) -> None:
6465
"""Close the module."""
6566
BaseCamera.close(self)
6667

@@ -70,31 +71,33 @@ def close(self):
7071
self._driver.close()
7172
self._driver = None
7273

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]:
7475
"""Returns full size of CCD.
7576
7677
Returns:
7778
Tuple with left, top, width, and height set.
7879
"""
80+
if self._driver is None:
81+
raise ValueError('No camera driver.')
7982
return self._driver.get_full_frame()
8083

81-
def get_window(self, *args, **kwargs) -> Tuple[int, int, int, int]:
84+
def get_window(self, **kwargs: Any) -> Tuple[int, int, int, int]:
8285
"""Returns the camera window.
8386
8487
Returns:
8588
Tuple with left, top, width, and height set.
8689
"""
8790
return self._window
8891

89-
def get_binning(self, *args, **kwargs) -> Tuple[int, int]:
92+
def get_binning(self, **kwargs: Any) -> Tuple[int, int]:
9093
"""Returns the camera binning.
9194
9295
Returns:
9396
Tuple with x and y.
9497
"""
9598
return self._binning
9699

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:
98101
"""Set the camera window.
99102
100103
Args:
@@ -109,7 +112,7 @@ def set_window(self, left: float, top: float, width: float, height: float, *args
109112
self._window = (left, top, width, height)
110113
log.info('Setting window to %dx%d at %d,%d...', width, height, left, top)
111114

112-
def set_binning(self, x: int, y: int, *args, **kwargs):
115+
def set_binning(self, x: int, y: int, **kwargs: Any) -> None:
113116
"""Set the camera binning.
114117
115118
Args:
@@ -122,18 +125,25 @@ def set_binning(self, x: int, y: int, *args, **kwargs):
122125
self._binning = (x, y)
123126
log.info('Setting binning to %dx%d...', x, y)
124127

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:
126129
"""Actually do the exposure, should be implemented by derived classes.
127130
128131
Args:
129-
exposure_time: The requested exposure time in ms.
132+
exposure_time: The requested exposure time in seconds.
130133
open_shutter: Whether or not to open the shutter.
131134
abort_event: Event that gets triggered when exposure should be aborted.
132135
133136
Returns:
134137
The actual image.
138+
139+
Raises:
140+
ValueError: If exposure was not successful.
135141
"""
136142

143+
# check driver
144+
if self._driver is None:
145+
raise ValueError('No camera driver.')
146+
137147
# set binning
138148
log.info("Set binning to %dx%d.", self._binning[0], self._binning[1])
139149
self._driver.set_binning(*self._binning)
@@ -151,11 +161,11 @@ def _expose(self, exposure_time: int, open_shutter: bool, abort_event: threading
151161
# set some stuff
152162
self._change_exposure_status(ExposureStatus.EXPOSING)
153163
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.))
155165

156166
# get date obs
157167
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)
159169
date_obs = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")
160170

161171
# do exposure
@@ -185,48 +195,50 @@ def _expose(self, exposure_time: int, open_shutter: bool, abort_event: threading
185195
img[row, :] = self._driver.grab_row(width)
186196

187197
# 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]')
194204

195205
# instrument and detector
196-
hdu.header['INSTRUME'] = (self._driver.name, 'Name of instrument')
206+
image.header['INSTRUME'] = (self._driver.name, 'Name of instrument')
197207

198208
# 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')
201211

202212
# 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')
205215

206216
# 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')
210220

211221
# biassec/trimsec
212222
full = self._driver.get_visible_frame()
213-
self.set_biassec_trimsec(hdu.header, *full)
223+
self.set_biassec_trimsec(image.header, *full)
214224

215225
# return FITS image
216226
log.info('Readout finished.')
217227
self._change_exposure_status(ExposureStatus.IDLE)
218-
return hdu
228+
return image
219229

220-
def _abort_exposure(self):
230+
def _abort_exposure(self) -> None:
221231
"""Abort the running exposure. Should be implemented by derived class.
222232
223233
Raises:
224234
ValueError: If an error occured.
225235
"""
236+
if self._driver is None:
237+
raise ValueError('No camera driver.')
226238
self._driver.cancel_exposure()
227239
self._camera_status = ExposureStatus.IDLE
228240

229-
def get_cooling_status(self, *args, **kwargs) -> Tuple[bool, float, float]:
241+
def get_cooling_status(self, **kwargs: Any) -> Tuple[bool, float, float]:
230242
"""Returns the current status for the cooling.
231243
232244
Returns:
@@ -235,21 +247,25 @@ def get_cooling_status(self, *args, **kwargs) -> Tuple[bool, float, float]:
235247
SetPoint (float): Setpoint for the cooling in celsius.
236248
Power (float): Current cooling power in percent or None.
237249
"""
250+
if self._driver is None:
251+
raise ValueError('No camera driver.')
238252
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()
240254

241-
def get_temperatures(self, *args, **kwargs) -> dict:
255+
def get_temperatures(self, **kwargs: Any) -> Dict[str, float]:
242256
"""Returns all temperatures measured by this module.
243257
244258
Returns:
245259
Dict containing temperatures.
246260
"""
261+
if self._driver is None:
262+
raise ValueError('No camera driver.')
247263
return {
248264
'CCD': self._driver.get_temp(FliTemperature.CCD),
249265
'Base': self._driver.get_temp(FliTemperature.BASE)
250266
}
251267

252-
def set_cooling(self, enabled: bool, setpoint: float, *args, **kwargs):
268+
def set_cooling(self, enabled: bool, setpoint: float, **kwargs: Any) -> None:
253269
"""Enables/disables cooling and sets setpoint.
254270
255271
Args:
@@ -259,6 +275,8 @@ def set_cooling(self, enabled: bool, setpoint: float, *args, **kwargs):
259275
Raises:
260276
ValueError: If cooling could not be set.
261277
"""
278+
if self._driver is None:
279+
raise ValueError('No camera driver.')
262280

263281
# log
264282
if enabled:

0 commit comments

Comments
 (0)