From 49ad768da27437f6d93c2a8ceb884f0eed7c90af Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Tue, 22 May 2018 03:24:49 +1000 Subject: [PATCH 1/8] Reverted focus peak fitting from spline to Lorentzian --- pocs/focuser/focuser.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/pocs/focuser/focuser.py b/pocs/focuser/focuser.py index 67a742147..a923790d1 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 @@ -437,22 +437,33 @@ 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 data around the maximum value to determine best focus position. + # Initialise models + fit = models.Lorentz1D(x_0=focus_positions[imax], amplitude=metric.max()) - # Fit smoothing spline to focus metric data - fit = UnivariateSpline(focus_positions, metric, w=weights, k=4, ext='raise') + # Initialise fitter + fitter = fitting.LevMarLSQFitter() - 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 + # 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(fit, + focus_positions[fitting_indices[0]:fitting_indices[1] + 1], + metric[fitting_indices[0]:fitting_indices[1] + 1]) + + best_focus = fit.x_0.value + + # 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[0] + + 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[-1] else: # Coarse focus, just use max value. From e364a6bfd315a68d6d08d4eb7917c5160e149344 Mon Sep 17 00:00:00 2001 From: Huntsman Array Date: Wed, 23 May 2018 00:29:50 +1000 Subject: [PATCH 2/8] Assorted small fixes --- conf_files/peas.yaml | 3 ++- pocs/camera/camera.py | 5 ++++ pocs/camera/sbigudrv.py | 1 + pocs/core.py | 8 +++++++ pocs/dome/bisque.py | 44 ++++++++++++++++++++++++++++++++++- pocs/observatory.py | 8 ++++++- pocs/utils/theskyx.py | 1 + resources/bisque/dome/park.js | 9 ++++++- 8 files changed, 75 insertions(+), 4 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..722c0bd2d 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..6ca1d3c1b 100644 --- a/pocs/core.py +++ b/pocs/core.py @@ -5,6 +5,7 @@ import warnings import multiprocessing import zmq +import requests from astropy import units as u @@ -181,6 +182,13 @@ def say(self, msg): self.logger.info('Unit says: {}', msg) self.send_message(msg, channel='PANCHAT') + if 'slack_webhook_url' in self.config: + try: + url = self.config['slack_webhook_url'] + response = requests.post(url, json={"text": msg}) + except Exception as e: + self.logger.debug("Error posting to slack: {}".format(e)) + def send_message(self, msg, channel='POCS'): """ Send a message diff --git a/pocs/dome/bisque.py b/pocs/dome/bisque.py index ad80f314b..51f58c0e2 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,18 @@ 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 +117,36 @@ 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 ################################################################################################## diff --git a/pocs/observatory.py b/pocs/observatory.py index 1d52b0390..cbc3aaf7b 100644 --- a/pocs/observatory.py +++ b/pocs/observatory.py @@ -52,6 +52,7 @@ def __init__(self, *args, **kwargs): self.cameras = OrderedDict() self._primary_camera = None self._create_cameras(**kwargs) + self._coarse_focus_done = False # TODO(jamessynge): Discuss with Wilfred the serial port validation behavior # here compared to that for the mount. @@ -455,7 +456,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 @@ -485,6 +486,9 @@ def autofocus_cameras(self, camera_list=None, coarse=False): autofocus_events = dict() + if coarse is None: + coarse = not self._coarse_focus_done + # Start autofocus with each camera for cam_name, camera in cameras.items(): self.logger.debug("Autofocusing camera: {}".format(cam_name)) @@ -507,6 +511,8 @@ def autofocus_cameras(self, camera_list=None, coarse=False): else: autofocus_events[cam_name] = autofocus_event + self._coarse_focus_done = True + return autofocus_events def open_dome(self): diff --git a/pocs/utils/theskyx.py b/pocs/utils/theskyx.py index c1607c0fa..d34ff9448 100644 --- a/pocs/utils/theskyx.py +++ b/pocs/utils/theskyx.py @@ -56,6 +56,7 @@ def read(self, timeout=5): try: response = self.socket.recv(4096).decode() + self.logger.debug('Response from reading TSX socket: {}'.format(response)) if '|' in response: response, err = response.split('|') 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 + }); }; From 320865e755e54f407325472f64d33a4cac58cb38 Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Wed, 23 May 2018 02:46:04 +1000 Subject: [PATCH 3/8] Change Lorentzian fitting to polynomial fitting around peak --- pocs/focuser/focuser.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pocs/focuser/focuser.py b/pocs/focuser/focuser.py index a923790d1..1e19af1fa 100644 --- a/pocs/focuser/focuser.py +++ b/pocs/focuser/focuser.py @@ -439,7 +439,11 @@ def _autofocus(self, elif not coarse: # Fit data around the maximum value to determine best focus position. # Initialise models - fit = models.Lorentz1D(x_0=focus_positions[imax], amplitude=metric.max()) + 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() @@ -448,22 +452,22 @@ def _autofocus(self, fitting_indices = (max(imax - 2, 0), min(imax + 2, n_positions - 1)) # Fit models to data - fit = fitter(fit, + fit = fitter(reparameterised_polynomial, focus_positions[fitting_indices[0]:fitting_indices[1] + 1], metric[fitting_indices[0]:fitting_indices[1] + 1]) - best_focus = fit.x_0.value + best_focus = -fit.offset_0 # 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[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[-1] + best_focus = focus_positions[-2] else: # Coarse focus, just use max value. @@ -473,8 +477,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() From 2d4c83d3231572ffdd3a070997b957ece134513a Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Wed, 23 May 2018 02:57:15 +1000 Subject: [PATCH 4/8] Add colorbar to pretty images --- pocs/camera/camera.py | 2 +- pocs/utils/images/__init__.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pocs/camera/camera.py b/pocs/camera/camera.py index 722c0bd2d..ed42bb888 100644 --- a/pocs/camera/camera.py +++ b/pocs/camera/camera.py @@ -252,7 +252,7 @@ def process_exposure(self, info, observation_event, exposure_event=None): try: info['exp_time'] = info['exp_time'].value except Exception: - pass + pass if info['is_primary']: self.logger.debug("Adding current observation to db: {}".format(image_id)) diff --git a/pocs/utils/images/__init__.py b/pocs/utils/images/__init__.py index c5b92b8cd..21372a41b 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') From 3553b9eac620b1d1db16073b183779a5218fd0f4 Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Wed, 23 May 2018 03:22:26 +1000 Subject: [PATCH 5/8] Fix focus plots so fit appeats --- pocs/focuser/focuser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pocs/focuser/focuser.py b/pocs/focuser/focuser.py index 1e19af1fa..1ac21d330 100644 --- a/pocs/focuser/focuser.py +++ b/pocs/focuser/focuser.py @@ -457,6 +457,7 @@ def _autofocus(self, 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]: From eec7a129a7eef66223c7e7eae004a23b3853fee2 Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Wed, 11 Jul 2018 03:25:11 +1000 Subject: [PATCH 6/8] Codestyle fixes --- pocs/dome/bisque.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pocs/dome/bisque.py b/pocs/dome/bisque.py index 51f58c0e2..c51f9e8bd 100644 --- a/pocs/dome/bisque.py +++ b/pocs/dome/bisque.py @@ -64,12 +64,11 @@ def status(self): @property def position(self): self.write(self._get_command('dome/position.js')) - return self.read() + return self.read() @property def is_parked(self): return self._is_parked - def connect(self): if not self.is_connected: @@ -117,7 +116,6 @@ def close(self): return self.is_closed - def park(self): if self.is_connected: self.write(self._get_command('dome/park.js')) @@ -125,8 +123,7 @@ def park(self): self._is_parked = response['success'] - return self.is_parked - + return self.is_parked def unpark(self): if self.is_connected: @@ -135,8 +132,7 @@ def unpark(self): self._is_parked = not response['success'] - return not self.is_parked - + return not self.is_parked def find_home(self): if self.is_connected: @@ -145,7 +141,7 @@ def find_home(self): self._is_parked = response['success'] - return self.is_parked + return self.is_parked ################################################################################################## # Communication Methods @@ -175,7 +171,6 @@ def read(self, timeout=5): return response_obj - ################################################################################################## # Private Methods ################################################################################################## From fe92b923907ee5c55410db8e52e01501f287be3a Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Wed, 11 Jul 2018 14:39:03 +1000 Subject: [PATCH 7/8] Removed Slack code & excessive TheSkyX logging --- pocs/core.py | 10 ---------- pocs/utils/theskyx.py | 1 - 2 files changed, 11 deletions(-) diff --git a/pocs/core.py b/pocs/core.py index 6ca1d3c1b..5642b9607 100644 --- a/pocs/core.py +++ b/pocs/core.py @@ -122,7 +122,6 @@ def has_messaging(self, value): def should_retry(self): return self._obs_run_retries >= 0 - ################################################################################################## # Methods ################################################################################################## @@ -182,13 +181,6 @@ def say(self, msg): self.logger.info('Unit says: {}', msg) self.send_message(msg, channel='PANCHAT') - if 'slack_webhook_url' in self.config: - try: - url = self.config['slack_webhook_url'] - response = requests.post(url, json={"text": msg}) - except Exception as e: - self.logger.debug("Error posting to slack: {}".format(e)) - def send_message(self, msg, channel='POCS'): """ Send a message @@ -404,7 +396,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 ################################################################################################## @@ -450,7 +441,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/utils/theskyx.py b/pocs/utils/theskyx.py index d34ff9448..c1607c0fa 100644 --- a/pocs/utils/theskyx.py +++ b/pocs/utils/theskyx.py @@ -56,7 +56,6 @@ def read(self, timeout=5): try: response = self.socket.recv(4096).decode() - self.logger.debug('Response from reading TSX socket: {}'.format(response)) if '|' in response: response, err = response.split('|') From bdad3724203e78c1f8c51d235365aed6978cda97 Mon Sep 17 00:00:00 2001 From: Anthony Horton Date: Fri, 13 Jul 2018 20:01:47 +1000 Subject: [PATCH 8/8] Removed coarse focus on 1st focus code --- pocs/core.py | 1 - pocs/observatory.py | 6 ------ 2 files changed, 7 deletions(-) diff --git a/pocs/core.py b/pocs/core.py index 5642b9607..7c2e72baf 100644 --- a/pocs/core.py +++ b/pocs/core.py @@ -5,7 +5,6 @@ import warnings import multiprocessing import zmq -import requests from astropy import units as u diff --git a/pocs/observatory.py b/pocs/observatory.py index cbc3aaf7b..383e9b1a7 100644 --- a/pocs/observatory.py +++ b/pocs/observatory.py @@ -52,7 +52,6 @@ def __init__(self, *args, **kwargs): self.cameras = OrderedDict() self._primary_camera = None self._create_cameras(**kwargs) - self._coarse_focus_done = False # TODO(jamessynge): Discuss with Wilfred the serial port validation behavior # here compared to that for the mount. @@ -486,9 +485,6 @@ def autofocus_cameras(self, camera_list=None, coarse=None): autofocus_events = dict() - if coarse is None: - coarse = not self._coarse_focus_done - # Start autofocus with each camera for cam_name, camera in cameras.items(): self.logger.debug("Autofocusing camera: {}".format(cam_name)) @@ -511,8 +507,6 @@ def autofocus_cameras(self, camera_list=None, coarse=None): else: autofocus_events[cam_name] = autofocus_event - self._coarse_focus_done = True - return autofocus_events def open_dome(self):