Skip to content

Commit

Permalink
Reorganisation of camera creation in accordance with panoptes#612
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony Horton committed Oct 3, 2018
1 parent 3f1991a commit 2c8d920
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 175 deletions.
93 changes: 92 additions & 1 deletion pocs/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
169 changes: 0 additions & 169 deletions pocs/observatory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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 """

Expand Down
8 changes: 3 additions & 5 deletions pocs/tests/test_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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']

Expand Down

0 comments on commit 2c8d920

Please sign in to comment.