diff --git a/.github/workflows/wfssrv-tests.yml b/.github/workflows/wfssrv-tests.yml
index 669bac3..3eb420a 100644
--- a/.github/workflows/wfssrv-tests.yml
+++ b/.github/workflows/wfssrv-tests.yml
@@ -10,12 +10,11 @@ jobs:
matrix_tests:
runs-on: ${{ matrix.os }}
- if: "!contains(github.event.head_commit.message, '[ci skip]')"
strategy:
matrix:
os: [ubuntu-latest]
- python-ver: [7, 8]
- tox-env: [cov, astropylts, astropydev, numpydev]
+ python-ver: [13]
+ tox-env: [cov, astropydev, numpydev]
steps:
- uses: actions/checkout@v1
- name: Set up python 3.${{ matrix.python-ver }} with tox environment ${{ matrix.tox-env }} on ${{ matrix.os }}
@@ -39,13 +38,12 @@ jobs:
doc_test:
runs-on: ubuntu-latest
- if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- uses: actions/checkout@v1
- name: Set up Python to build docs with sphinx
uses: actions/setup-python@v1
with:
- python-version: 3.8
+ python-version: 3.13
- name: Install base dependencies
run: |
python -m pip install --upgrade pip
@@ -57,13 +55,12 @@ jobs:
codestyle:
runs-on: ubuntu-latest
- if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- uses: actions/checkout@v1
- name: Python codestyle check
uses: actions/setup-python@v1
with:
- python-version: 3.8
+ python-version: 3.13
- name: Install base dependencies
run: |
python -m pip install --upgrade pip
diff --git a/Dockerfile b/Dockerfile
index 40bc902..009b003 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,13 +1,16 @@
-FROM mmtobservatory/mmtwfs:latest
+FROM python:3.13
-MAINTAINER T. E. Pickering "te.pickering@gmail.com"
+LABEL maintainer="te.pickering@gmail.com"
COPY . .
-RUN python -m pip install --upgrade pip
-RUN python -m pip install git+https://github.com/MMTObservatory/camsrv.git#egg=camsrv
-RUN python -m pip install git+https://github.com/MMTObservatory/cwfs.git#egg=cwfs
-RUN python -m pip install -e .[all]
+RUN apt-get update
+RUN apt-get install -y git
+
+RUN python -m pip install --upgrade pip setuptools setuptools_scm
+RUN python -m pip install git+https://github.com/MMTObservatory/camsrv#egg=camsrv
+RUN python -m pip install git+https://github.com/MMTObservatory/mmtwfs#egg=mmtwfs
+RUN python -m pip install .
EXPOSE 8080
diff --git a/LICENSE.rst b/LICENSE.rst
deleted file mode 100644
index 91bf16b..0000000
--- a/LICENSE.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright (c) 2018, MMT Observatory
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-* Redistributions in binary form must reproduce the above copyright notice, this
- list of conditions and the following disclaimer in the documentation and/or
- other materials provided with the distribution.
-* Neither the name of the MMT Observatory nor the names of its contributors may be
- used to endorse or promote products derived from this software without
- specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/wfssrv/cwfssrv.py b/OLD/cwfssrv.py
similarity index 100%
rename from wfssrv/cwfssrv.py
rename to OLD/cwfssrv.py
diff --git a/.github/workflows/docker.yml b/OLD/docker.yml
similarity index 100%
rename from .github/workflows/docker.yml
rename to OLD/docker.yml
diff --git a/README.md b/README.md
index 7f5c9f5..d9eb268 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,4 @@ MMT Wavefront Sensor Analysis Interface

