From 2c8d920125f885a3eb0bb191f59a0d4aea7721d8 Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Wed, 3 Oct 2018 18:44:41 +1000 Subject: [PATCH] Reorganisation of camera creation in accordance with #612 --- pocs/camera/__init__.py | 93 ++++++++++++++++++++- pocs/observatory.py | 169 -------------------------------------- pocs/tests/test_camera.py | 8 +- 3 files changed, 95 insertions(+), 175 deletions(-) diff --git a/pocs/camera/__init__.py b/pocs/camera/__init__.py index 766a8fcfc..81a1b9d1a 100644 --- a/pocs/camera/__init__.py +++ b/pocs/camera/__init__.py @@ -3,6 +3,8 @@ import shutil import subprocess +import Pyro4 + from pocs.utils import error from pocs.utils import load_module from pocs.utils.config import load_config @@ -40,6 +42,44 @@ def list_connected_cameras(): return ports +def list_distributed_cameras(ns_host=None, logger=None): + """Detect distributed cameras. + + Looks for a Pyro name server and queries it for the list of registered cameras. + + Args: + host (str, optional): hostname or IP address of the name server host. If not given + will attempt to locate the name server via UDP network broadcast. + logger (logging.Logger, optional): logger to use for messages, if not given will + ise the root logger. + + Returns: + dict: Dictionary of distributed camera name, URI pairs + """ + if not logger: + logger = logger_module.get_root_logger() + + try: + # Get a proxy for the name server (will raise NamingError if not found) + with Pyro4.locateNS(host=ns_host) as name_server: + # Find all the registered POCS cameras + camera_uris = name_server.list(metadata_all={'POCS', 'Camera'}) + camera_uris = OrderDict(sorted(camera_uris.items(), key=lambda t: t[0])) + n_cameras = len(camera_uris) + if n_cameras > 0: + msg = "Found {} distributed cameras on name server".format(n_cameras) + logger.debug(msg) + else: + msg = "Found name server but no distributed cameras" + logger.warning(msg) + except Pyro4.errors.NamingError() as err: + msg = "Couldn't connect to Pyro name server: {}".format(err) + logger.warning(msg) + camera_uris = OrderedDict() + + return camera_uris + + def create_cameras_from_config(config=None, logger=None, **kwargs): """Create camera object(s) based on the config. @@ -175,12 +215,63 @@ def kwargs_or_config(item, default=None): raise error.CameraNotFound( msg="No cameras available. Exiting.", exit=True) + distributed_cameras = kwargs_or_config('distributed_cameras', default=False) + if not a_simulator and distributed_cameras: + dist_cams, dist_primary = create_distributed_cameras(camera_info, logger=logger) + cameras.update(dist_cams) + if dist_primary is not None: + primary_camera = dist_primary + # If no camera was specified as primary use the first if primary_camera is None: - primary_camera = cameras['Cam00'] + camera_names = sorted(self.cameras.keys()) + primary_camera = cameras[camera_names[0]] primary_camera.is_primary = True logger.debug("Primary camera: {}", primary_camera) logger.debug("{} cameras created", len(cameras)) return cameras + + +def create_distributed_cameras(camera_info, logger=None): + """Create distributed camera object(s) based on detected cameras and config + + Creates a pocs.camera.pyro.Camera object for each distributed camera detected. + + Args: + camera_info: 'cameras' section from POCS config + logger (logging.Logger, optional): logger to use for messages, if not given will + use the root logger. + + Returns: + OrderedDict: An ordered dictionary of created camera objects, with the + camera name as key and camera instance as value. Returns an empty + OrderedDict if no distributed cameras are found. + """ + if not logger: + logger = logger_module.get_root_logger() + + # Get all distributed cameras + camera_uris = list_distributed_cameras(ns_host=camera_info.get(name_server_host, None), + logger=logger) + + # Create the camera objects. + # TODO: do this in parallel because initialising cameras can take a while. + cameras = OrderedDict() + primary_camera = None + primary_id = camera_info.get('primary') + for cam_name, cam_uri in camera_uris.items(): + cam = pyro.Camera(name=cam_name, uri=cam_uri) + is_primary = '' + if primary_id == cam.uid or primary_id == cam.name: + cam.is_primary = True + primary_camera = cam + is_primary = ' [Primary]' + + logger.debug("Camera created: {} {}{}".format( + cam.name, cam.uid, is_primary)) + + cameras[cam_name] = cam + + return cameras, primary_camera diff --git a/pocs/observatory.py b/pocs/observatory.py index 6d3357bf1..c6db9e8ee 100644 --- a/pocs/observatory.py +++ b/pocs/observatory.py @@ -9,7 +9,6 @@ from astropy.coordinates import EarthLocation from astropy.coordinates import get_moon from astropy.coordinates import get_sun -import Pyro4 from pocs.base import PanBase import pocs.dome @@ -23,7 +22,6 @@ from pocs.utils import horizon as horizon_utils from pocs.utils import load_module from pocs.camera import AbstractCamera -from pocs.camera import pyro class Observatory(PanBase): @@ -660,173 +658,6 @@ def _create_mount(self, mount_info=None): self.logger.debug('Mount created') - def _create_cameras(self, **kwargs): - """Creates a camera object(s) - - Loads the cameras via the configuration. - - Creates a camera for each camera item listed in the config. Ensures the - appropriate camera module is loaded. - - Note: We are currently only operating with one camera and the `take_pic.sh` - script automatically discovers the ports. - - Note: - This does not actually make a usb connection to the camera. To do so, - call the 'camear.connect()' explicitly. - - Args: - **kwargs (dict): Can pass a camera_config object that overrides the info in - the configuration file. Can also pass `auto_detect`(bool) to try and - automatically discover the ports. - - Returns: - list: A list of created camera objects. - - Raises: - error.CameraNotFound: Description - error.PanError: Description - """ - if kwargs.get('camera_info') is None: - camera_info = self.config.get('cameras') - - self.logger.debug("Camera config: \n {}".format(camera_info)) - - a_simulator = 'camera' in self.config.get('simulator', []) - if a_simulator: - self.logger.debug("Using simulator for camera") - - ports = list() - - # Lookup the connected ports if not using a simulator - auto_detect = kwargs.get( - 'auto_detect', camera_info.get('auto_detect', False)) - if not a_simulator and auto_detect: - self.logger.debug("Auto-detecting ports for cameras") - try: - ports = list_connected_cameras() - except Exception as e: - self.logger.warning(e) - - if len(ports) == 0: - raise error.PanError( - msg="No cameras detected. Use --simulator=camera for simulator.") - else: - self.logger.debug("Detected Ports: {}".format(ports)) - - for cam_num, camera_config in enumerate(camera_info.get('devices', [])): - cam_name = 'Cam{:02d}'.format(cam_num) - - if not a_simulator: - camera_model = camera_config.get('model') - - # Assign an auto-detected port. If none are left, skip - if auto_detect: - try: - camera_port = ports.pop() - except IndexError: - self.logger.warning( - "No ports left for {}, skipping.".format(cam_name)) - continue - else: - try: - camera_port = camera_config['port'] - except KeyError: - raise error.CameraNotFound( - msg="No port specified and auto_detect=False") - - camera_focuser = camera_config.get('focuser', None) - camera_readout = camera_config.get('readout_time', 6.0) - - else: - # Set up a simulated camera with fully configured simulated - # focuser - camera_model = 'simulator' - camera_port = '/dev/camera/simulator' - camera_focuser = {'model': 'simulator', - 'focus_port': '/dev/ttyFAKE', - 'initial_position': 20000, - 'autofocus_range': (40, 80), - 'autofocus_step': (10, 20), - 'autofocus_seconds': 0.1, - 'autofocus_size': 500} - camera_readout = 0.5 - - camera_set_point = camera_config.get('set_point', None) - camera_filter = camera_config.get('filter_type', None) - - self.logger.debug('Creating camera: {}'.format(camera_model)) - - try: - module = load_module('pocs.camera.{}'.format(camera_model)) - self.logger.debug('Camera module: {}'.format(module)) - except ImportError: - raise error.CameraNotFound(msg=camera_model) - else: - # Create the camera object - cam = module.Camera(name=cam_name, - model=camera_model, - port=camera_port, - set_point=camera_set_point, - filter_type=camera_filter, - focuser=camera_focuser, - readout_time=camera_readout) - - is_primary = '' - if camera_info.get('primary', '') == cam.uid: - self.primary_camera = cam - is_primary = ' [Primary]' - - self.logger.debug("Camera created: {} {} {}".format( - cam.name, cam.uid, is_primary)) - - self.cameras[cam_name] = cam - - distributed_cameras = kwargs.get('distributed_cameras', - camera_info.get('distributed_cameras', False)) - if not a_simulator and distributed_cameras: - self._create_distributed_cameras(camera_info) - - # If no camera was specified as primary use the first - if self.primary_camera is None: - camera_names = sorted(self.cameras.keys()) - self.primary_camera = self.cameras[camera_names[0]] - - if len(self.cameras) == 0: - raise error.CameraNotFound( - msg="No cameras available. Exiting.", exit=True) - - self.logger.debug("Cameras created") - - def _create_distributed_cameras(self, camera_info): - # Enable local display of remote tracebacks - sys.excepthook = Pyro4.util.excepthook - - # Get a proxy for the name server (will raise NamingError if not found) - try: - self._name_server = Pyro4.locateNS() - except Pyro4.errors.NamingError() as err: - msg = "Couldn't connect to Pyro name server: {}".format(err) - self.logger.error(msg) - else: - # Find all the registered cameras - camera_uris = self._name_server.list(metadata_all={'POCS', 'Camera'}) - msg = "Found {} distributed cameras on name server".format(len(camera_uris)) - self.logger.debug(msg) - - # Create the camera objects. - # TODO: do this in parallel because initialising cameras can take a while. - for cam_name, cam_uri in camera_uris.items(): - cam = pyro.Camera(name=cam_name, uri=cam_uri) - is_primary = '' - if camera_info.get('primary', '') == cam.uid: - self.primary_camera = cam - is_primary = ' [Primary]' - - self.logger.debug("Camera created: {} {} {}".format(cam.name, cam.uid, is_primary)) - - self.cameras[cam_name] = cam - def _create_scheduler(self): """ Sets up the scheduler that will be used by the observatory """ diff --git a/pocs/tests/test_camera.py b/pocs/tests/test_camera.py index 0043b9c32..34a819c10 100644 --- a/pocs/tests/test_camera.py +++ b/pocs/tests/test_camera.py @@ -6,6 +6,9 @@ from ctypes.util import find_library import astropy.units as u +import astropy.io.fits as fits + +import Pyro4 from pocs.camera.simulator import Camera as SimCamera from pocs.camera.pyro import Camera as PyroCamera @@ -19,11 +22,6 @@ from pocs.utils.error import NotFound from pocs.utils.images import fits as fits_utils - -import astropy.units as u -import astropy.io.fits as fits -import Pyro4 - params = [SimCamera, PyroCamera, SBIGCamera, FLICamera] ids = ['simulator', 'pyro', 'sbig', 'fli']