ewoksid02 0.1.0__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.
- ewoksid02/__init__.py +0 -0
- ewoksid02/ocl/__init__.py +0 -0
- ewoksid02/resources/__init__.py +8 -0
- ewoksid02/resources/saxs_loop.json +96 -0
- ewoksid02/resources/template_saxs.yaml +37 -0
- ewoksid02/scripts/__init__.py +0 -0
- ewoksid02/scripts/__main__.py +70 -0
- ewoksid02/scripts/parsers.py +224 -0
- ewoksid02/scripts/saxs/__init__.py +0 -0
- ewoksid02/scripts/saxs/main.py +255 -0
- ewoksid02/scripts/saxs/slurm_python_post_script.py +3 -0
- ewoksid02/scripts/saxs/slurm_python_pre_script.py +5 -0
- ewoksid02/scripts/utils.py +21 -0
- ewoksid02/scripts/xpcs/__init__.py +0 -0
- ewoksid02/scripts/xpcs/__main__.py +3 -0
- ewoksid02/tasks/__init__.py +7 -0
- ewoksid02/tasks/averagetask.py +179 -0
- ewoksid02/tasks/azimuthaltask.py +272 -0
- ewoksid02/tasks/cavingtask.py +170 -0
- ewoksid02/tasks/dahuprocessingtask.py +71 -0
- ewoksid02/tasks/end.py +35 -0
- ewoksid02/tasks/id02processingtask.py +2582 -0
- ewoksid02/tasks/looptask.py +672 -0
- ewoksid02/tasks/metadatatask.py +879 -0
- ewoksid02/tasks/normalizationtask.py +204 -0
- ewoksid02/tasks/scalerstask.py +46 -0
- ewoksid02/tasks/secondaryscatteringtask.py +159 -0
- ewoksid02/tasks/sumtask.py +45 -0
- ewoksid02/tests/__init__.py +3 -0
- ewoksid02/tests/conftest.py +639 -0
- ewoksid02/tests/debug.py +64 -0
- ewoksid02/tests/test_2scat_node.py +119 -0
- ewoksid02/tests/test_ave_node.py +106 -0
- ewoksid02/tests/test_azim_node.py +89 -0
- ewoksid02/tests/test_cave_node.py +118 -0
- ewoksid02/tests/test_norm_node.py +190 -0
- ewoksid02/tests/test_saxs.py +69 -0
- ewoksid02/tests/test_sumtask.py +10 -0
- ewoksid02/tests/utils.py +514 -0
- ewoksid02/utils/__init__.py +22 -0
- ewoksid02/utils/average.py +158 -0
- ewoksid02/utils/blissdata.py +1157 -0
- ewoksid02/utils/caving.py +851 -0
- ewoksid02/utils/cupyutils.py +42 -0
- ewoksid02/utils/io.py +722 -0
- ewoksid02/utils/normalization.py +804 -0
- ewoksid02/utils/pyfai.py +424 -0
- ewoksid02/utils/secondaryscattering.py +597 -0
- ewoksid02-0.1.0.dist-info/METADATA +76 -0
- ewoksid02-0.1.0.dist-info/RECORD +54 -0
- ewoksid02-0.1.0.dist-info/WHEEL +5 -0
- ewoksid02-0.1.0.dist-info/entry_points.txt +5 -0
- ewoksid02-0.1.0.dist-info/licenses/LICENSE.md +20 -0
- ewoksid02-0.1.0.dist-info/top_level.txt +1 -0
ewoksid02/utils/io.py
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from typing import Dict, List, Optional, Union
|
|
6
|
+
from silx.io.h5py_utils import open_item as open_item_silx
|
|
7
|
+
from silx.utils.retry import RetryTimeoutError
|
|
8
|
+
from pyFAI.utils.mathutil import binning as binning_tool
|
|
9
|
+
from pyFAI.average import average_dark
|
|
10
|
+
|
|
11
|
+
import fabio
|
|
12
|
+
import h5py
|
|
13
|
+
import hdf5plugin # noqa
|
|
14
|
+
import numpy
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import cupy
|
|
19
|
+
except ImportError:
|
|
20
|
+
CUPY_AVAILABLE = False
|
|
21
|
+
else:
|
|
22
|
+
CUPY_AVAILABLE = True
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
KEY_PIXEL_SIZE_1 = "PSize_1"
|
|
27
|
+
KEY_PIXEL_SIZE_2 = "PSize_2"
|
|
28
|
+
KEY_BINNING_1 = "BSize_1"
|
|
29
|
+
KEY_BINNING_2 = "BSize_2"
|
|
30
|
+
KEY_WAVELENGTH = "WaveLength"
|
|
31
|
+
KEY_SAMPLEDETECTOR_DISTANCE = "SampleDistance"
|
|
32
|
+
KEY_NORMALIZATION_FACTOR = "NormalizationFactor"
|
|
33
|
+
KEY_DETECTOR_MASK_FOLDER = "DetectorMaskFilePath"
|
|
34
|
+
KEY_DETECTOR_MASK_FILE = "DetectorMaskFileName"
|
|
35
|
+
KEY_BEAMSTOP_MASK_FOLDER = "MaskFilePath"
|
|
36
|
+
KEY_BEAMSTOP_MASK_FILE = "MaskFileName"
|
|
37
|
+
KEY_POLARIZATION_FACTOR = "polarization_factor"
|
|
38
|
+
KEY_POLARIZATION_AXIS = "polarization_axis_offset"
|
|
39
|
+
KEY_VARIANCE_FORMULA = "variance_formula"
|
|
40
|
+
KEY_WINDOW_ROI_SIZE = "WindowRoiSize"
|
|
41
|
+
KEY_DARK_FOLDER = "DarkFilePath"
|
|
42
|
+
KEY_DARK_FILE = "DarkFileName"
|
|
43
|
+
KEY_FLAT_FOLDER = "FlatfieldFilePath"
|
|
44
|
+
KEY_FLAT_FILE = "FlatfieldFileName"
|
|
45
|
+
KEY_WINDOW_FOLDER = "WindowFilePath"
|
|
46
|
+
KEY_WINDOW_FILE = "WindowFileName"
|
|
47
|
+
KEY_DUMMY = "Dummy"
|
|
48
|
+
KEY_DELTA_DUMMY = "DDummy"
|
|
49
|
+
KEY_CENTER_1 = "Center_1"
|
|
50
|
+
KEY_CENTER_2 = "Center_2"
|
|
51
|
+
KEY_NPT2_RAD = "npt2_rad"
|
|
52
|
+
KEY_NPT2_AZIM = "npt2_azim"
|
|
53
|
+
KEY_UNIT = "unit"
|
|
54
|
+
KEY_TITLEEXTENSION = "TitleExtension"
|
|
55
|
+
KEY_ALGORITHM_NORMALIZATION = "NormAlgorithm"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_isotime(forceTime: Optional[float] = None) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Get the current time as an ISO8601 string.
|
|
61
|
+
|
|
62
|
+
Inputs:
|
|
63
|
+
- forceTime (Optional[float], optional): Enforce a given time (current by default). Defaults to None.
|
|
64
|
+
Outputs:
|
|
65
|
+
- str: The current time as an ISO8601 string.
|
|
66
|
+
"""
|
|
67
|
+
if forceTime is None:
|
|
68
|
+
forceTime = time.time()
|
|
69
|
+
localtime = time.localtime(forceTime)
|
|
70
|
+
gmtime = time.gmtime(forceTime)
|
|
71
|
+
tz_h = localtime.tm_hour - gmtime.tm_hour
|
|
72
|
+
tz_m = localtime.tm_min - gmtime.tm_min
|
|
73
|
+
return "%s%+03i:%02i" % (time.strftime("%Y-%m-%dT%H:%M:%S", localtime), tz_h, tz_m)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def refactor_stream_name_raw(stream_name: str, cut_name: bool = False):
|
|
77
|
+
if "roi_counters" in stream_name:
|
|
78
|
+
lst = stream_name.split(":")
|
|
79
|
+
return f"{lst[0]}_{lst[-1]}"
|
|
80
|
+
if not cut_name:
|
|
81
|
+
return "_".join(re.split(":|_", stream_name))
|
|
82
|
+
stream_name = stream_name.split(":")[-1]
|
|
83
|
+
return "_".join(re.split(":|_", stream_name))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def refactor_stream_name_interpreted(stream_name: str):
|
|
87
|
+
return "_".join(re.split(":|_", stream_name))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_titleextension_template(template: str):
|
|
91
|
+
# First, find the curly brackets
|
|
92
|
+
pattern = r"\{(.*?)\}"
|
|
93
|
+
template_parsed = template
|
|
94
|
+
template_info = []
|
|
95
|
+
for template_element in re.findall(pattern, template):
|
|
96
|
+
# Clear empty spaces
|
|
97
|
+
template_element_parsed = template_element.replace(" ", "")
|
|
98
|
+
try:
|
|
99
|
+
stream_name, format_spec = template_element_parsed.split(":")
|
|
100
|
+
template_info.append(
|
|
101
|
+
{"stream_name": stream_name, "format_spec": format_spec}
|
|
102
|
+
)
|
|
103
|
+
template_parsed = template_parsed.replace(
|
|
104
|
+
template_element, f"{stream_name}:{format_spec}"
|
|
105
|
+
)
|
|
106
|
+
except Exception:
|
|
107
|
+
logger.error(
|
|
108
|
+
f"{template_element} in {template} is not a valid format for TitleExtension"
|
|
109
|
+
)
|
|
110
|
+
continue
|
|
111
|
+
return template_parsed, template_info
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_from_headers(
|
|
115
|
+
key: str,
|
|
116
|
+
headers: Optional[Dict[str, Union[str, float]]] = None,
|
|
117
|
+
metadata_file_group: Optional[h5py.Group] = None,
|
|
118
|
+
to_integer: bool = False,
|
|
119
|
+
) -> Optional[Union[str, float, int]]:
|
|
120
|
+
"""
|
|
121
|
+
Retrieve a header value from the header object (for online processing) or from an HDF5 group (for offline processing).
|
|
122
|
+
|
|
123
|
+
Inputs:
|
|
124
|
+
- key (str): The key to retrieve.
|
|
125
|
+
- headers (Optional[Dict[str, Union[str, float]]], optional): The header object. Defaults to None.
|
|
126
|
+
- metadata_file_group (Optional[h5py.Group], optional): The HDF5 group. Defaults to None.
|
|
127
|
+
- to_integer (bool, optional): Whether to convert the value to an integer. Defaults to False.
|
|
128
|
+
Outputs:
|
|
129
|
+
- Optional[Union[str, float, int]]: The retrieved value or None if not found.
|
|
130
|
+
"""
|
|
131
|
+
value = None
|
|
132
|
+
|
|
133
|
+
if headers:
|
|
134
|
+
# Retrieve directly from the header object
|
|
135
|
+
if key not in headers:
|
|
136
|
+
logger.warning(f"Key {key} is not in headers")
|
|
137
|
+
return
|
|
138
|
+
value = headers[key]
|
|
139
|
+
elif metadata_file_group:
|
|
140
|
+
# Retrieve from a group in the metadata file
|
|
141
|
+
if key not in metadata_file_group:
|
|
142
|
+
logger.warning(f"Key {key} not in {metadata_file_group}")
|
|
143
|
+
return
|
|
144
|
+
value = metadata_file_group[key][()]
|
|
145
|
+
else:
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
if isinstance(value, bytes):
|
|
149
|
+
value = value.decode("UTF-8")
|
|
150
|
+
try:
|
|
151
|
+
value = float(value)
|
|
152
|
+
if to_integer:
|
|
153
|
+
return int(value)
|
|
154
|
+
return value
|
|
155
|
+
except Exception:
|
|
156
|
+
return value
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_value_from_file(filename: str, h5path: str, key: str, to_integer: bool = False):
|
|
160
|
+
params = {
|
|
161
|
+
"filename": filename,
|
|
162
|
+
"name": h5path,
|
|
163
|
+
"retry_timeout": 0.1,
|
|
164
|
+
}
|
|
165
|
+
try:
|
|
166
|
+
with open_item_silx(**params) as grp:
|
|
167
|
+
if grp is None:
|
|
168
|
+
return
|
|
169
|
+
if key not in grp:
|
|
170
|
+
return
|
|
171
|
+
value = grp[key][()]
|
|
172
|
+
except RetryTimeoutError:
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
if isinstance(value, bytes):
|
|
176
|
+
value = value.decode("UTF-8")
|
|
177
|
+
try:
|
|
178
|
+
value = float(value)
|
|
179
|
+
if to_integer:
|
|
180
|
+
return int(value)
|
|
181
|
+
return value
|
|
182
|
+
except Exception:
|
|
183
|
+
return value
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_flat_filename(**kwargs):
|
|
187
|
+
"""Returns the whole filename for the flat field correction from the headers."""
|
|
188
|
+
flat_folder = get_from_headers(key=KEY_FLAT_FOLDER, **kwargs)
|
|
189
|
+
flat_file = get_from_headers(key=KEY_FLAT_FILE, **kwargs)
|
|
190
|
+
if flat_folder is None or flat_file is None:
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
flat_filename = os.path.join(flat_folder, flat_file)
|
|
194
|
+
if not os.path.exists(flat_filename):
|
|
195
|
+
return
|
|
196
|
+
return flat_filename
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_mask_detector_filename(**kwargs):
|
|
200
|
+
"""Returns the whole filename for the detector gaps mask from the headers."""
|
|
201
|
+
mask_folder = get_from_headers(key=KEY_DETECTOR_MASK_FOLDER, **kwargs)
|
|
202
|
+
mask_file = get_from_headers(key=KEY_DETECTOR_MASK_FILE, **kwargs)
|
|
203
|
+
if mask_folder is None or mask_file is None:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
mask_filename = os.path.join(mask_folder, mask_file)
|
|
207
|
+
if not os.path.exists(mask_filename):
|
|
208
|
+
return
|
|
209
|
+
return mask_filename
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_mask_beamstop_filename(**kwargs):
|
|
213
|
+
"""Returns the whole filename for the beamstop mask from the headers."""
|
|
214
|
+
mask_folder = get_from_headers(key=KEY_BEAMSTOP_MASK_FOLDER, **kwargs)
|
|
215
|
+
mask_file = get_from_headers(key=KEY_BEAMSTOP_MASK_FILE, **kwargs)
|
|
216
|
+
if mask_folder is None or mask_file is None:
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
mask_filename = os.path.join(mask_folder, mask_file)
|
|
220
|
+
if not os.path.exists(mask_filename):
|
|
221
|
+
return
|
|
222
|
+
return mask_filename
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def get_dark_filename(**kwargs):
|
|
226
|
+
"""Returns the whole filename for the dark current correction from the headers."""
|
|
227
|
+
dark_folder = get_from_headers(key=KEY_DARK_FOLDER, **kwargs)
|
|
228
|
+
dark_file = get_from_headers(key=KEY_DARK_FILE, **kwargs)
|
|
229
|
+
if dark_folder is None or dark_file is None:
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
dark_filename = os.path.join(dark_folder, dark_file)
|
|
233
|
+
if not os.path.exists(dark_filename):
|
|
234
|
+
return
|
|
235
|
+
return dark_filename
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def load_data(
|
|
239
|
+
filename: Union[str, List[str]],
|
|
240
|
+
binning: tuple = (1, 1),
|
|
241
|
+
data_signal_shape: tuple = None,
|
|
242
|
+
use_cupy: bool = False,
|
|
243
|
+
datatype: str = None,
|
|
244
|
+
dark_filter: str = "median",
|
|
245
|
+
dark_filter_quantil_lower: int = 0,
|
|
246
|
+
dark_filter_quantil_upper: int = 1,
|
|
247
|
+
**kwargs,
|
|
248
|
+
) -> Optional[numpy.ndarray]:
|
|
249
|
+
"""
|
|
250
|
+
Load data from a file or a list of files.
|
|
251
|
+
|
|
252
|
+
Inputs:
|
|
253
|
+
- filename (Union[str, List[str]]): The filename or list of filenames.
|
|
254
|
+
- binning (tuple): binning of the data signal
|
|
255
|
+
- data_signal_shape (tuple): shape of the data array (2-dimensional)
|
|
256
|
+
- use_cupy (bool): if True, returns a cupy.asarray
|
|
257
|
+
- datatype (str): format of the imported array, if None, datatype is respected
|
|
258
|
+
Outputs:
|
|
259
|
+
- Optional[numpy.ndarray]: The loaded data or None if the file does not exist.
|
|
260
|
+
"""
|
|
261
|
+
if filename is None:
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
# Import data
|
|
265
|
+
data = None
|
|
266
|
+
if isinstance(filename, (tuple, list)):
|
|
267
|
+
for _, file in enumerate(filename):
|
|
268
|
+
data_ = _load_data(file)
|
|
269
|
+
if data_ is None:
|
|
270
|
+
continue
|
|
271
|
+
if data is None:
|
|
272
|
+
data = data_
|
|
273
|
+
else:
|
|
274
|
+
data += data_
|
|
275
|
+
elif isinstance(filename, str):
|
|
276
|
+
data = _load_data(filename)
|
|
277
|
+
|
|
278
|
+
if data is None:
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
# Set datatype
|
|
282
|
+
if datatype is not None and data.dtype != datatype:
|
|
283
|
+
data = data.astype(datatype, copy=False)
|
|
284
|
+
|
|
285
|
+
# Dataset filter
|
|
286
|
+
if data.ndim > 2:
|
|
287
|
+
if dark_filter.startswith("quantil"):
|
|
288
|
+
data = average_dark(
|
|
289
|
+
data,
|
|
290
|
+
center_method=dark_filter,
|
|
291
|
+
quantiles=(dark_filter_quantil_lower, dark_filter_quantil_upper),
|
|
292
|
+
)
|
|
293
|
+
else:
|
|
294
|
+
data = average_dark(data, center_method=dark_filter)
|
|
295
|
+
|
|
296
|
+
# Binning unification
|
|
297
|
+
if data_signal_shape and data.shape != data_signal_shape:
|
|
298
|
+
binning_additional_data = _get_data_binning(filename=filename)
|
|
299
|
+
binning_relative = (
|
|
300
|
+
int(binning[0] / binning_additional_data[0]),
|
|
301
|
+
int(binning[1] / binning_additional_data[1]),
|
|
302
|
+
)
|
|
303
|
+
data_binned = binning_tool(data, binning_relative, norm=False)
|
|
304
|
+
if data_binned.shape != data_signal_shape:
|
|
305
|
+
raise ValueError(
|
|
306
|
+
f"Data shape after binning {binning} from {filename} does not match the expected shape: {data_binned.shape} != {data_signal_shape}"
|
|
307
|
+
)
|
|
308
|
+
data = data_binned
|
|
309
|
+
elif data_signal_shape is None and binning == (1, 1):
|
|
310
|
+
...
|
|
311
|
+
|
|
312
|
+
if use_cupy and CUPY_AVAILABLE:
|
|
313
|
+
return cupy.asarray(data)
|
|
314
|
+
return data
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _load_data(filename: str) -> Optional[numpy.ndarray]:
|
|
318
|
+
"""
|
|
319
|
+
Load data from a single file.
|
|
320
|
+
|
|
321
|
+
Inputs:
|
|
322
|
+
- filename (str): The filename.
|
|
323
|
+
Outputs:
|
|
324
|
+
- Optional[numpy.ndarray]: The loaded data or None if the file does not exist.
|
|
325
|
+
"""
|
|
326
|
+
if not os.path.exists(filename):
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
if filename.endswith(".h5"):
|
|
330
|
+
filename += "::/entry_0000/measurement/data"
|
|
331
|
+
try:
|
|
332
|
+
with fabio.open(filename) as f:
|
|
333
|
+
if f.nframes == 1:
|
|
334
|
+
return f.data
|
|
335
|
+
else:
|
|
336
|
+
return f.dataset[:]
|
|
337
|
+
except Exception as e:
|
|
338
|
+
logger.error(f"File {filename} could not be open with fabio: {e}")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _get_data_binning(filename: str):
|
|
342
|
+
"""
|
|
343
|
+
Load data from a single file.
|
|
344
|
+
|
|
345
|
+
Inputs:
|
|
346
|
+
- filename (str): The filename.
|
|
347
|
+
Outputs:
|
|
348
|
+
- Optional[numpy.ndarray]: The loaded data or None if the file does not exist.
|
|
349
|
+
"""
|
|
350
|
+
if not os.path.exists(filename):
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
if filename.endswith(".h5"):
|
|
354
|
+
filename += "::/entry_0000/measurement/data"
|
|
355
|
+
try:
|
|
356
|
+
with fabio.open(filename) as f:
|
|
357
|
+
b1 = f.header.get("Bsize_1")
|
|
358
|
+
b1_ = f.header.get("BSize_1")
|
|
359
|
+
b2 = f.header.get("Bsize_2")
|
|
360
|
+
b2_ = f.header.get("BSize_2")
|
|
361
|
+
b1 = b1 or b1_
|
|
362
|
+
b2 = b2 or b2_
|
|
363
|
+
return (int(b1), int(b2))
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.error(f"File {filename} could not be open with fabio: {e}")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_headers(
|
|
369
|
+
headers: Optional[Dict[str, Union[str, float]]] = None,
|
|
370
|
+
metadata_file_group: Optional[h5py.Group] = None,
|
|
371
|
+
) -> Optional[Dict[str, Union[str, float]]]:
|
|
372
|
+
"""
|
|
373
|
+
Retrieve headers from a dictionary or an HDF5 group.
|
|
374
|
+
|
|
375
|
+
Inputs:
|
|
376
|
+
- headers (Optional[Dict[str, Union[str, float]]], optional): The header dictionary. Defaults to None.
|
|
377
|
+
- metadata_file_group (Optional[h5py.Group], optional): The HDF5 group. Defaults to None.
|
|
378
|
+
Outputs:
|
|
379
|
+
- Optional[Dict[str, Union[str, float]]]: The headers or None if not found.
|
|
380
|
+
"""
|
|
381
|
+
if headers:
|
|
382
|
+
return headers
|
|
383
|
+
elif metadata_file_group:
|
|
384
|
+
headers = {}
|
|
385
|
+
for key in metadata_file_group:
|
|
386
|
+
value = metadata_file_group[key][()]
|
|
387
|
+
if isinstance(value, bytes):
|
|
388
|
+
value = value.decode("UTF-8")
|
|
389
|
+
headers[key] = value
|
|
390
|
+
return headers
|
|
391
|
+
else:
|
|
392
|
+
return {}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def get_free_memory(device_id):
|
|
396
|
+
"""Retrieves the available memory on a GPU device"""
|
|
397
|
+
if not CUPY_AVAILABLE:
|
|
398
|
+
logger.warning("Cupy is not available.")
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
with cupy.cuda.Device(device_id):
|
|
402
|
+
free_mem, total_mem = cupy.cuda.runtime.memGetInfo()
|
|
403
|
+
return free_mem
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_best_gpu():
|
|
407
|
+
"""Decides the best GPU in terms of memory available"""
|
|
408
|
+
if not CUPY_AVAILABLE:
|
|
409
|
+
logger.warning("Cupy is not available.")
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
best_device = None
|
|
413
|
+
max_free_memory = 0
|
|
414
|
+
|
|
415
|
+
for device_id in range(cupy.cuda.runtime.getDeviceCount()):
|
|
416
|
+
free_memory = get_free_memory(device_id)
|
|
417
|
+
if free_memory is not None and free_memory > max_free_memory:
|
|
418
|
+
max_free_memory = free_memory
|
|
419
|
+
best_device = device_id
|
|
420
|
+
|
|
421
|
+
return best_device
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def use_best_gpu():
|
|
425
|
+
"""
|
|
426
|
+
Set the best available GPU for cupy operations.
|
|
427
|
+
"""
|
|
428
|
+
best_device = get_best_gpu()
|
|
429
|
+
if best_device is not None:
|
|
430
|
+
cupy.cuda.Device(best_device).use()
|
|
431
|
+
logger.info(f"Using GPU {best_device} with the most free memory.")
|
|
432
|
+
else:
|
|
433
|
+
logger.warning("No suitable GPU found or cupy is not available.")
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def serialize_h5py_task(h5py_group: h5py.Group) -> dict:
|
|
437
|
+
"""
|
|
438
|
+
Recursively convert an h5py Group or File into a nested Python dictionary.
|
|
439
|
+
Datasets become numpy arrays or scalars.
|
|
440
|
+
"""
|
|
441
|
+
result = {}
|
|
442
|
+
for key, item in h5py_group.items():
|
|
443
|
+
if isinstance(item, h5py.Dataset):
|
|
444
|
+
data = item[()]
|
|
445
|
+
if isinstance(data, bytes):
|
|
446
|
+
data = data.decode()
|
|
447
|
+
# Convert 0-d arrays to scalars for readability
|
|
448
|
+
if isinstance(data, numpy.ndarray) and data.shape == ():
|
|
449
|
+
data = data.item()
|
|
450
|
+
result[key] = data
|
|
451
|
+
elif isinstance(item, h5py.Group):
|
|
452
|
+
result[key] = serialize_h5py_task(item)
|
|
453
|
+
else:
|
|
454
|
+
result[key] = str(item)
|
|
455
|
+
return result
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def deserialize_h5py_task(h5dict: dict, h5py_parent: h5py.Group) -> None:
|
|
459
|
+
for key, value in h5dict.items():
|
|
460
|
+
if isinstance(value, dict):
|
|
461
|
+
child_group = h5py_parent.create_group(name=key)
|
|
462
|
+
child_group.attrs["NX_class"] = "NXcollection"
|
|
463
|
+
deserialize_h5py_task(h5dict=value, h5py_parent=child_group)
|
|
464
|
+
else:
|
|
465
|
+
try:
|
|
466
|
+
h5py_parent.create_dataset(
|
|
467
|
+
name=key,
|
|
468
|
+
data=value,
|
|
469
|
+
)
|
|
470
|
+
except Exception as e:
|
|
471
|
+
print(e, key, value)
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def get_persistent_array_mask(
|
|
476
|
+
filename_mask: str,
|
|
477
|
+
data_signal_shape: tuple,
|
|
478
|
+
datatype: str = "bool",
|
|
479
|
+
binning: tuple = (1, 1),
|
|
480
|
+
use_cupy: bool = False,
|
|
481
|
+
**kwargs,
|
|
482
|
+
):
|
|
483
|
+
if filename_mask and os.path.exists(filename_mask):
|
|
484
|
+
mtime_mask = os.path.getmtime(filename_mask)
|
|
485
|
+
else:
|
|
486
|
+
mtime_mask = None
|
|
487
|
+
filename_mask = None
|
|
488
|
+
return _get_persistent_array(
|
|
489
|
+
filename=filename_mask,
|
|
490
|
+
mtime=mtime_mask,
|
|
491
|
+
datatype=datatype,
|
|
492
|
+
data_signal_shape=data_signal_shape,
|
|
493
|
+
binning=binning,
|
|
494
|
+
use_cupy=use_cupy,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def get_persistent_array_flat(
|
|
499
|
+
filename_flat: str,
|
|
500
|
+
data_signal_shape: tuple,
|
|
501
|
+
datatype: str = None,
|
|
502
|
+
binning: tuple = (1, 1),
|
|
503
|
+
dummy: int = None,
|
|
504
|
+
delta_dummy: float = None,
|
|
505
|
+
filename_mask: str = None,
|
|
506
|
+
use_cupy: bool = False,
|
|
507
|
+
**kwargs,
|
|
508
|
+
):
|
|
509
|
+
if filename_flat and os.path.exists(filename_flat):
|
|
510
|
+
mtime_flat = os.path.getmtime(filename_flat)
|
|
511
|
+
else:
|
|
512
|
+
mtime_flat = None
|
|
513
|
+
filename_flat = None
|
|
514
|
+
|
|
515
|
+
if filename_mask and os.path.exists(filename_mask):
|
|
516
|
+
mtime_mask = os.path.getmtime(filename_mask)
|
|
517
|
+
else:
|
|
518
|
+
mtime_mask = None
|
|
519
|
+
filename_mask = None
|
|
520
|
+
|
|
521
|
+
return _get_persistent_array_flat(
|
|
522
|
+
filename_flat=filename_flat,
|
|
523
|
+
mtime_flat=mtime_flat,
|
|
524
|
+
data_signal_shape=data_signal_shape,
|
|
525
|
+
datatype=datatype,
|
|
526
|
+
binning=binning,
|
|
527
|
+
dummy=dummy,
|
|
528
|
+
delta_dummy=delta_dummy,
|
|
529
|
+
filename_mask=filename_mask,
|
|
530
|
+
mtime_mask=mtime_mask,
|
|
531
|
+
use_cupy=use_cupy,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
@lru_cache(maxsize=5)
|
|
536
|
+
def _get_persistent_array_flat(
|
|
537
|
+
filename_flat: str,
|
|
538
|
+
mtime_flat: float,
|
|
539
|
+
**kwargs,
|
|
540
|
+
):
|
|
541
|
+
if not filename_flat:
|
|
542
|
+
return
|
|
543
|
+
logger.info(
|
|
544
|
+
f"No cache hit. Creating new flat-field array from {filename_flat=}, {mtime_flat=}, {kwargs}"
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
array_flat = load_data(
|
|
548
|
+
filename=filename_flat,
|
|
549
|
+
**kwargs,
|
|
550
|
+
)
|
|
551
|
+
if array_flat is None:
|
|
552
|
+
return
|
|
553
|
+
|
|
554
|
+
array_mask = _get_persistent_array(
|
|
555
|
+
filename=kwargs.get("filename_mask"),
|
|
556
|
+
mtime=kwargs.get("mtime_mask"),
|
|
557
|
+
datatype="bool",
|
|
558
|
+
data_signal_shape=kwargs.get("data_signal_shape"),
|
|
559
|
+
binning=kwargs.get("binning"),
|
|
560
|
+
use_cupy=kwargs.get("use_cupy"),
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
dummy = kwargs.get("dummy")
|
|
564
|
+
delta_dummy = kwargs.get("delta_dummy")
|
|
565
|
+
if array_mask is not None and dummy is not None and delta_dummy is not None:
|
|
566
|
+
if delta_dummy == 0:
|
|
567
|
+
array_mask |= array_flat == dummy
|
|
568
|
+
else:
|
|
569
|
+
array_mask |= abs(array_flat - dummy) < delta_dummy
|
|
570
|
+
return array_flat
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def get_persistent_array_dark(
|
|
574
|
+
filename_dark: str,
|
|
575
|
+
data_signal_shape: tuple,
|
|
576
|
+
datatype: str = None,
|
|
577
|
+
binning: tuple = (1, 1),
|
|
578
|
+
dummy: int = None,
|
|
579
|
+
delta_dummy: float = None,
|
|
580
|
+
filename_mask: str = None,
|
|
581
|
+
dark_filter: str = None,
|
|
582
|
+
dark_filter_quantil_lower: float = 0.1,
|
|
583
|
+
dark_filter_quantil_upper: float = 0.9,
|
|
584
|
+
use_cupy: bool = False,
|
|
585
|
+
**kwargs,
|
|
586
|
+
):
|
|
587
|
+
if filename_dark and os.path.exists(filename_dark):
|
|
588
|
+
mtime_dark = os.path.getmtime(filename_dark)
|
|
589
|
+
else:
|
|
590
|
+
mtime_dark = None
|
|
591
|
+
filename_dark = None
|
|
592
|
+
|
|
593
|
+
if filename_mask and os.path.exists(filename_mask):
|
|
594
|
+
mtime_mask = os.path.getmtime(filename_mask)
|
|
595
|
+
else:
|
|
596
|
+
mtime_mask = None
|
|
597
|
+
filename_mask = None
|
|
598
|
+
|
|
599
|
+
return _get_persistent_array_dark(
|
|
600
|
+
filename_dark=filename_dark,
|
|
601
|
+
mtime_dark=mtime_dark,
|
|
602
|
+
data_signal_shape=data_signal_shape,
|
|
603
|
+
datatype=datatype,
|
|
604
|
+
binning=binning,
|
|
605
|
+
dummy=dummy,
|
|
606
|
+
delta_dummy=delta_dummy,
|
|
607
|
+
filename_mask=filename_mask,
|
|
608
|
+
mtime_mask=mtime_mask,
|
|
609
|
+
dark_filter=dark_filter,
|
|
610
|
+
dark_filter_quantil_lower=dark_filter_quantil_lower,
|
|
611
|
+
dark_filter_quantil_upper=dark_filter_quantil_upper,
|
|
612
|
+
use_cupy=use_cupy,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@lru_cache(maxsize=5)
|
|
617
|
+
def _get_persistent_array_dark(
|
|
618
|
+
filename_dark: str,
|
|
619
|
+
mtime_dark: float,
|
|
620
|
+
**kwargs,
|
|
621
|
+
):
|
|
622
|
+
if not filename_dark:
|
|
623
|
+
return
|
|
624
|
+
logger.info(
|
|
625
|
+
f"No cache hit. Creating new dark-current array from {filename_dark=}, {mtime_dark}, {kwargs}"
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
array_dark = load_data(
|
|
629
|
+
filename=filename_dark,
|
|
630
|
+
**kwargs,
|
|
631
|
+
)
|
|
632
|
+
if array_dark is None:
|
|
633
|
+
return
|
|
634
|
+
|
|
635
|
+
array_mask = _get_persistent_array(
|
|
636
|
+
filename=kwargs.get("filename_mask"),
|
|
637
|
+
mtime=kwargs.get("mtime_mask"),
|
|
638
|
+
datatype="bool",
|
|
639
|
+
data_signal_shape=kwargs.get("data_signal_shape"),
|
|
640
|
+
binning=kwargs.get("binning"),
|
|
641
|
+
use_cupy=kwargs.get("use_cupy"),
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
dummy = kwargs.get("dummy")
|
|
645
|
+
delta_dummy = kwargs.get("delta_dummy")
|
|
646
|
+
if array_mask is not None and dummy is not None and delta_dummy is not None:
|
|
647
|
+
if delta_dummy == 0:
|
|
648
|
+
array_mask |= array_dark == dummy
|
|
649
|
+
else:
|
|
650
|
+
array_mask |= abs(array_dark - dummy) < delta_dummy
|
|
651
|
+
return array_dark
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def get_persistent_array_window_wagon(
|
|
655
|
+
filename_window_wagon: str,
|
|
656
|
+
data_signal_shape: tuple,
|
|
657
|
+
datatype: str = None,
|
|
658
|
+
binning: tuple = (1, 1),
|
|
659
|
+
use_cupy: bool = False,
|
|
660
|
+
):
|
|
661
|
+
if filename_window_wagon and os.path.exists(filename_window_wagon):
|
|
662
|
+
window_mtime = os.path.getmtime(filename_window_wagon)
|
|
663
|
+
else:
|
|
664
|
+
window_mtime = None
|
|
665
|
+
filename_window_wagon = None
|
|
666
|
+
|
|
667
|
+
return _get_persistent_array(
|
|
668
|
+
filename=filename_window_wagon,
|
|
669
|
+
mtime=window_mtime,
|
|
670
|
+
data_signal_shape=data_signal_shape,
|
|
671
|
+
datatype=datatype,
|
|
672
|
+
binning=binning,
|
|
673
|
+
use_cupy=use_cupy,
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def get_dataset_signal_from_processed_file(
|
|
678
|
+
filename: str,
|
|
679
|
+
index_range: tuple = None,
|
|
680
|
+
):
|
|
681
|
+
if filename and os.path.exists(filename):
|
|
682
|
+
with h5py.File(filename, "r") as f:
|
|
683
|
+
nxprocess_path = "entry_0000/PyFAI"
|
|
684
|
+
|
|
685
|
+
if nxprocess_path not in f:
|
|
686
|
+
logger.error(f"{filename} does not contain the {nxprocess_path} format")
|
|
687
|
+
return
|
|
688
|
+
nxprocess_grp = f[nxprocess_path]
|
|
689
|
+
|
|
690
|
+
nxdata_name = next(
|
|
691
|
+
(name for name in nxprocess_grp if "result_" in name), None
|
|
692
|
+
)
|
|
693
|
+
if nxdata_name is None:
|
|
694
|
+
logger.error(f"There is no result_xxx group in {nxprocess_path}")
|
|
695
|
+
return
|
|
696
|
+
|
|
697
|
+
nxdata_grp = nxprocess_grp[nxdata_name]
|
|
698
|
+
if "data" not in nxdata_grp:
|
|
699
|
+
logger.error(
|
|
700
|
+
f"There is no data dataset in {nxprocess_path}/{nxdata_name}"
|
|
701
|
+
)
|
|
702
|
+
return
|
|
703
|
+
if index_range is not None:
|
|
704
|
+
return nxdata_grp["data"][index_range[0] : index_range[-1]]
|
|
705
|
+
return nxdata_grp["data"][:]
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
@lru_cache(maxsize=10)
|
|
709
|
+
def _get_persistent_array(
|
|
710
|
+
filename: str,
|
|
711
|
+
mtime: float,
|
|
712
|
+
**kwargs,
|
|
713
|
+
):
|
|
714
|
+
if not filename:
|
|
715
|
+
return
|
|
716
|
+
logger.info(
|
|
717
|
+
f"No cache hit. Creating new array from {filename=}, {mtime=}, {kwargs}"
|
|
718
|
+
)
|
|
719
|
+
return load_data(
|
|
720
|
+
filename=filename,
|
|
721
|
+
**kwargs,
|
|
722
|
+
)
|