-
-
[](https://codecov.io/gh/MMTObservatory/WFSsrv)
diff --git a/docs/conf.py b/docs/conf.py
index 6568bf6..9ae0109 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -36,13 +36,6 @@
print('ERROR: the documentation requires the sphinx-astropy package to be installed')
sys.exit(1)
-# Get configuration information from setup.cfg
-from configparser import ConfigParser
-conf = ConfigParser()
-
-conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')])
-setup_cfg = dict(conf.items('metadata'))
-
# -- General configuration ----------------------------------------------------
# By default, highlight as Python 3.
@@ -67,17 +60,17 @@
# -- Project information ------------------------------------------------------
# This does not *have* to match the package name, but typically does
-project = setup_cfg['name']
-author = setup_cfg['author']
+project = "wfssrv"
+author = "T. E. Pickering"
copyright = '{0}, {1}'.format(
- datetime.datetime.now().year, setup_cfg['author'])
+ datetime.datetime.now().year, "T. E. Pickering")
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
-import_module(setup_cfg['name'])
-package = sys.modules[setup_cfg['name']]
+import_module("wfssrv")
+package = sys.modules["wfssrv"]
# The short X.Y version.
version = package.__version__.split('-', 1)[0]
@@ -106,7 +99,7 @@
html_theme_options = {
- 'logotext1': 'mmtwfs', # white, semi-bold
+ 'logotext1': 'wfssrv', # white, semi-bold
'logotext2': '', # orange, light
'logotext3': ':docs' # white, light
}
@@ -151,21 +144,8 @@
man_pages = [('index', project.lower(), project + u' Documentation',
[author], 1)]
-
-# -- Options for the edit_on_github extension ---------------------------------
-
-if setup_cfg.get('edit_on_github').lower() == 'true':
-
- extensions += ['sphinx_astropy.ext.edit_on_github']
-
- edit_on_github_project = setup_cfg['github_project']
- edit_on_github_branch = "master"
-
- edit_on_github_source_root = ""
- edit_on_github_doc_root = "docs"
-
# -- Resolving issue number to links in changelog -----------------------------
-github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_project'])
+github_issues_url = 'https://github.com/{0}/issues/'.format("wfssrv")
# -- Turn on nitpicky mode for sphinx (to warn about references not found) ----
#
diff --git a/pyproject.toml b/pyproject.toml
index 7e7daea..38fd7ae 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,111 @@
-[build-system]
+[project]
+name = "wfssrv"
+dynamic = ["version"]
+authors = [
+ { name = "T. E. Pickering", email = "te.pickering@gmail.com"}
+]
+license = {file = "licenses/LICENSE.rst"}
+readme = "README.rst"
+description = "MMTO Wavefront Sensor Analysis Server"
+requires-python = ">=3.12"
+dependencies = [
+ "tornado",
+ "redis",
+ "matplotlib",
+ "camsrv@git+https://github.com/MMTObservatory/camsrv",
+ "mmtwfs@git+https://github.com/MMTObservatory/mmtwfs",
+]
+
+[project.optional-dependencies]
+test = [
+ "tox",
+ "coverage",
+ "pytest-astropy",
+ "black",
+ "flake8",
+ "codecov",
+]
+docs = [
+ "sphinx-astropy",
+]
+
+[project.scripts]
+"wfs_header.py" = "wfssrv.scripts.wfs_header:main"
+wfssrv = "wfssrv.wfssrv:main"
+
+[project.urls]
+Repository = "https://github.com/mmtobservatory/wfssrv.git"
+Documentation = "https://wfssrv.readthedocs.io/"
+
+[tool.setuptools]
+include-package-data = true
+
+[tool.setuptools.package-data]
+"wfssrv.templates" = ["**"]
+"wfssrv.static" = ["**"]
+
+[tool.setuptools.packages]
+find = {}
-requires = ["setuptools",
- "setuptools_scm",
- "wheel"]
+[tool.setuptools_scm]
+version_file = "wfssrv/version.py"
+
+[build-system]
+requires = [
+ "setuptools",
+ "setuptools_scm",
+]
build-backend = 'setuptools.build_meta'
+
+[tool.pytest.ini_options]
+minversion = 7.0
+testpaths = [
+ "wfssrv/tests",
+]
+astropy_header = true
+doctest_plus = "enabled"
+text_file_format = "rst"
+addopts = [
+ "--color=yes",
+ "--doctest-rst",
+]
+xfail_strict = true
+filterwarnings = [
+ "error",
+ "ignore:numpy\\.ufunc size changed:RuntimeWarning",
+ "ignore:numpy\\.ndarray size changed:RuntimeWarning",
+ # weird no event loop deprecation warning
+ "ignore:.*There is no current event loop:DeprecationWarning",
+]
+
+[tool.coverage]
+
+ [tool.coverage.run]
+ omit = [
+ "wfssrv/_astropy_init*",
+ "wfssrv/conftest.py",
+ "wfssrv/tests/*",
+ "wfssrv/version*",
+ "*/wfssrv/_astropy_init*",
+ "*/wfssrv/conftest.py",
+ "*/wfssrv/tests/*",
+ "*/wfssrv/version*",
+ ]
+
+ [tool.coverage.report]
+ exclude_lines = [
+ # Have to re-enable the standard pragma
+ "pragma: no cover",
+ # Don't complain about packages we have installed
+ "except ImportError",
+ # Don't complain if tests don't hit defensive assertion code:
+ "raise AssertionError",
+ "raise NotImplementedError",
+ # Don't complain about script hooks
+ "'def main(.*):'",
+ # Ignore branches that don't pertain to this version of Python
+ "pragma: py{ignore_python_version}",
+ # Don't complain about IPython completion helper
+ "def _ipython_key_completions_",
+ ]
diff --git a/scripts/sao-wfs-server b/scripts/sao-wfs-server
index 421fdd2..fd7c2f4 100755
--- a/scripts/sao-wfs-server
+++ b/scripts/sao-wfs-server
@@ -13,7 +13,7 @@ if {[info exists env(WFSROOT)]} {
if {[info exists env(CONDA_PREFIX)]} {
set header_script [file join $env(CONDA_PREFIX) bin wfs_header.py]
} else {
- set header_script /mmt/conda/bin/wfs_header.py
+ set header_script /mmt/condaforge/envs/mmtwfs/bin/wfs_header.py
}
# nominally the port should be 9876, but query for it via DNS to be sure and in case it gets moved
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 96b6985..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,100 +0,0 @@
-[metadata]
-name = wfssrv
-description = MMTO Wavefront Sensor Analysis Server
-long_description = file: README.md
-author = T. E. Pickering (MMT Observatory)
-author_email = tim@mmto.org
-license = BSD-3
-license_file = LICENSE.rst
-url = https://github.com/MMTObservatory/WFSsrv
-edit_on_github = True
-github_project = MMTObservatory/WFSsrv
-
-[options]
-zip_safe = False
-packages = find:
-python_requires = >=3.7
-setup_requires = setuptools_scm
-install_requires =
- tornado
- astropy
- mmtwfs
- camsrv
-
-include_package_data = True
-
-[options.extras_require]
-docs =
- sphinx-astropy
-test =
- tox
- pytest
- pytest-cov
- pytest-astropy
- nose
- coverage
- codecov
-all =
- matplotlib
- scipy
- astropy
- photutils
- scikit-image
- dnspython
- poppy
- lmfit
- ccdproc
- astroscrappy
- redis
-
-[options.entry_points]
-console_scripts =
- wfs_header.py = wfssrv.scripts.wfs_header:main
- wfssrv = wfssrv.wfssrv:main
-
-[options.package_data]
-wfssrv.templates = *.html
-wfssrv.static = */*
-wfssrv.tests = coveragerc
-
-[tool:pytest]
-testpaths = "wfssrv" "docs"
-astropy_header = true
-doctest_plus = enabled
-text_file_format = rst
-addopts = --doctest-rst
-
-[coverage:run]
-parallel = True
-branch = True
-omit =
- wfssrv/_astropy_init*
- wfssrv/conftest.py
- wfssrv/*setup_package*
- wfssrv/tests/*
- wfssrv/*/tests/*
- wfssrv/extern/*
- wfssrv/version*
- */wfssrv/_astropy_init*
- */wfssrv/conftest.py
- */wfssrv/*setup_package*
- */wfssrv/tests/*
- */wfssrv/*/tests/*
- */wfssrv/extern/*
- */wfssrv/version*
-
-[coverage:report]
-exclude_lines =
- # Have to re-enable the standard pragma
- pragma: no cover
- # Don't complain about packages we have installed
- except ImportError
- # Don't complain if tests don't hit assertions
- raise AssertionError
- raise NotImplementedError
- # Don't complain about script hooks
- def main\(.*\):
- # Ignore branches that don't pertain to this version of Python
- pragma: py{ignore_python_version}
- # Don't complain about IPython completion helper
- def _ipython_key_completions_
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 8fb0105..0000000
--- a/setup.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python
-# Licensed under a 3-clause BSD style license - see LICENSE.rst
-
-# NOTE: The configuration for the package, including the name, version, and
-# other information are set in the setup.cfg file.
-
-import os
-import sys
-
-from setuptools import setup
-from setuptools.config import read_configuration
-
-
-# First provide helpful messages if contributors try and run legacy commands
-# for tests or docs.
-
-TEST_HELP = """
-Note: running tests is no longer done using 'python setup.py test'. Instead
-you will need to run:
-
- tox -e test
-
-If you don't already have tox installed, you can install it with:
-
- pip install tox
-
-If you only want to run part of the test suite, you can also use pytest
-directly with::
-
- pip install -e .[test]
- pytest
-
-For more information, see:
-
- http://docs.astropy.org/en/latest/development/testguide.html#running-tests
-"""
-
-if 'test' in sys.argv:
- print(TEST_HELP)
- sys.exit(1)
-
-DOCS_HELP = """
-Note: building the documentation is no longer done using
-'python setup.py build_docs'. Instead you will need to run:
-
- tox -e build_docs
-
-If you don't already have tox installed, you can install it with:
-
- pip install tox
-
-You can also build the documentation with Sphinx directly using::
-
- pip install -e .[docs]
- cd docs
- make html
-
-For more information, see:
-
- http://docs.astropy.org/en/latest/install.html#builddocs
-"""
-
-if 'build_docs' in sys.argv or 'build_sphinx' in sys.argv:
- print(DOCS_HELP)
- sys.exit(1)
-
-VERSION_TEMPLATE = """
-# Note that we need to fall back to the hard-coded version if either
-# setuptools_scm can't be imported or setuptools_scm can't determine the
-# version, so we catch the generic 'Exception'.
-try:
- from setuptools_scm import get_version
- version = get_version(root='..', relative_to=__file__)
-except Exception:
- version = '{version}'
-""".lstrip()
-
-
-setup(use_scm_version={'write_to': os.path.join('wfssrv', 'version.py'),
- 'write_to_template': VERSION_TEMPLATE})
diff --git a/tox.ini b/tox.ini
index 945f4f6..7839a6b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,7 @@
[tox]
envlist =
- py{37,38}{,-test}{,-cov}
- py{37,38}-astropylts
- py{37,38}-{numpy,astropy}dev
+ py{312,313}{,-test}{,-cov}
+ py{312,313}-{numpy,astropy}dev
build_docs
linkcheck
codestyle
@@ -16,7 +15,7 @@ isolated_build = true
usedevelop = True
# Pass through the following environemnt variables which may be needed for the CI
-passenv = HOME WINDIR LC_ALL LC_CTYPE CC CI
+passenv = HOME,WINDIR,LC_ALL,LC_CTYPE,CC,CI
# Run the tests in a temporary directory to make sure that we don't import
# astropy from the source tree
@@ -33,16 +32,10 @@ changedir = {toxworkdir}/tox_testing
description =
test: run tests
cov: with coverage enabled
- astropylts: with astropy LTS
{numpy,astropy}dev: with latest master from github repo
cov_report: generate HTML coverage report
deps =
- git+https://github.com/MMTObservatory/cwfs.git#egg=cwfs
- git+https://github.com/MMTObservatory/mmtwfs.git#egg=mmtwfs
- git+https://github.com/MMTObservatory/camsrv.git#egg=camsrv
- git+https://github.com/MMTObservatory/indiclient.git#egg=indiclient
- astropylts: astropy==4.0.*
numpydev: git+https://github.com/numpy/numpy.git#egg=numpy
astropydev: git+https://github.com/astropy/astropy.git#egg=astropy
@@ -53,19 +46,17 @@ depends =
# The following indicates which extras_require from setup.cfg will be installed
extras =
test
- all
commands =
pip freeze
!cov: pytest --pyargs wfssrv {toxinidir}/docs {posargs}
- cov: pytest --pyargs wfssrv {toxinidir}/docs --cov wfssrv --cov-config={toxinidir}/setup.cfg {posargs}
+ cov: pytest --pyargs wfssrv {toxinidir}/docs --cov wfssrv --cov-config={toxinidir}/pyproject.toml {posargs}
cov: coverage xml -o {toxinidir}/coverage.xml
[testenv:build_docs]
changedir = docs
description = invoke sphinx-build to build the HTML docs
extras =
- all
docs
commands =
pip freeze
@@ -75,7 +66,6 @@ commands =
changedir = docs
description = check the links in the HTML docs
extras =
- all
docs
commands =
pip freeze
diff --git a/wfssrv/__init__.py b/wfssrv/__init__.py
index 43f77e9..000a5a2 100644
--- a/wfssrv/__init__.py
+++ b/wfssrv/__init__.py
@@ -1,25 +1,8 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
-# Affiliated packages may add whatever they like to this file, but
-# should keep this content at the top.
-# ----------------------------------------------------------------------------
-from ._astropy_init import * # noqa
-# ----------------------------------------------------------------------------
-
-# Enforce Python version check during package import.
-# This is the same check as the one at the top of setup.py
-import sys
-from distutils.version import LooseVersion
-
-__minimum_python_version__ = "3.7"
-
__all__ = []
-
-class UnsupportedPythonError(Exception):
- pass
-
-
-if LooseVersion(sys.version) < LooseVersion(__minimum_python_version__):
- raise UnsupportedPythonError("wfssrv does not support Python < {}"
- .format(__minimum_python_version__))
+try:
+ from .version import version as __version__
+except ImportError:
+ __version__ = ""
diff --git a/wfssrv/_astropy_init.py b/wfssrv/_astropy_init.py
deleted file mode 100644
index 2dffe8f..0000000
--- a/wfssrv/_astropy_init.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Licensed under a 3-clause BSD style license - see LICENSE.rst
-
-__all__ = ['__version__']
-
-# this indicates whether or not we are in the package's setup.py
-try:
- _ASTROPY_SETUP_
-except NameError:
- import builtins
- builtins._ASTROPY_SETUP_ = False
-
-try:
- from .version import version as __version__
-except ImportError:
- __version__ = ''
-
-
-if not _ASTROPY_SETUP_: # noqa
- import os
- from warnings import warn
- from astropy.config.configuration import (
- update_default_config,
- ConfigurationDefaultMissingError,
- ConfigurationDefaultMissingWarning)
-
- # Create the test function for self test
- from astropy.tests.runner import TestRunner
- test = TestRunner.make_test_runner_in(os.path.dirname(__file__))
- test.__test__ = False
- __all__ += ['test']
-
- # add these here so we only need to cleanup the namespace at the end
- config_dir = None
-
- if not os.environ.get('ASTROPY_SKIP_CONFIG_UPDATE', False):
- config_dir = os.path.dirname(__file__)
- config_template = os.path.join(config_dir, __package__ + ".cfg")
- if os.path.isfile(config_template):
- try:
- update_default_config(
- __package__, config_dir, version=__version__)
- except TypeError as orig_error:
- try:
- update_default_config(__package__, config_dir)
- except ConfigurationDefaultMissingError as e:
- wmsg = (e.args[0] +
- " Cannot install default profile. If you are "
- "importing from source, this is expected.")
- warn(ConfigurationDefaultMissingWarning(wmsg))
- del e
- except Exception:
- raise orig_error
diff --git a/wfssrv/conftest.py b/wfssrv/conftest.py
index 672b273..b164677 100644
--- a/wfssrv/conftest.py
+++ b/wfssrv/conftest.py
@@ -3,21 +3,12 @@
# get picked up when running the tests inside an interpreter using
# packagename.test
-import os
+try:
+ from pytest_astropy_header.display import TESTED_VERSIONS
-from astropy.version import version as astropy_version
-
-# For Astropy 3.0 and later, we can use the standalone pytest plugin
-if astropy_version < '3.0':
- from astropy.tests.pytest_plugins import * # noqa
- del pytest_report_header
ASTROPY_HEADER = True
-else:
- try:
- from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS
- ASTROPY_HEADER = True
- except ImportError:
- ASTROPY_HEADER = False
+except ImportError:
+ ASTROPY_HEADER = False
def pytest_configure(config):
@@ -26,24 +17,6 @@ def pytest_configure(config):
config.option.astropy_header = True
- # Customize the following lines to add/remove entries from the list of
- # packages for which version numbers are displayed when running the tests.
- PYTEST_HEADER_MODULES.pop('Pandas', None)
- PYTEST_HEADER_MODULES['scikit-image'] = 'skimage'
-
- from . import __version__
- packagename = os.path.basename(os.path.dirname(__file__))
- TESTED_VERSIONS[packagename] = __version__
-
-# Uncomment the last two lines in this block to treat all DeprecationWarnings as
-# exceptions. For Astropy v2.0 or later, there are 2 additional keywords,
-# as follow (although default should work for most cases).
-# To ignore some packages that produce deprecation warnings on import
-# (in addition to 'compiler', 'scipy', 'pygments', 'ipykernel', and
-# 'setuptools'), add:
-# modules_to_ignore_on_import=['module_1', 'module_2']
-# To ignore some specific deprecation warning messages for Python version
-# MAJOR.MINOR or later, add:
-# warnings_to_ignore_by_pyver={(MAJOR, MINOR): ['Message to ignore']}
-# from astropy.tests.helper import enable_deprecations_as_exceptions # noqa
-# enable_deprecations_as_exceptions()
+ from wfssrv import __version__
+
+ TESTED_VERSIONS["camsrv"] = __version__
diff --git a/wfssrv/templates/wfs.html b/wfssrv/templates/wfs.html
index be688ef..9048feb 100644
--- a/wfssrv/templates/wfs.html
+++ b/wfssrv/templates/wfs.html
@@ -4,8 +4,10 @@
-
-
+
+
+
+
@@ -41,7 +43,7 @@
}
-
+
@@ -84,6 +86,7 @@
// The HTML element in which to place the figure
$('div#{{ figure }}')
);
+
{% end %}
getZerns();
diff --git a/wfssrv/tests/test_app.py b/wfssrv/tests/test_app.py
index 03a3c38..1ffc9c4 100644
--- a/wfssrv/tests/test_app.py
+++ b/wfssrv/tests/test_app.py
@@ -6,4 +6,4 @@
def test_wfs():
app = wfs()
- assert(app is not None)
+ assert app is not None
diff --git a/wfssrv/wfssrv.py b/wfssrv/wfssrv.py
index dbd1e29..bb09688 100644
--- a/wfssrv/wfssrv.py
+++ b/wfssrv/wfssrv.py
@@ -9,6 +9,10 @@
import os
import json
import pathlib
+import signal
+import socket
+from pathlib import Path
+
import redis
import numpy as np
@@ -35,16 +39,20 @@
import matplotlib
import matplotlib.pyplot as plt
-from matplotlib.backends.backend_webagg_core import (FigureCanvasWebAggCore, new_figure_manager_given_figure)
+from matplotlib.backends.backend_webagg import (
+ FigureCanvasWebAgg,
+ FigureManagerWebAgg,
+ new_figure_manager_given_figure,
+)
from mmtwfs.wfs import WFSFactory
from mmtwfs.zernike import ZernikeVector
from mmtwfs.telescope import MMT
-matplotlib.use('webagg')
-glog = logging.getLogger('')
-log = logging.getLogger('WFSsrv')
+matplotlib.use("webagg")
+glog = logging.getLogger("")
+log = logging.getLogger("WFSsrv")
log.setLevel(logging.DEBUG)
@@ -53,43 +61,59 @@ def create_default_figures():
figures = {}
ax = {}
data = np.zeros((512, 512))
- tel = MMT(secondary='f5') # secondary doesn't matter, just need methods for mirror forces/plots
+ tel = MMT(
+ secondary="f5"
+ ) # secondary doesn't matter, just need methods for mirror forces/plots
forces = tel.bending_forces(zv=zv)
# stub for plot showing bkg-subtracted WFS image with aperture positions
- figures['slopes'], ax['slopes'] = plt.subplots()
- figures['slopes'].set_label("Aperture Positions and Spot Movement")
- ax['slopes'].imshow(data, cmap='Greys', origin='lower', interpolation='None')
+ figures["slopes"], ax["slopes"] = plt.subplots()
+ figures["slopes"].set_label("Aperture Positions and Spot Movement")
+ ax["slopes"].imshow(data, cmap="Greys", origin="lower", interpolation="None")
# stub for plot showing bkg-subtracted WFS image and residuals slopes of wavefront fit
- figures['residuals'], ax['residuals'] = plt.subplots()
- figures['residuals'].set_label("Zernike Fit Residuals")
- ax['residuals'].imshow(data, cmap='Greys', origin='lower', interpolation='None')
+ figures["residuals"], ax["residuals"] = plt.subplots()
+ figures["residuals"].set_label("Zernike Fit Residuals")
+ ax["residuals"].imshow(data, cmap="Greys", origin="lower", interpolation="None")
# stub for zernike bar chart
- figures['barchart'] = zv.bar_chart()
+ figures["barchart"] = zv.bar_chart()
# stub for zernike fringe bar chart
- figures['fringebarchart'] = zv.fringe_bar_chart()
+ figures["fringebarchart"] = zv.fringe_bar_chart()
# stubs for mirror forces
- figures['forces'] = tel.plot_forces(forces)
- figures['forces'].set_label("Requested M1 Actuator Forces")
+ figures["forces"] = tel.plot_forces(forces)
+ figures["forces"].set_label("Requested M1 Actuator Forces")
# stubs for mirror forces
- figures['totalforces'] = tel.plot_forces(forces)
- figures['totalforces'].set_label("Total M1 Actuator Forces")
+ figures["totalforces"] = tel.plot_forces(forces)
+ figures["totalforces"].set_label("Total M1 Actuator Forces")
return figures
class WFSsrv(tornado.web.Application):
+
+ class MplJs(tornado.web.RequestHandler):
+ def get(self):
+ self.set_header("Content-Type", "application/javascript")
+
+ js_content = FigureManagerWebAgg.get_javascript()
+
+ self.write(js_content)
+
class HomeHandler(tornado.web.RequestHandler):
"""
Serves the main HTML page.
"""
+
def get(self):
- self.render("home.html", current=self.application.wfs, wfslist=self.application.wfs_names)
+ self.render(
+ "home.html",
+ current=self.application.wfs,
+ wfslist=self.application.wfs_names,
+ )
class SelectHandler(tornado.web.RequestHandler):
def get(self):
@@ -118,7 +142,9 @@ def get(self):
manager = self.application.managers[k]
fig_ids.append(manager.num)
figkeys.append(k)
- ws_uri = "ws://{req.host}/{figdiv}/ws".format(req=self.request, figdiv=k)
+ ws_uri = "ws://{req.host}/{figdiv}/ws".format(
+ req=self.request, figdiv=k
+ )
ws_uris.append(ws_uri)
self.render(
@@ -132,7 +158,7 @@ def get(self):
default_mode=self.application.wfs.default_mode,
m1_gain=self.application.wfs.m1_gain,
m2_gain=self.application.wfs.m2_gain,
- log_uri=log_uri
+ log_uri=log_uri,
)
except Exception as e:
log.warning(f"Must specify valid wfs: {wfs}. ({e})")
@@ -146,7 +172,9 @@ def get(self):
if self.application.wfs.connected:
log.info(f"{self.application.wfs.name} is connected.")
else:
- log.warning(f"Couldn't connect to {self.application.wfs.name}. Offline?")
+ log.warning(
+ f"Couldn't connect to {self.application.wfs.name}. Offline?"
+ )
else:
log.info(f"{self.application.wfs.name} already connected")
self.finish()
@@ -173,42 +201,44 @@ def async_plot(self, func, *args):
def make_barchart(self, zernikes, zrms, residual):
log.debug("Making bar chart...")
waves = zrms.value / 550.0
- self.application.figures['barchart'] = zernikes.bar_chart(
+ self.application.figures["barchart"] = zernikes.bar_chart(
residual=residual,
- title=f"Total Wavefront RMS: {zrms.round(1)} ({np.round(waves, 2)} waves)"
+ title=f"Total Wavefront RMS: {zrms.round(1)} ({np.round(waves, 2)} waves)",
)
- return 'barchart'
+ return "barchart"
@run_on_executor
def make_fringebarchart(self, zernikes, focus, cc_x, cc_y):
log.debug("Making fringe bar chart...")
- self.application.figures['fringebarchart'] = zernikes.fringe_bar_chart(
+ self.application.figures["fringebarchart"] = zernikes.fringe_bar_chart(
title="Focus: {0:0.1f} CC_X: {1:0.1f} CC_Y: {2:0.1f}".format(
focus,
cc_x,
cc_y,
),
- max_c=1500*u.nm,
+ max_c=1500 * u.nm,
)
- return 'fringebarchart'
+ return "fringebarchart"
@run_on_executor
def make_totalforces(self, telescope, forces, m1focus):
log.debug("Making total forces plot...")
- self.application.figures['totalforces'] = telescope.plot_forces(forces, m1focus)
- self.application.figures['totalforces'].set_label("Unmasked M1 Actuator Forces")
- return 'totalforces'
+ self.application.figures["totalforces"] = telescope.plot_forces(
+ forces, m1focus
+ )
+ self.application.figures["totalforces"].set_label(
+ "Unmasked M1 Actuator Forces"
+ )
+ return "totalforces"
@run_on_executor
def make_pendingforces(self, telescope, forces, m1focus, limit):
log.debug("Making pending forces plot...")
- self.application.figures['forces'] = telescope.plot_forces(
- forces,
- m1focus,
- limit=limit
+ self.application.figures["forces"] = telescope.plot_forces(
+ forces, m1focus, limit=limit
)
- self.application.figures['forces'].set_label("Requested M1 Actuator Forces")
- return 'forces'
+ self.application.figures["forces"].set_label("Requested M1 Actuator Forces")
+ return "forces"
def complete_refresh(self, key):
self.application.refresh_figure(key, self.application.figures[key])
@@ -226,11 +256,13 @@ def get(self):
spher = self.get_argument("spher", default=False)
if spher == "true":
- spher_mask = ['Z11', 'Z22']
+ spher_mask = ["Z11", "Z22"]
log.info(f"Ignoring all spherical terms {str(spher_mask)}...")
else:
- spher_mask = ['Z22']
- log.info(f"Only ignoring the high-order spherical terms {str(spher_mask)}...")
+ spher_mask = ["Z22"]
+ log.info(
+ f"Only ignoring the high-order spherical terms {str(spher_mask)}..."
+ )
if os.path.isfile(filename) and not self.application.busy:
self.application.busy = True
@@ -240,102 +272,140 @@ def get(self):
self.application.wfs.disconnect()
log.debug("Measuring slopes...")
- results = self.application.wfs.measure_slopes(filename, mode=mode, plot=True)
- if results['slopes'] is not None:
- if 'seeing' in results:
- log.info(f"Seeing (zenith): {results['seeing'].round(2)}")
- log.info(f"Seeing (raw): {results['raw_seeing'].round(2)}")
+ results = self.application.wfs.measure_slopes(
+ filename, mode=mode, plot=True
+ )
+ if results["slopes"] is not None:
+ if "vlt_seeing" in results:
+ log.info(f"Seeing (zenith): {results['vlt_seeing'].round(2)}")
+ log.info(f"Seeing (raw): {results['raw_vlt_seeing'].round(2)}")
if self.application.wfs.connected:
log.info("Publishing seeing values to redis.")
self.application.update_seeing(results)
tel = self.application.wfs.telescope
log.debug("Making slopes plot...")
- self.application.figures['slopes'] = results['figures']['slopes']
- self.application.refresh_figure('slopes', self.application.figures['slopes'])
+ self.application.figures["slopes"] = results["figures"]["slopes"]
+ self.application.refresh_figure(
+ "slopes", self.application.figures["slopes"]
+ )
log.debug("Fitting wavefront...")
zresults = self.application.wfs.fit_wavefront(results, plot=True)
- if zresults['fit_report'].success:
+ if zresults["fit_report"].success:
log.info(f"Residual RMS: {zresults['residual_rms'].round(2)}")
- self.application.figures['residuals'] = zresults['resid_plot']
- self.application.refresh_figure('residuals', self.application.figures['residuals'])
+ self.application.figures["residuals"] = zresults["resid_plot"]
+ self.application.refresh_figure(
+ "residuals", self.application.figures["residuals"]
+ )
- zvec = zresults['zernike']
- zvec_raw = zresults['rot_zernike']
- zvec_ref = zresults['ref_zernike']
+ zvec = zresults["zernike"]
+ zvec_raw = zresults["rot_zernike"]
+ zvec_ref = zresults["ref_zernike"]
self.application.wavefront_fit = zvec.copy()
m1gain = self.application.wfs.m1_gain
# this is the total if we try to correct everything as fit
- totforces, totm1focus, zv_totmasked = tel.calculate_primary_corrections(zvec.copy(), gain=m1gain)
+ totforces, totm1focus, zv_totmasked = (
+ tel.calculate_primary_corrections(zvec.copy(), gain=m1gain)
+ )
- self.async_plot(self.make_barchart, zvec.copy(), zresults['zernike_rms'], zresults['residual_rms'])
- self.async_plot(self.make_totalforces, tel, totforces, totm1focus)
+ self.async_plot(
+ self.make_barchart,
+ zvec.copy(),
+ zresults["zernike_rms"],
+ zresults["residual_rms"],
+ )
+ self.async_plot(
+ self.make_totalforces, tel, totforces, totm1focus
+ )
log.debug("Saving files and calculating corrections...")
zvec_file = self.application.datadir / (filename + ".zernike")
- zvec_raw_file = self.application.datadir / (filename + ".raw.zernike")
- zvec_ref_file = self.application.datadir / (filename + ".ref.zernike")
+ zvec_raw_file = self.application.datadir / (
+ filename + ".raw.zernike"
+ )
+ zvec_ref_file = self.application.datadir / (
+ filename + ".ref.zernike"
+ )
zvec.save(filename=zvec_file)
zvec_raw.save(filename=zvec_raw_file)
zvec_ref.save(filename=zvec_ref_file)
# check the RMS of the wavefront fit and only apply corrections if the fit is good enough.
# M2 can be more lenient to take care of large amounts of focus or coma.
- if zresults['residual_rms'] < 4000 * u.nm:
+ if zresults["residual_rms"] < 4000 * u.nm:
self.application.has_pending_m1 = True
self.application.has_pending_coma = True
self.application.has_pending_focus = True
log.info(f"{filename}: all proposed corrections valid.")
- elif zresults['residual_rms'] <= 7000 * u.nm:
+ elif zresults["residual_rms"] <= 7000 * u.nm:
self.application.has_pending_focus = True
log.warning(f"{filename}: only focus corrections valid.")
- elif zresults['residual_rms'] > 7000 * u.nm:
- log.error(f"{filename}: wavefront fit too poor; no valid corrections")
+ elif zresults["residual_rms"] > 7000 * u.nm:
+ log.error(
+ f"{filename}: wavefront fit too poor; no valid corrections"
+ )
self.application.has_pending_recenter = True
- self.application.pending_focus = self.application.wfs.calculate_focus(zvec.copy())
+ self.application.pending_focus = (
+ self.application.wfs.calculate_focus(zvec.copy())
+ )
# only allow M1 corrections if we are reasonably close to good focus...
if self.application.pending_focus > 150 * u.um:
self.application.has_pending_m1 = False
- self.application.pending_cc_x, self.application.pending_cc_y = self.application.wfs.calculate_cc(zvec.copy())
+ self.application.pending_cc_x, self.application.pending_cc_y = (
+ self.application.wfs.calculate_cc(zvec.copy())
+ )
self.async_plot(
self.make_fringebarchart,
zvec.copy(),
self.application.pending_focus,
self.application.pending_cc_x,
- self.application.pending_cc_y
+ self.application.pending_cc_y,
)
log.debug("Calculating pending forces...")
- self.application.pending_az, self.application.pending_el = self.application.wfs.calculate_recenter(results)
- self.application.pending_forces, self.application.pending_m1focus, zv_masked = \
- self.application.wfs.calculate_primary(zvec.copy(), mask=spher_mask)
- self.application.pending_forcefile = self.application.datadir / (filename + ".forces")
- zvec_masked_file = self.application.datadir / (filename + ".masked.zernike")
+ self.application.pending_az, self.application.pending_el = (
+ self.application.wfs.calculate_recenter(results)
+ )
+ (
+ self.application.pending_forces,
+ self.application.pending_m1focus,
+ zv_masked,
+ ) = self.application.wfs.calculate_primary(
+ zvec.copy(), mask=spher_mask
+ )
+ self.application.pending_forcefile = (
+ self.application.datadir / (filename + ".forces")
+ )
+ zvec_masked_file = self.application.datadir / (
+ filename + ".masked.zernike"
+ )
zv_masked.save(filename=zvec_masked_file)
- limit = np.round(np.abs(self.application.pending_forces['force']).max())
+ limit = np.round(
+ np.abs(self.application.pending_forces["force"]).max()
+ )
self.async_plot(
self.make_pendingforces,
tel,
self.application.pending_forces,
self.application.pending_m1focus,
- limit
+ limit,
)
else:
log.error(f"Wavefront fit failed: {filename}")
figures = create_default_figures()
- figures['slopes'] = results['figures']['slopes']
+ figures["slopes"] = results["figures"]["slopes"]
self.application.refresh_figures(figures=figures)
else:
log.error(f"Wavefront measurement failed: {filename}")
figures = create_default_figures()
- figures['slopes'] = results['figures']['slopes']
+ figures["slopes"] = results["figures"]["slopes"]
self.application.refresh_figures(figures=figures)
else:
@@ -352,15 +422,23 @@ def get(self):
self.application.wfs.telescope.correct_primary(
self.application.pending_forces,
self.application.pending_m1focus,
- filename=self.application.pending_forcefile
+ filename=self.application.pending_forcefile,
)
- max_f = self.application.pending_forces['force'].max()
- min_f = self.application.pending_forces['force'].min()
+ max_f = self.application.pending_forces["force"].max()
+ min_f = self.application.pending_forces["force"].min()
log.info(f"Maximum force {round(max_f, 2)} N")
log.info(f"Minimum force {round(min_f, 2)} N")
- log.info("Adjusting M1 focus by {0:0.1f}".format(self.application.pending_m1focus))
+ log.info(
+ "Adjusting M1 focus by {0:0.1f}".format(
+ self.application.pending_m1focus
+ )
+ )
self.application.has_pending_m1 = False
- self.write("Sending forces to cell and {0:0.1f} focus to secondary...".format(self.application.pending_m1focus))
+ self.write(
+ "Sending forces to cell and {0:0.1f} focus to secondary...".format(
+ self.application.pending_m1focus
+ )
+ )
else:
log.info("No M1 corrections sent")
self.write("No M1 corrections sent")
@@ -386,11 +464,12 @@ class ComaCorrectHandler(tornado.web.RequestHandler):
def get(self):
log.info("M2 coma corrections...")
if self.application.has_pending_coma and self.application.wfs.connected:
- self.application.wfs.secondary.correct_coma(self.application.pending_cc_x, self.application.pending_cc_y)
+ self.application.wfs.secondary.correct_coma(
+ self.application.pending_cc_x, self.application.pending_cc_y
+ )
self.application.has_pending_coma = False
log_str = "Sending {0:0.1f}/{1:0.1f} CC_X/CC_Y to secondary...".format(
- self.application.pending_cc_x,
- self.application.pending_cc_y
+ self.application.pending_cc_x, self.application.pending_cc_y
)
log.info(log_str)
self.write(log_str)
@@ -410,9 +489,8 @@ def get(self):
# self.application.pending_el
# )
log_str = "Hexapod recentering disabled. "
- log_str += "Please apply mount offsets of EL={0:0.1f}\" and AZ={1:0.1f}\"...".format(
- self.application.pending_el,
- self.application.pending_az
+ log_str += 'Please apply mount offsets of EL={0:0.1f}" and AZ={1:0.1f}"...'.format(
+ self.application.pending_el, self.application.pending_az
)
log.warning(log_str)
self.write(log_str)
@@ -424,7 +502,7 @@ def get(self):
class RestartHandler(tornado.web.RequestHandler):
def get(self):
try:
- wfs = self.get_argument('wfs')
+ wfs = self.get_argument("wfs")
self.application.restart_wfs(wfs)
log.info(f"restarting {wfs}")
except Exception as e:
@@ -454,7 +532,7 @@ def get(self):
def post(self):
self.set_header("Content-Type", "text/plain")
try:
- gain = float(self.get_body_argument('gain'))
+ gain = float(self.get_body_argument("gain"))
if self.application.wfs is not None:
if gain >= 0.0 and gain <= 1.0:
self.application.wfs.m1_gain = gain
@@ -476,7 +554,7 @@ def get(self):
def post(self):
self.set_header("Content-Type", "text/plain")
try:
- gain = float(self.get_body_argument('gain'))
+ gain = float(self.get_body_argument("gain"))
if self.application.wfs is not None:
if gain >= 0.0 and gain <= 1.0:
self.application.wfs.m2_gain = gain
@@ -510,7 +588,9 @@ def get(self):
except PermissionError as e:
# started getting weird permission errors on hacksaw that looks like NFS race bug.
# running 'ls' in the directory clears the error...
- log.warning(f"Permission error, {e.__class__}, while listing files in {p}...")
+ log.warning(
+ f"Permission error, {e.__class__}, while listing files in {p}..."
+ )
os.system(f"ls {p} > /dev/null")
fullfiles = []
files = []
@@ -551,14 +631,14 @@ def get(self):
class CompMirrorStatus(tornado.web.RequestHandler):
def get(self):
- compmirror = self.application.wfs_systems['newf9'].compmirror
+ compmirror = self.application.wfs_systems["newf9"].compmirror
status = compmirror.get_mirror()
self.write(json.dumps(status))
self.finish()
class CompMirrorToggle(tornado.web.RequestHandler):
def get(self):
- compmirror = self.application.wfs_systems['newf9'].compmirror
+ compmirror = self.application.wfs_systems["newf9"].compmirror
status = compmirror.toggle_mirror()
self.write(json.dumps(status))
self.finish()
@@ -567,21 +647,22 @@ class Download(tornado.web.RequestHandler):
"""
Handles downloading of the figure in various file formats.
"""
+
def get(self, fig, fmt):
managers = self.application.managers
mimetypes = {
- 'ps': 'application/postscript',
- 'eps': 'application/postscript',
- 'pdf': 'application/pdf',
- 'svg': 'image/svg+xml',
- 'png': 'image/png',
- 'jpeg': 'image/jpeg',
- 'tif': 'image/tiff',
- 'emf': 'application/emf'
+ "ps": "application/postscript",
+ "eps": "application/postscript",
+ "pdf": "application/pdf",
+ "svg": "image/svg+xml",
+ "png": "image/png",
+ "jpeg": "image/jpeg",
+ "tif": "image/tiff",
+ "emf": "application/emf",
}
- self.set_header('Content-Type', mimetypes.get(fmt, 'binary'))
+ self.set_header("Content-Type", mimetypes.get(fmt, "binary"))
buff = io.BytesIO()
managers[fig].canvas.print_figure(buff, format=fmt)
@@ -592,15 +673,18 @@ class LogStreamer(tornado.websocket.WebSocketHandler):
"""
A websocket for streaming log messages from log file to the browser.
"""
+
def open(self):
- if hasattr(self, 'set_nodelay'):
+ if hasattr(self, "set_nodelay"):
self.set_nodelay(False)
filename = self.application.logfile
- self.proc = Subprocess(["tail", "-f", "-n", "0", filename],
- stdout=Subprocess.STREAM,
- stdin=Subprocess.STREAM,
- stderr=Subprocess.STREAM,
- bufsize=1)
+ self.proc = Subprocess(
+ ["tail", "-f", "-n", "0", filename],
+ stdout=Subprocess.STREAM,
+ stdin=Subprocess.STREAM,
+ stderr=Subprocess.STREAM,
+ bufsize=1,
+ )
self.proc.set_exit_callback(self._close)
tornado.ioloop.IOLoop.current().spawn_callback(self.stream_output)
@@ -621,7 +705,7 @@ def stream_output(self):
try:
while True:
line = yield self.proc.stdout.read_until(b"\n")
- self.write_line(line.decode('utf-8'))
+ self.write_line(line.decode("utf-8"))
except StreamClosedError:
pass
@@ -632,10 +716,16 @@ def write_line(self, html):
color = "text-danger"
else:
color = "text-success"
- if "tornado.access" not in html and "poppy" not in html and "DEBUG" not in html:
+ if (
+ "tornado.access" not in html
+ and "poppy" not in html
+ and "DEBUG" not in html
+ ):
html = "%s" % (color, html)
- html += ""
- self.write_message(html.encode('utf-8'))
+ html += (
+ ''
+ )
+ self.write_message(html.encode("utf-8"))
class WebSocket(tornado.websocket.WebSocketHandler):
"""
@@ -654,6 +744,7 @@ class WebSocket(tornado.websocket.WebSocketHandler):
- ``send_binary(blob)`` is called to send binary image data
to the browser.
"""
+
supports_binary = True
def open(self, figname):
@@ -661,7 +752,7 @@ def open(self, figname):
self.figname = figname
manager = self.application.managers[figname]
manager.add_web_socket(self)
- if hasattr(self, 'set_nodelay'):
+ if hasattr(self, "set_nodelay"):
self.set_nodelay(True)
def on_close(self):
@@ -677,11 +768,14 @@ def on_message(self, message):
# Every message has a "type" and a "figure_id".
message = json.loads(message)
- if message['type'] == 'supports_binary':
- self.supports_binary = message['value']
+ if message["type"] == "supports_binary":
+ self.supports_binary = message["value"]
else:
- manager = self.application.fig_id_map[message['figure_id']]
- manager.handle_json(message)
+ manager = self.application.fig_id_map[message["figure_id"]]
+ try:
+ manager.handle_json(message)
+ except Exception:
+ pass
def send_json(self, content):
self.write_message(json.dumps(content))
@@ -691,7 +785,8 @@ def send_binary(self, blob):
self.write_message(blob, binary=True)
else:
data_uri = "data:image/png;base64,{0}".format(
- blob.encode('base64').replace('\n', ''))
+ blob.encode("base64").replace("\n", "")
+ )
self.write_message(data_uri)
def restart_wfs(self, wfs):
@@ -703,7 +798,7 @@ def restart_wfs(self, wfs):
def close_figures(self):
if self.figures is not None:
- plt.close('all')
+ plt.close("all")
def refresh_figure(self, k, figure):
if k not in self.managers:
@@ -711,10 +806,9 @@ def refresh_figure(self, k, figure):
self.managers[k] = new_figure_manager_given_figure(fignum, figure)
self.fig_id_map[fignum] = self.managers[k]
else:
- canvas = FigureCanvasWebAggCore(figure)
+ canvas = FigureCanvasWebAgg(figure)
self.managers[k].canvas = canvas
self.managers[k].canvas.manager = self.managers[k]
- self.managers[k]._get_toolbar(canvas)
self.managers[k]._send_event("refresh")
self.managers[k].canvas.draw()
@@ -742,20 +836,28 @@ def set_redis(self, key, value):
def update_seeing(self, results):
try:
- wfs_seeing = results['seeing'].round(2).value
- wfs_raw_seeing = results['raw_seeing'].round(2).value
- r1 = self.set_redis('wfs_seeing', wfs_seeing)
- r2 = self.set_redis('wfs_raw_seeing', wfs_raw_seeing)
+ wfs_seeing = results["vlt_seeing"].round(2).value
+ wfs_raw_seeing = results["raw_vlt_seeing"].round(2).value
+ wfs_ellipticity = results["ellipticity"].round(3)
+ r1 = self.set_redis("wfs_seeing", wfs_seeing)
+ r2 = self.set_redis("wfs_raw_seeing", wfs_raw_seeing)
if None not in r1 and None not in r2:
- log.info(f"Set redis values wfs_seeing={wfs_seeing} and wfs_raw_seeing={wfs_raw_seeing}")
+ log.info(
+ f"Set redis values wfs_seeing={wfs_seeing} and wfs_raw_seeing={wfs_raw_seeing}"
+ )
else:
log.warning("Problem sending seeing values to redis...")
+ r3 = self.set_redis("wfs_ellipticity", wfs_ellipticity)
+ if None not in r3:
+ log.info(f"Set redis values wfs_ellipticity={wfs_ellipticity}")
+ else:
+ log.warning("Problem sending ellipticity value to redis...")
except Exception as e:
- log.warning(f'Error connecting to MMTO API server... : {e}')
+ log.warning(f"Error connecting to MMTO API server... : {e}")
def __init__(self):
- if 'WFSROOT' in os.environ:
- self.datadir = pathlib.Path(os.environ['WFSROOT'])
+ if "WFSROOT" in os.environ:
+ self.datadir = pathlib.Path(os.environ["WFSROOT"])
else:
self.datadir = pathlib.Path.cwd()
@@ -768,7 +870,9 @@ def __init__(self):
if os.path.isdir(self.datadir):
self.logfile = self.datadir / "wfs.log"
- formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+ formatter = logging.Formatter(
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+ )
handler = logging.handlers.WatchedFileHandler(self.logfile)
handler.setFormatter(formatter)
glog.addHandler(handler)
@@ -778,11 +882,13 @@ def __init__(self):
self.wfs = None
self.wfs_systems = {}
- self.wfs_keys = ['newf9', 'f5', 'mmirs', 'binospec']
+ self.wfs_keys = ["newf9", "f5", "mmirs", "binospec"]
self.wfs_names = {}
for w in self.wfs_keys:
self.wfs_systems[w] = WFSFactory(wfs=w)
- self.wfs_systems[w].nzern = 10 # hard-code this for now, but should be configurable/settable
+ self.wfs_systems[w].nzern = (
+ 10 # hard-code this for now, but should be configurable/settable
+ )
self.wfs_names[w] = self.wfs_systems[w].name
self.busy = False
@@ -797,15 +903,27 @@ def __init__(self):
self.refresh_figures()
self.wavefront_fit = ZernikeVector(Z04=1)
- if 'REDISHOST' in os.environ:
- redis_host = os.environ['REDISHOST']
+ if "REDISHOST" in os.environ:
+ redis_host = os.environ["REDISHOST"]
else:
- redis_host = 'redis.mmto.arizona.edu'
+ redis_host = "redis.mmto.arizona.edu"
self.redis_server = redis.StrictRedis(host=redis_host, port=6379, db=0)
handlers = [
+ # Static files for the CSS and JS
+ (
+ r"/_static/(.*)",
+ tornado.web.StaticFileHandler,
+ {"path": FigureManagerWebAgg.get_static_file_path()},
+ ),
+ # Static images for the toolbar
+ (
+ r"/_images/(.*)",
+ tornado.web.StaticFileHandler,
+ {"path": Path(matplotlib.get_data_path(), "images")},
+ ),
(r"/", self.HomeHandler),
- (r"/mpl\.js", tornado.web.RedirectHandler, dict(url="static/js/mpl.js")),
+ (r"/mpl.js", self.MplJs),
(r"/select", self.SelectHandler),
(r"/wfspage", self.WFSPageHandler),
(r"/connect", self.ConnectHandler),
@@ -827,15 +945,15 @@ def __init__(self):
(r"/clearm2", self.ClearM2Handler),
(r"/compmirror", self.CompMirrorStatus),
(r"/compmirrortoggle", self.CompMirrorToggle),
- (r'/download_([a-z]+).([a-z0-9.]+)', self.Download),
- (r'/log', self.LogStreamer),
- (r'/([a-z0-9.]+)/ws', self.WebSocket)
+ (r"/download_([a-z]+).([a-z0-9.]+)", self.Download),
+ (r"/log", self.LogStreamer),
+ (r"/([a-z0-9.]+)/ws", self.WebSocket),
]
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
- debug=True
+ debug=True,
)
super(WFSsrv, self).__init__(handlers, **settings)
@@ -844,12 +962,30 @@ def main():
application = WFSsrv()
http_server = tornado.httpserver.HTTPServer(application)
- http_server.listen(8080)
-
- print("http://127.0.0.1:8080/")
+ sockets = tornado.netutil.bind_sockets(8080, "")
+ http_server.add_sockets(sockets)
+
+ for s in sockets:
+ addr, port = s.getsockname()[:2]
+ if s.family is socket.AF_INET6:
+ addr = f"[{addr}]"
+ print(f"WFSSrv listening on http://{addr}:{port}/")
print("Press Ctrl+C to quit")
- tornado.ioloop.IOLoop.instance().start()
+ ioloop = tornado.ioloop.IOLoop.instance()
+
+ def shutdown():
+ ioloop.stop()
+ print("Server stopped")
+
+ old_handler = signal.signal(
+ signal.SIGINT, lambda sig, frame: ioloop.add_callback_from_signal(shutdown)
+ )
+
+ try:
+ ioloop.start()
+ finally:
+ signal.signal(signal.SIGINT, old_handler)
if __name__ == "__main__":