Skip to content

Commit ecd3587

Browse files
authored
Merge pull request #21 from hz-b/dev/feature/bdata
[TASK] perparation of bdata now handled by separate module (not in view)
2 parents db16815 + dfa06c3 commit ecd3587

File tree

6 files changed

+148
-71
lines changed

6 files changed

+148
-71
lines changed

src/bpm_data_combiner/app/bdata.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from typing import Sequence, Mapping
2+
import numpy as np
3+
4+
from ..data_model.bpm_data_collection import BPMDataCollectionStats
5+
6+
# is this the correct way to convert the data ?
7+
scale_bits = 2 ** 15 / 10
8+
9+
# scale rms so that the slow orbit feedback accepts the data
10+
# factor 100 seems to be enough.
11+
# I think I should add some check that the noise is large enough
12+
scale_rms = 20
13+
14+
nm2mm = 1e-6
15+
16+
17+
def convert(data: Sequence[float], scale_axis: float = 1.0):
18+
"""
19+
Todo:
20+
include conversion to np.int16 at this stage?
21+
"""
22+
return (np.asarray(data) * (nm2mm * scale_bits * scale_axis)).astype(np.int16)
23+
24+
25+
def convert_noise(data, scale_axis: float = 1):
26+
noise = convert(data.std, scale_axis=scale_axis * scale_rms)
27+
# at least one bit has to be set ...
28+
# otherwise it will not consider it as noise
29+
noise[data.n_readings > 0] = np.clip(noise, 1, None)[data.n_readings > 0]
30+
# so sofb Orbit will consider it as not existing
31+
noise[data.n_readings <= 0] = 0
32+
return noise
33+
34+
35+
def stat_data_to_bdata(
36+
data: BPMDataCollectionStats,
37+
*,
38+
device_index: Mapping[str, int],
39+
n_bpms: int,
40+
scale_x_axis: float
41+
):
42+
"""
43+
device_names: need to contain empty ones too, e.g. as None empty or any
44+
other unique place holder
45+
46+
Todo:
47+
do I need to map data to positions again?
48+
How is stat handling it
49+
"""
50+
n_entries = len(data.x.values)
51+
if n_entries > n_bpms:
52+
raise ValueError("number of bpms %s too many. max %s", n_entries, n_bpms)
53+
54+
bdata = np.empty([8, n_bpms], dtype=np.int16)
55+
bdata.fill(0.0)
56+
57+
indices = [device_index[name] for name in data.names]
58+
# flipping coordinate system to get the dispersion on the correct side
59+
# todo: check at which state this should be done
60+
# fmt:off
61+
bdata[0, indices] = - convert(data.x.values, scale_axis=scale_x_axis)
62+
bdata[1, indices] = convert(data.y.values)
63+
# fmt:on
64+
# intensity z 1.3
65+
# bdata[2] = 3
66+
# intensityz z 1.3
67+
# bdata[3] = 3
68+
# AGC status needs to be three for valid data
69+
# todo: find out what to set if only one plane is valid?
70+
bdata[4, indices] = np.where(
71+
(data.x.n_readings > 0) | (data.y.n_readings > 0), 3, 0
72+
)
73+
bdata[4, -1] = 2
74+
75+
bdata[6, indices] = convert_noise(data.x, scale_axis=scale_x_axis)
76+
bdata[7, indices] = convert_noise(data.y)
77+
78+
return bdata
79+
80+
81+
__all__ = ["stat_data_to_bdata"]

src/bpm_data_combiner/app/controller.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
from enum import Enum
22
import logging
3-
from typing import Sequence, Union, Tuple
3+
from typing import Sequence, Union
44

5-
import numpy as np
65

76
from collector import Collector, CollectionItemInterface
87

98
from ..bl.accumulator import Accumulator
109
from ..interfaces.controller import ControllerInterface
11-
from ..monitor_devices import MonitorDevicesStatus, MonitorDeviceSynchronisation, StatusField
12-
from ..post_processor.combine import collection_to_bpm_data_collection, accumulated_collections_to_array
10+
from ..monitor_devices import (
11+
MonitorDevicesStatus,
12+
MonitorDeviceSynchronisation,
13+
StatusField,
14+
)
15+
from ..post_processor.combine import (
16+
collection_to_bpm_data_collection,
17+
accumulated_collections_to_array,
18+
)
1319
from ..post_processor.handle_active_planes import pass_data_for_active_planes
1420
from ..post_processor.statistics import compute_mean_weights_for_planes
1521

22+
from .bdata import stat_data_to_bdata
1623
from .config import Config
1724
from .view import Views
1825

