blissdata-mosca 2.0.2__tar.gz
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,112 @@
|
|
|
1
|
+
pixi.lock
|
|
2
|
+
|
|
3
|
+
# Byte-compiled / optimized / DLL files
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
|
|
8
|
+
# C extensions
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Ignore any hidden files from the project root
|
|
12
|
+
/.*
|
|
13
|
+
!.gitlab-ci.yml
|
|
14
|
+
|
|
15
|
+
conda-bld/
|
|
16
|
+
|
|
17
|
+
# Distribution / packaging
|
|
18
|
+
.Python
|
|
19
|
+
env/
|
|
20
|
+
build/
|
|
21
|
+
develop-eggs/
|
|
22
|
+
dist/
|
|
23
|
+
downloads/
|
|
24
|
+
eggs/
|
|
25
|
+
.eggs/
|
|
26
|
+
lib/
|
|
27
|
+
lib64/
|
|
28
|
+
parts/
|
|
29
|
+
sdist/
|
|
30
|
+
var/
|
|
31
|
+
wheels/
|
|
32
|
+
*.egg-info/
|
|
33
|
+
.installed.cfg
|
|
34
|
+
*.egg
|
|
35
|
+
|
|
36
|
+
# PyInstaller
|
|
37
|
+
# Usually these files are written by a python script from a template
|
|
38
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
39
|
+
*.manifest
|
|
40
|
+
*.spec
|
|
41
|
+
|
|
42
|
+
# Installer logs
|
|
43
|
+
pip-log.txt
|
|
44
|
+
pip-delete-this-directory.txt
|
|
45
|
+
|
|
46
|
+
# Unit test / coverage reports
|
|
47
|
+
htmlcov*/
|
|
48
|
+
.tox/
|
|
49
|
+
.coverage
|
|
50
|
+
.coverage.*
|
|
51
|
+
.cache
|
|
52
|
+
nosetests.xml
|
|
53
|
+
coverage.xml
|
|
54
|
+
report.xml
|
|
55
|
+
*.cover
|
|
56
|
+
.hypothesis/
|
|
57
|
+
|
|
58
|
+
# Translations
|
|
59
|
+
*.mo
|
|
60
|
+
*.pot
|
|
61
|
+
|
|
62
|
+
# Django stuff:
|
|
63
|
+
*.log
|
|
64
|
+
local_settings.py
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# pyenv
|
|
83
|
+
.python-version
|
|
84
|
+
|
|
85
|
+
# celery beat schedule file
|
|
86
|
+
celerybeat-schedule
|
|
87
|
+
|
|
88
|
+
# SageMath parsed files
|
|
89
|
+
*.sage.py
|
|
90
|
+
|
|
91
|
+
# dotenv
|
|
92
|
+
.env
|
|
93
|
+
|
|
94
|
+
# virtualenv
|
|
95
|
+
.venv
|
|
96
|
+
venv/
|
|
97
|
+
ENV/
|
|
98
|
+
|
|
99
|
+
# Spyder project settings
|
|
100
|
+
.spyderproject
|
|
101
|
+
.spyproject
|
|
102
|
+
|
|
103
|
+
# Rope project settings
|
|
104
|
+
.ropeproject
|
|
105
|
+
|
|
106
|
+
# mkdocs documentation
|
|
107
|
+
doc/site/
|
|
108
|
+
|
|
109
|
+
# mypy
|
|
110
|
+
.mypy_cache/
|
|
111
|
+
|
|
112
|
+
*.mprg
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project.entry-points.blissdata]
|
|
2
|
+
mosca = "blissdata_mosca"
|
|
3
|
+
|
|
4
|
+
[project]
|
|
5
|
+
name = "blissdata-mosca"
|
|
6
|
+
requires-python = ">=3.10,<3.13"
|
|
7
|
+
version = "2.0.2"
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
"numpy",
|
|
11
|
+
"h5py",
|
|
12
|
+
"blissdata",
|
|
13
|
+
"mosca_client"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
18
|
+
requires = ["hatchling"]
|
|
19
|
+
|
|
20
|
+
[tool.pixi.package]
|
|
21
|
+
name = "blissdata-mosca"
|
|
22
|
+
version = { workspace = true }
|
|
23
|
+
|
|
24
|
+
[tool.pixi.package.build]
|
|
25
|
+
backend = { name = "pixi-build-python", version = "0.*" }
|
|
26
|
+
|
|
27
|
+
[tool.pixi.package.host-dependencies]
|
|
28
|
+
hatchling = "==1.26.3"
|
|
29
|
+
|
|
30
|
+
[tool.pixi.pypi-dependencies]
|
|
31
|
+
blissdata_mosca = { path = ".", editable = true }
|
|
@@ -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: int,
|
|
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
|
+
)
|