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.
Files changed (54) hide show
  1. ewoksid02/__init__.py +0 -0
  2. ewoksid02/ocl/__init__.py +0 -0
  3. ewoksid02/resources/__init__.py +8 -0
  4. ewoksid02/resources/saxs_loop.json +96 -0
  5. ewoksid02/resources/template_saxs.yaml +37 -0
  6. ewoksid02/scripts/__init__.py +0 -0
  7. ewoksid02/scripts/__main__.py +70 -0
  8. ewoksid02/scripts/parsers.py +224 -0
  9. ewoksid02/scripts/saxs/__init__.py +0 -0
  10. ewoksid02/scripts/saxs/main.py +255 -0
  11. ewoksid02/scripts/saxs/slurm_python_post_script.py +3 -0
  12. ewoksid02/scripts/saxs/slurm_python_pre_script.py +5 -0
  13. ewoksid02/scripts/utils.py +21 -0
  14. ewoksid02/scripts/xpcs/__init__.py +0 -0
  15. ewoksid02/scripts/xpcs/__main__.py +3 -0
  16. ewoksid02/tasks/__init__.py +7 -0
  17. ewoksid02/tasks/averagetask.py +179 -0
  18. ewoksid02/tasks/azimuthaltask.py +272 -0
  19. ewoksid02/tasks/cavingtask.py +170 -0
  20. ewoksid02/tasks/dahuprocessingtask.py +71 -0
  21. ewoksid02/tasks/end.py +35 -0
  22. ewoksid02/tasks/id02processingtask.py +2582 -0
  23. ewoksid02/tasks/looptask.py +672 -0
  24. ewoksid02/tasks/metadatatask.py +879 -0
  25. ewoksid02/tasks/normalizationtask.py +204 -0
  26. ewoksid02/tasks/scalerstask.py +46 -0
  27. ewoksid02/tasks/secondaryscatteringtask.py +159 -0
  28. ewoksid02/tasks/sumtask.py +45 -0
  29. ewoksid02/tests/__init__.py +3 -0
  30. ewoksid02/tests/conftest.py +639 -0
  31. ewoksid02/tests/debug.py +64 -0
  32. ewoksid02/tests/test_2scat_node.py +119 -0
  33. ewoksid02/tests/test_ave_node.py +106 -0
  34. ewoksid02/tests/test_azim_node.py +89 -0
  35. ewoksid02/tests/test_cave_node.py +118 -0
  36. ewoksid02/tests/test_norm_node.py +190 -0
  37. ewoksid02/tests/test_saxs.py +69 -0
  38. ewoksid02/tests/test_sumtask.py +10 -0
  39. ewoksid02/tests/utils.py +514 -0
  40. ewoksid02/utils/__init__.py +22 -0
  41. ewoksid02/utils/average.py +158 -0
  42. ewoksid02/utils/blissdata.py +1157 -0
  43. ewoksid02/utils/caving.py +851 -0
  44. ewoksid02/utils/cupyutils.py +42 -0
  45. ewoksid02/utils/io.py +722 -0
  46. ewoksid02/utils/normalization.py +804 -0
  47. ewoksid02/utils/pyfai.py +424 -0
  48. ewoksid02/utils/secondaryscattering.py +597 -0
  49. ewoksid02-0.1.0.dist-info/METADATA +76 -0
  50. ewoksid02-0.1.0.dist-info/RECORD +54 -0
  51. ewoksid02-0.1.0.dist-info/WHEEL +5 -0
  52. ewoksid02-0.1.0.dist-info/entry_points.txt +5 -0
  53. ewoksid02-0.1.0.dist-info/licenses/LICENSE.md +20 -0
  54. 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
+ )