From c952500211cd20a47ef2155b7a41915c490a7813 Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Fri, 13 Jul 2018 20:59:11 +1000 Subject: [PATCH] Autofocus & bisque dome changes from May Huntsman commissioning run (#535) * Reverted focus peak fitting from spline to Lorentzian * Assorted small fixes * Change Lorentzian fitting to polynomial fitting around peak * Add colorbar to pretty images * Fix focus plots so fit appeats * Codestyle fixes * Removed Slack code & excessive TheSkyX logging * Removed coarse focus on 1st focus code --- conf_files/peas.yaml | 3 +- pocs/camera/camera.py | 5 ++++ pocs/camera/sbigudrv.py | 1 + pocs/core.py | 3 -- pocs/dome/bisque.py | 41 ++++++++++++++++++++++++-- pocs/focuser/focuser.py | 55 +++++++++++++++++++++++------------ pocs/observatory.py | 2 +- pocs/utils/images/__init__.py | 5 ++-- resources/bisque/dome/park.js | 9 +++++- 9 files changed, 95 insertions(+), 29 deletions(-) diff --git a/conf_files/peas.yaml b/conf_files/peas.yaml index bd1fde58e..bec7030aa 100644 --- a/conf_files/peas.yaml +++ b/conf_files/peas.yaml @@ -14,7 +14,8 @@ environment: weather: station: mongo aag_cloud: - serial_port: '/dev/ttyUSB1' + # serial_port: '/dev/ttyUSB1' + serial_port: '/dev/tty.USA19H2P1.1' threshold_cloudy: -25 threshold_very_cloudy: -15. threshold_windy: 50. diff --git a/pocs/camera/camera.py b/pocs/camera/camera.py index e89c6bb93..ed42bb888 100644 --- a/pocs/camera/camera.py +++ b/pocs/camera/camera.py @@ -249,6 +249,10 @@ def process_exposure(self, info, observation_event, exposure_event=None): self.logger.warning('Problem with extracting pretty image: {}'.format(e)) file_path = self._process_fits(file_path, info) + try: + info['exp_time'] = info['exp_time'].value + except Exception: + pass if info['is_primary']: self.logger.debug("Adding current observation to db: {}".format(image_id)) @@ -261,6 +265,7 @@ def process_exposure(self, info, observation_event, exposure_event=None): fits_utils.fpack(file_path) self.logger.debug("Adding image metadata to db: {}".format(image_id)) + self.db.insert('observations', { 'data': info, 'date': current_time(datetime=True), diff --git a/pocs/camera/sbigudrv.py b/pocs/camera/sbigudrv.py index 202476712..162be7f16 100644 --- a/pocs/camera/sbigudrv.py +++ b/pocs/camera/sbigudrv.py @@ -11,6 +11,7 @@ import platform import ctypes from ctypes.util import find_library +from warnings import warn import _ctypes import os import time diff --git a/pocs/core.py b/pocs/core.py index 1e47b1985..7c2e72baf 100644 --- a/pocs/core.py +++ b/pocs/core.py @@ -121,7 +121,6 @@ def has_messaging(self, value): def should_retry(self): return self._obs_run_retries >= 0 - ################################################################################################## # Methods ################################################################################################## @@ -396,7 +395,6 @@ def has_free_space(self, required_space=0.25 * u.gigabyte): free_space = get_free_space() return free_space.value >= required_space.to(u.gigabyte).value - ################################################################################################## # Convenience Methods ################################################################################################## @@ -442,7 +440,6 @@ def wait_until_safe(self): while not self.is_safe(no_warning=True): self.sleep(delay=self._safe_delay) - ################################################################################################## # Class Methods ################################################################################################## diff --git a/pocs/dome/bisque.py b/pocs/dome/bisque.py index ad80f314b..c51f9e8bd 100644 --- a/pocs/dome/bisque.py +++ b/pocs/dome/bisque.py @@ -25,6 +25,7 @@ def __init__(self, *args, **kwargs): "Bisque Mounts required a template directory") self.template_dir = template_dir + self._is_parked = True @property def is_connected(self): @@ -57,7 +58,17 @@ def read_slit_state(self): @property def status(self): - return self.read_slit_state() + self.write(self._get_command('dome/status.js')) + return self.read() + + @property + def position(self): + self.write(self._get_command('dome/position.js')) + return self.read() + + @property + def is_parked(self): + return self._is_parked def connect(self): if not self.is_connected: @@ -105,6 +116,33 @@ def close(self): return self.is_closed + def park(self): + if self.is_connected: + self.write(self._get_command('dome/park.js')) + response = self.read() + + self._is_parked = response['success'] + + return self.is_parked + + def unpark(self): + if self.is_connected: + self.write(self._get_command('dome/unpark.js')) + response = self.read() + + self._is_parked = not response['success'] + + return not self.is_parked + + def find_home(self): + if self.is_connected: + self.write(self._get_command('dome/home.js')) + response = self.read() + + self._is_parked = response['success'] + + return self.is_parked + ################################################################################################## # Communication Methods ################################################################################################## @@ -133,7 +171,6 @@ def read(self, timeout=5): return response_obj - ################################################################################################## # Private Methods ################################################################################################## diff --git a/pocs/focuser/focuser.py b/pocs/focuser/focuser.py index 074c3dd66..494501781 100644 --- a/pocs/focuser/focuser.py +++ b/pocs/focuser/focuser.py @@ -1,7 +1,7 @@ import matplotlib.colors as colours import matplotlib.pyplot as plt -from scipy.interpolate import UnivariateSpline +from astropy.modeling import models, fitting from scipy.ndimage import binary_dilation import numpy as np @@ -463,22 +463,38 @@ def _autofocus(self, best_focus = focus_positions[imax] elif not coarse: - # Crude guess at a standard deviation for focus metric, 40% of the maximum value - weights = np.ones(len(focus_positions)) / (spline_smoothing * metric.max()) - - # Fit smoothing spline to focus metric data - fit = UnivariateSpline(focus_positions, metric, w=weights, k=4, ext='raise') - - try: - stationary_points = fit.derivative().roots() - except ValueError as err: - self.logger.warning('Error finding extrema of spline fit: {}'.format(err)) - best_focus = focus_positions[imax] - else: - extrema = fit(stationary_points) - if len(extrema) > 0: - best_focus = stationary_points[extrema.argmax()] - fitted = True + # Fit data around the maximum value to determine best focus position. + # Initialise models + shift = models.Shift(offset=-focus_positions[imax]) + poly = models.Polynomial1D(degree=4, c0=1, c1=0, c2=-1e-2, c3=0, c4=-1e-4, + fixed={'c0': True, 'c1': True, 'c3': True}) + scale = models.Scale(factor=metric[imax]) + reparameterised_polynomial = shift | poly | scale + + # Initialise fitter + fitter = fitting.LevMarLSQFitter() + + # Select data range for fitting. Tries to use 2 points either side of max, if in range. + fitting_indices = (max(imax - 2, 0), min(imax + 2, n_positions - 1)) + + # Fit models to data + fit = fitter(reparameterised_polynomial, + focus_positions[fitting_indices[0]:fitting_indices[1] + 1], + metric[fitting_indices[0]:fitting_indices[1] + 1]) + + best_focus = -fit.offset_0 + fitted = True + + # Guard against fitting failures, force best focus to stay within sweep range + if best_focus < focus_positions[0]: + self.logger.warning("Fitting failure: best focus {} below sweep limit {}".format(best_focus, + focus_positions[0])) + best_focus = focus_positions[1] + + if best_focus > focus_positions[-1]: + self.logger.warning("Fitting failure: best focus {} above sweep limit {}".format(best_focus, + focus_positions[-1])) + best_focus = focus_positions[-2] else: # Coarse focus, just use max value. @@ -488,8 +504,9 @@ def _autofocus(self, ax2 = fig.add_subplot(3, 1, 2) ax2.plot(focus_positions, metric, 'bo', label='{}'.format(merit_function)) if fitted: - fs = np.arange(focus_positions[0], focus_positions[-1] + 1) - ax2.plot(fs, fit(fs), 'b-', label='Smoothing spline fit') + fs = np.arange(focus_positions[fitting_indices[0]], + focus_positions[fitting_indices[1]] + 1) + ax2.plot(fs, fit(fs), 'b-', label='Polynomial fit') ax2.set_xlim(focus_positions[0] - focus_step / 2, focus_positions[-1] + focus_step / 2) u_limit = 1.10 * metric.max() diff --git a/pocs/observatory.py b/pocs/observatory.py index 1d52b0390..383e9b1a7 100644 --- a/pocs/observatory.py +++ b/pocs/observatory.py @@ -455,7 +455,7 @@ def get_standard_headers(self, observation=None): return headers - def autofocus_cameras(self, camera_list=None, coarse=False): + def autofocus_cameras(self, camera_list=None, coarse=None): """ Perform autofocus on all cameras with focus capability, or a named subset of these. Optionally will perform a coarse autofocus first, otherwise will diff --git a/pocs/utils/images/__init__.py b/pocs/utils/images/__init__.py index 1f2dc81ce..708e5d049 100644 --- a/pocs/utils/images/__init__.py +++ b/pocs/utils/images/__init__.py @@ -113,7 +113,7 @@ def _make_pretty_from_fits( title = '{} ({}s {}) {}'.format(title, exp_time, filter_type, date_time) norm = ImageNormalize(interval=PercentileInterval(percent_value), stretch=LogStretch()) - plt.figure(figsize=figsize, dpi=dpi) + fig = plt.figure(figsize=figsize, dpi=dpi) if wcs.is_celestial: ax = plt.subplot(projection=wcs) @@ -143,7 +143,8 @@ def _make_pretty_from_fits( ax.set_xlabel('X / pixels') ax.set_ylabel('Y / pixels') - ax.imshow(data, norm=norm, cmap=palette, origin='lower') + im = ax.imshow(data, norm=norm, cmap=palette, origin='lower') + fig.colorbar(im) plt.title(title) new_filename = fname.replace('.fits', '.jpg') diff --git a/resources/bisque/dome/park.js b/resources/bisque/dome/park.js index 02618e698..a23c8e457 100644 --- a/resources/bisque/dome/park.js +++ b/resources/bisque/dome/park.js @@ -3,5 +3,12 @@ sky6Dome.Connect(); if (sky6Dome.IsConnected == 0) { Out = "Not connected" } else { - Out = sky6Dome.Park(); + sky6Dome.Park(); + while (!sky6Dome.IsParkComplete) { + sky6Web.Sleep(1000); + } + + Out = JSON.stringify({ + "success": sky6Dome.IsParkComplete + }); };