Пример #1
0
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()
Пример #2
0
def test_open_close():
    """Test basic open/close of BaseCamera."""

    # create camera, open and close it
    camera = BaseCamera(comm=DummyComm())
    camera.open()
    camera.close()
Пример #3
0
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()
Пример #4
0
    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
Пример #5
0
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()
Пример #6
0
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)
Пример #7
0
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()
Пример #8
0
    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