Skip to content

Commit

Permalink
v1.6.0
Browse files Browse the repository at this point in the history
version 1.6.0
  • Loading branch information
thusser authored Nov 7, 2023
2 parents 98bb996 + 0d3d08f commit a8366c6
Show file tree
Hide file tree
Showing 29 changed files with 1,351 additions and 688 deletions.
16 changes: 8 additions & 8 deletions docs/source/config_examples/iagvt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ Module for performing a fine-acquisition on the sun
* A log file where all the offsets from the acquisitions are stored, can be useful for checking the pointing model
(line 8).
* The (initial) exposure time for the camera (line 11).
* Tolerances for the acquisiton: it succeeds if the telescope is closer than 10" to the position on the sund
get larger than 7200". 10 moves are allowed (lines 14-16).
* Tolerances for the acquisiton: It succeeds if the telescope is closer than 10" to the target position on the sun.
It fails if the offsets get larger than 7200". 10 moves are allowed (lines 14-16).
* The pipeline defines steps performed on the images in order to get the offsets for the next step (lines 18-20):

#. ``DiskOffset`` is a custom class that takes the WCS from the :ref:`suncamera` and calculates offsets to go
Expand Down Expand Up @@ -103,8 +103,8 @@ Module for operating the camera looking at the mirror with the two fiber holes
FPS: 2
* ``FiberCamera`` is a class derived from :class:`pyobs_aravis.araviscamera.AravisCamera` with some extensions.
* Both fiber holes are defines and one is also defined as the center (lines 5-6).
* The rotation of the camera is given as well as that the image should be flipped (lines 9-10).
* The fiber hole and gregory hole are defined and one is also defined as the image center (lines 5-6).
* The rotation of the camera is given and whether that the image should be flipped (lines 9-10).
* The plate scale is given (line 13).
* URLs for FITS files and the video stream are defined (lines 16-17).
* A SIP calibration is given to be added to the WCS of the FITS file (line 20).
Expand All @@ -123,7 +123,7 @@ Module used for distributing images among the other modules
port: 37075
* :class:`~pyobs.modules.utils.HttpFileCache` provides a HTTP server that can be used for distributing files (line 1).
* It needs a port to run on (line 3).
* It needs a host and port to run on (line 3).


filewatcher
Expand Down Expand Up @@ -176,12 +176,12 @@ Module for operating the Fourier Transform Spectrograph (FTS)
* ``FTS`` is a class deriving from :class:`pyobs.modules.camera.basespectrograph.BaseSpectrograph` to implement
the functionality of the spectrograph (line 1).
* The rest of the config is omitted here.
* The rest of the config is omitted here, since this class is not freely accessible.


gregorycamera
"""""""""""""
Module for operating a camera that looks at the gregory hole fiber
Module for operating a camera that looks at the gregory hole of the :ref:`fibercamera`.

.. code-block:: YAML
:linenos:
Expand Down Expand Up @@ -287,7 +287,7 @@ Module for running full roboric operations
acquisition: acquisition
autoguider: guiding
* ``SolarMastermind`` is a custom class that combined the task runner and the scheduler in one class (line 1).
* ``SolarMastermind`` is a custom class that combines the task runner and the scheduler in one class (line 1).
* The schedule is calculated using the custom class ``VTTaskSchedule`` (line 3-5).
* A task runner is defined which runs a custom script (lines 7-15).

Expand Down
684 changes: 281 additions & 403 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pyobs/comm/local/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .localcomm import LocalComm
from .localnetwork import LocalNetwork
97 changes: 97 additions & 0 deletions pyobs/comm/local/localcomm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import pyobs.comm

from typing import Optional, List, Type, Dict, Any, Callable, Coroutine

from pyobs.comm import Comm
from pyobs.events import Event
from pyobs.interfaces import Interface
from pyobs.utils.types import cast_response_to_real


class LocalComm(Comm):
def __init__(self, name: str, *args, **kwargs):

