ChessAnalysisPipeline 0.0.13__py3-none-any.whl → 0.0.15__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.
Potentially problematic release.
This version of ChessAnalysisPipeline might be problematic. Click here for more details.
- CHAP/__init__.py +1 -1
- CHAP/common/__init__.py +10 -0
- CHAP/common/models/map.py +389 -124
- CHAP/common/processor.py +1494 -59
- CHAP/common/reader.py +180 -8
- CHAP/common/writer.py +192 -15
- CHAP/edd/__init__.py +12 -3
- CHAP/edd/models.py +868 -451
- CHAP/edd/processor.py +2383 -462
- CHAP/edd/reader.py +672 -0
- CHAP/edd/utils.py +906 -172
- CHAP/foxden/__init__.py +6 -0
- CHAP/foxden/processor.py +42 -0
- CHAP/foxden/writer.py +65 -0
- CHAP/pipeline.py +35 -3
- CHAP/runner.py +43 -16
- CHAP/tomo/models.py +15 -5
- CHAP/tomo/processor.py +871 -761
- CHAP/utils/__init__.py +1 -0
- CHAP/utils/fit.py +1339 -1309
- CHAP/utils/general.py +568 -105
- CHAP/utils/models.py +567 -0
- CHAP/utils/scanparsers.py +460 -77
- ChessAnalysisPipeline-0.0.15.dist-info/LICENSE +60 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/WHEEL +1 -1
- ChessAnalysisPipeline-0.0.13.dist-info/LICENSE +0 -21
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/top_level.txt +0 -0
CHAP/utils/scanparsers.py
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
# -*- coding: utf-8 -*-
|
|
4
4
|
|
|
5
|
+
"""Parsing data from certain CHESS SPEC scans is supported by a family
|
|
6
|
+
of classes derived from the base class, `ScanParser` (defined
|
|
7
|
+
below). An instance of `ScanParser` represents a single SPEC scan --
|
|
8
|
+
each instance is initialized with the name of a specific spec file and
|
|
9
|
+
integer scan number. Access to certain data collected by that scan
|
|
10
|
+
(counter data, positioner values, scan shape, detector data, etc.) are
|
|
11
|
+
made available through the properties and methods of that object.
|
|
12
|
+
|
|
13
|
+
`ScanParser` is just an incomplete abstraction -- one should not
|
|
14
|
+
declare or work with an instance of `ScanParser` directly. Instead,
|
|
15
|
+
one must find the appropriate concrete subclass to use for the
|
|
16
|
+
particular type of scan one wishes to parse, then declare an instance
|
|
17
|
+
of that specific class to begin accessing data from that scan.
|
|
18
|
+
|
|
19
|
+
Basic usage examples:
|
|
20
|
+
1. Print out the position of a the SPEC positioner motor with mnemonic
|
|
21
|
+
`'mne0'` for a SAXS/WAXS scan collected at FMB:
|
|
22
|
+
```python
|
|
23
|
+
from CHAP.utils.scanparsers import FMBSAXSWAXSScanParser
|
|
24
|
+
sp = FMBSAXSWAXSScanParser('/path/to/fmb/saxswaxs/spec/file', 1)
|
|
25
|
+
print(sp.get_spec_positioner_value('mne0'))
|
|
26
|
+
```
|
|
27
|
+
1. Store all the detector data collected by the detector with prefix
|
|
28
|
+
`'det'` over a rotation series collected at SMB in the variable
|
|
29
|
+
`data`:
|
|
30
|
+
```python
|
|
31
|
+
from CHAP.utils.scanparsers import SMBRotationScanParser
|
|
32
|
+
sp = SMBRotationScanParser('/path/to/smb/rotation/spec/file', 1)
|
|
33
|
+
data = sp.get_detector_data('det')
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
5
37
|
# System modules
|
|
6
38
|
from csv import reader
|
|
7
39
|
from fnmatch import filter as fnmatch_filter
|
|
@@ -15,6 +47,9 @@ import numpy as np
|
|
|
15
47
|
from pyspec.file.spec import FileSpec
|
|
16
48
|
from pyspec.file.tiff import TiffFile
|
|
17
49
|
|
|
50
|
+
@cache
|
|
51
|
+
def filespec(spec_file_name):
|
|
52
|
+
return FileSpec(spec_file_name)
|
|
18
53
|
|
|
19
54
|
class ScanParser:
|
|
20
55
|
"""Partial implementation of a class representing a SPEC scan and
|
|
@@ -55,6 +90,7 @@ class ScanParser:
|
|
|
55
90
|
and len(scanparser.spec_args) == 5):
|
|
56
91
|
self._rams4_args = scanparser.spec_args
|
|
57
92
|
|
|
93
|
+
def __repr_(self):
|
|
58
94
|
return (f'{self.__class__.__name__}'
|
|
59
95
|
f'({self.spec_file_name}, {self.scan_number}) '
|
|
60
96
|
f'-- {self.spec_command}')
|
|
@@ -64,7 +100,7 @@ class ScanParser:
|
|
|
64
100
|
# NB This FileSpec instance is not stored as a private
|
|
65
101
|
# attribute because it cannot be pickled (and therefore could
|
|
66
102
|
# cause problems for parallel code that uses ScanParsers).
|
|
67
|
-
return
|
|
103
|
+
return filespec(self.spec_file_name)
|
|
68
104
|
|
|
69
105
|
@property
|
|
70
106
|
def scan_path(self):
|
|
@@ -300,6 +336,7 @@ class SMBScanParser(ScanParser):
|
|
|
300
336
|
|
|
301
337
|
self._pars = None
|
|
302
338
|
self._par_file_pattern = f'*-*-{self.scan_name}'
|
|
339
|
+
self._par_file = None
|
|
303
340
|
|
|
304
341
|
def get_scan_name(self):
|
|
305
342
|
return os.path.basename(self.scan_path)
|
|
@@ -323,6 +360,10 @@ class SMBScanParser(ScanParser):
|
|
|
323
360
|
json_files = fnmatch_filter(
|
|
324
361
|
os.listdir(self.scan_path),
|
|
325
362
|
f'{self._par_file_pattern}.json')
|
|
363
|
+
if not json_files:
|
|
364
|
+
json_files = fnmatch_filter(
|
|
365
|
+
os.listdir(self.scan_path),
|
|
366
|
+
f'*.json')
|
|
326
367
|
if len(json_files) != 1:
|
|
327
368
|
raise RuntimeError(f'{self.scan_title}: cannot find the '
|
|
328
369
|
'.json file to decode the .par file')
|
|
@@ -336,15 +377,20 @@ class SMBScanParser(ScanParser):
|
|
|
336
377
|
raise RuntimeError(f'{self.scan_title}: cannot find scan pars '
|
|
337
378
|
'without a "SCAN_N" column in the par file')
|
|
338
379
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
380
|
+
if getattr(self, '_par_file'):
|
|
381
|
+
par_file = self._par_file
|
|
382
|
+
else:
|
|
383
|
+
par_files = fnmatch_filter(
|
|
384
|
+
os.listdir(self.scan_path),
|
|
385
|
+
f'{self._par_file_pattern}.par')
|
|
386
|
+
if len(par_files) != 1:
|
|
387
|
+
raise RuntimeError(f'{self.scan_title}: cannot find the .par '
|
|
388
|
+
'file for this scan directory')
|
|
389
|
+
par_file = os.path.join(self.scan_path, par_files[0])
|
|
390
|
+
self._par_file = par_file
|
|
345
391
|
par_dict = None
|
|
346
|
-
with open(
|
|
347
|
-
par_reader = reader(
|
|
392
|
+
with open(par_file) as f:
|
|
393
|
+
par_reader = reader(f, delimiter=' ')
|
|
348
394
|
for row in par_reader:
|
|
349
395
|
if len(row) == len(par_col_names):
|
|
350
396
|
row_scann = int(row[scann_col_idx])
|
|
@@ -362,22 +408,21 @@ class SMBScanParser(ScanParser):
|
|
|
362
408
|
except:
|
|
363
409
|
pass
|
|
364
410
|
par_dict[par_col_name] = par_value
|
|
365
|
-
|
|
366
411
|
if par_dict is None:
|
|
367
412
|
raise RuntimeError(f'{self.scan_title}: could not find scan pars '
|
|
368
413
|
f'for scan number {self.scan_number}')
|
|
369
414
|
return par_dict
|
|
370
415
|
|
|
371
416
|
def get_counter_gain(self, counter_name):
|
|
372
|
-
"""Return the gain of a counter as recorded in the
|
|
373
|
-
a scan in a SPEC file converted to nA/V.
|
|
417
|
+
"""Return the gain of a counter as recorded in the user lines
|
|
418
|
+
of a scan in a SPEC file converted to nA/V.
|
|
374
419
|
|
|
375
420
|
:param counter_name: the name of the counter
|
|
376
421
|
:type counter_name: str
|
|
377
422
|
:rtype: str
|
|
378
423
|
"""
|
|
379
424
|
counter_gain = None
|
|
380
|
-
for comment in self.spec_scan.comments:
|
|
425
|
+
for comment in self.spec_scan.comments + self.spec_scan.user_lines:
|
|
381
426
|
match = re.search(
|
|
382
427
|
f'{counter_name} gain: ' # start of counter gain comments
|
|
383
428
|
'(?P<gain_value>\d+) ' # gain numerical value
|
|
@@ -388,6 +433,7 @@ class SMBScanParser(ScanParser):
|
|
|
388
433
|
gain_scalar = 1 if unit_prefix == 'n' \
|
|
389
434
|
else 1e3 if unit_prefix == 'u' else 1e6
|
|
390
435
|
counter_gain = f'{float(match["gain_value"])*gain_scalar} nA/V'
|
|
436
|
+
break
|
|
391
437
|
|
|
392
438
|
if counter_gain is None:
|
|
393
439
|
raise RuntimeError(f'{self.scan_title}: could not get gain for '
|
|
@@ -404,6 +450,7 @@ class LinearScanParser(ScanParser):
|
|
|
404
450
|
|
|
405
451
|
self._spec_scan_motor_mnes = None
|
|
406
452
|
self._spec_scan_motor_vals = None
|
|
453
|
+
self._spec_scan_motor_vals_relative = None
|
|
407
454
|
self._spec_scan_shape = None
|
|
408
455
|
self._spec_scan_dwell = None
|
|
409
456
|
|
|
@@ -416,9 +463,17 @@ class LinearScanParser(ScanParser):
|
|
|
416
463
|
@property
|
|
417
464
|
def spec_scan_motor_vals(self):
|
|
418
465
|
if self._spec_scan_motor_vals is None:
|
|
419
|
-
self._spec_scan_motor_vals = self.get_spec_scan_motor_vals(
|
|
466
|
+
self._spec_scan_motor_vals = self.get_spec_scan_motor_vals(
|
|
467
|
+
relative=False)
|
|
420
468
|
return self._spec_scan_motor_vals
|
|
421
469
|
|
|
470
|
+
@property
|
|
471
|
+
def spec_scan_motor_vals_relative(self):
|
|
472
|
+
if self._spec_scan_motor_vals_relative is None:
|
|
473
|
+
self._spec_scan_motor_vals_relative = \
|
|
474
|
+
self.get_spec_scan_motor_vals(relative=True)
|
|
475
|
+
return self._spec_scan_motor_vals_relative
|
|
476
|
+
|
|
422
477
|
@property
|
|
423
478
|
def spec_scan_shape(self):
|
|
424
479
|
if self._spec_scan_shape is None:
|
|
@@ -442,13 +497,17 @@ class LinearScanParser(ScanParser):
|
|
|
442
497
|
"""
|
|
443
498
|
raise NotImplementedError
|
|
444
499
|
|
|
445
|
-
def get_spec_scan_motor_vals(self):
|
|
500
|
+
def get_spec_scan_motor_vals(self, relative=False):
|
|
446
501
|
"""Return the values visited by each of the scanned motors. If
|
|
447
502
|
there is more than one motor scanned (in a "flymesh" scan, for
|
|
448
503
|
example), the order of motor values in the returned tuple will
|
|
449
504
|
go from the fastest moving motor's values first to the slowest
|
|
450
505
|
moving motor's values last.
|
|
451
506
|
|
|
507
|
+
:param relative: If `True`, return scanned motor positions
|
|
508
|
+
*relative* to the scanned motors' positions before the scan
|
|
509
|
+
started, defaults to False.
|
|
510
|
+
:type relative: bool, optional
|
|
452
511
|
:rtype: tuple
|
|
453
512
|
"""
|
|
454
513
|
raise NotImplementedError
|
|
@@ -531,7 +590,7 @@ class FMBLinearScanParser(LinearScanParser, FMBScanParser):
|
|
|
531
590
|
"""
|
|
532
591
|
|
|
533
592
|
def get_spec_scan_motor_mnes(self):
|
|
534
|
-
if self.spec_macro
|
|
593
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
535
594
|
m1_mne = self.spec_args[0]
|
|
536
595
|
try:
|
|
537
596
|
# Try post-summer-2022 format
|
|
@@ -543,15 +602,15 @@ class FMBLinearScanParser(LinearScanParser, FMBScanParser):
|
|
|
543
602
|
m2_mne_i = 5
|
|
544
603
|
m2_mne = self.spec_args[m2_mne_i]
|
|
545
604
|
return (m1_mne, m2_mne)
|
|
546
|
-
if self.spec_macro in ('flyscan', 'ascan'):
|
|
605
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
547
606
|
return (self.spec_args[0],)
|
|
548
607
|
if self.spec_macro in ('tseries', 'loopscan'):
|
|
549
608
|
return ('Time',)
|
|
550
609
|
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
551
610
|
f'for scans of type {self.spec_macro}')
|
|
552
611
|
|
|
553
|
-
def get_spec_scan_motor_vals(self):
|
|
554
|
-
if self.spec_macro
|
|
612
|
+
def get_spec_scan_motor_vals(self, relative=False):
|
|
613
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
555
614
|
m1_start = float(self.spec_args[1])
|
|
556
615
|
m1_end = float(self.spec_args[2])
|
|
557
616
|
m1_npt = int(self.spec_args[3]) + 1
|
|
@@ -572,19 +631,27 @@ class FMBLinearScanParser(LinearScanParser, FMBScanParser):
|
|
|
572
631
|
m2_npt = int(self.spec_args[m2_nint_i]) + 1
|
|
573
632
|
fast_mot_vals = np.linspace(m1_start, m1_end, m1_npt)
|
|
574
633
|
slow_mot_vals = np.linspace(m2_start, m2_end, m2_npt)
|
|
634
|
+
if relative:
|
|
635
|
+
fast_mot_vals -= self.get_spec_positioner_value(
|
|
636
|
+
self.spec_scan_motor_mnes[0])
|
|
637
|
+
slow_mot_vals -= self.get_spec_positioner_value(
|
|
638
|
+
self.spec_scan_motor_mnes[1])
|
|
575
639
|
return (fast_mot_vals, slow_mot_vals)
|
|
576
|
-
if self.spec_macro in ('flyscan', 'ascan'):
|
|
640
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
577
641
|
mot_vals = np.linspace(float(self.spec_args[1]),
|
|
578
642
|
float(self.spec_args[2]),
|
|
579
643
|
int(self.spec_args[3])+1)
|
|
644
|
+
if relative:
|
|
645
|
+
mot_vals -= self.get_spec_positioner_value(
|
|
646
|
+
self.spec_scan_motor_mnes[0])
|
|
580
647
|
return (mot_vals,)
|
|
581
648
|
if self.spec_macro in ('tseries', 'loopscan'):
|
|
582
|
-
return self.spec_scan.data[:,0]
|
|
649
|
+
return (self.spec_scan.data[:,0],)
|
|
583
650
|
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
584
651
|
f'for scans of type {self.spec_macro}')
|
|
585
652
|
|
|
586
653
|
def get_spec_scan_shape(self):
|
|
587
|
-
if self.spec_macro
|
|
654
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
588
655
|
fast_mot_npts = int(self.spec_args[3]) + 1
|
|
589
656
|
try:
|
|
590
657
|
# Try post-summer-2022 format
|
|
@@ -596,16 +663,16 @@ class FMBLinearScanParser(LinearScanParser, FMBScanParser):
|
|
|
596
663
|
m2_nint_i = 8
|
|
597
664
|
slow_mot_npts = int(self.spec_args[m2_nint_i]) + 1
|
|
598
665
|
return (fast_mot_npts, slow_mot_npts)
|
|
599
|
-
if self.spec_macro in ('flyscan', 'ascan'):
|
|
666
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
600
667
|
mot_npts = int(self.spec_args[3])+1
|
|
601
668
|
return (mot_npts,)
|
|
602
669
|
if self.spec_macro in ('tseries', 'loopscan'):
|
|
603
|
-
return len(np.array(self.spec_scan.data[:,0]))
|
|
670
|
+
return (len(np.array(self.spec_scan.data[:,0])),)
|
|
604
671
|
raise RuntimeError(f'{self.scan_title}: cannot determine scan shape '
|
|
605
672
|
f'for scans of type {self.spec_macro}')
|
|
606
673
|
|
|
607
674
|
def get_spec_scan_dwell(self):
|
|
608
|
-
if self.macro
|
|
675
|
+
if self.macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
609
676
|
try:
|
|
610
677
|
# Try post-summer-2022 format
|
|
611
678
|
dwell = float(self.spec_args[4])
|
|
@@ -613,7 +680,7 @@ class FMBLinearScanParser(LinearScanParser, FMBScanParser):
|
|
|
613
680
|
# Accommodate pre-summer-2022 format
|
|
614
681
|
dwell = float(self.spec_args[8])
|
|
615
682
|
return dwell
|
|
616
|
-
if self.spec_macro in ('flyscan', 'ascan'):
|
|
683
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
617
684
|
return float(self.spec_args[4])
|
|
618
685
|
if self.spec_macro in ('tseries', 'loopscan'):
|
|
619
686
|
return float(self.spec_args[1])
|
|
@@ -656,14 +723,28 @@ class FMBSAXSWAXSScanParser(FMBLinearScanParser):
|
|
|
656
723
|
def get_detector_data_file(self, detector_prefix, scan_step_index:int):
|
|
657
724
|
detector_files = list_fmb_saxswaxs_detector_files(
|
|
658
725
|
self.detector_data_path, detector_prefix)
|
|
659
|
-
|
|
660
|
-
|
|
726
|
+
if len(detector_files) == self.spec_scan_npts:
|
|
727
|
+
return os.path.join(
|
|
728
|
+
self.detector_data_path, detector_files[scan_step_index])
|
|
729
|
+
else:
|
|
730
|
+
scan_step = self.get_scan_step(scan_step_index)
|
|
731
|
+
for f in detector_files:
|
|
732
|
+
filename, _ = os.path.splitext(f)
|
|
733
|
+
file_indices = tuple(
|
|
734
|
+
[int(i) for i in \
|
|
735
|
+
filename.split('_')[-len(self.spec_scan_shape):]])
|
|
736
|
+
if file_indices == scan_step:
|
|
737
|
+
return os.path.join(self.detector_data_path, f)
|
|
738
|
+
raise RuntimeError(
|
|
739
|
+
'Could not find a matching detector data file for detector '
|
|
740
|
+
+ f'{detector_prefix} at scan step index {scan_step_index}')
|
|
661
741
|
|
|
662
742
|
def get_detector_data(self, detector_prefix, scan_step_index:int):
|
|
743
|
+
import fabio
|
|
663
744
|
image_file = self.get_detector_data_file(detector_prefix,
|
|
664
745
|
scan_step_index)
|
|
665
|
-
with
|
|
666
|
-
image_data =
|
|
746
|
+
with fabio.open(image_file) as det_file:
|
|
747
|
+
image_data = det_file.data
|
|
667
748
|
return image_data
|
|
668
749
|
|
|
669
750
|
|
|
@@ -704,7 +785,15 @@ class SMBLinearScanParser(LinearScanParser, SMBScanParser):
|
|
|
704
785
|
"""
|
|
705
786
|
|
|
706
787
|
def get_spec_scan_dwell(self):
|
|
707
|
-
if self.spec_macro in ('flymesh', '
|
|
788
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
789
|
+
try:
|
|
790
|
+
# Try post-summer-2022 format
|
|
791
|
+
dwell = float(self.spec_args[4])
|
|
792
|
+
except:
|
|
793
|
+
# Accommodate pre-summer-2022 format
|
|
794
|
+
dwell = float(self.spec_args[8])
|
|
795
|
+
return dwell
|
|
796
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
708
797
|
return float(self.spec_args[4])
|
|
709
798
|
if self.spec_macro == 'tseries':
|
|
710
799
|
return float(self.spec_args[1])
|
|
@@ -714,55 +803,87 @@ class SMBLinearScanParser(LinearScanParser, SMBScanParser):
|
|
|
714
803
|
f'for scans of type {self.spec_macro}')
|
|
715
804
|
|
|
716
805
|
def get_spec_scan_motor_mnes(self):
|
|
717
|
-
if self.spec_macro
|
|
718
|
-
|
|
719
|
-
|
|
806
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
807
|
+
m1_mne = self.spec_args[0]
|
|
808
|
+
try:
|
|
809
|
+
# Try post-summer-2022 format
|
|
810
|
+
dwell = float(self.spec_args[4])
|
|
811
|
+
except:
|
|
812
|
+
# Accommodate pre-summer-2022 format
|
|
813
|
+
m2_mne_i = 4
|
|
814
|
+
else:
|
|
815
|
+
m2_mne_i = 5
|
|
816
|
+
m2_mne = self.spec_args[m2_mne_i]
|
|
817
|
+
return (m1_mne, m2_mne)
|
|
818
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
720
819
|
return (self.spec_args[0],)
|
|
721
820
|
if self.spec_macro in ('tseries', 'loopscan'):
|
|
722
821
|
return ('Time',)
|
|
723
822
|
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
724
823
|
f'for scans of type {self.spec_macro}')
|
|
725
824
|
|
|
726
|
-
def get_spec_scan_motor_vals(self):
|
|
727
|
-
if self.spec_macro
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
825
|
+
def get_spec_scan_motor_vals(self, relative=False):
|
|
826
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
827
|
+
m1_start = float(self.spec_args[1])
|
|
828
|
+
m1_end = float(self.spec_args[2])
|
|
829
|
+
m1_npt = int(self.spec_args[3]) + 1
|
|
830
|
+
try:
|
|
831
|
+
# Try post-summer-2022 format
|
|
832
|
+
dwell = float(self.spec_args[4])
|
|
833
|
+
except:
|
|
834
|
+
# Accommodate pre-summer-2022 format
|
|
835
|
+
m2_start_i = 5
|
|
836
|
+
m2_end_i = 6
|
|
837
|
+
m2_nint_i = 7
|
|
838
|
+
else:
|
|
839
|
+
m2_start_i = 6
|
|
840
|
+
m2_end_i = 7
|
|
841
|
+
m2_nint_i = 8
|
|
842
|
+
m2_start = float(self.spec_args[m2_start_i])
|
|
843
|
+
m2_end = float(self.spec_args[m2_end_i])
|
|
844
|
+
m2_npt = int(self.spec_args[m2_nint_i]) + 1
|
|
845
|
+
fast_mot_vals = np.linspace(m1_start, m1_end, m1_npt)
|
|
846
|
+
slow_mot_vals = np.linspace(m2_start, m2_end, m2_npt)
|
|
847
|
+
if relative:
|
|
848
|
+
fast_mot_vals -= self.spec_positioner_values[
|
|
849
|
+
self.spec_scan_motor_mnes[0]]
|
|
850
|
+
slow_mot_vals -= self.spec_positioner_values[
|
|
851
|
+
self.spec_scan_motor_mnes[1]]
|
|
734
852
|
return (fast_mot_vals, slow_mot_vals)
|
|
735
|
-
if self.spec_macro in ('flyscan', 'ascan'):
|
|
853
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
736
854
|
mot_vals = np.linspace(float(self.spec_args[1]),
|
|
737
855
|
float(self.spec_args[2]),
|
|
738
856
|
int(self.spec_args[3])+1)
|
|
857
|
+
if relative:
|
|
858
|
+
mot_vals -= self.get_spec_positioner_value(
|
|
859
|
+
self.spec_scan_motor_mnes[0])
|
|
739
860
|
return (mot_vals,)
|
|
740
861
|
if self.spec_macro in ('tseries', 'loopscan'):
|
|
741
|
-
return self.spec_scan.data[:,0]
|
|
862
|
+
return (self.spec_scan.data[:,0],)
|
|
742
863
|
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
743
864
|
f'for scans of type {self.spec_macro}')
|
|
744
865
|
|
|
745
866
|
def get_spec_scan_shape(self):
|
|
746
|
-
if self.spec_macro
|
|
747
|
-
fast_mot_npts = int(self.spec_args[3])+1
|
|
748
|
-
|
|
867
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
868
|
+
fast_mot_npts = int(self.spec_args[3]) + 1
|
|
869
|
+
try:
|
|
870
|
+
# Try post-summer-2022 format
|
|
871
|
+
dwell = float(self.spec_args[4])
|
|
872
|
+
except:
|
|
873
|
+
# Accommodate pre-summer-2022 format
|
|
874
|
+
m2_nint_i = 7
|
|
875
|
+
else:
|
|
876
|
+
m2_nint_i = 8
|
|
877
|
+
slow_mot_npts = int(self.spec_args[m2_nint_i]) + 1
|
|
749
878
|
return (fast_mot_npts, slow_mot_npts)
|
|
750
|
-
if self.spec_macro in ('flyscan', 'ascan'):
|
|
879
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
751
880
|
mot_npts = int(self.spec_args[3])+1
|
|
752
881
|
return (mot_npts,)
|
|
753
882
|
if self.spec_macro in ('tseries', 'loopscan'):
|
|
754
|
-
return len(np.array(self.spec_scan.data[:,0]))
|
|
883
|
+
return (len(np.array(self.spec_scan.data[:,0])),)
|
|
755
884
|
raise RuntimeError(f'{self.scan_title}: cannot determine scan shape '
|
|
756
885
|
f'for scans of type {self.spec_macro}')
|
|
757
886
|
|
|
758
|
-
def get_spec_scan_dwell(self):
|
|
759
|
-
if self.spec_macro == 'flymesh':
|
|
760
|
-
return float(self.spec_args[4])
|
|
761
|
-
if self.spec_macro in ('flyscan', 'ascan'):
|
|
762
|
-
return float(self.spec_args[-1])
|
|
763
|
-
raise RuntimeError(f'{self.scan_title}: cannot determine dwell time '
|
|
764
|
-
f'for scans of type {self.spec_macro}')
|
|
765
|
-
|
|
766
887
|
def get_detector_data_path(self):
|
|
767
888
|
return os.path.join(self.scan_path, str(self.scan_number))
|
|
768
889
|
|
|
@@ -963,12 +1084,14 @@ class SMBRotationScanParser(RotationScanParser, SMBScanParser):
|
|
|
963
1084
|
with the typical tomography setup at SMB.
|
|
964
1085
|
"""
|
|
965
1086
|
|
|
966
|
-
def __init__(self, spec_file_name, scan_number):
|
|
1087
|
+
def __init__(self, spec_file_name, scan_number, par_file=None):
|
|
967
1088
|
self._scan_type = None
|
|
968
1089
|
super().__init__(spec_file_name, scan_number)
|
|
969
1090
|
|
|
970
1091
|
self._katefix = 0 # RV remove when no longer needed
|
|
971
1092
|
self._par_file_pattern = f'id*-*tomo*-{self.scan_name}'
|
|
1093
|
+
if par_file is not None:
|
|
1094
|
+
self._par_file = par_file
|
|
972
1095
|
|
|
973
1096
|
@property
|
|
974
1097
|
def scan_type(self):
|
|
@@ -1038,7 +1161,8 @@ class SMBRotationScanParser(RotationScanParser, SMBScanParser):
|
|
|
1038
1161
|
if os.path.isfile(file_name_full):
|
|
1039
1162
|
return file_name_full
|
|
1040
1163
|
raise RuntimeError(f'{self.scan_title}: could not find detector image '
|
|
1041
|
-
f'file for scan step
|
|
1164
|
+
f'file ({file_name_full}) for scan step '
|
|
1165
|
+
f'({scan_step_index})')
|
|
1042
1166
|
|
|
1043
1167
|
def get_detector_data(self, detector_prefix, scan_step_index=None):
|
|
1044
1168
|
if scan_step_index is None:
|
|
@@ -1090,10 +1214,101 @@ class SMBMCAScanParser(MCAScanParser, SMBLinearScanParser):
|
|
|
1090
1214
|
"""Concrete implementation of a class representing a scan taken
|
|
1091
1215
|
with the typical EDD setup at SMB or FAST.
|
|
1092
1216
|
"""
|
|
1217
|
+
detector_data_formats = ('spec', 'h5')
|
|
1218
|
+
def __init__(self, spec_file_name, scan_number, detector_data_format=None):
|
|
1219
|
+
"""Constructor for SMBMCAScnaParser.
|
|
1220
|
+
|
|
1221
|
+
:param spec_file: Path to scan's SPEC file
|
|
1222
|
+
:type spec_file: str
|
|
1223
|
+
:param scan_number: Number of the SPEC scan
|
|
1224
|
+
:type scan_number: int
|
|
1225
|
+
:param detector_data_format: Format of the MCA data collected,
|
|
1226
|
+
defaults to None
|
|
1227
|
+
:type detector_data_format: Optional[Literal["spec", "h5"]]
|
|
1228
|
+
"""
|
|
1229
|
+
super().__init__(spec_file_name, scan_number)
|
|
1093
1230
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1231
|
+
self.detector_data_format = None
|
|
1232
|
+
if detector_data_format is None:
|
|
1233
|
+
self.init_detector_data_format()
|
|
1234
|
+
else:
|
|
1235
|
+
if detector_data_format.lower() in self.detector_data_formats:
|
|
1236
|
+
self.detector_data_format = detector_data_format.lower()
|
|
1237
|
+
else:
|
|
1238
|
+
raise ValueError(
|
|
1239
|
+
'Unrecognized value for detector_data_format: '
|
|
1240
|
+
+ f'{detector_data_format}. Allowed values are: '
|
|
1241
|
+
+ ', '.join(self.detector_data_formats))
|
|
1242
|
+
|
|
1243
|
+
def get_spec_scan_motor_vals(self, relative=True):
|
|
1244
|
+
if not relative:
|
|
1245
|
+
# The scanned motor's recorded position in the spec.log
|
|
1246
|
+
# file's "#P" lines does not always give the right offset
|
|
1247
|
+
# to use to obtain absolute motor postions from relative
|
|
1248
|
+
# motor positions (or relative from actual). Sometimes,
|
|
1249
|
+
# the labx/y/z/ometotal value from the scan's .par file is
|
|
1250
|
+
# the quantity for the offset that _should_ be used, but
|
|
1251
|
+
# there is currently no consistent way to determine when
|
|
1252
|
+
# to use the labx/y/z/ometotal .par file value and when to
|
|
1253
|
+
# use the spec file "#P" lines value. Because the relative
|
|
1254
|
+
# motor values are the only ones currently used in EDD
|
|
1255
|
+
# workflows, obtain them from relevant values available in
|
|
1256
|
+
# the .par file, and defer implementation for absolute
|
|
1257
|
+
# motor postions to later.
|
|
1258
|
+
return super().get_spec_scan_motor_vals(relative=True)
|
|
1259
|
+
# raise NotImplementedError('Only relative motor values are available.')
|
|
1260
|
+
if self.spec_macro in ('flymesh', 'mesh', 'flydmesh', 'dmesh'):
|
|
1261
|
+
mot_vals_axis0 = np.linspace(self.pars['fly_axis0_start'],
|
|
1262
|
+
self.pars['fly_axis0_end'],
|
|
1263
|
+
self.pars['fly_axis0_npts'])
|
|
1264
|
+
mot_vals_axis1 = np.linspace(self.pars['fly_axis1_start'],
|
|
1265
|
+
self.pars['fly_axis1_end'],
|
|
1266
|
+
self.pars['fly_axis1_npts'])
|
|
1267
|
+
return (mot_vals_axis0, mot_vals_axis1)
|
|
1268
|
+
if self.spec_macro in ('flyscan', 'ascan', 'flydscan', 'dscan'):
|
|
1269
|
+
mot_vals = np.linspace(self.pars['fly_axis0_start'],
|
|
1270
|
+
self.pars['fly_axis0_end'],
|
|
1271
|
+
self.pars['fly_axis0_npts'])
|
|
1272
|
+
return (mot_vals,)
|
|
1273
|
+
if self.spec_macro in ('tseries', 'loopscan'):
|
|
1274
|
+
return (self.spec_scan.data[:,0],)
|
|
1275
|
+
raise RuntimeError(f'{self.scan_title}: cannot determine scan motors '
|
|
1276
|
+
f'for scans of type {self.spec_macro}')
|
|
1277
|
+
|
|
1278
|
+
def init_detector_data_format(self):
|
|
1279
|
+
"""Determine and set a value for the instance variable
|
|
1280
|
+
`detector_data_format` based on the presence / absence of
|
|
1281
|
+
detector data files of different formats conventionally
|
|
1282
|
+
associated with this scan. Also set the corresponding
|
|
1283
|
+
appropriate value for `_detector_data_path`.
|
|
1284
|
+
"""
|
|
1285
|
+
try:
|
|
1286
|
+
self._detector_data_path = self.scan_path
|
|
1287
|
+
detector_file = self.get_detector_data_file_spec()
|
|
1288
|
+
except OSError:
|
|
1289
|
+
try:
|
|
1290
|
+
self._detector_data_path = os.path.join(
|
|
1291
|
+
self.scan_path, str(self.scan_number), 'edd')
|
|
1292
|
+
detector_file = self.get_detector_data_file_h5()
|
|
1293
|
+
except OSError:
|
|
1294
|
+
raise RuntimeError(
|
|
1295
|
+
f"{self.scan_title}: Can't determine detector data format")
|
|
1296
|
+
else:
|
|
1297
|
+
self.detector_data_format = 'h5'
|
|
1298
|
+
else:
|
|
1299
|
+
self.detector_data_format = 'spec'
|
|
1300
|
+
|
|
1301
|
+
def get_detector_data_path(self):
|
|
1302
|
+
raise NotImplementedError
|
|
1303
|
+
|
|
1304
|
+
def get_detector_num_bins(self, element_index=0):
|
|
1305
|
+
if self.detector_data_format == 'spec':
|
|
1306
|
+
return self.get_detector_num_bins_spec()
|
|
1307
|
+
elif self.detector_data_format == 'h5':
|
|
1308
|
+
return self.get_detector_num_bins_h5(element_index)
|
|
1309
|
+
|
|
1310
|
+
def get_detector_num_bins_spec(self):
|
|
1311
|
+
with open(self.get_detector_data_file_spec()) as detector_file:
|
|
1097
1312
|
lines = detector_file.readlines()
|
|
1098
1313
|
for line in lines:
|
|
1099
1314
|
if line.startswith('#@CHANN'):
|
|
@@ -1103,33 +1318,101 @@ class SMBMCAScanParser(MCAScanParser, SMBLinearScanParser):
|
|
|
1103
1318
|
return int(number_saved)
|
|
1104
1319
|
except:
|
|
1105
1320
|
continue
|
|
1106
|
-
raise RuntimeError(f'{self.scan_title}: could not find num_bins
|
|
1107
|
-
f'detector {detector_prefix}')
|
|
1108
|
-
|
|
1109
|
-
def get_detector_data_path(self):
|
|
1110
|
-
return self.scan_path
|
|
1321
|
+
raise RuntimeError(f'{self.scan_title}: could not find num_bins')
|
|
1111
1322
|
|
|
1112
|
-
def
|
|
1323
|
+
def get_detector_num_bins_h5(self, element_index):
|
|
1324
|
+
from h5py import File
|
|
1325
|
+
detector_file = self.get_detector_data_file_h5()
|
|
1326
|
+
with File(detector_file) as h5_file:
|
|
1327
|
+
dset_shape = h5_file['/entry/data/data'].shape
|
|
1328
|
+
return dset_shape[-1]
|
|
1329
|
+
|
|
1330
|
+
def get_detector_data_file(self, scan_step_index=0):
|
|
1331
|
+
if self.detector_data_format == 'spec':
|
|
1332
|
+
return self.get_detector_data_file_spec()
|
|
1333
|
+
elif self.detector_data_format == 'h5':
|
|
1334
|
+
return self.get_detector_data_file_h5(
|
|
1335
|
+
scan_step_index=scan_step_index)
|
|
1336
|
+
|
|
1337
|
+
def get_detector_data_file_spec(self):
|
|
1338
|
+
"""Return the filename (full absolute path) to the file
|
|
1339
|
+
containing spec-formatted MCA data for this scan.
|
|
1340
|
+
"""
|
|
1113
1341
|
file_name = f'spec.log.scan{self.scan_number}.mca1.mca'
|
|
1114
1342
|
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1115
1343
|
if os.path.isfile(file_name_full):
|
|
1116
1344
|
return file_name_full
|
|
1117
|
-
raise
|
|
1118
|
-
|
|
1345
|
+
raise OSError(
|
|
1346
|
+
'{self.scan_title}: could not find detector image file'
|
|
1347
|
+
)
|
|
1348
|
+
|
|
1349
|
+
def get_detector_data_file_h5(self, scan_step_index=0):
|
|
1350
|
+
"""Return the filename (full absolute path) to the file
|
|
1351
|
+
containing h5-formatted MCA data for this scan.
|
|
1352
|
+
|
|
1353
|
+
:param scan_step_index:
|
|
1354
|
+
"""
|
|
1355
|
+
scan_step = self.get_scan_step(scan_step_index)
|
|
1356
|
+
if len(self.spec_scan_shape) == 1:
|
|
1357
|
+
filename_index = 0
|
|
1358
|
+
elif len(self.spec_scan_shape) == 2:
|
|
1359
|
+
scan_step = self.get_scan_step(scan_step_index)
|
|
1360
|
+
filename_index = scan_step[0]
|
|
1361
|
+
else:
|
|
1362
|
+
raise NotImplementedError(
|
|
1363
|
+
'Cannot find detector file for scans with dimension > 2')
|
|
1364
|
+
file_name = list_smb_mca_detector_files_h5(
|
|
1365
|
+
self.detector_data_path)[filename_index]
|
|
1366
|
+
file_name_full = os.path.join(self.detector_data_path, file_name)
|
|
1367
|
+
if os.path.isfile(file_name_full):
|
|
1368
|
+
return file_name_full
|
|
1369
|
+
raise OSError(
|
|
1370
|
+
'{self.scan_title}: could not find detector image file'
|
|
1371
|
+
)
|
|
1119
1372
|
|
|
1120
|
-
|
|
1373
|
+
|
|
1374
|
+
def get_all_detector_data(self, detector):
|
|
1375
|
+
"""Return a 2D array of all MCA spectra collected in this scan
|
|
1376
|
+
by the detector element indicated with `detector`.
|
|
1377
|
+
|
|
1378
|
+
:param detector: For detector data collected in SPEC format,
|
|
1379
|
+
this is the detector prefix as it appears in the spec MCA
|
|
1380
|
+
data file. For detector data collected in H5 format, this
|
|
1381
|
+
is the index of a particular detector element.
|
|
1382
|
+
:type detector: Union[str, int]
|
|
1383
|
+
:rtype: numpy.ndarray
|
|
1384
|
+
"""
|
|
1385
|
+
if self.detector_data_format == 'spec':
|
|
1386
|
+
return self.get_all_detector_data_spec(detector)
|
|
1387
|
+
elif self.detector_data_format == 'h5':
|
|
1388
|
+
try:
|
|
1389
|
+
element_index = int(detector)
|
|
1390
|
+
except:
|
|
1391
|
+
raise TypeError(f'{detector} is not an integer element index')
|
|
1392
|
+
return self.get_all_detector_data_h5(element_index)
|
|
1393
|
+
|
|
1394
|
+
def get_all_detector_data_spec(self, detector_prefix):
|
|
1395
|
+
"""Return a 2D array of all MCA spectra collected by a
|
|
1396
|
+
detector in the spec MCA file format during the scan.
|
|
1397
|
+
|
|
1398
|
+
:param detector_prefix: Detector name at is appears in the
|
|
1399
|
+
spec MCA file.
|
|
1400
|
+
:type detector_prefix: str
|
|
1401
|
+
:returns: 2D array of MCA spectra
|
|
1402
|
+
:rtype: numpy.ndarray
|
|
1403
|
+
"""
|
|
1121
1404
|
# This should be easy with pyspec, but there are bugs in
|
|
1122
1405
|
# pyspec for MCA data..... or is the 'bug' from a nonstandard
|
|
1123
1406
|
# implementation of some macro on our end? According to spec
|
|
1124
1407
|
# manual and pyspec code, mca data should always begin w/ '@A'
|
|
1125
|
-
# In example scans, it begins with '@
|
|
1408
|
+
# In example scans, it begins with '@{detector_prefix}'
|
|
1409
|
+
# instead
|
|
1126
1410
|
data = []
|
|
1127
1411
|
|
|
1128
|
-
with open(self.
|
|
1129
|
-
as detector_file:
|
|
1412
|
+
with open(self.get_detector_data_file_spec()) as detector_file:
|
|
1130
1413
|
lines = [line.strip("\\\n") for line in detector_file.readlines()]
|
|
1131
1414
|
|
|
1132
|
-
num_bins = self.get_detector_num_bins(
|
|
1415
|
+
num_bins = self.get_detector_num_bins()
|
|
1133
1416
|
|
|
1134
1417
|
counter = 0
|
|
1135
1418
|
for line in lines:
|
|
@@ -1156,6 +1439,106 @@ class SMBMCAScanParser(MCAScanParser, SMBLinearScanParser):
|
|
|
1156
1439
|
|
|
1157
1440
|
return np.array(data)
|
|
1158
1441
|
|
|
1159
|
-
def
|
|
1160
|
-
|
|
1442
|
+
def get_all_detector_data_h5(self, element_index):
|
|
1443
|
+
"""Return a 2D array of all MCA spectra collected by a
|
|
1444
|
+
detector in the h5 file format during the scan.
|
|
1445
|
+
|
|
1446
|
+
:param element_index: The index of a particualr MCA element to
|
|
1447
|
+
return data for.
|
|
1448
|
+
:type element_index: int
|
|
1449
|
+
:returns: 2D array of MCA spectra
|
|
1450
|
+
:rtype: numpy.ndarray
|
|
1451
|
+
"""
|
|
1452
|
+
detector_data = np.empty(
|
|
1453
|
+
(self.spec_scan_npts,
|
|
1454
|
+
self.get_detector_num_bins_h5(element_index)))
|
|
1455
|
+
detector_files = list_smb_mca_detector_files_h5(
|
|
1456
|
+
self.detector_data_path)
|
|
1457
|
+
for i, detector_file in enumerate(detector_files):
|
|
1458
|
+
full_filename = os.path.join(
|
|
1459
|
+
self.detector_data_path, detector_file)
|
|
1460
|
+
element_data = get_all_mca_data_h5(
|
|
1461
|
+
full_filename)[:,element_index,:]
|
|
1462
|
+
i_0 = i * self.spec_scan_shape[0]
|
|
1463
|
+
if len(self.spec_scan_shape) == 2:
|
|
1464
|
+
i_f = i_0 + self.spec_scan_shape[0]
|
|
1465
|
+
else:
|
|
1466
|
+
i_f = self.spec_scan_npts
|
|
1467
|
+
detector_data[i_0:i_f] = element_data
|
|
1468
|
+
return detector_data
|
|
1469
|
+
|
|
1470
|
+
def get_detector_data(self, detector, scan_step_index:int):
|
|
1471
|
+
"""Return a single MCA spectrum for the detector indicated.
|
|
1472
|
+
|
|
1473
|
+
:param detector: If this scan collected MCA data in "spec"
|
|
1474
|
+
format, this is the detector prefix as it appears in the
|
|
1475
|
+
spec MCA data file. If this scan collected data in .h5
|
|
1476
|
+
format, this is the index of the detector element of
|
|
1477
|
+
interest.:type detector: typing.Union[str, int]
|
|
1478
|
+
:param scan_step_index: Index of the scan step to return the
|
|
1479
|
+
spectrum from.
|
|
1480
|
+
:type scan_step_index: int
|
|
1481
|
+
:returns: A single MCA spectrum
|
|
1482
|
+
:rtype: numpy.ndarray
|
|
1483
|
+
"""
|
|
1484
|
+
detector_data = self.get_all_detector_data(detector)
|
|
1161
1485
|
return detector_data[scan_step_index]
|
|
1486
|
+
|
|
1487
|
+
@cache
|
|
1488
|
+
def list_smb_mca_detector_files_h5(detector_data_path):
|
|
1489
|
+
"""Return a sorted list of all *.hdf5 files in a directory
|
|
1490
|
+
|
|
1491
|
+
:param detector_data_path: Directory to return *.hdf5 files from
|
|
1492
|
+
:type detector_data_path: str
|
|
1493
|
+
:returns: Sorted list of detector data filenames
|
|
1494
|
+
:rtype: list[str]
|
|
1495
|
+
"""
|
|
1496
|
+
return sorted(
|
|
1497
|
+
[f for f in os.listdir(detector_data_path) if f.endswith('.hdf5')])
|
|
1498
|
+
|
|
1499
|
+
@cache
|
|
1500
|
+
def get_all_mca_data_h5(filename):
|
|
1501
|
+
"""Return all data from all elements from an MCA data file
|
|
1502
|
+
|
|
1503
|
+
:param filename: Name of the MCA h5 data file
|
|
1504
|
+
:type filename: str
|
|
1505
|
+
:returns: 3D array of MCA spectra where the first axis is scan
|
|
1506
|
+
step, second index is detector element, third index is channel
|
|
1507
|
+
energy.
|
|
1508
|
+
:rtype: numpy.ndarray
|
|
1509
|
+
"""
|
|
1510
|
+
import os
|
|
1511
|
+
|
|
1512
|
+
from h5py import File
|
|
1513
|
+
import numpy as np
|
|
1514
|
+
|
|
1515
|
+
with File(filename) as h5_file:
|
|
1516
|
+
data = h5_file['/entry/data/data'][:]
|
|
1517
|
+
|
|
1518
|
+
# Prior to 2023-12-12, there was an issue where the XPS23 detector
|
|
1519
|
+
# was capturing one or two frames of all 0s at the start of the
|
|
1520
|
+
# dataset in every hdf5 file. In both cases, there is only ONE
|
|
1521
|
+
# extra frame of data relative to the number of frames that should
|
|
1522
|
+
# be there (based on the number of points in the spec scan). If
|
|
1523
|
+
# one frame of all 0s is present: skip it and deliver only the
|
|
1524
|
+
# real data. If two frames of all 0s are present: detector data
|
|
1525
|
+
# will be missing for the LAST step in the scan. Skip the first
|
|
1526
|
+
# two frames of all 0s in the hdf5 dataset, then add a frame of
|
|
1527
|
+
# fake data (all 0-s) to the end of that real data so that the
|
|
1528
|
+
# number of detector data frames matches the number of points in
|
|
1529
|
+
# the spec scan.
|
|
1530
|
+
check_zeros_before = 1702357200
|
|
1531
|
+
file_mtime = os.path.getmtime(filename)
|
|
1532
|
+
if file_mtime <= check_zeros_before:
|
|
1533
|
+
if not np.any(data[0]):
|
|
1534
|
+
# If present, remove first frame of blank data
|
|
1535
|
+
print('Warning: removing blank first frame of detector data')
|
|
1536
|
+
data = data[1:]
|
|
1537
|
+
if not np.any(data[0]):
|
|
1538
|
+
# If present, shift second frame of blank data to the
|
|
1539
|
+
# end
|
|
1540
|
+
print('Warning: shifting second frame of blank detector data '
|
|
1541
|
+
+ 'to the end of the scan')
|
|
1542
|
+
data = np.concatenate((data[1:], np.asarray([data[0]])))
|
|
1543
|
+
|
|
1544
|
+
return data
|