ChessAnalysisPipeline 0.0.15__py3-none-any.whl → 0.0.16__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 +4 -0
- CHAP/common/models/integration.py +29 -26
- CHAP/common/models/map.py +186 -255
- CHAP/common/processor.py +956 -160
- CHAP/common/reader.py +93 -27
- CHAP/common/writer.py +15 -5
- CHAP/edd/__init__.py +2 -2
- CHAP/edd/models.py +299 -449
- CHAP/edd/processor.py +639 -448
- CHAP/edd/reader.py +232 -15
- CHAP/giwaxs/__init__.py +8 -0
- CHAP/giwaxs/models.py +100 -0
- CHAP/giwaxs/processor.py +520 -0
- CHAP/giwaxs/reader.py +5 -0
- CHAP/giwaxs/writer.py +5 -0
- CHAP/pipeline.py +47 -9
- CHAP/runner.py +160 -71
- CHAP/tomo/models.py +25 -25
- CHAP/tomo/processor.py +51 -79
- CHAP/utils/general.py +18 -0
- CHAP/utils/models.py +76 -49
- CHAP/utils/parfile.py +10 -2
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/WHEEL +1 -1
- CHAP/utils/scanparsers.py +0 -1544
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/top_level.txt +0 -0
CHAP/common/models/map.py
CHANGED
|
@@ -15,16 +15,17 @@ from typing import (
|
|
|
15
15
|
import numpy as np
|
|
16
16
|
from pydantic import (
|
|
17
17
|
BaseModel,
|
|
18
|
+
Field,
|
|
19
|
+
FilePath,
|
|
20
|
+
PrivateAttr,
|
|
18
21
|
conint,
|
|
19
22
|
conlist,
|
|
20
23
|
constr,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
root_validator,
|
|
24
|
-
validator,
|
|
24
|
+
field_validator,
|
|
25
|
+
model_validator,
|
|
25
26
|
)
|
|
26
27
|
from pyspec.file.spec import FileSpec
|
|
27
|
-
|
|
28
|
+
from typing_extensions import Annotated
|
|
28
29
|
|
|
29
30
|
class Sample(BaseModel):
|
|
30
31
|
"""Class representing a sample metadata configuration.
|
|
@@ -35,7 +36,7 @@ class Sample(BaseModel):
|
|
|
35
36
|
:type description: str, optional
|
|
36
37
|
"""
|
|
37
38
|
name: constr(min_length=1)
|
|
38
|
-
description: Optional[str]
|
|
39
|
+
description: Optional[str] = ''
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class SpecScans(BaseModel):
|
|
@@ -44,24 +45,23 @@ class SpecScans(BaseModel):
|
|
|
44
45
|
:ivar spec_file: Path to the SPEC file.
|
|
45
46
|
:type spec_file: str
|
|
46
47
|
:ivar scan_numbers: List of scan numbers to use.
|
|
47
|
-
:type scan_numbers: list[int]
|
|
48
|
+
:type scan_numbers: Union(int, list[int], str)
|
|
48
49
|
:ivar par_file: Path to a non-default SMB par file.
|
|
49
50
|
:type par_file: str, optional
|
|
50
51
|
"""
|
|
51
52
|
spec_file: FilePath
|
|
52
|
-
scan_numbers: conlist(item_type=conint(gt=0),
|
|
53
|
-
par_file: Optional[FilePath]
|
|
53
|
+
scan_numbers: conlist(item_type=conint(gt=0), min_length=1)
|
|
54
|
+
par_file: Optional[FilePath] = None
|
|
54
55
|
|
|
55
|
-
@
|
|
56
|
-
|
|
56
|
+
@field_validator('spec_file')
|
|
57
|
+
@classmethod
|
|
58
|
+
def validate_spec_file(cls, spec_file):
|
|
57
59
|
"""Validate the specified SPEC file.
|
|
58
60
|
|
|
59
61
|
:param spec_file: Path to the SPEC file.
|
|
60
62
|
:type spec_file: str
|
|
61
|
-
:param values: Dictionary of validated class field values.
|
|
62
|
-
:type values: dict
|
|
63
63
|
:raises ValueError: If the SPEC file is invalid.
|
|
64
|
-
:return: Absolute path to the SPEC file
|
|
64
|
+
:return: Absolute path to the SPEC file.
|
|
65
65
|
:rtype: str
|
|
66
66
|
"""
|
|
67
67
|
try:
|
|
@@ -71,26 +71,29 @@ class SpecScans(BaseModel):
|
|
|
71
71
|
raise ValueError(f'Invalid SPEC file {spec_file}')
|
|
72
72
|
return spec_file
|
|
73
73
|
|
|
74
|
-
@
|
|
75
|
-
|
|
74
|
+
@field_validator('scan_numbers', mode='before')
|
|
75
|
+
@classmethod
|
|
76
|
+
def validate_scan_numbers(cls, scan_numbers, info):
|
|
76
77
|
"""Validate the specified list of scan numbers.
|
|
77
78
|
|
|
78
79
|
:param scan_numbers: List of scan numbers.
|
|
79
|
-
:type scan_numbers: list
|
|
80
|
-
:param
|
|
81
|
-
:type
|
|
80
|
+
:type scan_numbers: Union(int, list[int], str)
|
|
81
|
+
:param info: Pydantic validator info object.
|
|
82
|
+
:type info: pydantic_core._pydantic_core.ValidationInfo
|
|
82
83
|
:raises ValueError: If a specified scan number is not found in
|
|
83
84
|
the SPEC file.
|
|
84
85
|
:return: List of scan numbers.
|
|
85
|
-
:rtype: list
|
|
86
|
+
:rtype: list[int]
|
|
86
87
|
"""
|
|
87
|
-
if isinstance(scan_numbers,
|
|
88
|
+
if isinstance(scan_numbers, int):
|
|
89
|
+
scan_numbers = [scan_numbers]
|
|
90
|
+
elif isinstance(scan_numbers, str):
|
|
88
91
|
# Local modules
|
|
89
92
|
from CHAP.utils.general import string_to_list
|
|
90
93
|
|
|
91
94
|
scan_numbers = string_to_list(scan_numbers)
|
|
92
95
|
|
|
93
|
-
spec_file =
|
|
96
|
+
spec_file = info.data.get('spec_file')
|
|
94
97
|
if spec_file is not None:
|
|
95
98
|
spec_scans = FileSpec(spec_file)
|
|
96
99
|
for scan_number in scan_numbers:
|
|
@@ -100,16 +103,15 @@ class SpecScans(BaseModel):
|
|
|
100
103
|
f'No scan number {scan_number} in {spec_file}')
|
|
101
104
|
return scan_numbers
|
|
102
105
|
|
|
103
|
-
@
|
|
104
|
-
|
|
106
|
+
@field_validator('par_file')
|
|
107
|
+
@classmethod
|
|
108
|
+
def validate_par_file(cls, par_file):
|
|
105
109
|
"""Validate the specified SMB par file.
|
|
106
110
|
|
|
107
111
|
:param par_file: Path to a non-default SMB par file.
|
|
108
112
|
:type par_file: str
|
|
109
|
-
:param values: Dictionary of validated class field values.
|
|
110
|
-
:type values: dict
|
|
111
113
|
:raises ValueError: If the SMB par file is invalid.
|
|
112
|
-
:return: Absolute path to the SMB par file
|
|
114
|
+
:return: Absolute path to the SMB par file.
|
|
113
115
|
:rtype: str
|
|
114
116
|
"""
|
|
115
117
|
if par_file is None or not par_file:
|
|
@@ -244,7 +246,8 @@ class PointByPointScanData(BaseModel):
|
|
|
244
246
|
'smb_par', 'expression']
|
|
245
247
|
name: constr(strip_whitespace=True, min_length=1)
|
|
246
248
|
|
|
247
|
-
@
|
|
249
|
+
@field_validator('label')
|
|
250
|
+
@classmethod
|
|
248
251
|
def validate_label(cls, label):
|
|
249
252
|
"""Validate that the supplied `label` does not conflict with
|
|
250
253
|
any of the values for `label` reserved for certain data needed
|
|
@@ -253,8 +256,7 @@ class PointByPointScanData(BaseModel):
|
|
|
253
256
|
:param label: The value of `label` to validate.
|
|
254
257
|
:type label: str
|
|
255
258
|
:raises ValueError: If `label` is one of the reserved values.
|
|
256
|
-
:return: The
|
|
257
|
-
allowed.
|
|
259
|
+
:return: The originally supplied value `label`.
|
|
258
260
|
:rtype: str
|
|
259
261
|
"""
|
|
260
262
|
if ((not issubclass(cls,CorrectionsData))
|
|
@@ -329,6 +331,7 @@ class PointByPointScanData(BaseModel):
|
|
|
329
331
|
needed for evaluating the expression.
|
|
330
332
|
:return: None
|
|
331
333
|
"""
|
|
334
|
+
# Third party modules
|
|
332
335
|
from ast import parse
|
|
333
336
|
from asteval import get_ast_names
|
|
334
337
|
|
|
@@ -401,7 +404,7 @@ class PointByPointScanData(BaseModel):
|
|
|
401
404
|
return get_smb_par_value(spec_scans.spec_file,
|
|
402
405
|
scan_number,
|
|
403
406
|
self.name)
|
|
404
|
-
|
|
407
|
+
if self.data_type == 'expression':
|
|
405
408
|
return get_expression_value(spec_scans,
|
|
406
409
|
scan_number,
|
|
407
410
|
scan_step_index,
|
|
@@ -526,8 +529,10 @@ def get_expression_value(spec_scans:SpecScans, scan_number:int,
|
|
|
526
529
|
:return: The value of the .par file value for the scan requested.
|
|
527
530
|
:rtype: float
|
|
528
531
|
"""
|
|
532
|
+
# Third party modules
|
|
529
533
|
from ast import parse
|
|
530
534
|
from asteval import get_ast_names, Interpreter
|
|
535
|
+
|
|
531
536
|
labels = get_ast_names(parse(expression))
|
|
532
537
|
symtable = {}
|
|
533
538
|
for l in labels:
|
|
@@ -540,35 +545,35 @@ def get_expression_value(spec_scans:SpecScans, scan_number:int,
|
|
|
540
545
|
aeval = Interpreter(symtable=symtable)
|
|
541
546
|
return aeval(expression)
|
|
542
547
|
|
|
543
|
-
def validate_data_source_for_map_config(data_source,
|
|
548
|
+
def validate_data_source_for_map_config(data_source, info):
|
|
544
549
|
"""Confirm that an instance of PointByPointScanData is valid for
|
|
545
550
|
the station and scans provided by a map configuration dictionary.
|
|
546
551
|
|
|
547
552
|
:param data_source: The input object to validate.
|
|
548
|
-
:type data_source:
|
|
549
|
-
:param
|
|
550
|
-
:type
|
|
551
|
-
:raises Exception: If `data_source` cannot be validated
|
|
552
|
-
|
|
553
|
-
:return: `data_source`, if it is valid.
|
|
553
|
+
:type data_source: PointByPointScanData
|
|
554
|
+
:param info: Pydantic validator info object.
|
|
555
|
+
:type info: pydantic_core._pydantic_core.ValidationInfo
|
|
556
|
+
:raises Exception: If `data_source` cannot be validated.
|
|
557
|
+
:return: the validated `data_source` instance.
|
|
554
558
|
:rtype: PointByPointScanData
|
|
555
559
|
"""
|
|
556
560
|
def _validate_data_source_for_map_config(
|
|
557
|
-
data_source,
|
|
561
|
+
data_source, info, parent_list=None):
|
|
558
562
|
if isinstance(data_source, list):
|
|
559
563
|
return [_validate_data_source_for_map_config(
|
|
560
|
-
d_s,
|
|
564
|
+
d_s, info, parent_list=data_source) for d_s in data_source]
|
|
561
565
|
if data_source is not None:
|
|
566
|
+
values = info.data
|
|
562
567
|
if data_source.data_type == 'expression':
|
|
563
|
-
data_source.validate_for_scalar_data(
|
|
564
|
-
values.get('scalar_data', parent_list))
|
|
568
|
+
data_source.validate_for_scalar_data(values['scalar_data'])
|
|
565
569
|
else:
|
|
566
570
|
import_scanparser(
|
|
567
|
-
values
|
|
568
|
-
data_source.validate_for_station(values
|
|
569
|
-
data_source.validate_for_spec_scans(values
|
|
570
|
-
return
|
|
571
|
-
|
|
571
|
+
values['station'], values['experiment_type'])
|
|
572
|
+
data_source.validate_for_station(values['station'])
|
|
573
|
+
data_source.validate_for_spec_scans(values['spec_scans'])
|
|
574
|
+
return data_source
|
|
575
|
+
|
|
576
|
+
return _validate_data_source_for_map_config(data_source, info)
|
|
572
577
|
|
|
573
578
|
|
|
574
579
|
class IndependentDimension(PointByPointScanData):
|
|
@@ -601,11 +606,20 @@ class IndependentDimension(PointByPointScanData):
|
|
|
601
606
|
end: Optional[int] = None
|
|
602
607
|
step: Optional[conint(gt=0)] = 1
|
|
603
608
|
|
|
604
|
-
# @
|
|
605
|
-
#
|
|
606
|
-
#
|
|
609
|
+
# @field_validator('step')
|
|
610
|
+
# @classmethod
|
|
611
|
+
# def validate_step(cls, step):
|
|
612
|
+
# """Validate that the supplied value of `step`.
|
|
613
|
+
#
|
|
614
|
+
# :param step: The value of `step` to validate.
|
|
615
|
+
# :type step: str
|
|
616
|
+
# :raises ValueError: If `step` is zero.
|
|
617
|
+
# :return: The originally supplied value `step`.
|
|
618
|
+
# :rtype: int
|
|
619
|
+
# """
|
|
620
|
+
# if step == 0 :
|
|
607
621
|
# raise ValueError('slice step cannot be zero')
|
|
608
|
-
# return
|
|
622
|
+
# return step
|
|
609
623
|
|
|
610
624
|
|
|
611
625
|
class CorrectionsData(PointByPointScanData):
|
|
@@ -641,7 +655,7 @@ class CorrectionsData(PointByPointScanData):
|
|
|
641
655
|
:return: A list of reserved labels.
|
|
642
656
|
:rtype: list[str]
|
|
643
657
|
"""
|
|
644
|
-
return list((*cls.
|
|
658
|
+
return list((*cls.model_fields['label'].annotation.__args__, 'round'))
|
|
645
659
|
|
|
646
660
|
|
|
647
661
|
class PresampleIntensity(CorrectionsData):
|
|
@@ -713,60 +727,68 @@ class SpecConfig(BaseModel):
|
|
|
713
727
|
:type spec_scans: list[SpecScans]
|
|
714
728
|
"""
|
|
715
729
|
station: Literal['id1a3', 'id3a', 'id3b']
|
|
716
|
-
experiment_type: Literal['
|
|
717
|
-
spec_scans: conlist(item_type=SpecScans,
|
|
730
|
+
experiment_type: Literal['EDD', 'GIWAXS', 'SAXSWAXS', 'TOMO', 'XRF']
|
|
731
|
+
spec_scans: conlist(item_type=SpecScans, min_length=1)
|
|
718
732
|
|
|
719
|
-
@
|
|
720
|
-
|
|
733
|
+
@model_validator(mode='before')
|
|
734
|
+
@classmethod
|
|
735
|
+
def validate_config(cls, data):
|
|
721
736
|
"""Ensure that a valid configuration was provided and finalize
|
|
722
737
|
spec_file filepaths.
|
|
723
738
|
|
|
724
|
-
:param
|
|
725
|
-
:type
|
|
726
|
-
:return: The validated list of
|
|
739
|
+
:param data: Pydantic validator data object.
|
|
740
|
+
:type data: SpecConfig, pydantic_core._pydantic_core.ValidationInfo
|
|
741
|
+
:return: The currently validated list of class properties.
|
|
727
742
|
:rtype: dict
|
|
728
743
|
"""
|
|
729
|
-
inputdir =
|
|
744
|
+
inputdir = data.get('inputdir')
|
|
730
745
|
if inputdir is not None:
|
|
731
|
-
spec_scans =
|
|
746
|
+
spec_scans = data.get('spec_scans')
|
|
732
747
|
for i, scans in enumerate(deepcopy(spec_scans)):
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
748
|
+
if isinstance(scans, dict):
|
|
749
|
+
spec_file = scans['spec_file']
|
|
750
|
+
if not os.path.isabs(spec_file):
|
|
751
|
+
spec_scans[i]['spec_file'] = os.path.join(
|
|
752
|
+
inputdir, spec_file)
|
|
753
|
+
else:
|
|
754
|
+
spec_file = scans.spec_file
|
|
755
|
+
if not os.path.isabs(spec_file):
|
|
756
|
+
spec_scans[i].spec_file = os.path.join(
|
|
757
|
+
inputdir, spec_file)
|
|
758
|
+
data['spec_scans'] = spec_scans
|
|
759
|
+
return data
|
|
760
|
+
|
|
761
|
+
@field_validator('experiment_type')
|
|
762
|
+
@classmethod
|
|
763
|
+
def validate_experiment_type(cls, experiment_type, info):
|
|
743
764
|
"""Ensure values for the station and experiment_type fields are
|
|
744
765
|
compatible
|
|
745
766
|
|
|
746
|
-
:param
|
|
747
|
-
|
|
748
|
-
:
|
|
749
|
-
:
|
|
767
|
+
:param experiment_type: The value of `experiment_type` to
|
|
768
|
+
validate.
|
|
769
|
+
:type experiment_type: str
|
|
770
|
+
:param info: Pydantic validator info object.
|
|
771
|
+
:type info: pydantic_core._pydantic_core.ValidationInfo
|
|
750
772
|
:raises ValueError: Invalid experiment type.
|
|
751
773
|
:return: The validated field for `experiment_type`.
|
|
752
774
|
:rtype: str
|
|
753
775
|
"""
|
|
754
|
-
station =
|
|
776
|
+
station = info.data.get('station')
|
|
755
777
|
if station == 'id1a3':
|
|
756
|
-
allowed_experiment_types = ['
|
|
778
|
+
allowed_experiment_types = ['EDD', 'SAXSWAXS', 'TOMO']
|
|
757
779
|
elif station == 'id3a':
|
|
758
780
|
allowed_experiment_types = ['EDD', 'TOMO']
|
|
759
781
|
elif station == 'id3b':
|
|
760
|
-
allowed_experiment_types = ['
|
|
782
|
+
allowed_experiment_types = ['GIWAXS', 'SAXSWAXS', 'TOMO', 'XRF']
|
|
761
783
|
else:
|
|
762
784
|
allowed_experiment_types = []
|
|
763
|
-
if
|
|
785
|
+
if experiment_type not in allowed_experiment_types:
|
|
764
786
|
raise ValueError(
|
|
765
787
|
f'For station {station}, allowed experiment types are '
|
|
766
788
|
f'{", ".join(allowed_experiment_types)}. '
|
|
767
|
-
f'Supplied experiment type {
|
|
768
|
-
import_scanparser(station,
|
|
769
|
-
return
|
|
789
|
+
f'Supplied experiment type {experiment_type} is not allowed.')
|
|
790
|
+
import_scanparser(station, experiment_type)
|
|
791
|
+
return experiment_type
|
|
770
792
|
|
|
771
793
|
|
|
772
794
|
class MapConfig(BaseModel):
|
|
@@ -802,189 +824,124 @@ class MapConfig(BaseModel):
|
|
|
802
824
|
map. In the NeXus file representation of the map, datasets for
|
|
803
825
|
these values will be included, defaults to `[]`.
|
|
804
826
|
:type scalar_data: list[PointByPointScanData], optional
|
|
805
|
-
:ivar map_type: Type of map, structured or unstructured,
|
|
806
|
-
defaults to `'structured'`.
|
|
807
|
-
:type map_type: Literal['structured', 'unstructured'], optional
|
|
808
827
|
"""
|
|
809
828
|
title: constr(strip_whitespace=True, min_length=1)
|
|
810
829
|
station: Literal['id1a3', 'id3a', 'id3b']
|
|
811
|
-
experiment_type: Literal['
|
|
830
|
+
experiment_type: Literal['EDD', 'GIWAXS', 'SAXSWAXS', 'TOMO', 'XRF']
|
|
812
831
|
sample: Sample
|
|
813
|
-
spec_scans: conlist(item_type=SpecScans,
|
|
814
|
-
independent_dimensions: conlist(
|
|
815
|
-
item_type=IndependentDimension, min_items=1)
|
|
816
|
-
presample_intensity: Optional[PresampleIntensity]
|
|
817
|
-
dwell_time_actual: Optional[DwellTimeActual]
|
|
818
|
-
postsample_intensity: Optional[PostsampleIntensity]
|
|
832
|
+
spec_scans: conlist(item_type=SpecScans, min_length=1)
|
|
819
833
|
scalar_data: Optional[list[PointByPointScanData]] = []
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
834
|
+
independent_dimensions: conlist(
|
|
835
|
+
item_type=IndependentDimension, min_length=1)
|
|
836
|
+
presample_intensity: Optional[PresampleIntensity] = None
|
|
837
|
+
dwell_time_actual: Optional[DwellTimeActual] = None
|
|
838
|
+
postsample_intensity: Optional[PostsampleIntensity] = None
|
|
839
|
+
attrs: Optional[Annotated[dict, Field(validate_default=True)]] = {}
|
|
840
|
+
# _coords: dict = PrivateAttr()
|
|
823
841
|
_dims: tuple = PrivateAttr()
|
|
824
|
-
_scan_step_indices: list = PrivateAttr()
|
|
825
|
-
_shape: tuple = PrivateAttr()
|
|
826
|
-
|
|
827
|
-
_validate_independent_dimensions =
|
|
828
|
-
'independent_dimensions'
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
'
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
'scalar_data',
|
|
842
|
-
allow_reuse=True)(validate_data_source_for_map_config)
|
|
843
|
-
|
|
844
|
-
@root_validator(pre=True)
|
|
845
|
-
def validate_config(cls, values):
|
|
842
|
+
# _scan_step_indices: list = PrivateAttr()
|
|
843
|
+
# _shape: tuple = PrivateAttr()
|
|
844
|
+
|
|
845
|
+
_validate_independent_dimensions = field_validator(
|
|
846
|
+
'independent_dimensions')(validate_data_source_for_map_config)
|
|
847
|
+
_validate_presample_intensity = field_validator(
|
|
848
|
+
'presample_intensity')(validate_data_source_for_map_config)
|
|
849
|
+
_validate_dwell_time_actual = field_validator(
|
|
850
|
+
'dwell_time_actual')(validate_data_source_for_map_config)
|
|
851
|
+
_validate_postsample_intensity = field_validator(
|
|
852
|
+
'postsample_intensity')(validate_data_source_for_map_config)
|
|
853
|
+
_validate_scalar_data = field_validator(
|
|
854
|
+
'scalar_data')(validate_data_source_for_map_config)
|
|
855
|
+
|
|
856
|
+
@model_validator(mode='before')
|
|
857
|
+
@classmethod
|
|
858
|
+
def validate_config(cls, data):
|
|
846
859
|
"""Ensure that a valid configuration was provided and finalize
|
|
847
860
|
spec_file filepaths.
|
|
848
861
|
|
|
849
|
-
:param
|
|
850
|
-
:type
|
|
851
|
-
|
|
862
|
+
:param data: Pydantic validator data object.
|
|
863
|
+
:type data:
|
|
864
|
+
MapConfig, pydantic_core._pydantic_core.ValidationInfo
|
|
865
|
+
:return: The currently validated list of class properties.
|
|
852
866
|
:rtype: dict
|
|
853
867
|
"""
|
|
854
|
-
inputdir =
|
|
868
|
+
inputdir = data.get('inputdir')
|
|
855
869
|
if inputdir is not None:
|
|
856
|
-
spec_scans =
|
|
870
|
+
spec_scans = data.get('spec_scans')
|
|
857
871
|
for i, scans in enumerate(deepcopy(spec_scans)):
|
|
858
872
|
spec_file = scans['spec_file']
|
|
859
873
|
if not os.path.isabs(spec_file):
|
|
860
874
|
spec_scans[i]['spec_file'] = os.path.join(
|
|
861
875
|
inputdir, spec_file)
|
|
862
|
-
spec_scans[i] = SpecScans(**spec_scans[i], **
|
|
863
|
-
|
|
864
|
-
return
|
|
876
|
+
spec_scans[i] = SpecScans(**spec_scans[i], **data)
|
|
877
|
+
data['spec_scans'] = spec_scans
|
|
878
|
+
return data
|
|
865
879
|
|
|
866
|
-
@
|
|
867
|
-
|
|
880
|
+
@field_validator('experiment_type')
|
|
881
|
+
@classmethod
|
|
882
|
+
def validate_experiment_type(cls, experiment_type, info):
|
|
868
883
|
"""Ensure values for the station and experiment_type fields are
|
|
869
884
|
compatible.
|
|
870
885
|
|
|
871
|
-
:param
|
|
872
|
-
|
|
873
|
-
:
|
|
874
|
-
:
|
|
886
|
+
:param experiment_type: The value of `experiment_type` to
|
|
887
|
+
validate.
|
|
888
|
+
:type experiment_type: dict
|
|
889
|
+
:param info: Pydantic validator info object.
|
|
890
|
+
:type info: pydantic_core._pydantic_core.ValidationInfo
|
|
875
891
|
:raises ValueError: Invalid experiment type.
|
|
876
892
|
:return: The validated field for `experiment_type`.
|
|
877
893
|
:rtype: str
|
|
878
894
|
"""
|
|
879
|
-
station =
|
|
895
|
+
station = info.data['station']
|
|
880
896
|
if station == 'id1a3':
|
|
881
|
-
allowed_experiment_types = ['
|
|
897
|
+
allowed_experiment_types = ['EDD', 'SAXSWAXS', 'TOMO']
|
|
882
898
|
elif station == 'id3a':
|
|
883
899
|
allowed_experiment_types = ['EDD', 'TOMO']
|
|
884
900
|
elif station == 'id3b':
|
|
885
|
-
allowed_experiment_types = ['
|
|
901
|
+
allowed_experiment_types = ['GIWAXS', 'SAXSWAXS', 'TOMO', 'XRF']
|
|
886
902
|
else:
|
|
887
903
|
allowed_experiment_types = []
|
|
888
|
-
if
|
|
904
|
+
if experiment_type not in allowed_experiment_types:
|
|
889
905
|
raise ValueError(
|
|
890
906
|
f'For station {station}, allowed experiment types are '
|
|
891
907
|
f'{", ".join(allowed_experiment_types)}. '
|
|
892
|
-
f'Supplied experiment type {
|
|
893
|
-
return
|
|
908
|
+
f'Supplied experiment type {experiment_type} is not allowed.')
|
|
909
|
+
return experiment_type
|
|
894
910
|
|
|
895
|
-
|
|
896
|
-
|
|
911
|
+
#RV maybe better to use model_validator, see v2 docs?
|
|
912
|
+
@field_validator('attrs')
|
|
913
|
+
@classmethod
|
|
914
|
+
def validate_attrs(cls, attrs, info):
|
|
897
915
|
"""Read any additional attributes depending on the values for
|
|
898
916
|
the station and experiment_type fields.
|
|
899
917
|
|
|
900
|
-
:param
|
|
901
|
-
:type
|
|
902
|
-
:param
|
|
903
|
-
:type
|
|
918
|
+
:param attrs: Any additional attributes to the MapConfig class.
|
|
919
|
+
:type attrs: dict
|
|
920
|
+
:param info: Pydantic validator info object.
|
|
921
|
+
:type info: pydantic_core._pydantic_core.ValidationInfo
|
|
904
922
|
:raises ValueError: Invalid attribute.
|
|
905
923
|
:return: The validated field for `attrs`.
|
|
906
924
|
:rtype: dict
|
|
907
925
|
"""
|
|
908
926
|
# Get the map's scan_type for EDD experiments
|
|
927
|
+
values = info.data
|
|
909
928
|
station = values['station']
|
|
910
929
|
experiment_type = values['experiment_type']
|
|
911
930
|
if station in ['id1a3', 'id3a'] and experiment_type == 'EDD':
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
931
|
+
attrs['scan_type'] = cls.get_smb_par_attr(values, 'scan_type')
|
|
932
|
+
attrs['config_id'] = cls.get_smb_par_attr(values, 'config_id')
|
|
933
|
+
attrs['dataset_id'] = cls.get_smb_par_attr(values, 'dataset_id')
|
|
915
934
|
axes_labels = {1: 'fly_labx', 2: 'fly_laby', 3: 'fly_labz',
|
|
916
935
|
4: 'fly_ometotal'}
|
|
917
|
-
if
|
|
918
|
-
return
|
|
919
|
-
if
|
|
920
|
-
|
|
936
|
+
if attrs['scan_type'] is None:
|
|
937
|
+
return attrs
|
|
938
|
+
if attrs['scan_type'] != 0:
|
|
939
|
+
attrs['fly_axis_labels'] = [
|
|
921
940
|
axes_labels[cls.get_smb_par_attr(values, 'fly_axis0')]]
|
|
922
|
-
if
|
|
923
|
-
|
|
941
|
+
if attrs['scan_type'] in (2, 3, 5):
|
|
942
|
+
attrs['fly_axis_labels'].append(
|
|
924
943
|
axes_labels[cls.get_smb_par_attr(values, 'fly_axis1')])
|
|
925
|
-
return
|
|
926
|
-
|
|
927
|
-
@validator('map_type', pre=True, always=True)
|
|
928
|
-
def validate_map_type(cls, map_type, values):
|
|
929
|
-
"""Validate the map_type field.
|
|
930
|
-
|
|
931
|
-
:param map_type: Type of map, structured or unstructured,
|
|
932
|
-
defaults to `'structured'`.
|
|
933
|
-
:type map_type: Literal['structured', 'unstructured']]
|
|
934
|
-
:param values: Dictionary of validated class field values.
|
|
935
|
-
:type values: dict
|
|
936
|
-
:return: The validated value for map_type.
|
|
937
|
-
:rtype: str
|
|
938
|
-
"""
|
|
939
|
-
dims = {}
|
|
940
|
-
attrs = values.get('attrs', {})
|
|
941
|
-
scan_type = attrs.get('scan_type', -1)
|
|
942
|
-
fly_axis_labels = attrs.get('fly_axis_labels', [])
|
|
943
|
-
spec_scans = values['spec_scans']
|
|
944
|
-
independent_dimensions = values['independent_dimensions']
|
|
945
|
-
scalar_data = values['scalar_data']
|
|
946
|
-
import_scanparser(values['station'], values['experiment_type'])
|
|
947
|
-
for i, dim in enumerate(deepcopy(independent_dimensions)):
|
|
948
|
-
if dim.label in fly_axis_labels:
|
|
949
|
-
relative = True
|
|
950
|
-
ndigits = 3
|
|
951
|
-
else:
|
|
952
|
-
relative = False
|
|
953
|
-
ndigits = None
|
|
954
|
-
dims[dim.label] = []
|
|
955
|
-
for scans in spec_scans:
|
|
956
|
-
for scan_number in scans.scan_numbers:
|
|
957
|
-
scanparser = scans.get_scanparser(scan_number)
|
|
958
|
-
for scan_step_index in range(
|
|
959
|
-
scanparser.spec_scan_npts):
|
|
960
|
-
dims[dim.label].append(dim.get_value(
|
|
961
|
-
scans, scan_number, scan_step_index,
|
|
962
|
-
scalar_data, relative, ndigits))
|
|
963
|
-
dims[dim.label] = np.unique(dims[dim.label])
|
|
964
|
-
if dim.end is None:
|
|
965
|
-
dim.end = len(dims[dim.label])
|
|
966
|
-
dims[dim.label] = dims[dim.label][slice(
|
|
967
|
-
dim.start, dim.end, dim.step)]
|
|
968
|
-
independent_dimensions[i] = dim
|
|
969
|
-
|
|
970
|
-
coords = np.zeros([v.size for v in dims.values()], dtype=np.int64)
|
|
971
|
-
for scans in spec_scans:
|
|
972
|
-
for scan_number in scans.scan_numbers:
|
|
973
|
-
scanparser = scans.get_scanparser(scan_number)
|
|
974
|
-
for scan_step_index in range(scanparser.spec_scan_npts):
|
|
975
|
-
coords[tuple([
|
|
976
|
-
list(dims[dim.label]).index(
|
|
977
|
-
dim.get_value(scans, scan_number, scan_step_index,
|
|
978
|
-
scalar_data, True, 3))
|
|
979
|
-
if dim.label in fly_axis_labels else
|
|
980
|
-
list(dims[dim.label]).index(
|
|
981
|
-
dim.get_value(scans, scan_number, scan_step_index,
|
|
982
|
-
scalar_data))
|
|
983
|
-
for dim in independent_dimensions])] += 1
|
|
984
|
-
if any(True for v in coords.flatten() if v == 0 or v > 1):
|
|
985
|
-
return 'unstructured'
|
|
986
|
-
else:
|
|
987
|
-
return 'structured'
|
|
944
|
+
return attrs
|
|
988
945
|
|
|
989
946
|
@staticmethod
|
|
990
947
|
def get_smb_par_attr(class_fields, label, units='-', name=None):
|
|
@@ -1002,8 +959,8 @@ class MapConfig(BaseModel):
|
|
|
1002
959
|
except:
|
|
1003
960
|
print(
|
|
1004
961
|
f'Warning: No value found for .par file value "{name}"'
|
|
1005
|
-
|
|
1006
|
-
|
|
962
|
+
f' on scan {scan_number} in spec file '
|
|
963
|
+
f'{scans.spec_file}.')
|
|
1007
964
|
values.append(None)
|
|
1008
965
|
values = list(set(values))
|
|
1009
966
|
if len(values) != 1:
|
|
@@ -1030,6 +987,7 @@ class MapConfig(BaseModel):
|
|
|
1030
987
|
"""Return a dictionary of the values of each independent
|
|
1031
988
|
dimension across the map.
|
|
1032
989
|
"""
|
|
990
|
+
raise RuntimeError(f'property coords not implemented')
|
|
1033
991
|
if not hasattr(self, '_coords'):
|
|
1034
992
|
scan_type = self.attrs.get('scan_type', -1)
|
|
1035
993
|
fly_axis_labels = self.attrs.get('fly_axis_labels', [])
|
|
@@ -1061,8 +1019,7 @@ class MapConfig(BaseModel):
|
|
|
1061
1019
|
map.
|
|
1062
1020
|
"""
|
|
1063
1021
|
if not hasattr(self, '_dims'):
|
|
1064
|
-
self._dims = [
|
|
1065
|
-
dim.label for dim in self.independent_dimensions[::-1]]
|
|
1022
|
+
self._dims = [dim.label for dim in self.independent_dimensions]
|
|
1066
1023
|
return self._dims
|
|
1067
1024
|
|
|
1068
1025
|
@property
|
|
@@ -1071,6 +1028,7 @@ class MapConfig(BaseModel):
|
|
|
1071
1028
|
object, the scan number, and scan step index for every point
|
|
1072
1029
|
on the map.
|
|
1073
1030
|
"""
|
|
1031
|
+
raise RuntimeError(f'property scan_step_indices not implemented')
|
|
1074
1032
|
if not hasattr(self, '_scan_step_indices'):
|
|
1075
1033
|
scan_step_indices = []
|
|
1076
1034
|
for scans in self.spec_scans:
|
|
@@ -1087,10 +1045,10 @@ class MapConfig(BaseModel):
|
|
|
1087
1045
|
"""Return the shape of the map -- a tuple representing the
|
|
1088
1046
|
number of unique values of each dimension across the map.
|
|
1089
1047
|
"""
|
|
1048
|
+
raise RuntimeError(f'property shape not implemented')
|
|
1090
1049
|
if not hasattr(self, '_shape'):
|
|
1091
1050
|
if self.map_type == 'structured':
|
|
1092
|
-
self._shape = tuple(
|
|
1093
|
-
[len(v) for k, v in self.coords.items()][::-1])
|
|
1051
|
+
self._shape = tuple([len(v) for k, v in self.coords.items()])
|
|
1094
1052
|
else:
|
|
1095
1053
|
self._shape = (len(self.scan_step_indices),)
|
|
1096
1054
|
return self._shape
|
|
@@ -1104,6 +1062,7 @@ class MapConfig(BaseModel):
|
|
|
1104
1062
|
:return: A list of coordinate values.
|
|
1105
1063
|
:rtype: dict
|
|
1106
1064
|
"""
|
|
1065
|
+
raise RuntimeError(f'get_coords not implemented')
|
|
1107
1066
|
if self.map_type == 'structured':
|
|
1108
1067
|
scan_type = self.attrs.get('scan_type', -1)
|
|
1109
1068
|
fly_axis_labels = self.attrs.get('fly_axis_labels', [])
|
|
@@ -1131,6 +1090,7 @@ class MapConfig(BaseModel):
|
|
|
1131
1090
|
:return: One frame of raw detector data.
|
|
1132
1091
|
:rtype: np.ndarray
|
|
1133
1092
|
"""
|
|
1093
|
+
raise RuntimeError(f'get_detector_data not implemented')
|
|
1134
1094
|
scans, scan_number, scan_step_index = \
|
|
1135
1095
|
self.get_scan_step_index(map_index)
|
|
1136
1096
|
scanparser = scans.get_scanparser(scan_number)
|
|
@@ -1147,6 +1107,7 @@ class MapConfig(BaseModel):
|
|
|
1147
1107
|
step index.
|
|
1148
1108
|
:rtype: tuple[SpecScans, int, int]
|
|
1149
1109
|
"""
|
|
1110
|
+
raise RuntimeError(f'get_scan_step_index not implemented')
|
|
1150
1111
|
scan_type = self.attrs.get('scan_type', -1)
|
|
1151
1112
|
fly_axis_labels = self.attrs.get('fly_axis_labels', [])
|
|
1152
1113
|
if self.map_type == 'structured':
|
|
@@ -1179,6 +1140,7 @@ class MapConfig(BaseModel):
|
|
|
1179
1140
|
:type map_index: tuple
|
|
1180
1141
|
:return: Raw data value.
|
|
1181
1142
|
"""
|
|
1143
|
+
raise RuntimeError(f'get_value not implemented')
|
|
1182
1144
|
scans, scan_number, scan_step_index = \
|
|
1183
1145
|
self.get_scan_step_index(map_index)
|
|
1184
1146
|
return data.get_value(scans, scan_number, scan_step_index,
|
|
@@ -1194,40 +1156,9 @@ def import_scanparser(station, experiment):
|
|
|
1194
1156
|
:type station: str
|
|
1195
1157
|
:param experiment: The experiment type.
|
|
1196
1158
|
:type experiment: Literal[
|
|
1197
|
-
'
|
|
1159
|
+
'EDD', 'GIWAXS', 'SAXSWAXS', 'TOMO', 'XRF']
|
|
1198
1160
|
"""
|
|
1199
|
-
|
|
1200
|
-
station = station.lower()
|
|
1201
|
-
experiment = experiment.lower()
|
|
1202
|
-
|
|
1203
1161
|
# Local modules
|
|
1204
|
-
|
|
1205
|
-
if experiment in ('saxswaxs', 'powder'):
|
|
1206
|
-
from CHAP.utils.scanparsers \
|
|
1207
|
-
import SMBLinearScanParser as ScanParser
|
|
1208
|
-
elif experiment == 'edd':
|
|
1209
|
-
from CHAP.utils.scanparsers \
|
|
1210
|
-
import SMBMCAScanParser as ScanParser
|
|
1211
|
-
elif experiment == 'tomo':
|
|
1212
|
-
from CHAP.utils.scanparsers \
|
|
1213
|
-
import SMBRotationScanParser as ScanParser
|
|
1214
|
-
else:
|
|
1215
|
-
raise ValueError(
|
|
1216
|
-
f'Invalid experiment type for station {station}: {experiment}')
|
|
1217
|
-
elif station == 'id3b':
|
|
1218
|
-
if experiment == 'saxswaxs':
|
|
1219
|
-
from CHAP.utils.scanparsers \
|
|
1220
|
-
import FMBSAXSWAXSScanParser as ScanParser
|
|
1221
|
-
elif experiment == 'tomo':
|
|
1222
|
-
from CHAP.utils.scanparsers \
|
|
1223
|
-
import FMBRotationScanParser as ScanParser
|
|
1224
|
-
elif experiment == 'xrf':
|
|
1225
|
-
from CHAP.utils.scanparsers \
|
|
1226
|
-
import FMBXRFScanParser as ScanParser
|
|
1227
|
-
else:
|
|
1228
|
-
raise ValueError(
|
|
1229
|
-
f'Invalid experiment type for station {station}: {experiment}')
|
|
1230
|
-
else:
|
|
1231
|
-
raise ValueError(f'Invalid station: {station}')
|
|
1162
|
+
from chess_scanparsers import choose_scanparser
|
|
1232
1163
|
|
|
1233
|
-
globals()['ScanParser'] =
|
|
1164
|
+
globals()['ScanParser'] = choose_scanparser(station, experiment)
|