Skip to content

Commit d5f7952

Browse files
authored
v1.12.4
version 1.12.4
2 parents 9d0feb8 + 486911e commit d5f7952

File tree

13 files changed

+344
-163
lines changed

13 files changed

+344
-163
lines changed

pyobs/images/processors/offsets/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .projected import ProjectedOffsets
1111
from .fitsheader import FitsHeaderOffsets
1212
from .dummyoffsets import DummyOffsets
13+
from .dummyskyoffsets import DummySkyOffsets
1314

1415
__all__ = [
1516
"Offsets",
@@ -19,4 +20,5 @@
1920
"FitsHeaderOffsets",
2021
"BrightestStarOffsets",
2122
"DummyOffsets",
23+
"DummySkyOffsets"
2224
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from copy import copy
2+
from typing import Any, Union, Dict
3+
4+
from astropy.coordinates import SkyCoord
5+
6+
from pyobs.images import Image
7+
from pyobs.images.meta import SkyOffsets
8+
from pyobs.images.processors.offsets import Offsets
9+
from pyobs.object import get_object
10+
11+
12+
class DummySkyOffsets(Offsets):
13+
def __init__(self, coord0: Union[SkyCoord, Dict[str, Any]], coord1: Union[SkyCoord, Dict[str, Any]], **kwargs: Any) -> None:
14+
super().__init__(**kwargs)
15+
sky_coord0 = get_object(coord0, SkyCoord)
16+
sky_coord1 = get_object(coord1, SkyCoord)
17+
self._offset = SkyOffsets(sky_coord0, sky_coord1)
18+
19+
async def __call__(self, image: Image) -> Image:
20+
image.set_meta(copy(self._offset))
21+
return image
22+
23+
24+
__all__ = ["DummySkyOffsets"]

pyobs/modules/pointing/_baseguiding.py

Lines changed: 10 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,22 @@
11
import logging
2-
from abc import ABCMeta, abstractmethod, ABC
3-
from collections import defaultdict
4-
from datetime import datetime
2+
from abc import ABCMeta
53
from typing import Union, List, Dict, Tuple, Any, Optional
64

75
import astropy.units as u
8-
import numpy as np
96
from astropy.coordinates import SkyCoord
107

118
from pyobs.images import Image
129
from pyobs.interfaces import IAutoGuiding, IFitsHeaderBefore, IFitsHeaderAfter
1310
from pyobs.utils.time import Time
1411
from ._base import BasePointing
15-
from ...images.meta import PixelOffsets
12+
from .guidingstatistics import GuidingStatisticsUptime, GuidingStatisticsPixelOffset
13+
from .guidingstatistics.guidingstatistics import GuidingStatistics
1614
from ...interfaces import ITelescope
15+
from ...object import get_object
1716

1817
log = logging.getLogger(__name__)
1918

2019

21-
class _GuidingStatistics(ABC):
22-
"""Calculates statistics for guiding."""
23-
24-
def __init__(self):
25-
self._sessions: Dict[str, List[Any]] = defaultdict(list)
26-
27-
def init_stats(self, client: str, default: Any = None) -> None:
28-
"""
29-
Inits a stat measurement session for a client.
30-
31-
Args:
32-
client: name/id of the client
33-
default: first entry in session
34-
"""
35-
36-
self._sessions[client] = []
37-
38-
if default is not None:
39-
self._sessions[client].append(self._get_session_data(default))
40-
41-
@abstractmethod
42-
def _build_header(self, data: Any) -> Dict[str, Tuple[Any, str]]:
43-
raise NotImplementedError
44-
45-
def add_to_header(self, client: str, header: Dict[str, Tuple[Any, str]]) -> Dict[str, Tuple[Any, str]]:
46-
"""
47-
Add statistics to given header.
48-
49-
Args:
50-
client: id/name of the client
51-
header: Header dict to add statistics to.
52-
"""
53-
54-
data = self._sessions.pop(client)
55-
session_header = self._build_header(data)
56-
57-
return header | session_header
58-
59-
@abstractmethod
60-
def _get_session_data(self, input_data: Any) -> Any:
61-
raise NotImplementedError
62-
63-
def add_data(self, input_data: Any) -> None:
64-
"""
65-
Adds data to all client measurement sessions.
66-
Args:
67-
input_data: Image witch metadata
68-
"""
69-
70-
data = self._get_session_data(input_data)
71-
72-
for k in self._sessions.keys():
73-
self._sessions[k].append(data)
74-
75-
76-
class _GuidingStatisticsPixelOffset(_GuidingStatistics):
77-
@staticmethod
78-
def _calc_rms(data: List[Tuple[float, float]]) -> Optional[Tuple[float, float]]:
79-
"""
80-
Calculates RMS of data.
81-
82-
Args:
83-
data: Data to calculate RMS for.
84-
85-
Returns:
86-
Tuple of RMS.
87-
"""
88-
if len(data) < 3:
89-
return None
90-
91-
flattened_data = np.array(list(map(list, zip(*data))))
92-
data_len = len(flattened_data[0])
93-
rms = np.sqrt(np.sum(np.power(flattened_data, 2), axis=1) / data_len)
94-
return tuple(rms)
95-
96-
def _build_header(self, data: List[Tuple[float, float]]) -> Dict[str, Tuple[Any, str]]:
97-
header = {}
98-
rms = self._calc_rms(data)
99-
100-
if rms is not None:
101-
header["GUIDING RMS1"] = (float(rms[0]), "RMS for guiding on axis 1")
102-
header["GUIDING RMS2"] = (float(rms[1]), "RMS for guiding on axis 2")
103-
104-
return header
105-
106-
def _get_session_data(self, data: Image) -> Tuple[float, float]:
107-
if data.has_meta(PixelOffsets):
108-
meta = data.get_meta(PixelOffsets)
109-
primitive = tuple(meta.__dict__.values())
110-
return primitive
111-
else:
112-
log.warning("Image is missing the necessary meta information!")
113-
raise KeyError("Unknown meta.")
114-
115-
116-
class _GuidingStatisticsUptime(_GuidingStatistics):
117-
@staticmethod
118-
def _calc_uptime(states: List[Tuple[bool, datetime]]) -> int:
119-
uptimes: List[int] = []
120-
for i, entry in enumerate(states):
121-
state, timestamp = entry
122-
# is not closed?
123-
if not state or i + 1 == len(states):
124-
continue
125-
126-
uptime = (states[i + 1][1] - timestamp).seconds
127-
uptimes.append(uptime)
128-
129-
return sum(uptimes)
130-
131-
@staticmethod
132-
def _calc_total_time(states: List[Tuple[bool, datetime]]) -> int:
133-
initial_time = states[0][1]
134-
end_time = states[-1][1]
135-
return (end_time - initial_time).seconds
136-
137-
@staticmethod
138-
def _calc_uptime_percentage(states: List[Tuple[bool, datetime]]) -> float:
139-
uptime = _GuidingStatisticsUptime._calc_uptime(states)
140-
total_time = _GuidingStatisticsUptime._calc_total_time(states)
141-
142-
"""
143-
If no time has passed, return 100 if the loop is closed, 0 else.
144-
We have to take the second last value in the state array, since the last value is the stop value.
145-
"""
146-
if total_time == 0:
147-
return int(states[-2][0]) * 100.0
148-
149-
return uptime / total_time * 100.0
150-
151-
def _build_header(self, data: List[Tuple[bool, datetime]]) -> Dict[str, Tuple[Any, str]]:
152-
now = datetime.now()
153-
data.append((None, now))
154-
155-
uptime_percentage = self._calc_uptime_percentage(data)
156-
return {"GUIDING UPTIME": (uptime_percentage, "Time the guiding loop was closed [%]")}
157-
158-
def _get_session_data(self, input_data: bool) -> Tuple[bool, datetime]:
159-
now = datetime.now()
160-
return input_data, now
161-
162-
16320
class BaseGuiding(BasePointing, IAutoGuiding, IFitsHeaderBefore, IFitsHeaderAfter, metaclass=ABCMeta):
16421
"""Base class for guiding modules."""
16522

@@ -174,6 +31,7 @@ def __init__(
17431
pid: bool = False,
17532
reset_at_focus: bool = True,
17633
reset_at_filter: bool = True,
34+
guiding_statistic: Optional[Union[Dict[str, Any], GuidingStatistics]] = None,
17735
**kwargs: Any,
17836
):
17937
"""Initializes a new science frame auto guiding system.
@@ -205,8 +63,11 @@ def __init__(
20563
self._ref_header = None
20664

20765
# stats
208-
self._statistics = _GuidingStatisticsPixelOffset()
209-
self._uptime = _GuidingStatisticsUptime()
66+
if guiding_statistic is None:
67+
guiding_statistic = GuidingStatisticsPixelOffset()
68+
69+
self._statistics = get_object(guiding_statistic, GuidingStatistics)
70+
self._uptime = GuidingStatisticsUptime()
21071

21172
async def start(self, **kwargs: Any) -> None:
21273
"""Starts/resets auto-guiding."""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .uptime import GuidingStatisticsUptime
2+
from .pixeloffset import GuidingStatisticsPixelOffset
3+
from .skyoffsets import GuidingStatisticsSkyOffset
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from abc import abstractmethod, ABCMeta
2+
from collections import defaultdict
3+
from typing import List, Dict, Tuple, Any
4+
5+
from pyobs.images import Image
6+
7+
8+
class GuidingStatistics(object, metaclass=ABCMeta):
9+
"""Calculates statistics for guiding."""
10+
11+
def __init__(self) -> None:
12+
self._sessions: Dict[str, List[Any]] = defaultdict(list)
13+
14+
def init_stats(self, client: str, default: Any = None) -> None:
15+
"""
16+
Inits a stat measurement session for a client.
17+
18+
Args:
19+
client: name/id of the client
20+
default: first entry in session
21+
"""
22+
23+
self._sessions[client] = []
24+
25+
if default is not None:
26+
self._sessions[client].append(self._get_session_data(default))
27+
28+
@abstractmethod
29+
def _build_header(self, data: Any) -> Dict[str, Tuple[Any, str]]:
30+
raise NotImplementedError
31+
32+
def add_to_header(self, client: str, header: Dict[str, Tuple[Any, str]]) -> Dict[str, Tuple[Any, str]]:
33+
"""
34+
Add statistics to given header.
35+
36+
Args:
37+
client: id/name of the client
38+
header: Header dict to add statistics to.
39+
"""
40+
41+
data = self._sessions.pop(client)
42+
session_header = self._build_header(data)
43+
44+
return header | session_header
45+
46+
@abstractmethod
47+
def _get_session_data(self, input_data: Image) -> Any:
48+
raise NotImplementedError
49+
50+
def add_data(self, input_data: Image) -> None:
51+
"""
52+
Adds data to all client measurement sessions.
53+
Args:
54+
input_data: Image witch metadata
55+
"""
56+
57+
data = self._get_session_data(input_data)
58+
59+
for k in self._sessions.keys():
60+
self._sessions[k].append(data)
61+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import logging
2+
from typing import List, Dict, Tuple, Any, Optional
3+
4+
import numpy as np
5+
6+
from pyobs.images import Image
7+
from .guidingstatistics import GuidingStatistics
8+
from pyobs.images.meta import PixelOffsets
9+
10+
11+
log = logging.getLogger(__name__)
12+
13+
14+
class GuidingStatisticsPixelOffset(GuidingStatistics):
15+
@staticmethod
16+
def _calc_rms(data: List[Tuple[float, float]]) -> Optional[Tuple[float, float]]:
17+
"""
18+
Calculates RMS of data.
19+
20+
Args:
21+
data: Data to calculate RMS for.
22+
23+
Returns:
24+
Tuple of RMS.
25+
"""
26+
if len(data) < 3:
27+
return None
28+
29+
flattened_data = np.array(list(map(list, zip(*data))))
30+
data_len = len(flattened_data[0])
31+
rms = np.sqrt(np.sum(np.power(flattened_data, 2), axis=1) / data_len)
32+
return tuple(rms)
33+
34+
def _build_header(self, data: List[Tuple[float, float]]) -> Dict[str, Tuple[Any, str]]:
35+
header = {}
36+
rms = self._calc_rms(data)
37+
38+
if rms is not None:
39+
header["GUIDING RMS1"] = (float(rms[0]), "RMS for guiding on axis 1")
40+
header["GUIDING RMS2"] = (float(rms[1]), "RMS for guiding on axis 2")
41+
42+
return header
43+
44+
def _get_session_data(self, data: Image) -> Tuple[float, float]:
45+
if data.has_meta(PixelOffsets):
46+
meta = data.get_meta(PixelOffsets)
47+
primitive = tuple(meta.__dict__.values())
48+
return primitive
49+
else:
50+
log.warning("Image is missing the necessary meta information!")
51+
raise KeyError("Unknown meta.")
52+
53+
54+

0 commit comments

Comments
 (0)