blissdata-mosca 2.0.1__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.
@@ -0,0 +1,4 @@
1
+ from .stream import MoscaStream, MoscaView
2
+
3
+ stream_cls = MoscaStream
4
+ view_cls = MoscaView
@@ -0,0 +1,297 @@
1
+ import os
2
+ from typing import NamedTuple
3
+ from dataclasses import dataclass
4
+
5
+ import numpy as np
6
+ import h5py
7
+
8
+ from blissdata.streams import BaseStream, BaseView, StreamDefinition
9
+ from blissdata.streams.encoding.json import JsonStreamEncoder
10
+ from blissdata.exceptions import (
11
+ EndOfStream,
12
+ IndexWontBeThereError,
13
+ IndexNotYetThereError,
14
+ IndexNoMoreThereError,
15
+ EmptyViewException,
16
+ )
17
+
18
+ from mosca_client.tango.mosca import Reader as MoscaReader
19
+
20
+
21
+ PROTOCOL_VERSION = 1
22
+
23
+
24
+ @dataclass
25
+ class SpectraReference:
26
+ file_path: str
27
+ data_path: str | None
28
+ index: int
29
+
30
+
31
+ class SpectraData(NamedTuple):
32
+ array: np.ndarray
33
+ """Numpy array containing data of this frame"""
34
+
35
+ frame_id: int | None
36
+ """Number of the frame (0=first frame) in a sequence of frames."""
37
+
38
+
39
+ class MoscaStream(BaseStream):
40
+ PROTOCOL_VERSION = 1
41
+
42
+ def __init__(self, event_stream):
43
+ super().__init__(event_stream)
44
+
45
+ self._last_read_index = -1
46
+ self._last_saved_index = -1
47
+
48
+ info = event_stream.info
49
+ mosca_info = info["mosca_info"]
50
+
51
+ protocol = mosca_info["protocol_version"]
52
+ if MoscaStream.PROTOCOL_VERSION != protocol:
53
+ raise Exception(
54
+ f"{type(self).__name__} supports mosca json protocol {MoscaStream.PROTOCOL_VERSION}, found version {protocol}"
55
+ )
56
+
57
+ self._points_per_file = mosca_info["points_per_file"]
58
+ self._file_path = mosca_info["file_path"]
59
+ self._file_prefix = mosca_info["file_prefix"]
60
+ self._data_path = mosca_info["data_path"]
61
+ self._dtype = info["dtype"]
62
+ self._shape = info["shape"]
63
+ self._data_index = info["data_index"]
64
+ self._detector = info["detector"]
65
+
66
+ self._reader = MoscaReader(
67
+ event_stream.info["mosca_info"]["server_url"],
68
+ acq_id=event_stream.info["mosca_info"]["acq_id"]
69
+ )
70
+
71
+ @property
72
+ def kind(self):
73
+ return "array"
74
+
75
+ @property
76
+ def dtype(self):
77
+ return self._dtype
78
+
79
+ @property
80
+ def shape(self):
81
+ return self._shape
82
+
83
+ @property
84
+ def data_index(self):
85
+ return self._data_index
86
+
87
+ @staticmethod
88
+ def make_definition(name,
89
+ detector,
90
+ data_index,
91
+ dtype,
92
+ shape,
93
+ tango_url,
94
+ acq_id=None,
95
+ saving={},
96
+ info={}) -> StreamDefinition:
97
+ info = info.copy()
98
+ info["plugin"] = "mosca"
99
+ info["detector"] = detector
100
+ info["data_index"] = data_index
101
+ info["dtype"] = np.dtype(dtype).name
102
+ info["shape"] = shape
103
+ mosca_info = info.setdefault("mosca_info", {})
104
+
105
+ mosca_info["server_url"] = tango_url
106
+ mosca_info["acq_id"] = acq_id
107
+ mosca_info["protocol_version"] = MoscaStream.PROTOCOL_VERSION
108
+
109
+ if saving:
110
+ saving_keys = {
111
+ "file_path",
112
+ "data_path",
113
+ "file_format",
114
+ "file_prefix",
115
+ "points_per_file",
116
+ }
117
+ missing_keys = saving_keys - saving.keys()
118
+ extra_keys = saving.keys() - saving_keys
119
+ if missing_keys:
120
+ raise ValueError(
121
+ f"The following keys are missing from 'saving' dict: {missing_keys}"
122
+ )
123
+ if extra_keys:
124
+ raise ValueError(
125
+ f"The following keys are not expected in 'saving' dict: {extra_keys}"
126
+ )
127
+
128
+ mosca_info.update(saving)
129
+
130
+ return StreamDefinition(name, info, JsonStreamEncoder())
131
+
132
+ @property
133
+ def plugin(self):
134
+ return "mosca"
135
+
136
+ def __len__(self):
137
+ return self._reader.n_available_points
138
+
139
+ def __getitem__(self, key):
140
+ if self._last_read_index == -1:
141
+ self._update()
142
+
143
+ stop = None
144
+ if isinstance(key, int):
145
+ if key == -1:
146
+ return self.get_last_live_spectra()
147
+ start = key
148
+ stop = key + 1
149
+ elif isinstance(key, slice):
150
+ if key.step not in (1, None):
151
+ raise RuntimeError(f"slice.step not in (1, None)")
152
+ start = 0 if key.start is None else key.start
153
+ stop = self._last_read_index + 1 if key.stop in (-1, None) else key.stop
154
+ else:
155
+ raise TypeError(f"Invalid type for arg key: {type(key)}.")
156
+
157
+ data = self._read(start, stop)
158
+ return data
159
+
160
+ def _update(self):
161
+ last_event = self.event_stream[-1]
162
+ self._last_read_index = last_event["last_read_index"]
163
+ self._last_saved_index = last_event["last_saved_index"]
164
+
165
+ def _read(self, start, stop):
166
+ if stop - 1 <= self._last_saved_index:
167
+ data = self._get_from_files(start, stop)
168
+ elif start < self._last_saved_index:
169
+ file_data = self._get_from_files(start, self._last_saved_index + 1)
170
+ server_data = self._reader.read_spectra(self._last_saved_index + 1, stop)[self.data_index]
171
+ data = np.concatenate((file_data, server_data), axis=0)
172
+ else:
173
+ data = self._reader.read_spectra(start, stop)[self.data_index]
174
+ return data
175
+
176
+ def _get_from_files(self, start, stop):
177
+ n_points = stop - start
178
+
179
+ data = np.zeros((n_points, self.shape[-1]), self.dtype)
180
+
181
+ assert start < stop
182
+ data_path = f"{self._data_path}/spectra"
183
+
184
+ filenames, dset_idx = self._idx_to_files(start, stop)
185
+
186
+ data_idx = np.add.accumulate(np.diff(dset_idx, axis=1)[:, 0])
187
+ data_idx = np.concatenate(([0], data_idx))
188
+
189
+ for f_idx, filename in enumerate(filenames):
190
+ dset_0, dset_1 = dset_idx[f_idx]
191
+ data_0, data_1 = data_idx[f_idx:f_idx + 2]
192
+ with h5py.File(filename, "r") as h5f:
193
+ data[data_0:data_1, :] = h5f[data_path][self.data_index, dset_0:dset_1, :]
194
+
195
+ return data
196
+
197
+ def _idx_to_files(self, start, stop):
198
+ """ Given a range of points [start .. stop[, returns
199
+ a list of files containing those points,
200
+ along with the indices of those points within the files.
201
+
202
+ Returns three objects:
203
+ - ordered list of N file names
204
+ - ordered list of N 2-tuples, dataset indices range within each file """
205
+
206
+ assert start < stop
207
+ assert start >= 0
208
+
209
+ ppf = self._points_per_file
210
+ basename = os.path.join(self._file_path, f"{self._file_prefix}spectra_")
211
+
212
+ # file indices, points interval within the returned data
213
+ file_idx, data_idx = np.unique(np.arange(start, stop) // ppf,
214
+ return_index=True)
215
+ filenames = [f"{basename}{f_idx:04d}.h5" for f_idx in file_idx]
216
+
217
+ n_points = stop - start
218
+ dset_idx = np.zeros((len(filenames), 2), dtype=int)
219
+
220
+ def _last(n):
221
+ if n >= ppf:
222
+ return ppf
223
+ return n
224
+
225
+ dset_idx[0][:] = [start % ppf, _last((start % ppf) + n_points)]
226
+ for i, idx in enumerate(data_idx[1:], start=1):
227
+ dset_idx[i][:] = [0, _last(n_points - idx)]
228
+
229
+ return filenames, dset_idx
230
+
231
+ def get_references(self, start, stop):
232
+ if self._last_read_index == -1:
233
+ self._update()
234
+
235
+ data_path = f"{self._data_path}/spectra_{self._detector:02d}"
236
+ files, dset_idx = self._idx_to_files(start, stop)
237
+
238
+ references = [SpectraReference(file, data_path, idx)
239
+ for file, indices in zip(files, dset_idx)
240
+ for idx in range(indices[0], indices[1])]
241
+
242
+ return references
243
+
244
+ def _need_last_only(self, last_only):
245
+ # mosca uses json stream as a status,
246
+ # last one is the only valuable status
247
+ # forcing True
248
+ return True
249
+
250
+ def _build_view_from_events(self, index, events, last_only):
251
+ # print("===> EVENT", index, events, last_only)
252
+ lri = events.data[-1]["last_read_index"]
253
+ lsi = events.data[-1]["last_saved_index"]
254
+ # n_spec = len(self)
255
+ if lri < index:
256
+ raise EmptyViewException
257
+
258
+ if last_only:
259
+ start = lri
260
+ else:
261
+ start = index
262
+ self._last_read_index = lri
263
+ self._last_saved_index = lsi
264
+ return MoscaView(self, start, lri + 1)
265
+
266
+ def get_last_live_spectra(self):
267
+ print("R1", self.data_index)
268
+ if self._last_read_index == -1:
269
+ self._update()
270
+ index = self._last_read_index
271
+ data = self._reader.read_spectra(index, self._last_read_index + 1)[self.data_index, 0]
272
+ return SpectraData(array=data, frame_id=index)
273
+
274
+
275
+ class MoscaView(BaseView):
276
+ def __init__(self, stream, start, stop):
277
+ self._client = stream
278
+ self._view_range = range(start, stop)
279
+
280
+ @property
281
+ def index(self):
282
+ return self._view_range.start
283
+
284
+ def __len__(self):
285
+ return len(self._view_range)
286
+
287
+ def get_data(self, start=None, stop=None):
288
+ trimmed_range = self._view_range[start:stop]
289
+ return self._client[trimmed_range.start : trimmed_range.stop]
290
+
291
+ def get_references(
292
+ self, start=None, stop=None
293
+ ) -> SpectraReference | list[SpectraReference]:
294
+ trimmed_range = self._view_range[start:stop]
295
+ return self._client.get_references(
296
+ trimmed_range.start, trimmed_range.stop
297
+ )
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.5
2
+ Name: blissdata-mosca
3
+ Version: 2.0.1
4
+ Requires-Python: <3.13,>=3.10
5
+ Requires-Dist: blissdata
6
+ Requires-Dist: h5py
7
+ Requires-Dist: mosca-client
8
+ Requires-Dist: numpy
@@ -0,0 +1,6 @@
1
+ blissdata_mosca/__init__.py,sha256=rzK469i4fMLhpiGpm-kXwUCvn0XIQ-58s7Iwgo8jn8c,89
2
+ blissdata_mosca/stream.py,sha256=O9ZcU-hnibtS-vPkHCKIB4rHwlH8QZ7JhmJyrF9LaGU,9447
3
+ blissdata_mosca-2.0.1.dist-info/METADATA,sha256=71ebA0nElu2x0_XYJHLZcfGmQi1BN3sS_gwpTBaJxZs,183
4
+ blissdata_mosca-2.0.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
5
+ blissdata_mosca-2.0.1.dist-info/entry_points.txt,sha256=OVeoUjILiUEzwW__6kdtUlCnhRt56QfEBRtTi74FQ64,36
6
+ blissdata_mosca-2.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [blissdata]
2
+ mosca = blissdata_mosca