Comm.__init__(self, *args, **kwargs)

self._name = name
self._network = pyobs.comm.local.LocalNetwork()
self._network.connect_client(self)

@property
def name(self) -> Optional[str]:
"""Name of this client."""
return self._name

@property
def clients(self) -> List[str]:
"""Returns list of currently connected clients.
Returns:
(list) List of currently connected clients.
"""
return self._network.get_client_names()

async def get_interfaces(self, client: str) -> List[Type[Interface]]:
"""Returns list of interfaces for given client.
Args:
client: Name of client.
Returns:
List of supported interfaces.
Raises:
IndexError: If client cannot be found.
"""

remote_client: LocalComm = self._network.get_client(client)
return remote_client.module.interfaces

async def _supports_interface(self, client: str, interface: Type[Interface]) -> bool:
"""Checks, whether the given client supports the given interface.
Args:
client: Client to check.
interface: Interface to check.
Returns:
Whether or not interface is supported.
"""
interfaces = await self.get_interfaces(client)
return interfaces in interfaces

async def execute(self, client: str, method: str, annotation: Dict[str, Any], *args: Any) -> Any:
"""Execute a given method on a remote client.
Args:
client (str): ID of client.
method (str): Method to call.
annotation: Method annotation.
*args: List of parameters for given method.
Returns:
Passes through return from method call.
"""

remote_client = self._network.get_client(client)
simple_results = await remote_client.module.execute(method, *args)
real_results = cast_response_to_real(
simple_results, annotation["return"], self.cast_to_real_pre, self.cast_to_real_post
)
return real_results

async def send_event(self, event: Event) -> None:
"""Send an event to other clients.
Args:
event (Event): Event to send
"""

remote_clients = self._network.get_clients()
for client in remote_clients:
client._send_event_to_module(event, self.name)

async def _register_events(
self, events: List[Type[Event]], handler: Optional[Callable[[Event, str], Coroutine[Any, Any, bool]]] = None
) -> None:
pass
32 changes: 32 additions & 0 deletions pyobs/comm/local/localnetwork.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

from typing import Dict, List
from typing import TYPE_CHECKING

if TYPE_CHECKING:
import pyobs.comm


class LocalNetwork:
_instance = None

def __new__(cls):
if cls._instance is None:
print('Creating the object')
cls._instance = super(LocalNetwork, cls).__new__(cls)

cls._clients: Dict[str, pyobs.comm.local.LocalComm] = {}

return cls._instance

def connect_client(self, comm: pyobs.comm.local.LocalComm):
self._clients[comm.name] = comm

def get_client(self, name: str) -> pyobs.comm.local.LocalComm:
return self._clients[name]

def get_clients(self) -> List[pyobs.comm.local.LocalComm]:
return list(self._clients.values())

def get_client_names(self) -> List[str]:
return list(self._clients.keys())
49 changes: 49 additions & 0 deletions pyobs/images/processors/astrometry/_dotnet_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import Dict, Optional

import aiohttp

import pyobs.utils.exceptions as exc


class _DotNetRequest:
def __init__(self, request_data: Dict[str, any]):
self._request_data = request_data

self._response_data: Optional[Dict[str, any]] = None
self._status_code: Optional[int] = None

async def _send_request(self, url: str, timeout: int):
async with aiohttp.ClientSession() as session:
async with session.post(url, json=self._request_data, timeout=timeout) as response:
self._status_code = response.status
self._response_data = await response.json()

def _generate_request_error_msg(self) -> str:
if "error" not in self._response_data:
return "Could not connect to astrometry service."

if self._response_data["error"] == "Could not find WCS file.":
return "Could not determine WCS."

return f"Received error from astrometry service: {self._response_data['error']}"

def _handle_request_error(self):
error_msg = self._generate_request_error_msg()
raise exc.ImageError(error_msg)

def _is_request_successful(self):
return self._status_code != 200 or "error" in self._response_data

