-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
version 1.6.0
- Loading branch information
Showing
29 changed files
with
1,351 additions
and
688 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .localcomm import LocalComm | ||
from .localnetwork import LocalNetwork |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
69
pyobs/images/processors/astrometry/_dotnet_request_builder.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
44
pyobs/images/processors/astrometry/_dotnet_request_logger.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
Oops, something went wrong.