@@ -92,7 +99,9 @@ def _update(self, *, cmd, dev_name, tpro, **kwargs):
9299
else:
93100
raise AssertionError(f"plane {plane} unknown")
94101
elif cmd == ValidCommands.sync_stat:
95-
return self.dev_status(dev_name, StatusField.synchronised, kwargs["sync_stat"])
102+
return self.dev_status(
103+
dev_name, StatusField.synchronised, kwargs["sync_stat"]
104+
)
96105
elif cmd == ValidCommands.known_device_names:
97106
return self.set_device_names(device_names=kwargs["known_device_names"])
98107
elif cmd == ValidCommands.cfg_comp_median:
@@ -112,8 +121,7 @@ def new_value(self, dev_name: str, value: Sequence[int]):
112121
cnt, x, y = value
113122
collection = self.collector.new_item(
114123
pass_data_for_active_planes(
115-
cnt, x, y,
116-
device_status=self.monitor_devices.devices_status[dev_name]
124+
cnt, x, y, device_status=self.monitor_devices.devices_status[dev_name]
117125
)
118126
)
119127
if collection.ready:
@@ -133,12 +141,20 @@ def dev_status(
133141

134142
def periodic_trigger(self):
135143
"""Present new (averaged) bpm data on periodic trigger"""
136-
data = accumulated_collections_to_array(self.accumulator.get(), dev_names_index=self.dev_name_index)
144+
data = accumulated_collections_to_array(
145+
self.accumulator.get(), dev_names_index=self.dev_name_index
146+
)
137147
stat_data = compute_mean_weights_for_planes(data)
138148
self.views.periodic_data.update(stat_data)
139149
logger.debug("pushing stat data to bdata_view")
140150
#: todo ... need to get kwargs from config
141-
self.views.bdata.update(stat_data, n_bpms=32, scale_x_axis=1./1.4671)
151+
bdata = stat_data_to_bdata(
152+
stat_data,
153+
device_index=self.dev_name_index,
154+
n_bpms=32,
155+
scale_x_axis=1.0 / 1.4671,
156+
)
157+
self.views.bdata.update(bdata.ravel())
142158
logger.debug("pushing stat data to bdata_view done")
143159

144160
def _on_new_collection_ready(self, col: CollectionItemInterface):
@@ -166,4 +182,3 @@ def compute_show_median(self):
166182
self.views.monitor_device_sync.update(
167183
*self.monitor_device_synchronisation.offset_from_median()
168184
)
169-

src/bpm_data_combiner/app/view.py

Lines changed: 20 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
from ..data_model.bpm_data_collection import BPMDataCollection, BPMDataCollectionStats
55

66
import pydev
7-
pydev_supports_sequence = True
8-
97
import sys
8+
9+
pydev_supports_sequence = True
1010
stream = sys.stdout
1111

1212

@@ -18,12 +18,14 @@ class ViewBPMMonitoring:
1818
def __init__(self, prefix: str):
1919
self.prefix = prefix
2020

21-
def update(self, *,
22-
names: Sequence[str],
23-
active: Sequence[bool],
24-
synchronised: Sequence[bool],
25-
usable: Sequence[bool],
26-
):
21+
def update(
22+
self,
23+
*,
24+
names: Sequence[str],
25+
active: Sequence[bool],
26+
synchronised: Sequence[bool],
27+
usable: Sequence[bool],
28+
):
2729
# names = string_array_to_bytes(names)
2830
names = list(names)
2931
label = self.prefix + ":" + "names"
@@ -50,7 +52,6 @@ def update(self, *,
5052
pydev.iointr(label, usable.tolist())
5153

5254

53-
5455
class ViewBPMDataCollection:
5556
def __init__(self, prefix: str):
5657
self.prefix = prefix
@@ -134,53 +135,16 @@ class ViewBPMDataAsBData:
134135
def __init__(self, prefix: str):
135136
self.prefix = prefix
136137

137-
def update(self, data: BPMDataCollectionStats, *, n_bpms, scale_x_axis):
138-
"""prepare data as expected
139-
"""
140-
logger.debug("view bdata: publishing data %s", data)
141-
nm2mm = 1e-6
142-
n_entries = len(data.x.values)
143-
if n_entries > n_bpms:
144-
raise ValueError("number of bpms %s too many. max %s", n_entries, n_bpms)
145-
146-
bdata = np.empty([8, n_bpms], dtype=float)
147-
bdata.fill(0.0)
148-
# is this the correct way to convert the data ?
149-
scale_bits = 2**15/10
150-
151-
# flipping coordinate system to get the dispersion on the correct side
152-
# todo: check at which state this should be done
153-
# fmt:off
154-
def convert(data, scale_axis = 1.0):
155-
return data * (nm2mm * scale_bits * scale_axis)
156-
bdata[0, :n_entries] = - convert(data.x.values, scale_axis=scale_x_axis)
157-
bdata[1, :n_entries] = convert(data.y.values)
158-
# fmt:on
159-
# intensity z 1.3
160-
# bdata[2] = 3
161-
# intensityz z 1.3
162-
# bdata[3] = 3
163-
# AGC status needs to be three for valid data
164-
# todo: find out what to set if only one plane is valid?
165-
bdata[4,:n_entries] = np.where((data.x.n_readings > 0) | (data.y.n_readings > 0), 3, 0)
166-
bdata[4, -1] = 2
167-
# scale rms so that the slow orbit feedback accepts the data
168-
# factor 100 seems to be enough.
169-
# I think I should add some check that the noise is large enough
170-
scale_rms = 20
171-
def convert_noise(data, scale_axis = 1):
172-
noise = convert(data.std, scale_axis = scale_axis * scale_rms)
173-
noise[data.n_readings > 0] = np.clip(noise, 1, None)[data.n_readings>0]
174-
# so sofb Orbit will consider it as not existing
175-
noise[data.n_readings<= 0] = 0
176-
return noise
177-
bdata[6, :n_entries] = convert_noise(data.x, scale_axis=scale_x_axis)
178-
bdata[7, :n_entries] = convert_noise(data.y)
138+
def update(self, bdata: Sequence[int]):
139+
"""prepare data as expected"""
140+
logger.debug("view bdata: publishing data %s", bdata)
179141

180142
label = f"{self.prefix}"
181-
bdata = [float(v) for v in bdata.ravel().astype(np.int16)]
143+
bdata = np.asarray(bdata).astype(np.int16)
144+
if not pydev_supports_sequence:
145+
bdata = [int(v) for v in bdata]
182146
pydev.iointr(label, bdata)
183-
logger.debug("view bdata: label %s, %d n_entries", label, n_entries)
147+
logger.debug("view bdata: label %s, %d n_entries", label, len(bdata))
184148

185149

186150
class ViewStringBuffer:
@@ -209,8 +173,8 @@ def __init__(self, prefix: str):
209173
def update(self, median: int, offset_from_median: Sequence[np.int32]):
210174
# stream.write(f"updating {self.prefix} with median {median}\n")
211175
# stream.flush()
212-
pydev.iointr(self.prefix + ':median', median)
213-
pydev.iointr(self.prefix + ':offset', list(offset_from_median))
176+
pydev.iointr(self.prefix + ":median", median)
177+
pydev.iointr(self.prefix + ":offset", list(offset_from_median))
214178

215179

216180
class ViewConfiguration:
@@ -220,7 +184,7 @@ def __init__(self, prefix: str):
220184
def update(self, median_computation: bool):
221185
stream.write(f"updating {self.prefix} with median {median_computation}\n")
222186
stream.flush()
223-
pydev.iointr(self.prefix + ':comp:median', bool(median_computation))
187+
pydev.iointr(self.prefix + ":comp:median", bool(median_computation))
224188

225189

226190
class Views:

src/bpm_data_combiner/monitor_devices/interfaces/monitor_devices_status.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,8 @@ def update(self, dev_name : str, field: StatusField, info: Union[bool,Synchronis
4343
@abstractmethod
4444
def get_device_names(self) -> Sequence[str]:
4545
"""names of currently usable devices
46+
47+
Todo: should one provide device name index instead?
48+
does it belong here
4649
"""
4750
pass

src/bpm_data_combiner/post_processor/statistics.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import numpy as np
2-
from numpy import ma
3-
# which numpy version supports that ?
4-
# from numpy.typing import ArrayLike
52

6-
from bpm_data_combiner.data_model.bpm_data_accumulation import BPMDataAccumulationForPlane, BPMDataAccumulation
7-
from bpm_data_combiner.data_model.bpm_data_collection import BPMDataCollectionStatsPlane, BPMDataCollectionStats
3+
from ..data_model.bpm_data_accumulation import BPMDataAccumulationForPlane, BPMDataAccumulation
4+
from ..data_model.bpm_data_collection import BPMDataCollectionStatsPlane, BPMDataCollectionStats
5+
86

97
def compute_weights_scaled(values#: ArrayLike,
108
,*, n_readings: int): # -> ArrayLike:

src/tests/test_statistics.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import numpy as np
2+
3+
from bpm_data_combiner.data_model.bpm_data_accumulation import BPMDataAccumulationForPlane
4+
from bpm_data_combiner.post_processor.statistics import compute_mean_weight
5+
6+
7+
def test_compute_mean_weight_single_data():
8+
p = BPMDataAccumulationForPlane(
9+
values=np.array([[1, 2, 3, 4]]),
10+
valid=np.array([[True]*4])
11+
)
12+
print(p)
13+
r = compute_mean_weight(p)
14+
r.values == [1, 2, 3, 4]
15+
assert (r.n_readings == 1).all()
16+
assert (r.std == 0).all()

0 commit comments

Comments
 (0)