def test_abort(): """Do a dummy exposure.""" # create comm and environment comm = DummyComm() environment = Environment(timezone='utc', location={ 'longitude': 20.810808, 'latitude': -32.375823, 'elevation': 1798. }) # open camera camera = DummyCam(filenames=None, comm=comm, environment=environment) camera.open() def expose(): with pytest.raises(ValueError): camera.set_exposure_time(1.) camera.set_image_type(ImageType.OBJECT) camera.expose() # expose thread = threading.Thread(target=expose) thread.start() # abort camera.abort() # thread should be closed assert False == thread.is_alive() # close camera camera.close()
def test_open_close(): """Test basic open/close of BaseCamera.""" # create camera, open and close it camera = BaseCamera(comm=DummyComm()) camera.open() camera.close()
def test_expose(): """Do a dummy exposure.""" # create comm and environment comm = DummyComm() environment = Environment(timezone='utc', location={ 'longitude': 20.810808, 'latitude': -32.375823, 'elevation': 1798. }) # open camera camera = DummyCam(filenames=None, comm=comm, environment=environment) camera.open() # status must be idle assert ICamera.ExposureStatus.IDLE == camera.get_exposure_status() # expose camera.set_exposure_time(0) camera.set_image_type(ImageType.OBJECT) camera.expose() # status must be idle again assert ICamera.ExposureStatus.IDLE == camera.get_exposure_status() # close camera camera.close()
def __init__(self, name: str = None, label: str = None, comm: Union[Comm, dict] = None, *args, **kwargs): """Initializes a new pyobs module. Args: name: Name of module. If None, ID from comm object is used. label: Label for module. If None, name is used. comm: Comm object to use """ Object.__init__(self, *args, **kwargs) # get list of client interfaces self._interfaces: List[Type] = [] self._methods: Dict[str, Tuple[Callable, inspect.Signature]] = {} self._get_interfaces_and_methods() # get configuration options, i.e. all parameters from c'tor self._config_options = self._get_config_options() # comm object self.comm: Comm if comm is None: self.comm = DummyComm() elif isinstance(comm, Comm): self.comm = comm elif isinstance(comm, dict): log.info('Creating comm object...') self.comm = get_object(comm) else: raise ValueError('Invalid Comm object') # name and label self._name: str = name if name is not None else self.comm.name self._label: str = label if label is not None else self._name
def test_remaining(): """Test the methods for remaining exposure time and progress.""" # open camera camera = BaseCamera(comm=DummyComm()) camera.open() # no exposure, so both should be zero assert 0 == camera.get_exposure_time_left() assert 0 == camera.get_exposure_progress() # more tests will be done with DummyCamera # close camera camera.close()
class Module(Object, IModule, IConfig): """Base class for all pyobs modules.""" def __init__(self, name: str = None, label: str = None, comm: Union[Comm, dict] = None, *args, **kwargs): """Initializes a new pyobs module. Args: name: Name of module. If None, ID from comm object is used. label: Label for module. If None, name is used. comm: Comm object to use """ Object.__init__(self, *args, **kwargs) # get list of client interfaces self._interfaces: List[Type] = [] self._methods: Dict[str, Tuple[Callable, inspect.Signature]] = {} self._get_interfaces_and_methods() # get configuration options, i.e. all parameters from c'tor self._config_options = self._get_config_options() # comm object self.comm: Comm if comm is None: self.comm = DummyComm() elif isinstance(comm, Comm): self.comm = comm elif isinstance(comm, dict): log.info('Creating comm object...') self.comm = get_object(comm) else: raise ValueError('Invalid Comm object') # name and label self._name: str = name if name is not None else self.comm.name self._label: str = label if label is not None else self._name def open(self): """Open module.""" Object.open(self) # open comm if self.comm is not None: # open it and connect module self.comm.open() self.comm.module = self def close(self): """Close module.""" Object.close(self) # close comm if self.comm is not None: log.info('Closing connection to server...') self.comm.close() def proxy(self, name_or_object: Union[str, object], obj_type: Type = None): """Returns object directly if it is of given type. Otherwise get proxy of client with given name and check type. If name_or_object is an object: - If it is of type (or derived), return object. - Otherwise raise exception. If name_name_or_object is string: - Create proxy from name and raise exception, if it doesn't exist. - Check type and raise exception if wrong. - Return object. Args: name_or_object: Name of object or object itself. obj_type: Expected class of object. Returns: Object or proxy to object. Raises: ValueError: If proxy does not exist or wrong type. """ return self.comm.proxy(name_or_object, obj_type) def main(self): """Main loop for application.""" while not self.closing.is_set(): self.closing.wait(1) def name(self, *args, **kwargs) -> str: """Returns name of module.""" return self._name def label(self, *args, **kwargs) -> str: """Returns label of module.""" return self._label def implements(self, interface): """checks, whether this object implements a given interface""" return interface.implemented_by(self) @property def interfaces(self): """List of implemented interfaces.""" return self._interfaces @property def methods(self): """List of methods.""" return self._methods def _get_interfaces_and_methods(self): """List interfaces and methods of this module.""" import pyobs.interfaces # get interfaces self._interfaces = [] self._methods = {} for _, interface in inspect.getmembers(pyobs.interfaces, predicate=inspect.isclass): # is module a sub-class of that class that inherits from Interface? if isinstance(self, interface) and issubclass( interface, pyobs.interfaces.Interface): # we ignore the interface "Interface" if interface == pyobs.interfaces.Interface: continue # add interface self._interfaces += [interface] # loop methods of that interface for method_name, method in inspect.getmembers( interface, predicate=inspect.isfunction): # get method and signature func = getattr(self, method_name) signature = inspect.signature(func) # fill dict of name->(method, signature) self._methods[method_name] = (func, signature) def quit(self): """Quit module.""" self.closing.set() def execute(self, method, *args, **kwargs) -> Any: """Execute a local method safely with type conversion All incoming variables in args and kwargs must be of simple type (i.e. int, float, str, bool, tuple) and will be converted to the requested type automatically. All outgoing variables are converted to simple types automatically as well. Args: method: Name of method to execute. *args: Parameters for method. **kwargs: Parameters for method. Returns: Response from method call. Raises: KeyError: If method does not exist. """ # get method and signature (may raise KeyError) func, signature = self.methods[method] # bind parameters ba = signature.bind(*args, **kwargs) ba.apply_defaults() # cast to types requested by method cast_bound_arguments_to_real(ba, signature) # get additional args and kwargs and delete from ba func_args = ba.arguments['args'] func_kwargs = ba.arguments['kwargs'] del ba.arguments['args'] del ba.arguments['kwargs'] try: # call method response = func(*func_args, **ba.arguments, **func_kwargs) # finished return cast_response_to_simple(response) except Exception as e: log.exception('Error on remote procedure call: %s' % str(e)) def _get_config_options(self) -> dict: """Returns a dictionary with config options.""" # init dict of options and types opts = {} # loop super classes for cls in inspect.getmro(self.__class__): # ignore Object and Module if cls in [Object, Module]: continue # get signature sig = inspect.signature(cls.__init__) for name in sig.parameters: # ignore self, args, kwargs if name in ['self', 'args', 'kwargs']: continue # check for getter and setter getter = hasattr(self, '_get_config_' + name) setter = hasattr(self, '_set_config_' + name) opts[name] = (getter, setter) # finished return opts def get_config_options(self, *args, **kwargs) -> Dict[str, Tuple[bool, bool]]: """Returns dict of all config options. First value is whether it has a getter, second is for the setter. Returns: Dict with config options """ return self._config_options def get_config_value(self, name: str, *args, **kwargs) -> Any: """Returns current value of config item with given name. Args: name: Name of config item. Returns: Current value. Raises: ValueError if config item of given name does not exist. """ # valid parameter? if name not in self._config_options: raise ValueError('Invalid parameter %s' % name) if not self._config_options[name][0]: raise ValueError('Parameter %s is not remotely accessible.') # get getter method and call it getter = getattr(self, '_get_config_' + name) return getter() def set_config_value(self, name: str, value: Any, *args, **kwargs): """Sets value of config item with given name. Args: name: Name of config item. value: New value. Raises: ValueError if config item of given name does not exist or value is invalid. """ # valid parameter? if name not in self._config_options: raise ValueError('Invalid parameter %s' % name) if not self._config_options[name][1]: raise ValueError('Parameter %s is not remotely settable.') # get setter and call it setter = getattr(self, '_set_config_' + name) setter(value)
def test_add_fits_headers(): """Check adding FITS headers. Only check for existence, only for some we check actual value.""" # create comm comm = DummyComm() # open camera centre = {'x': 100, 'y': 100} rotation = 42 camera = BaseCamera(centre=centre, rotation=rotation, comm=comm, location='SAAO') camera.open() # try empty header hdr = fits.Header() camera._add_fits_headers(hdr) assert 0 == len(hdr) # add DATE-OBS and IMAGETYP hdr['DATE-OBS'] = '2019-01-31T03:00:00.000' hdr['IMAGETYP'] = 'object' camera._add_fits_headers(hdr) # now we should get some values assert 2000 == hdr['EQUINOX'] assert '2019-01-30' == hdr['DAY-OBS'] assert camera.observer.location.lon.degree == hdr['LONGITUD'] assert camera.observer.location.lat.degree == hdr['LATITUDE'] assert 'RA---TAN' == hdr['CTYPE1'] assert 'DEC--TAN' == hdr['CTYPE2'] assert centre['x'] == hdr['DET-CPX1'] assert centre['y'] == hdr['DET-CPX2'] assert 'PC1_1' in hdr assert 'PC2_1' in hdr assert 'PC1_2' in hdr assert 'PC2_2' in hdr # add pixel size, focus and binning hdr['DET-PIXL'] = 0.015 hdr['TEL-FOCL'] = 8400.0 hdr['DET-BIN1'] = 1 hdr['DET-BIN2'] = 1 camera._add_fits_headers(hdr) # some WCS stuff assert 'CDELT1' in hdr assert 'CDELT2' in hdr assert 'deg' == hdr['CUNIT1'] assert 'deg' == hdr['CUNIT2'] assert 2 == hdr['WCSAXES'] # windowing hdr['XORGSUBF'] = 0 hdr['YORGSUBF'] = 0 camera._add_fits_headers(hdr) # now we should have reference pixels assert 'CRPIX1' in hdr assert 'CRPIX2' in hdr # close camera camera.close()
def __init__( self, vfs: Optional[Union["VirtualFileSystem", Dict[str, Any]]] = None, comm: Optional[Union[Comm, Dict[str, Any]]] = None, timezone: Union[str, datetime.tzinfo] = "utc", location: Optional[Union[str, Dict[str, Any], EarthLocation]] = None, observer: Optional[Observer] = None, **kwargs: Any, ): """ .. note:: Objects must always be opened and closed using :meth:`~pyobs.object.Object.open` and :meth:`~pyobs.object.Object.close`, respectively. This class provides a :class:`~pyobs.vfs.VirtualFileSystem`, a timezone and a location. From the latter two, an observer object is automatically created. Object also adds support for easily adding threads using the :meth:`~pyobs.object.Object.add_thread_func` method as well as a watchdog thread that automatically restarts threads, if requested. Using :meth:`~pyobs.object.Object.add_child_object`, other objects can be (created an) attached to this object, which then automatically handles calls to :meth:`~pyobs.object.Object.open` and :meth:`~pyobs.object.Object.close` on those objects. Args: vfs: VFS to use (either object or config) comm: Comm object to use timezone: Timezone at observatory. location: Location of observatory, either a name or a dict containing latitude, longitude, and elevation. """ from pyobs.vfs import VirtualFileSystem # child objects self._child_objects: List[Any] = [] # create vfs if vfs: self.vfs = get_object(vfs, VirtualFileSystem) else: self.vfs = VirtualFileSystem() # timezone if isinstance(timezone, datetime.tzinfo): self.timezone = timezone elif isinstance(timezone, str): self.timezone = pytz.timezone(timezone) else: raise ValueError("Unknown format for timezone.") # location if location is None: self.location = None elif isinstance(location, EarthLocation): self.location = location elif isinstance(location, str): self.location = EarthLocation.of_site(location) elif isinstance(location, dict): self.location = EarthLocation.from_geodetic( location["longitude"], location["latitude"], location["elevation"]) else: raise ValueError("Unknown format for location.") # create observer self.observer = observer if self.observer is None and self.location is not None and self.timezone is not None: log.info( "Setting location to longitude=%s, latitude=%s, and elevation=%s.", self.location.lon, self.location.lat, self.location.height, ) self.observer = Observer(location=self.location, timezone=timezone) # comm object self.comm: Comm if comm is None: self.comm = DummyComm() elif isinstance(comm, Comm): self.comm = comm elif isinstance(comm, dict): log.info("Creating comm object...") self.comm = get_object(comm, Comm) else: raise ValueError("Invalid Comm object") # opened? self._opened = False # background tasks self._background_tasks: Dict[Callable[..., Coroutine[Any, Any, None]], Tuple[Optional[asyncio.Task[bool]], bool]] = {} self._watchdog_task: Optional[asyncio.Task[None]] = None