pyobs-core 2.0.0.dev5__py3-none-any.whl → 2.0.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyobs/application.py +22 -2
- pyobs/cli/pyobsd.py +2 -2
- pyobs/comm/comm.py +7 -0
- pyobs/comm/local/localcomm.py +4 -0
- pyobs/comm/proxy.py +6 -3
- pyobs/comm/xmpp/rpc.py +4 -4
- pyobs/comm/xmpp/serializer.py +2 -2
- pyobs/comm/xmpp/xep_0009/binding.py +2 -2
- pyobs/comm/xmpp/xmppcomm.py +11 -6
- pyobs/events/event.py +2 -0
- pyobs/images/image.py +6 -4
- pyobs/images/meta/skyoffsets.py +3 -3
- pyobs/images/processors/annotation/_pillow.py +1 -1
- pyobs/images/processors/annotation/text.py +1 -1
- pyobs/images/processors/detection/daophot.py +2 -2
- pyobs/images/processors/image/download.py +1 -1
- pyobs/images/processors/image/normalize.py +1 -1
- pyobs/images/processors/misc/catalogcircularmask.py +1 -1
- pyobs/images/processors/modules/getfitsheaders.py +1 -1
- pyobs/images/processors/offsets/brighteststar.py +1 -1
- pyobs/images/processors/offsets/brighteststar_guiding.py +1 -1
- pyobs/images/processors/offsets/spilled_light.py +7 -6
- pyobs/images/processors/photometry/_photometry_calculator.py +2 -2
- pyobs/images/processors/wcs/solarhelioprojective.py +1 -1
- pyobs/interfaces/__init__.py +0 -3
- pyobs/interfaces/interface.py +13 -2
- pyobs/mixins/camerasettings.py +3 -3
- pyobs/mixins/fitsheader.py +1 -1
- pyobs/mixins/follow.py +25 -7
- pyobs/modules/camera/basecamera.py +1 -1
- pyobs/modules/camera/basevideo.py +1 -1
- pyobs/modules/camera/dummycamera.py +4 -3
- pyobs/modules/flatfield/flatfield.py +6 -3
- pyobs/modules/focus/focusmodel.py +12 -7
- pyobs/modules/focus/focusseries.py +14 -7
- pyobs/modules/image/imagewatcher.py +1 -1
- pyobs/modules/module.py +3 -2
- pyobs/modules/pointing/acquisition.py +21 -7
- pyobs/modules/pointing/autoguiding.py +6 -3
- pyobs/modules/robotic/scheduler.py +4 -4
- pyobs/modules/roof/basedome.py +3 -2
- pyobs/modules/utils/kiosk.py +7 -4
- pyobs/modules/utils/matrix.py +1 -1
- pyobs/modules/utils/stellarium.py +2 -1
- pyobs/modules/utils/telegram.py +1 -1
- pyobs/object.py +1 -1
- pyobs/robotic/observation.py +1 -1
- pyobs/robotic/scheduler/astroplanscheduler.py +3 -3
- pyobs/robotic/scheduler/constraints/timeconstraint.py +1 -1
- pyobs/robotic/scheduler/dataprovider.py +1 -1
- pyobs/robotic/scheduler/merits/aftertime.py +1 -1
- pyobs/robotic/scheduler/merits/beforetime.py +1 -1
- pyobs/robotic/scheduler/merits/timewindow.py +1 -1
- pyobs/robotic/scheduler/ondemandscheduler.py +1 -1
- pyobs/robotic/scheduler/targets/helioprojectiveradialarget.py +1 -1
- pyobs/robotic/scripts/calibration/darkbias.py +5 -2
- pyobs/robotic/scripts/calibration/pointing.py +2 -1
- pyobs/robotic/scripts/calibration/skyflats.py +5 -3
- pyobs/robotic/scripts/imaging/autofocus.py +3 -2
- pyobs/robotic/scripts/imaging/imaging.py +12 -4
- pyobs/robotic/storage/filesystem/observationarchive.py +4 -4
- pyobs/robotic/storage/lco/_portal.py +1 -1
- pyobs/robotic/storage/lco/mockobservationarchive.py +1 -1
- pyobs/robotic/storage/lco/scripts/autofocus.py +5 -3
- pyobs/robotic/storage/lco/scripts/default.py +10 -4
- pyobs/robotic/utils/archive/pyobs_archive.py +1 -1
- pyobs/robotic/utils/exptime/stellarexptime.py +8 -3
- pyobs/robotic/utils/skyflats/exptimeeval.py +4 -4
- pyobs/robotic/utils/skyflats/flatfielder.py +11 -3
- pyobs/robotic/utils/skyflats/scheduler.py +1 -1
- pyobs/utils/grids/filters.py +1 -1
- pyobs/utils/grids/grid.py +3 -3
- pyobs/utils/grids/pipeline.py +1 -1
- pyobs/utils/gui/camera/binningwidget.py +1 -1
- pyobs/utils/gui/camera/datadisplaywidget.py +6 -6
- pyobs/utils/gui/camera/exposewidget.py +7 -7
- pyobs/utils/gui/camera/exposuretimewidget.py +2 -2
- pyobs/utils/gui/camera/imageformatwidget.py +1 -1
- pyobs/utils/gui/camera/windowingwidget.py +4 -4
- pyobs/utils/influxdb.py +3 -3
- pyobs/utils/modulegui.py +1 -1
- pyobs/utils/offsets/applyaltazoffsets.py +4 -3
- pyobs/utils/offsets/applyoffsets.py +8 -4
- pyobs/utils/offsets/applyradecoffsets.py +4 -3
- pyobs/utils/serialization.py +1 -1
- pyobs/utils/time.py +2 -2
- pyobs/vfs/httpfile.py +1 -1
- pyobs/vfs/memfile.py +1 -1
- pyobs/vfs/smbfile.py +1 -1
- {pyobs_core-2.0.0.dev5.dist-info → pyobs_core-2.0.0.dev7.dist-info}/METADATA +2 -1
- {pyobs_core-2.0.0.dev5.dist-info → pyobs_core-2.0.0.dev7.dist-info}/RECORD +94 -94
- {pyobs_core-2.0.0.dev5.dist-info → pyobs_core-2.0.0.dev7.dist-info}/WHEEL +0 -0
- {pyobs_core-2.0.0.dev5.dist-info → pyobs_core-2.0.0.dev7.dist-info}/entry_points.txt +0 -0
- {pyobs_core-2.0.0.dev5.dist-info → pyobs_core-2.0.0.dev7.dist-info}/licenses/LICENSE +0 -0
pyobs/application.py
CHANGED
|
@@ -19,6 +19,26 @@ from pyobs.utils.logging.context import ModuleNameFilter
|
|
|
19
19
|
log = logging.getLogger(__name__)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
# --- IERS offline fallback ---------------------------------------------
|
|
23
|
+
# When PYOBS_IERS_OFFLINE=1, skip live IERS/USNO downloads and rely on the
|
|
24
|
+
# astropy-iers-data snapshot bundled with the installed astropy package.
|
|
25
|
+
# Useful when the upstream IERS servers are down/unreachable, or when
|
|
26
|
+
# running on a telescope control machine without outbound internet access.
|
|
27
|
+
if os.environ.get("PYOBS_IERS_OFFLINE", "").lower() in ("1", "true", "yes"):
|
|
28
|
+
from astropy.utils.iers import conf as iers_conf
|
|
29
|
+
|
|
30
|
+
iers_conf.auto_download = False
|
|
31
|
+
iers_conf.auto_max_age = None
|
|
32
|
+
iers_conf.iers_degraded_accuracy = "warn"
|
|
33
|
+
|
|
34
|
+
log.warning(
|
|
35
|
+
"PYOBS_IERS_OFFLINE is set — disabling IERS auto-download and "
|
|
36
|
+
"relying on the bundled astropy-iers-data snapshot. Earth "
|
|
37
|
+
"orientation/UT1-UTC accuracy may degrade for predictions far "
|
|
38
|
+
"from the snapshot date."
|
|
39
|
+
)
|
|
40
|
+
# -------------------------------------------------------------------------
|
|
41
|
+
|
|
22
42
|
# turn RuntimeWarnings into errors
|
|
23
43
|
warnings.filterwarnings("error", category=RuntimeWarning)
|
|
24
44
|
|
|
@@ -89,9 +109,9 @@ class Application:
|
|
|
89
109
|
|
|
90
110
|
# systemd journal handler?
|
|
91
111
|
if syslog:
|
|
92
|
-
from logging_journald import JournaldLogHandler
|
|
112
|
+
from logging_journald import JournaldLogHandler
|
|
93
113
|
|
|
94
|
-
class PyobsJournaldLogHandler(JournaldLogHandler):
|
|
114
|
+
class PyobsJournaldLogHandler(JournaldLogHandler):
|
|
95
115
|
"""JournaldLogHandler that adds SYSLOG_IDENTIFIER=pyobs and PYOBS_MODULE per record."""
|
|
96
116
|
|
|
97
117
|
def __init__(self, **kw: Any) -> None:
|
pyobs/cli/pyobsd.py
CHANGED
|
@@ -340,9 +340,9 @@ class PyobsDaemon:
|
|
|
340
340
|
stdout, stderr = proc.communicate(timeout=2)
|
|
341
341
|
print(f"Warning: {module} launched but PID not confirmed.")
|
|
342
342
|
if stdout:
|
|
343
|
-
print(f" stdout: {stdout.decode(errors='replace').strip()}")
|
|
343
|
+
print(f" stdout: {stdout.decode(errors='replace').strip()}") # type: ignore[attr-defined]
|
|
344
344
|
if stderr:
|
|
345
|
-
print(f" stderr: {stderr.decode(errors='replace').strip()}")
|
|
345
|
+
print(f" stderr: {stderr.decode(errors='replace').strip()}") # type: ignore[attr-defined]
|
|
346
346
|
|
|
347
347
|
def _stop_service(self, module: str) -> None:
|
|
348
348
|
pid = self._running_pid(module)
|
pyobs/comm/comm.py
CHANGED
|
@@ -514,6 +514,13 @@ class Comm:
|
|
|
514
514
|
def _get_client_state(self, module: str) -> tuple[ModuleState, str] | None:
|
|
515
515
|
return None
|
|
516
516
|
|
|
517
|
+
def get_own_state(self, interface: type[Interface]) -> Any:
|
|
518
|
+
"""Return the last state published by this module for the given interface, or None."""
|
|
519
|
+
return self._get_own_state(interface)
|
|
520
|
+
|
|
521
|
+
def _get_own_state(self, interface: type[Interface]) -> Any:
|
|
522
|
+
return None
|
|
523
|
+
|
|
517
524
|
async def subscribe_state(
|
|
518
525
|
self,
|
|
519
526
|
module: str,
|
pyobs/comm/local/localcomm.py
CHANGED
|
@@ -79,6 +79,10 @@ class LocalComm(Comm):
|
|
|
79
79
|
# State
|
|
80
80
|
# -------------------------------------------------------------------------
|
|
81
81
|
|
|
82
|
+
def _get_own_state(self, interface: type[Interface]) -> Any:
|
|
83
|
+
node = f"pyobs:state:{self._name}:{interface.__name__}:{interface.version}"
|
|
84
|
+
return self._states.get(node)
|
|
85
|
+
|
|
82
86
|
async def _set_state(self, interface: type[Interface], state: Any) -> None:
|
|
83
87
|
"""Publish state locally and dispatch to subscribers."""
|
|
84
88
|
node = f"pyobs:state:{self._name}:{interface.__name__}:{interface.version}"
|
pyobs/comm/proxy.py
CHANGED
|
@@ -55,7 +55,7 @@ class Proxy:
|
|
|
55
55
|
|
|
56
56
|
# add interfaces as base classes
|
|
57
57
|
cls = self.__class__
|
|
58
|
-
self.__class__ =
|
|
58
|
+
self.__class__ = type("Proxy", tuple([cls] + interfaces), {})
|
|
59
59
|
|
|
60
60
|
# create methods
|
|
61
61
|
self._methods = self._create_methods()
|
|
@@ -139,6 +139,9 @@ class Proxy:
|
|
|
139
139
|
for interface in self._interfaces:
|
|
140
140
|
# loop all methods:
|
|
141
141
|
for func_name, func in inspect.getmembers(interface, predicate=inspect.isfunction):
|
|
142
|
+
# skip base Interface infrastructure methods — Proxy provides its own implementations
|
|
143
|
+
if func_name in Interface.__dict__:
|
|
144
|
+
continue
|
|
142
145
|
# set method
|
|
143
146
|
my_func = types.MethodType(self._remote_function_wrapper(func_name), self)
|
|
144
147
|
setattr(self, func_name, my_func)
|
|
@@ -174,11 +177,11 @@ class Proxy:
|
|
|
174
177
|
"""Clear all cached state. Called by Comm when the remote module disconnects."""
|
|
175
178
|
self._state.clear()
|
|
176
179
|
|
|
177
|
-
def
|
|
180
|
+
def get_state(self, interface: type[Interface]) -> Any | None:
|
|
178
181
|
"""Latest known state for the given interface, or None if nothing has arrived yet."""
|
|
179
182
|
return self._state.get(interface)
|
|
180
183
|
|
|
181
|
-
def
|
|
184
|
+
def get_capabilities(self, interface: type[Interface]) -> Any | None:
|
|
182
185
|
"""Capabilities for the given interface, populated once at Proxy construction."""
|
|
183
186
|
return self._capabilities.get(interface)
|
|
184
187
|
|
pyobs/comm/xmpp/rpc.py
CHANGED
|
@@ -115,8 +115,8 @@ def xml_to_fault(fault_elem: ET.Element) -> tuple[str, str]:
|
|
|
115
115
|
return "RemoteError", "Unknown error"
|
|
116
116
|
exc_el = pyobs_fault.find("exception")
|
|
117
117
|
msg_el = pyobs_fault.find("message")
|
|
118
|
-
exc_name = exc_el.text if exc_el is not None else "RemoteError"
|
|
119
|
-
msg = msg_el.text if msg_el is not None else ""
|
|
118
|
+
exc_name = (exc_el.text if exc_el is not None else None) or "RemoteError"
|
|
119
|
+
msg = (msg_el.text if msg_el is not None else None) or ""
|
|
120
120
|
return exc_name, msg
|
|
121
121
|
|
|
122
122
|
|
|
@@ -201,7 +201,7 @@ class RPC:
|
|
|
201
201
|
if hasattr(method, "timeout"):
|
|
202
202
|
timeout = await getattr(method, "timeout")(self._handler, **ba.arguments)
|
|
203
203
|
if timeout:
|
|
204
|
-
response = self._client.plugin["xep_0009_timeout"].make_iq_method_timeout(
|
|
204
|
+
response = self._client.plugin["xep_0009_timeout"].make_iq_method_timeout( # type: ignore[typeddict-item]
|
|
205
205
|
iq["id"], iq["from"], int(timeout)
|
|
206
206
|
)
|
|
207
207
|
response.send()
|
|
@@ -272,7 +272,7 @@ class RPC:
|
|
|
272
272
|
if issubclass(exception_class, exc.RemoteError):
|
|
273
273
|
exception = exception_class(message=msg, module=sender)
|
|
274
274
|
else:
|
|
275
|
-
exception = exception_class(
|
|
275
|
+
exception = exception_class(msg)
|
|
276
276
|
|
|
277
277
|
if not future.done():
|
|
278
278
|
future.set_exception(exc.InvocationError(module=sender, exception=exception))
|
pyobs/comm/xmpp/serializer.py
CHANGED
|
@@ -174,10 +174,10 @@ def xml_to_value(elem: ET.Element, type_hint: Any) -> Any:
|
|
|
174
174
|
return elem.text == "true"
|
|
175
175
|
|
|
176
176
|
if tag == "int":
|
|
177
|
-
return int(elem.text)
|
|
177
|
+
return int(elem.text) if elem.text is not None else 0
|
|
178
178
|
|
|
179
179
|
if tag == "double":
|
|
180
|
-
return float(elem.text)
|
|
180
|
+
return float(elem.text) if elem.text is not None else 0.0
|
|
181
181
|
|
|
182
182
|
if tag == "string":
|
|
183
183
|
text = elem.text or ""
|
|
@@ -42,7 +42,7 @@ def py2xml(*args: Any) -> ET.Element:
|
|
|
42
42
|
return params
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def _py2xml(*args: Any) -> ET.Element:
|
|
45
|
+
def _py2xml(*args: Any) -> ET.Element: # type: ignore[return]
|
|
46
46
|
for x in args:
|
|
47
47
|
val = ET.Element(f"{{{_namespace}}}value")
|
|
48
48
|
if x is None:
|
|
@@ -100,7 +100,7 @@ def xml2py(params: ET.Element) -> list[Any]:
|
|
|
100
100
|
return vals
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
def _xml2py(value:
|
|
103
|
+
def _xml2py(value: Any) -> Any:
|
|
104
104
|
namespace = "jabber:iq:rpc"
|
|
105
105
|
find_value = value.find
|
|
106
106
|
if find_value(f"{{{namespace}}}nil") is not None:
|
pyobs/comm/xmpp/xmppcomm.py
CHANGED
|
@@ -185,6 +185,7 @@ class XmppComm(Comm):
|
|
|
185
185
|
self._state_node_handlers: dict[str, tuple[type[Interface], list[Callable[[Any], None]]]] = {}
|
|
186
186
|
self._client_states: dict[str, tuple[ModuleState, str]] = {} # jid -> (state, error_string)
|
|
187
187
|
self._capabilities: dict[type, Any] = {} # interface → Capabilities instance
|
|
188
|
+
self._own_states: dict[type, Any] = {} # interface → last published state for this module
|
|
188
189
|
self._presence_callbacks: dict[str, list[Callable[[ModuleState, str], None]]] = {}
|
|
189
190
|
|
|
190
191
|
def _set_module(self, module: Module) -> None:
|
|
@@ -273,7 +274,7 @@ class XmppComm(Comm):
|
|
|
273
274
|
|
|
274
275
|
# connect
|
|
275
276
|
await self._xmpp.connect(host=server, port=port)
|
|
276
|
-
self._xmpp.init_plugins()
|
|
277
|
+
self._xmpp.init_plugins()
|
|
277
278
|
|
|
278
279
|
# wait for connected
|
|
279
280
|
if not await self._xmpp.wait_connect():
|
|
@@ -657,7 +658,7 @@ class XmppComm(Comm):
|
|
|
657
658
|
interface, callbacks = self._state_node_handlers[node]
|
|
658
659
|
item_xml = items_xml.find(f"{{{pubsub_ns}}}item")
|
|
659
660
|
payload = list(item_xml)[0] if item_xml is not None and len(item_xml) > 0 else None
|
|
660
|
-
if payload is not None:
|
|
661
|
+
if payload is not None and interface.state is not None:
|
|
661
662
|
state_obj = _xml_to_dataclass(payload, interface.state)
|
|
662
663
|
for callback in callbacks:
|
|
663
664
|
callback(state_obj)
|
|
@@ -727,7 +728,7 @@ class XmppComm(Comm):
|
|
|
727
728
|
await asyncio.sleep(self._safe_send_wait)
|
|
728
729
|
|
|
729
730
|
# never should reach this
|
|
730
|
-
raise slixmpp.exceptions.IqTimeout(iq)
|
|
731
|
+
raise slixmpp.exceptions.IqTimeout(iq)
|
|
731
732
|
|
|
732
733
|
def cast_to_simple_pre(self, value: Any, annotation: Any | None = None) -> tuple[bool, Any]:
|
|
733
734
|
"""Special treatment of single parameters when converting them to be sent via Comm.
|
|
@@ -785,13 +786,17 @@ class XmppComm(Comm):
|
|
|
785
786
|
items_xml = pubsub_xml.find(f"{{{pubsub_ns}}}items") if pubsub_xml is not None else None
|
|
786
787
|
item_xml = items_xml.find(f"{{{pubsub_ns}}}item") if items_xml is not None else None
|
|
787
788
|
payload = list(item_xml)[0] if item_xml is not None and len(item_xml) > 0 else None
|
|
788
|
-
if payload is not None:
|
|
789
|
+
if payload is not None and interface.state is not None:
|
|
789
790
|
callback(_xml_to_dataclass(payload, interface.state))
|
|
790
791
|
except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
|
|
791
792
|
pass
|
|
792
793
|
|
|
794
|
+
def _get_own_state(self, interface: type[Interface]) -> Any:
|
|
795
|
+
return self._own_states.get(interface)
|
|
796
|
+
|
|
793
797
|
async def _set_state(self, interface: type[Interface], state: Any) -> None:
|
|
794
|
-
|
|
798
|
+
self._own_states[interface] = state
|
|
799
|
+
node = self._state_node(self._module.name, interface) # type: ignore[union-attr]
|
|
795
800
|
stanza = StateStanza()
|
|
796
801
|
stanza.xml = _dataclass_to_xml(state, self._state_namespace(interface))
|
|
797
802
|
await self._safe_send(self.client["xep_0060"].publish, self._pubsub_service, node, payload=stanza)
|
|
@@ -900,7 +905,7 @@ class XmppComm(Comm):
|
|
|
900
905
|
items_xml = pubsub_xml.find(f"{{{pubsub_ns}}}items") if pubsub_xml is not None else None
|
|
901
906
|
item_xml = items_xml.find(f"{{{pubsub_ns}}}item") if items_xml is not None else None
|
|
902
907
|
payload = list(item_xml)[0] if item_xml is not None and len(item_xml) > 0 else None
|
|
903
|
-
if payload is not None and node in self._state_node_handlers:
|
|
908
|
+
if payload is not None and node in self._state_node_handlers and interface.state is not None:
|
|
904
909
|
_, callbacks = self._state_node_handlers[node]
|
|
905
910
|
state_obj = _xml_to_dataclass(payload, interface.state)
|
|
906
911
|
for cb in callbacks:
|
pyobs/events/event.py
CHANGED
pyobs/images/image.py
CHANGED
|
@@ -403,11 +403,13 @@ class Image:
|
|
|
403
403
|
# import PIL Image
|
|
404
404
|
import PIL.Image
|
|
405
405
|
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
-
if data is None:
|
|
406
|
+
# check data
|
|
407
|
+
if self._data is None:
|
|
409
408
|
raise ValueError("No data in image.")
|
|
410
409
|
|
|
410
|
+
# copy data
|
|
411
|
+
data: NDArray[Any] = np.copy(self._data)
|
|
412
|
+
|
|
411
413
|
# no vmin/vmax?
|
|
412
414
|
if vmin is None or vmax is None:
|
|
413
415
|
flattened = sorted(data.flatten())
|
|
@@ -569,7 +571,7 @@ class Image:
|
|
|
569
571
|
@property
|
|
570
572
|
def meta(self) -> dict[Any, Any]:
|
|
571
573
|
if self._meta is None:
|
|
572
|
-
self._meta = {}
|
|
574
|
+
self._meta = {} # type: ignore[assignment]
|
|
573
575
|
return self._meta
|
|
574
576
|
|
|
575
577
|
@meta.setter
|
pyobs/images/meta/skyoffsets.py
CHANGED
|
@@ -8,7 +8,7 @@ class SkyOffsets:
|
|
|
8
8
|
self.coord0 = coord0
|
|
9
9
|
self.coord1 = coord1
|
|
10
10
|
|
|
11
|
-
def separation(self, frame: BaseCoordinateFrame = None) -> Angle:
|
|
11
|
+
def separation(self, frame: BaseCoordinateFrame | None = None) -> Angle:
|
|
12
12
|
"""Returns separatation between both coordinates, either in their own or a given frame.
|
|
13
13
|
|
|
14
14
|
Args:
|
|
@@ -22,7 +22,7 @@ class SkyOffsets:
|
|
|
22
22
|
coord0, coord1 = self._to_frame(frame)
|
|
23
23
|
return coord0.separation(coord1)
|
|
24
24
|
|
|
25
|
-
def spherical_offsets(self, frame: BaseCoordinateFrame = None) -> tuple[Angle, Angle]:
|
|
25
|
+
def spherical_offsets(self, frame: BaseCoordinateFrame | None = None) -> tuple[Angle, Angle]:
|
|
26
26
|
"""Calculates spherical offset from first coordinate to second.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
@@ -37,7 +37,7 @@ class SkyOffsets:
|
|
|
37
37
|
off0, off1 = coord0.spherical_offsets_to(coord1.frame)
|
|
38
38
|
return off0, off1
|
|
39
39
|
|
|
40
|
-
def _to_frame(self, frame: BaseCoordinateFrame = None) -> tuple[SkyCoord, SkyCoord]:
|
|
40
|
+
def _to_frame(self, frame: BaseCoordinateFrame | None = None) -> tuple[SkyCoord, SkyCoord]:
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
Args:
|
|
@@ -188,7 +188,7 @@ class Text(ImageProcessor):
|
|
|
188
188
|
x, y = PillowHelper.position(image, self._x, self._y, self._wcs)
|
|
189
189
|
fill = PillowHelper.color(self._fill)
|
|
190
190
|
try:
|
|
191
|
-
text = self._text.format(**image.header) # noqa: UP031
|
|
191
|
+
text = self._text.format(**image.header) # noqa: UP031 # type: ignore[arg-type]
|
|
192
192
|
except KeyError:
|
|
193
193
|
text = self._text
|
|
194
194
|
|
|
@@ -142,7 +142,7 @@ class DaophotSourceDetection(SourceDetection):
|
|
|
142
142
|
daofind = DAOStarFinder(fwhm=self.fwhm, threshold=self.threshold * std)
|
|
143
143
|
|
|
144
144
|
loop = asyncio.get_running_loop()
|
|
145
|
-
return await loop.run_in_executor(None, daofind, data)
|
|
145
|
+
return await loop.run_in_executor(None, daofind, data) # type: ignore[arg-type]
|
|
146
146
|
|
|
147
147
|
async def __call__(self, image: Image) -> Image:
|
|
148
148
|
"""Find stars in given image and append catalog.
|
|
@@ -164,7 +164,7 @@ class DaophotSourceDetection(SourceDetection):
|
|
|
164
164
|
_, median, std = sigma_clipped_stats(background_corrected_data, sigma=3.0)
|
|
165
165
|
|
|
166
166
|
median_corrected_data = background_corrected_data - median
|
|
167
|
-
sources = await self._find_stars(median_corrected_data, std)
|
|
167
|
+
sources = await self._find_stars(median_corrected_data, int(std))
|
|
168
168
|
|
|
169
169
|
sources_catalog = _SourceCatalog.from_table(sources)
|
|
170
170
|
sources_catalog.apply_fits_origin_convention()
|
|
@@ -118,7 +118,7 @@ class Normalize(ImageProcessor):
|
|
|
118
118
|
return image
|
|
119
119
|
|
|
120
120
|
output_image = image.copy()
|
|
121
|
-
output_image.data = ((output_image.data - vmin) / (vmax - vmin) * 255.0).astype(np.uint8)
|
|
121
|
+
output_image.data = ((output_image.data - vmin) / (vmax - vmin) * 255.0).astype(np.uint8) # type: ignore[assignment]
|
|
122
122
|
|
|
123
123
|
return output_image
|
|
124
124
|
|
|
@@ -142,7 +142,7 @@ class CatalogCircularMask(ImageProcessor):
|
|
|
142
142
|
if isinstance(self._center[0], str) and isinstance(self._center[1], str):
|
|
143
143
|
return image.header[self._center[0]], image.header[self._center[1]]
|
|
144
144
|
else:
|
|
145
|
-
return self._center
|
|
145
|
+
return self._center # type: ignore[return-value]
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
__all__ = ["CatalogCircularMask"]
|
|
@@ -103,7 +103,7 @@ class GetFitsHeaders(ImageProcessor):
|
|
|
103
103
|
for sender in self._senders:
|
|
104
104
|
# request headers
|
|
105
105
|
async with self.proxy(sender, IFitsHeaderBefore) as proxy:
|
|
106
|
-
requests.append(asyncio.create_task(proxy.get_fits_header_before(self._namespace)))
|
|
106
|
+
requests.append(asyncio.create_task(proxy.get_fits_header_before(self._namespace))) # type: ignore[arg-type]
|
|
107
107
|
|
|
108
108
|
# copy image
|
|
109
109
|
out = image.copy()
|
|
@@ -126,7 +126,7 @@ class BrightestStarOffsets(Offsets):
|
|
|
126
126
|
|
|
127
127
|
@staticmethod
|
|
128
128
|
def _get_brightest_star_position(catalog: Table) -> tuple[float, float]:
|
|
129
|
-
brightest_star: Row = max(catalog, key=lambda row: row["flux"])
|
|
129
|
+
brightest_star: Row = max(catalog, key=lambda row: row["flux"]) # type: ignore[type-var]
|
|
130
130
|
return brightest_star["x"], brightest_star["y"]
|
|
131
131
|
|
|
132
132
|
@staticmethod
|
|
@@ -154,7 +154,7 @@ class BrightestStarGuiding(Offsets):
|
|
|
154
154
|
|
|
155
155
|
@staticmethod
|
|
156
156
|
def _get_brightest_star_position(catalog: Table) -> tuple[float, float]:
|
|
157
|
-
brightest_star: Row = max(catalog, key=lambda row: row["flux"])
|
|
157
|
+
brightest_star: Row = max(catalog, key=lambda row: row["flux"]) # type: ignore[type-var]
|
|
158
158
|
log.info("Found brightest star at x=%.2f, y=%.2f", brightest_star["x"], brightest_star["y"])
|
|
159
159
|
return brightest_star["x"], brightest_star["y"]
|
|
160
160
|
|
|
@@ -79,8 +79,8 @@ class Ring:
|
|
|
79
79
|
ny, nx = self.data.shape
|
|
80
80
|
x, y = np.arange(0, nx), np.arange(0, ny)
|
|
81
81
|
x_coordinates, y_coordinates = np.meshgrid(x, y)
|
|
82
|
-
section_mask = (min_angle < self.get_angle_from_position(x_coordinates, y_coordinates)) & (
|
|
83
|
-
max_angle > self.get_angle_from_position(x_coordinates, y_coordinates)
|
|
82
|
+
section_mask = (min_angle < self.get_angle_from_position(x_coordinates, y_coordinates)) & ( # type: ignore[call-overload]
|
|
83
|
+
max_angle > self.get_angle_from_position(x_coordinates, y_coordinates) # type: ignore[call-overload]
|
|
84
84
|
)
|
|
85
85
|
section = self.data.copy()
|
|
86
86
|
section *= section_mask
|
|
@@ -251,8 +251,8 @@ class SpilledLightGuiding(Offsets):
|
|
|
251
251
|
Offsets.__init__(self, **kwargs)
|
|
252
252
|
|
|
253
253
|
self._fibers = fibers
|
|
254
|
-
self._fibre_position:
|
|
255
|
-
self._inner_radius:
|
|
254
|
+
self._fibre_position: tuple[float, float] = (0.0, 0.0)
|
|
255
|
+
self._inner_radius: float = 0.0
|
|
256
256
|
self._radius_ratio = radius_ratio
|
|
257
257
|
self._max_relative_sigma = max_relative_sigma
|
|
258
258
|
self._section_angular_width = section_angular_width
|
|
@@ -300,8 +300,9 @@ class SpilledLightGuiding(Offsets):
|
|
|
300
300
|
|
|
301
301
|
async def _load_fibre_information(self) -> None:
|
|
302
302
|
async with self.comm.proxy(self._fibers, IMultiFiber) as proxy:
|
|
303
|
-
|
|
304
|
-
self.
|
|
303
|
+
fiber_state = proxy.get_state(IMultiFiber)
|
|
304
|
+
self._fibre_position = (fiber_state.pixel_x, fiber_state.pixel_y) if fiber_state is not None else (0.0, 0.0)
|
|
305
|
+
self._inner_radius = fiber_state.radius if fiber_state is not None else 0.0
|
|
305
306
|
|
|
306
307
|
async def _correct_for_binning(self, binning: int) -> None:
|
|
307
308
|
self._fibre_position = (self._fibre_position[0] / binning, self._fibre_position[1] / binning)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import ABCMeta, abstractmethod
|
|
2
2
|
|
|
3
|
-
from astropy.table import
|
|
3
|
+
from astropy.table import Table
|
|
4
4
|
|
|
5
5
|
from pyobs.images import Image
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ class _PhotometryCalculator(metaclass=ABCMeta):
|
|
|
10
10
|
|
|
11
11
|
@property
|
|
12
12
|
@abstractmethod
|
|
13
|
-
def catalog(self) ->
|
|
13
|
+
def catalog(self) -> Table | None: ...
|
|
14
14
|
|
|
15
15
|
@abstractmethod
|
|
16
16
|
def set_data(self, image: Image) -> None: ...
|
|
@@ -115,7 +115,7 @@ class SolarHelioprojective(ImageProcessor):
|
|
|
115
115
|
Returns:
|
|
116
116
|
Image with new WCS.
|
|
117
117
|
"""
|
|
118
|
-
import sunpy.coordinates
|
|
118
|
+
import sunpy.coordinates
|
|
119
119
|
|
|
120
120
|
obs_time = Time(image.header["DATE-OBS"], scale="utc")
|
|
121
121
|
cdelt = sunpy.coordinates.sun.angular_radius(obs_time).degree / image.header[self._keyword_radius]
|
pyobs/interfaces/__init__.py
CHANGED
|
@@ -34,7 +34,6 @@ from .IFocusModel import IFocusModel
|
|
|
34
34
|
from .IGain import GainState, IGain
|
|
35
35
|
from .IImageFormat import IImageFormat, ImageFormatCapabilities, ImageFormatState
|
|
36
36
|
from .IImageType import IImageType, ImageTypeState
|
|
37
|
-
from .ILatLon import ILatLon, LatLonCapabilities
|
|
38
37
|
from .IMode import IMode, ModeCapabilities, ModeState
|
|
39
38
|
from .IModule import IModule, ModuleCapabilities
|
|
40
39
|
from .IMotion import DeviceMotionStatus, IMotion, MotionState
|
|
@@ -65,8 +64,6 @@ from .IWindow import IWindow, WindowCapabilities, WindowState
|
|
|
65
64
|
__all__ = [
|
|
66
65
|
"IAbortable",
|
|
67
66
|
"IAcquisition",
|
|
68
|
-
"ILatLon",
|
|
69
|
-
"LatLonCapabilities",
|
|
70
67
|
"IAutoFocus",
|
|
71
68
|
"IAutoGuiding",
|
|
72
69
|
"IAutonomous",
|
pyobs/interfaces/interface.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABCMeta
|
|
2
|
-
from typing import ClassVar
|
|
2
|
+
from typing import Any, ClassVar
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Interface(metaclass=ABCMeta):
|
|
@@ -10,7 +10,18 @@ class Interface(metaclass=ABCMeta):
|
|
|
10
10
|
capabilities: ClassVar[type | None] = None
|
|
11
11
|
|
|
12
12
|
__module__ = "pyobs.interfaces"
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
def get_state(self, interface: "type[Interface]") -> Any | None:
|
|
15
|
+
"""Return the last received state for the given interface, or None."""
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
def get_capabilities(self, interface: "type[Interface]") -> Any | None:
|
|
19
|
+
"""Return the capabilities for the given interface, or None."""
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
async def wait_for_state(self, interface: "type[Interface]", timeout: float = 10.0) -> Any | None:
|
|
23
|
+
"""Return state immediately if available, otherwise wait for the first update."""
|
|
24
|
+
return None
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
__all__ = ["Interface"]
|
pyobs/mixins/camerasettings.py
CHANGED
|
@@ -54,9 +54,9 @@ class CameraSettingsMixin:
|
|
|
54
54
|
await camera.set_binning(self.__camerasettings_binning, self.__camerasettings_binning)
|
|
55
55
|
if isinstance(camera, IWindow):
|
|
56
56
|
log.info("Set window to full frame...")
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
await camera.set_window(
|
|
57
|
+
cap = camera.get_capabilities(IWindow)
|
|
58
|
+
if cap is not None:
|
|
59
|
+
await camera.set_window(cap.full_frame_x, cap.full_frame_y, cap.full_frame_width, cap.full_frame_height)
|
|
60
60
|
else:
|
|
61
61
|
raise ValueError("Could not get full frame size.")
|
|
62
62
|
|
pyobs/mixins/fitsheader.py
CHANGED
|
@@ -185,7 +185,7 @@ class FitsHeaderMixin:
|
|
|
185
185
|
|
|
186
186
|
# date of night this observation is in
|
|
187
187
|
if self._fitsheadermixin_night_obs:
|
|
188
|
-
hdr["DAY-OBS"] = (date_obs.night_obs(module._observer).strftime("%Y-%m-%d"), "Night of observation")
|
|
188
|
+
hdr["DAY-OBS"] = (date_obs.night_obs(module._observer).strftime("%Y-%m-%d"), "Night of observation") # type: ignore[arg-type]
|
|
189
189
|
else:
|
|
190
190
|
hdr["DAY-OBS"] = (date_obs.strftime("%Y-%m-%d"), "Day of observation")
|
|
191
191
|
|
pyobs/mixins/follow.py
CHANGED
|
@@ -6,7 +6,8 @@ from typing import Any
|
|
|
6
6
|
import astropy.units as u
|
|
7
7
|
from astropy.coordinates import SkyCoord
|
|
8
8
|
|
|
9
|
-
from pyobs.
|
|
9
|
+
from pyobs.comm import Proxy
|
|
10
|
+
from pyobs.interfaces import AltAzState, IPointingAltAz, IPointingRaDec, IReady, RaDecState, ReadyState
|
|
10
11
|
from pyobs.modules import Module
|
|
11
12
|
from pyobs.utils import exceptions as exc
|
|
12
13
|
|
|
@@ -26,10 +27,26 @@ async def get_coords(
|
|
|
26
27
|
Return from method call.
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
|
-
if mode == IPointingAltAz
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
if mode == IPointingAltAz:
|
|
31
|
+
if isinstance(obj, Proxy):
|
|
32
|
+
state: AltAzState | None = obj.get_state(IPointingAltAz)
|
|
33
|
+
elif isinstance(obj, Module):
|
|
34
|
+
state = obj.comm.get_own_state(IPointingAltAz)
|
|
35
|
+
else:
|
|
36
|
+
state = None
|
|
37
|
+
if state is None:
|
|
38
|
+
raise ValueError("No AltAz state available.")
|
|
39
|
+
return state.alt, state.az
|
|
40
|
+
elif mode == IPointingRaDec:
|
|
41
|
+
if isinstance(obj, Proxy):
|
|
42
|
+
state2: RaDecState | None = obj.get_state(IPointingRaDec)
|
|
43
|
+
elif isinstance(obj, Module):
|
|
44
|
+
state2 = obj.comm.get_own_state(IPointingRaDec)
|
|
45
|
+
else:
|
|
46
|
+
state2 = None
|
|
47
|
+
if state2 is None:
|
|
48
|
+
raise ValueError("No RaDec state available.")
|
|
49
|
+
return state2.ra, state2.dec
|
|
33
50
|
else:
|
|
34
51
|
raise ValueError("Unknown mode.")
|
|
35
52
|
|
|
@@ -117,8 +134,9 @@ class FollowMixin:
|
|
|
117
134
|
connected = True
|
|
118
135
|
while True:
|
|
119
136
|
# not ready?
|
|
120
|
-
if isinstance(self, IReady):
|
|
121
|
-
|
|
137
|
+
if isinstance(self, IReady) and isinstance(self, Module):
|
|
138
|
+
ready_state: ReadyState | None = self.comm.get_own_state(IReady)
|
|
139
|
+
if (ready_state is None or not ready_state.ready) and this.__follow_only_when_ready:
|
|
122
140
|
await asyncio.sleep(this.__follow_interval)
|
|
123
141
|
continue
|
|
124
142
|
|
|
@@ -39,7 +39,7 @@ class ExposureInfo(NamedTuple):
|
|
|
39
39
|
exposure_time: float
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
async def calc_expose_timeout(camera:
|
|
42
|
+
async def calc_expose_timeout(camera: BaseCamera, *args: Any, **kwargs: Any) -> float:
|
|
43
43
|
"""Calculates timeout for expose()."""
|
|
44
44
|
return camera._exposure_time + 30
|
|
45
45
|
|
|
@@ -333,7 +333,7 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
|
|
|
333
333
|
|
|
334
334
|
# flip image?
|
|
335
335
|
if self._flip:
|
|
336
|
-
data: NDArray[Any] = np.flip(data, axis=0)
|
|
336
|
+
data: NDArray[Any] = np.flip(data, axis=0)
|
|
337
337
|
|
|
338
338
|
# got a requested image in the queue?
|
|
339
339
|
image, filename = None, None
|
|
@@ -14,7 +14,6 @@ from astropy.modeling import models
|
|
|
14
14
|
from astropy.table import Table
|
|
15
15
|
from astropy.wcs import WCS
|
|
16
16
|
from numpy.typing import NDArray
|
|
17
|
-
from photutils.datasets import make_model_image, make_noise_image
|
|
18
17
|
|
|
19
18
|
from pyobs.images import Image
|
|
20
19
|
from pyobs.interfaces import (
|
|
@@ -165,7 +164,7 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
165
164
|
self._cooling = CoolingStatus(
|
|
166
165
|
enabled=self._cooling.enabled,
|
|
167
166
|
set_point=self._cooling.set_point,
|
|
168
|
-
power=power,
|
|
167
|
+
power=int(power),
|
|
169
168
|
temperatures=temps,
|
|
170
169
|
)
|
|
171
170
|
await self.comm.set_state(
|
|
@@ -185,6 +184,8 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
185
184
|
return -18.0
|
|
186
185
|
|
|
187
186
|
def _simulate_image(self, exp_time: float, open_shutter: bool) -> NDArray[Any]:
|
|
187
|
+
from photutils.datasets import make_model_image, make_noise_image
|
|
188
|
+
|
|
188
189
|
shape = (int(self._window[3]), int(self._window[2]))
|
|
189
190
|
data = make_noise_image(shape, distribution="gaussian", mean=10, stddev=1.0)
|
|
190
191
|
|
|
@@ -242,7 +243,7 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
242
243
|
job = tap.launch_job(query)
|
|
243
244
|
self._catalog = job.get_results()
|
|
244
245
|
self._catalog_coords = self._telescope_pos
|
|
245
|
-
return self._catalog
|
|
246
|
+
return self._catalog # type: ignore[return-value]
|
|
246
247
|
|
|
247
248
|
def _get_sources_table(self, exp_time: float) -> Table:
|
|
248
249
|
tmp = 360.0 / (2.0 * np.pi) * self._pixel_size / self._focal_length
|