brkraw-sordino 0.1.3__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.
- brkraw_sordino/__init__.py +7 -0
- brkraw_sordino/brkraw_hook.yaml +8 -0
- brkraw_sordino/docs.md +99 -0
- brkraw_sordino/helper.py +81 -0
- brkraw_sordino/hook.py +164 -0
- brkraw_sordino/orientation.py +45 -0
- brkraw_sordino/recon.py +125 -0
- brkraw_sordino/rules/sordino.yaml +35 -0
- brkraw_sordino/specs/info_spec.yaml +164 -0
- brkraw_sordino/specs/metadata_spec.yaml +167 -0
- brkraw_sordino/specs/prune4recon.yaml +44 -0
- brkraw_sordino/specs/recon_spec.yaml +103 -0
- brkraw_sordino/specs/utils.py +212 -0
- brkraw_sordino/spoketiming.py +168 -0
- brkraw_sordino/traj.py +316 -0
- brkraw_sordino/typing.py +25 -0
- brkraw_sordino-0.1.3.dist-info/METADATA +114 -0
- brkraw_sordino-0.1.3.dist-info/RECORD +21 -0
- brkraw_sordino-0.1.3.dist-info/WHEEL +5 -0
- brkraw_sordino-0.1.3.dist-info/entry_points.txt +2 -0
- brkraw_sordino-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
__meta__:
|
|
2
|
+
name: "sordino_info"
|
|
3
|
+
version: "0.2.0"
|
|
4
|
+
description: "Scan info spec for SORDINO, based on brkraw scan.yaml plus tuning parameters."
|
|
5
|
+
category: "info_spec"
|
|
6
|
+
transforms_source: "./utils.py"
|
|
7
|
+
|
|
8
|
+
Method:
|
|
9
|
+
sources:
|
|
10
|
+
- file: acqp
|
|
11
|
+
key: ACQ_method
|
|
12
|
+
transform: strip_jcamp_string
|
|
13
|
+
|
|
14
|
+
Protocol:
|
|
15
|
+
sources:
|
|
16
|
+
- file: acqp
|
|
17
|
+
key: ACQ_protocol_name
|
|
18
|
+
transform: strip_jcamp_string
|
|
19
|
+
|
|
20
|
+
Dim:
|
|
21
|
+
sources:
|
|
22
|
+
- file: acqp
|
|
23
|
+
key: ACQ_dim
|
|
24
|
+
|
|
25
|
+
DimDesc:
|
|
26
|
+
sources:
|
|
27
|
+
- file: acqp
|
|
28
|
+
key: ACQ_dim_desc
|
|
29
|
+
transform: convert_to_list
|
|
30
|
+
|
|
31
|
+
NumEchoes:
|
|
32
|
+
sources:
|
|
33
|
+
- file: acqp
|
|
34
|
+
key: NECHOES
|
|
35
|
+
|
|
36
|
+
TE (ms):
|
|
37
|
+
sources:
|
|
38
|
+
- file: method
|
|
39
|
+
key: PVM_EchoTime
|
|
40
|
+
- file: acqp
|
|
41
|
+
key: ACQ_echo_time
|
|
42
|
+
transform: convert_to_list
|
|
43
|
+
|
|
44
|
+
TR (ms):
|
|
45
|
+
sources:
|
|
46
|
+
- file: method
|
|
47
|
+
key: PVM_RepetitionTime
|
|
48
|
+
- file: method
|
|
49
|
+
key: RepetitionTime
|
|
50
|
+
- file: acqp
|
|
51
|
+
key: ACQ_repetition_time
|
|
52
|
+
transform: convert_to_list
|
|
53
|
+
|
|
54
|
+
FlipAngle (degree):
|
|
55
|
+
sources:
|
|
56
|
+
- file: acqp
|
|
57
|
+
key: ACQ_flip_angle
|
|
58
|
+
|
|
59
|
+
Shape:
|
|
60
|
+
sources:
|
|
61
|
+
- file: method
|
|
62
|
+
key: PVM_Matrix
|
|
63
|
+
transform: convert_to_list
|
|
64
|
+
|
|
65
|
+
FOV (mm):
|
|
66
|
+
sources:
|
|
67
|
+
- file: method
|
|
68
|
+
key: PVM_Fov
|
|
69
|
+
transform: convert_to_list
|
|
70
|
+
|
|
71
|
+
NumSlicePack:
|
|
72
|
+
sources:
|
|
73
|
+
- file: method
|
|
74
|
+
key: PVM_NSPacks
|
|
75
|
+
|
|
76
|
+
SliceOrient:
|
|
77
|
+
sources:
|
|
78
|
+
- file: method
|
|
79
|
+
key: PVM_SPackArrSliceOrient
|
|
80
|
+
transform: convert_to_list
|
|
81
|
+
|
|
82
|
+
ReadOrient:
|
|
83
|
+
sources:
|
|
84
|
+
- file: method
|
|
85
|
+
key: PVM_SPackArrReadOrient
|
|
86
|
+
transform: convert_to_list
|
|
87
|
+
|
|
88
|
+
SliceDistance (mm):
|
|
89
|
+
sources:
|
|
90
|
+
- file: method
|
|
91
|
+
key: PVM_SPackArrSliceDistance
|
|
92
|
+
transform: convert_to_list
|
|
93
|
+
|
|
94
|
+
SliceGap (mm):
|
|
95
|
+
sources:
|
|
96
|
+
- file: method
|
|
97
|
+
key: PVM_SPackArrSliceGap
|
|
98
|
+
transform: convert_to_list
|
|
99
|
+
|
|
100
|
+
NumAverage:
|
|
101
|
+
sources:
|
|
102
|
+
- file: method
|
|
103
|
+
key: PVM_NAverages
|
|
104
|
+
|
|
105
|
+
NumRepeat:
|
|
106
|
+
sources:
|
|
107
|
+
- file: method
|
|
108
|
+
key: PVM_NRepetitions
|
|
109
|
+
|
|
110
|
+
EncNReceivers:
|
|
111
|
+
sources:
|
|
112
|
+
- file: method
|
|
113
|
+
key: PVM_EncNReceivers
|
|
114
|
+
|
|
115
|
+
NPoints:
|
|
116
|
+
sources:
|
|
117
|
+
- file: method
|
|
118
|
+
key: NPoints
|
|
119
|
+
|
|
120
|
+
NPro:
|
|
121
|
+
sources:
|
|
122
|
+
- file: method
|
|
123
|
+
key: NPro
|
|
124
|
+
|
|
125
|
+
EffBandwidth (Hz):
|
|
126
|
+
sources:
|
|
127
|
+
- file: method
|
|
128
|
+
key: PVM_EffSWh
|
|
129
|
+
|
|
130
|
+
OverSampling:
|
|
131
|
+
sources:
|
|
132
|
+
- file: method
|
|
133
|
+
key: OverSampling
|
|
134
|
+
|
|
135
|
+
AcqDelayTotal (us):
|
|
136
|
+
sources:
|
|
137
|
+
- file: method
|
|
138
|
+
key: AcqDelayTotal
|
|
139
|
+
|
|
140
|
+
UnderSampling:
|
|
141
|
+
sources:
|
|
142
|
+
- file: method
|
|
143
|
+
key: ProUnderSampling
|
|
144
|
+
|
|
145
|
+
SpatialResolution (mm):
|
|
146
|
+
sources:
|
|
147
|
+
- file: method
|
|
148
|
+
key: PVM_SpatResol
|
|
149
|
+
transform: convert_to_list
|
|
150
|
+
|
|
151
|
+
HalfAcquisition:
|
|
152
|
+
sources:
|
|
153
|
+
- file: method
|
|
154
|
+
key: HalfAcquisition
|
|
155
|
+
|
|
156
|
+
UseOrigin:
|
|
157
|
+
sources:
|
|
158
|
+
- file: method
|
|
159
|
+
key: UseOrigin
|
|
160
|
+
|
|
161
|
+
Reorder:
|
|
162
|
+
sources:
|
|
163
|
+
- file: method
|
|
164
|
+
key: Reorder
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
__meta__:
|
|
2
|
+
name: "sordino_metadata"
|
|
3
|
+
version: "0.1.0"
|
|
4
|
+
description: "Draft metadata mapping for ZTE-based fMRI (BEP proposal alignment)."
|
|
5
|
+
category: "metadata_spec"
|
|
6
|
+
transforms_source: "./utils.py"
|
|
7
|
+
|
|
8
|
+
RepetitionTime:
|
|
9
|
+
sources:
|
|
10
|
+
- file: method
|
|
11
|
+
key: PVM_RepetitionTime
|
|
12
|
+
- file: method
|
|
13
|
+
key: RepetitionTime
|
|
14
|
+
transform: ms_to_s
|
|
15
|
+
|
|
16
|
+
EchoTime:
|
|
17
|
+
sources:
|
|
18
|
+
- file: method
|
|
19
|
+
key: PVM_EchoTime
|
|
20
|
+
transform: ms_to_s
|
|
21
|
+
|
|
22
|
+
FlipAngle:
|
|
23
|
+
sources:
|
|
24
|
+
- file: acqp
|
|
25
|
+
key: ACQ_flip_angle
|
|
26
|
+
transform: first_float
|
|
27
|
+
|
|
28
|
+
MRAcquisitionType:
|
|
29
|
+
inputs:
|
|
30
|
+
value:
|
|
31
|
+
const: "3D"
|
|
32
|
+
transform: value_only
|
|
33
|
+
|
|
34
|
+
Manufacturer:
|
|
35
|
+
inputs:
|
|
36
|
+
value:
|
|
37
|
+
const: "Bruker"
|
|
38
|
+
transform: value_only
|
|
39
|
+
|
|
40
|
+
SequenceName:
|
|
41
|
+
sources:
|
|
42
|
+
- file: method
|
|
43
|
+
key: Method
|
|
44
|
+
transform: strip_bruker_string
|
|
45
|
+
|
|
46
|
+
ZTETechnique:
|
|
47
|
+
inputs:
|
|
48
|
+
method:
|
|
49
|
+
sources:
|
|
50
|
+
- file: method
|
|
51
|
+
key: Method
|
|
52
|
+
- file: method
|
|
53
|
+
key: PULPROG
|
|
54
|
+
transform: infer_zte_technique
|
|
55
|
+
|
|
56
|
+
ZTETrajectoryType:
|
|
57
|
+
sources:
|
|
58
|
+
- file: method
|
|
59
|
+
key: PVM_TrajType
|
|
60
|
+
- file: method
|
|
61
|
+
key: PVM_EncTraj
|
|
62
|
+
- file: method
|
|
63
|
+
key: TrajType
|
|
64
|
+
transform: normalize_trajectory
|
|
65
|
+
|
|
66
|
+
ZTEReadoutPoints:
|
|
67
|
+
sources:
|
|
68
|
+
- file: method
|
|
69
|
+
key: NPoints
|
|
70
|
+
transform: first_int
|
|
71
|
+
|
|
72
|
+
ZTEProjections:
|
|
73
|
+
sources:
|
|
74
|
+
- file: method
|
|
75
|
+
key: NPro
|
|
76
|
+
transform: first_int
|
|
77
|
+
|
|
78
|
+
ZTEOverSampling:
|
|
79
|
+
sources:
|
|
80
|
+
- file: method
|
|
81
|
+
key: OverSampling
|
|
82
|
+
transform: first_float
|
|
83
|
+
|
|
84
|
+
ZTEEffectiveBandwidth:
|
|
85
|
+
sources:
|
|
86
|
+
- file: method
|
|
87
|
+
key: PVM_EffSWh
|
|
88
|
+
transform: first_float
|
|
89
|
+
|
|
90
|
+
ZTEAcqDelayTotal:
|
|
91
|
+
sources:
|
|
92
|
+
- file: method
|
|
93
|
+
key: AcqDelayTotal
|
|
94
|
+
transform: first_float
|
|
95
|
+
|
|
96
|
+
ZTEUnderSampling:
|
|
97
|
+
sources:
|
|
98
|
+
- file: method
|
|
99
|
+
key: ProUnderSampling
|
|
100
|
+
transform: first_float
|
|
101
|
+
|
|
102
|
+
ZTEReceiverChannels:
|
|
103
|
+
sources:
|
|
104
|
+
- file: method
|
|
105
|
+
key: PVM_EncNReceivers
|
|
106
|
+
transform: first_int
|
|
107
|
+
|
|
108
|
+
ReconMatrix:
|
|
109
|
+
sources:
|
|
110
|
+
- file: method
|
|
111
|
+
key: PVM_Matrix
|
|
112
|
+
transform: to_int_list
|
|
113
|
+
|
|
114
|
+
FieldOfView:
|
|
115
|
+
sources:
|
|
116
|
+
- file: method
|
|
117
|
+
key: PVM_Fov
|
|
118
|
+
transform: to_float_list
|
|
119
|
+
|
|
120
|
+
SpatialResolution:
|
|
121
|
+
sources:
|
|
122
|
+
- file: method
|
|
123
|
+
key: PVM_SpatResol
|
|
124
|
+
transform: to_float_list
|
|
125
|
+
|
|
126
|
+
SliceOrientation:
|
|
127
|
+
sources:
|
|
128
|
+
- file: method
|
|
129
|
+
key: PVM_SPackArrSliceOrient
|
|
130
|
+
transform: convert_to_list
|
|
131
|
+
|
|
132
|
+
ReadoutOrientation:
|
|
133
|
+
sources:
|
|
134
|
+
- file: method
|
|
135
|
+
key: PVM_SPackArrReadOrient
|
|
136
|
+
transform: convert_to_list
|
|
137
|
+
|
|
138
|
+
ConversionSoftware:
|
|
139
|
+
inputs:
|
|
140
|
+
value:
|
|
141
|
+
const: "brkraw-sordino"
|
|
142
|
+
transform: value_only
|
|
143
|
+
|
|
144
|
+
ConversionSoftwareVersion:
|
|
145
|
+
inputs:
|
|
146
|
+
value:
|
|
147
|
+
const: "brkraw-sordino"
|
|
148
|
+
transform: plugin_version
|
|
149
|
+
|
|
150
|
+
ConversionMethod:
|
|
151
|
+
inputs:
|
|
152
|
+
package:
|
|
153
|
+
const: "brkraw-sordino"
|
|
154
|
+
name:
|
|
155
|
+
const: "brkraw-sordino"
|
|
156
|
+
transform: conversion_method
|
|
157
|
+
|
|
158
|
+
SourceDataset:
|
|
159
|
+
inputs:
|
|
160
|
+
value:
|
|
161
|
+
const: null
|
|
162
|
+
transform: source_dataset_env
|
|
163
|
+
|
|
164
|
+
ScanIdentifier:
|
|
165
|
+
inputs:
|
|
166
|
+
scan_id: "$scan_id"
|
|
167
|
+
transform: scan_id_to_string
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
__meta__:
|
|
2
|
+
name: "prune4recon"
|
|
3
|
+
version: "0.1.0"
|
|
4
|
+
description: >
|
|
5
|
+
De-identification pruner spec for Bruker ParaVision datasets that preserves the
|
|
6
|
+
FID file for reconstruction purposes. The spec drops the subject file and redacts
|
|
7
|
+
potential PII from JCAMP-DX parameters. Supports template variables:
|
|
8
|
+
$subject_id, $subject_name, $study_id.
|
|
9
|
+
category: "pruner_spec"
|
|
10
|
+
|
|
11
|
+
mode: "keep"
|
|
12
|
+
|
|
13
|
+
files:
|
|
14
|
+
- "method"
|
|
15
|
+
- "acqp"
|
|
16
|
+
- "reco"
|
|
17
|
+
- "rawdata.job0"
|
|
18
|
+
- "visu_pars"
|
|
19
|
+
- "2dseq"
|
|
20
|
+
|
|
21
|
+
update_params:
|
|
22
|
+
acqp:
|
|
23
|
+
ACQ_operator: ""
|
|
24
|
+
ACQ_institution: ""
|
|
25
|
+
ACQ_station: ""
|
|
26
|
+
ACQ_time: "<00:00:00 01 Jan 2000>"
|
|
27
|
+
ACQ_abs_time: "0"
|
|
28
|
+
|
|
29
|
+
visu_pars:
|
|
30
|
+
VisuInstitution: ""
|
|
31
|
+
VisuSubjectId: "$subject_id"
|
|
32
|
+
VisuSubjectName: "$subject_name"
|
|
33
|
+
VisuStudyId: "$study_id"
|
|
34
|
+
VisuStudyDate: "<00:00:00 01 Jan 2000>"
|
|
35
|
+
VisuStudyUid: "<1.2.3.4.5>"
|
|
36
|
+
VisuSeriesUid: "<1.2.3.4.5.1>"
|
|
37
|
+
VisuFrameUid: "<1.2.3.4.5.2>"
|
|
38
|
+
VisuSystemOrderNumber: "<>"
|
|
39
|
+
VisuInstitution: "<>"
|
|
40
|
+
|
|
41
|
+
reco:
|
|
42
|
+
RECO_base_image_uid: "<1.2.3.4.5.6>"
|
|
43
|
+
|
|
44
|
+
add_root: true
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
__meta__:
|
|
2
|
+
name: "sordino_recon"
|
|
3
|
+
version: "0.1.0"
|
|
4
|
+
description: "Parameter mapping for SORDINO reconstruction."
|
|
5
|
+
category: "recon_spec"
|
|
6
|
+
transforms_source: "./utils.py"
|
|
7
|
+
|
|
8
|
+
SliceOrientation:
|
|
9
|
+
sources:
|
|
10
|
+
- file: method
|
|
11
|
+
key: PVM_SPackArrSliceOrient
|
|
12
|
+
transform: first
|
|
13
|
+
|
|
14
|
+
ReadoutOrientation:
|
|
15
|
+
sources:
|
|
16
|
+
- file: method
|
|
17
|
+
key: PVM_SPackArrReadOrient
|
|
18
|
+
transform: first
|
|
19
|
+
|
|
20
|
+
GradientOrientation:
|
|
21
|
+
sources:
|
|
22
|
+
- file: method
|
|
23
|
+
key: PVM_SPackArrGradOrient
|
|
24
|
+
transform: squeeze_ndarray
|
|
25
|
+
|
|
26
|
+
EncNReceivers:
|
|
27
|
+
sources:
|
|
28
|
+
- file: method
|
|
29
|
+
key: PVM_EncNReceivers
|
|
30
|
+
transform: first_int
|
|
31
|
+
|
|
32
|
+
NPoints:
|
|
33
|
+
sources:
|
|
34
|
+
- file: method
|
|
35
|
+
key: NPoints
|
|
36
|
+
transform: first_int
|
|
37
|
+
|
|
38
|
+
NPro:
|
|
39
|
+
sources:
|
|
40
|
+
- file: method
|
|
41
|
+
key: NPro
|
|
42
|
+
transform: first_int
|
|
43
|
+
|
|
44
|
+
RepetitionTime_ms:
|
|
45
|
+
sources:
|
|
46
|
+
- file: method
|
|
47
|
+
key: RepetitionTime
|
|
48
|
+
- file: method
|
|
49
|
+
key: PVM_RepetitionTime
|
|
50
|
+
transform: first_float
|
|
51
|
+
|
|
52
|
+
EffBandwidth_Hz:
|
|
53
|
+
sources:
|
|
54
|
+
- file: method
|
|
55
|
+
key: PVM_EffSWh
|
|
56
|
+
transform: first_float
|
|
57
|
+
|
|
58
|
+
OverSampling:
|
|
59
|
+
sources:
|
|
60
|
+
- file: method
|
|
61
|
+
key: OverSampling
|
|
62
|
+
transform: first_float
|
|
63
|
+
|
|
64
|
+
AcqDelayTotal_us:
|
|
65
|
+
sources:
|
|
66
|
+
- file: method
|
|
67
|
+
key: AcqDelayTotal
|
|
68
|
+
transform: first_float
|
|
69
|
+
|
|
70
|
+
NRepetitions:
|
|
71
|
+
sources:
|
|
72
|
+
- file: method
|
|
73
|
+
key: PVM_NRepetitions
|
|
74
|
+
transform: first_int
|
|
75
|
+
|
|
76
|
+
Matrix:
|
|
77
|
+
sources:
|
|
78
|
+
- file: method
|
|
79
|
+
key: PVM_Matrix
|
|
80
|
+
|
|
81
|
+
UnderSampling:
|
|
82
|
+
sources:
|
|
83
|
+
- file: method
|
|
84
|
+
key: ProUnderSampling
|
|
85
|
+
transform: first_float
|
|
86
|
+
|
|
87
|
+
HalfAcquisition:
|
|
88
|
+
sources:
|
|
89
|
+
- file: method
|
|
90
|
+
key: HalfAcquisition
|
|
91
|
+
transform: to_bool
|
|
92
|
+
|
|
93
|
+
UseOrigin:
|
|
94
|
+
sources:
|
|
95
|
+
- file: method
|
|
96
|
+
key: UseOrigin
|
|
97
|
+
transform: to_bool
|
|
98
|
+
|
|
99
|
+
Reorder:
|
|
100
|
+
sources:
|
|
101
|
+
- file: method
|
|
102
|
+
key: Reorder
|
|
103
|
+
transform: to_bool
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Transform helpers for brkraw-sordino metadata and info mapping."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import numpy as np
|
|
8
|
+
from typing import Any, Optional, cast
|
|
9
|
+
from importlib import metadata
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def squeeze_ndarray(value: np.ndarray) -> np.ndarray:
|
|
13
|
+
return value.squeeze()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def strip_bruker_string(value: Any) -> Any:
|
|
17
|
+
if value is None:
|
|
18
|
+
return None
|
|
19
|
+
text = str(value).strip()
|
|
20
|
+
if text.startswith("<") and text.endswith(">"):
|
|
21
|
+
text = text[1:-1]
|
|
22
|
+
return text.strip()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def strip_jcamp_string(value: Optional[str]) -> str:
|
|
26
|
+
if value is None:
|
|
27
|
+
return "Unknown"
|
|
28
|
+
text = str(value).strip()
|
|
29
|
+
if text.startswith("<") and text.endswith(">"):
|
|
30
|
+
text = text[1:-1]
|
|
31
|
+
text = re.sub(r"\^+", " ", text)
|
|
32
|
+
return " ".join(text.split())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ensure_list(value: Any) -> Optional[list]:
|
|
36
|
+
if value is None:
|
|
37
|
+
return None
|
|
38
|
+
if isinstance(value, (list, tuple)):
|
|
39
|
+
return list(value)
|
|
40
|
+
return [value]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def convert_to_list(value: Any):
|
|
44
|
+
if value is None:
|
|
45
|
+
return []
|
|
46
|
+
if hasattr(value, "tolist"):
|
|
47
|
+
return cast(Any, value).tolist()
|
|
48
|
+
if isinstance(value, (list, tuple)):
|
|
49
|
+
return list(value)
|
|
50
|
+
return [value]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def first(value: Any) -> Any:
|
|
54
|
+
if value is None:
|
|
55
|
+
return None
|
|
56
|
+
if isinstance(value, (list, tuple)) and value:
|
|
57
|
+
return value[0]
|
|
58
|
+
try:
|
|
59
|
+
import numpy as np
|
|
60
|
+
if isinstance(value, np.ndarray) and value.size:
|
|
61
|
+
return value.flat[0]
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
return value
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def first_float(value: Any) -> Optional[float]:
|
|
68
|
+
val = first(value)
|
|
69
|
+
if val is None:
|
|
70
|
+
return None
|
|
71
|
+
try:
|
|
72
|
+
return float(val)
|
|
73
|
+
except Exception:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def first_int(value: Any) -> Optional[int]:
|
|
78
|
+
val = first(value)
|
|
79
|
+
if val is None:
|
|
80
|
+
return None
|
|
81
|
+
try:
|
|
82
|
+
return int(val)
|
|
83
|
+
except Exception:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def to_float_list(value: Any) -> Optional[list]:
|
|
88
|
+
values = ensure_list(value)
|
|
89
|
+
if values is None:
|
|
90
|
+
return None
|
|
91
|
+
out = []
|
|
92
|
+
for item in values:
|
|
93
|
+
try:
|
|
94
|
+
out.append(float(item))
|
|
95
|
+
except Exception:
|
|
96
|
+
return None
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def to_int_list(value: Any) -> Optional[list]:
|
|
101
|
+
values = ensure_list(value)
|
|
102
|
+
if values is None:
|
|
103
|
+
return None
|
|
104
|
+
out = []
|
|
105
|
+
for item in values:
|
|
106
|
+
try:
|
|
107
|
+
out.append(int(item))
|
|
108
|
+
except Exception:
|
|
109
|
+
return None
|
|
110
|
+
return out
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def to_bool(value: Any) -> bool:
|
|
114
|
+
if isinstance(value, str):
|
|
115
|
+
return value.strip().lower() in {"yes", "true", "1", "on"}
|
|
116
|
+
return bool(value)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def ms_to_s(value: Any) -> Optional[float]:
|
|
120
|
+
val = first_float(value)
|
|
121
|
+
if val is None:
|
|
122
|
+
return None
|
|
123
|
+
return val * 1e-3
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def dwell_time_seconds(*, sw_hz: Any = None, dwell_us: Any = None) -> Optional[float]:
|
|
127
|
+
sw = first_float(sw_hz)
|
|
128
|
+
if sw:
|
|
129
|
+
return 1.0 / sw
|
|
130
|
+
dwell = first_float(dwell_us)
|
|
131
|
+
if dwell is None:
|
|
132
|
+
return None
|
|
133
|
+
return float(dwell) * 1e-6 * 2.0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def lower_string(value: Any) -> Optional[str]:
|
|
137
|
+
if value is None:
|
|
138
|
+
return None
|
|
139
|
+
return str(value).strip().lower()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _normalize_text(value: Any) -> Optional[str]:
|
|
143
|
+
if value is None:
|
|
144
|
+
return None
|
|
145
|
+
text = str(value).strip()
|
|
146
|
+
return " ".join(text.split()) if text else None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def infer_zte_technique(*, method: Any = None, sequence: Any = None) -> Optional[str]:
|
|
150
|
+
text = _normalize_text(method) or _normalize_text(sequence)
|
|
151
|
+
if not text:
|
|
152
|
+
return None
|
|
153
|
+
lowered = text.lower()
|
|
154
|
+
if "sordino" in lowered:
|
|
155
|
+
return "SORDINO"
|
|
156
|
+
if "swift" in lowered:
|
|
157
|
+
return "MB-SWIFT"
|
|
158
|
+
if "zte" in lowered:
|
|
159
|
+
return "ZTE"
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def normalize_trajectory(value: Any) -> Optional[str]:
|
|
164
|
+
text = _normalize_text(value)
|
|
165
|
+
if not text:
|
|
166
|
+
return None
|
|
167
|
+
lowered = text.lower()
|
|
168
|
+
mapping = {
|
|
169
|
+
"radial": "radial",
|
|
170
|
+
"spiral": "spiral",
|
|
171
|
+
"cartesian": "cartesian",
|
|
172
|
+
"cart": "cartesian",
|
|
173
|
+
"cones": "cones",
|
|
174
|
+
"rosette": "rosette",
|
|
175
|
+
}
|
|
176
|
+
for key, target in mapping.items():
|
|
177
|
+
if key in lowered:
|
|
178
|
+
return target
|
|
179
|
+
return lowered
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def value_only(*, value: Any) -> Any:
|
|
183
|
+
return value
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def plugin_version(*, value: Any) -> str:
|
|
187
|
+
pkg = str(value or "brkraw-sordino")
|
|
188
|
+
try:
|
|
189
|
+
return metadata.version(pkg)
|
|
190
|
+
except Exception:
|
|
191
|
+
return "unknown"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def conversion_method(*, package: Any = None, name: Any = None) -> str:
|
|
195
|
+
pkg = str(package or name or "brkraw-sordino")
|
|
196
|
+
base = str(name or pkg)
|
|
197
|
+
try:
|
|
198
|
+
version = metadata.version(pkg)
|
|
199
|
+
except Exception:
|
|
200
|
+
version = None
|
|
201
|
+
return f"{base} v{version}" if version else base
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def source_dataset_env(*, value: Any) -> Optional[str]:
|
|
205
|
+
_ = value
|
|
206
|
+
return os.environ.get("BRKRAW_SOURCE_DATASET")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def scan_id_to_string(*, scan_id: Any) -> Optional[str]:
|
|
210
|
+
if scan_id is None:
|
|
211
|
+
return None
|
|
212
|
+
return str(scan_id)
|