async def send(self, url: str, timeout: int):
await self._send_request(url, timeout)

if self._is_request_successful():
self._handle_request_error()

@property
def request_data(self) -> Dict[str, any]:
return self._request_data

@property
def response_data(self) -> Optional[Dict[str, any]]:
return self._response_data
69 changes: 69 additions & 0 deletions pyobs/images/processors/astrometry/_dotnet_request_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from typing import Dict, Any

import pandas as pd
from astropy.io.fits import Header

from pyobs.images import Image
import pyobs.utils.exceptions as exc
from pyobs.images.processors.astrometry._dotnet_request import _DotNetRequest


class _DotNetRequestBuilder:
def __init__(self, source_count: int, radius: float):
self._source_count = source_count
self._radius = radius

self._request_data = {}
self._catalog: pd.DataFrame = None
self._header: Header = None

def add_catalog_from_image(self, image: Image):
if image.catalog is None:
raise exc.ImageError("No catalog found in image.")

self._catalog = image.catalog[["x", "y", "flux", "peak"]].to_pandas()

def add_header_from_image(self, image: Image):
self._header = image.header

def _filter_catalog(self):
self._catalog = self._catalog.dropna(how="any")
self._catalog = self._catalog[self._catalog["peak"] < 60000]

def _validate_catalog(self):
if self._catalog is None or len(self._catalog) < 3:
raise exc.ImageError("Not enough sources for astrometry.")

def _select_brightest_stars(self):
self._catalog = self._catalog.sort_values("flux", ascending=False)
self._catalog = self._catalog[: self._source_count]

def _validate_header(self):
if "CDELT1" not in self._header:
raise exc.ImageError("No CDELT1 found in header.")

def _build_request_data(self):
scale = abs(self._header["CDELT1"]) * 3600
self._request_data = {
"ra": self._header["TEL-RA"],
"dec": self._header["TEL-DEC"],
"scale_low": scale * 0.9,
"scale_high": scale * 1.1,
"radius": self._radius,
"nx": self._header["NAXIS1"],
"ny": self._header["NAXIS2"],
"x": self._catalog["x"].tolist(),
"y": self._catalog["y"].tolist(),
"flux": self._catalog["flux"].tolist(),
}

def __call__(self) -> _DotNetRequest:
self._filter_catalog()
self._validate_catalog()
self._select_brightest_stars()

self._validate_header()

self._build_request_data()

return _DotNetRequest(self._request_data)
44 changes: 44 additions & 0 deletions pyobs/images/processors/astrometry/_dotnet_request_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import logging
from typing import Dict, Any

from astropy.coordinates import SkyCoord
import astropy.units as u
from astropy.wcs import WCS

from pyobs.images import Image


class _RequestLogger:
def __init__(self, logger: logging.Logger, image: Image, request_data: Dict[str, Any]):
self._logger = logger
self._image = image
self._request_data = request_data

def log_request_data(self):
ra_dec = SkyCoord(ra=self._request_data["ra"] * u.deg, dec=self._request_data["dec"] * u.deg, frame="icrs")
center_x, center_y = self._image.header["CRPIX1"], self._image.header["CRPIX2"]

self._logger.info(
"Found original RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.",
ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True),
self._request_data["ra"],
ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True),
self._request_data["dec"],
center_x,
center_y,
)

def log_request_result(self, image_wcs: WCS):
center_x, center_y = self._image.header["CRPIX1"], self._image.header["CRPIX2"]
final_ra, final_dec = image_wcs.all_pix2world(center_x, center_y, 0)
final_ra_dec = SkyCoord(ra=final_ra * u.deg, dec=final_dec * u.deg, frame="icrs")

self._logger.info(
"Found final RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.",
final_ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True),
self._request_data["ra"],
final_ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True),
self._request_data["dec"],
center_x,
center_y,
)
Loading

0 comments on commit a8366c6

Please sign in to comment.