Mercurial > repos > rv43 > tomo
comparison detector.py @ 49:26f99fdd8d61 draft
"planemo upload for repository https://github.com/rolfverberg/galaxytools commit 4f7738d02f4a3fd91373f43937ed311b6fe11a12"
author | rv43 |
---|---|
date | Thu, 28 Jul 2022 16:05:24 +0000 |
parents | |
children | 98a83f03d91b |
comparison
equal
deleted
inserted
replaced
48:059819ea1f0e | 49:26f99fdd8d61 |
---|---|
1 import logging | |
2 import os | |
3 import yaml | |
4 from functools import cache | |
5 from copy import deepcopy | |
6 | |
7 #from .general import * | |
8 from general import illegal_value, is_int, is_num, input_yesno | |
9 | |
10 #from hexrd.instrument import HEDMInstrument, PlanarDetector | |
11 | |
12 class DetectorConfig: | |
13 def __init__(self, config_source): | |
14 self._config_source = config_source | |
15 | |
16 if isinstance(self._config_source, str): | |
17 self._config_file = self._config_source | |
18 self._config = self._load_config_file() | |
19 elif isinstance(self._config_source, dict): | |
20 self._config_file = None | |
21 self._config = self._config_source | |
22 else: | |
23 self._config_file = None | |
24 self._config = False | |
25 | |
26 self._valid = self._validate() | |
27 | |
28 if not self.valid: | |
29 logging.error(f'Cannot create a valid instance of {self.__class__.__name__} '+ | |
30 f'from {self._config_source}') | |
31 | |
32 def __repr__(self): | |
33 return(f'{self.__class__.__name__}({self._config_source.__repr__()})') | |
34 def __str__(self): | |
35 return(f'{self.__class__.__name__} generated from {self._config_source}') | |
36 | |
37 @property | |
38 def config_file(self): | |
39 return(self._config_file) | |
40 | |
41 @property | |
42 def config(self): | |
43 return(deepcopy(self._config)) | |
44 | |
45 @property | |
46 def valid(self): | |
47 return(self._valid) | |
48 | |
49 def load_config_file(self): | |
50 raise(NotImplementedError) | |
51 | |
52 def validate(self): | |
53 raise(NotImplementedError) | |
54 | |
55 def _load_config_file(self): | |
56 return(self.load_config_file()) | |
57 | |
58 def _validate(self): | |
59 if not self.config: | |
60 logging.error('A configuration must be loaded prior to calling Detector._validate') | |
61 return(False) | |
62 else: | |
63 return(self.validate()) | |
64 | |
65 def _write_to_file(self, out_file): | |
66 out_file = os.path.abspath(out_file) | |
67 | |
68 current_config_valid = self.validate() | |
69 if not current_config_valid: | |
70 write_invalid_config = input_yesno(s=f'This {self.__class__.__name__} is currently '+ | |
71 f'invalid. Write the configuration to {out_file} anyways?', default='no') | |
72 if not write_invalid_config: | |
73 logging.info('In accordance with user input, the invalid configuration will '+ | |
74 f'not be written to {out_file}') | |
75 return | |
76 | |
77 if os.access(out_file, os.W_OK): | |
78 if os.path.exists(out_file): | |
79 overwrite = input_yesno(s=f'{out_file} already exists. Overwrite?', default='no') | |
80 if overwrite: | |
81 self.write_to_file(out_file) | |
82 else: | |
83 logging.info(f'In accordance with user input, {out_file} will not be '+ | |
84 'overwritten') | |
85 else: | |
86 self.write_to_file(out_file) | |
87 else: | |
88 logging.error(f'Insufficient permissions to write to {out_file}') | |
89 | |
90 def write_to_file(self, out_file): | |
91 raise(NotImplementedError) | |
92 | |
93 class YamlDetectorConfig(DetectorConfig): | |
94 def __init__(self, config_source, validate_yaml_pars=[]): | |
95 self._validate_yaml_pars = validate_yaml_pars | |
96 super().__init__(config_source) | |
97 | |
98 def load_config_file(self): | |
99 if not os.path.splitext(self._config_file)[1]: | |
100 if os.path.isfile(f'{self._config_file}.yml'): | |
101 self._config_file = f'{self._config_file}.yml' | |
102 if os.path.isfile(f'{self._config_file}.yaml'): | |
103 self._config_file = f'{self._config_file}.yaml' | |
104 if not os.path.isfile(self._config_file): | |
105 logging.error(f'Unable to load {self._config_file}') | |
106 return(False) | |
107 with open(self._config_file, 'r') as infile: | |
108 config = yaml.safe_load(infile) | |
109 if isinstance(config, dict): | |
110 return(config) | |
111 else: | |
112 logging.error(f'Unable to load {self._config_file} as a dictionary') | |
113 return(False) | |
114 | |
115 def validate(self): | |
116 if not self._validate_yaml_pars: | |
117 logging.warning('There are no required parameters provided for this detector '+ | |
118 'configuration') | |
119 return(True) | |
120 | |
121 def validate_nested_pars(config, validate_yaml_par): | |
122 yaml_par_levels = validate_yaml_par.split(':') | |
123 first_level_par = yaml_par_levels[0] | |
124 try: | |
125 first_level_par = int(first_level_par) | |
126 except: | |
127 pass | |
128 try: | |
129 next_level_config = config[first_level_par] | |
130 if len(yaml_par_levels) > 1: | |
131 next_level_pars = ':'.join(yaml_par_levels[1:]) | |
132 return(validate_nested_pars(next_level_config, next_level_pars)) | |
133 else: | |
134 return(True) | |
135 except: | |
136 return(False) | |
137 | |
138 pars_missing = [p for p in self._validate_yaml_pars | |
139 if not validate_nested_pars(self.config, p)] | |
140 if len(pars_missing) > 0: | |
141 logging.error(f'Missing item(s) in configuration: {", ".join(pars_missing)}') | |
142 return(False) | |
143 else: | |
144 return(True) | |
145 | |
146 def write_to_file(self, out_file): | |
147 with open(out_file, 'w') as outf: | |
148 yaml.dump(self.config, outf) | |
149 | |
150 | |
151 class TomoDetectorConfig(YamlDetectorConfig): | |
152 def __init__(self, config_source): | |
153 validate_yaml_pars = ['detector', | |
154 'lens_magnification', | |
155 'detector:pixels:rows', | |
156 'detector:pixels:columns', | |
157 *[f'detector:pixels:size:{i}' for i in range(2)]] | |
158 super().__init__(config_source, validate_yaml_pars=validate_yaml_pars) | |
159 | |
160 @property | |
161 @cache | |
162 def lens_magnification(self): | |
163 lens_magnification = self.config.get('lens_magnification') | |
164 if not isinstance(lens_magnification, (int, float)) or lens_magnification <= 0.: | |
165 illegal_value(lens_magnification, 'lens_magnification', 'detector file') | |
166 logging.warning('Using default lens_magnification value of 1.0') | |
167 return(1.0) | |
168 else: | |
169 return(lens_magnification) | |
170 | |
171 @property | |
172 @cache | |
173 def pixel_size(self): | |
174 pixel_size = self.config['detector'].get('pixels').get('size') | |
175 if isinstance(pixel_size, (int, float)): | |
176 if pixel_size <= 0.: | |
177 illegal_value(pixel_size, 'pixel_size', 'detector file') | |
178 return(None) | |
179 pixel_size /= self.lens_magnification | |
180 elif isinstance(pixel_size, list): | |
181 if ((len(pixel_size) > 2) or | |
182 (len(pixel_size) == 2 and pixel_size[0] != pixel_size[1])): | |
183 illegal_value(pixel_size, 'pixel size', 'detector file') | |
184 return(None) | |
185 elif not is_num(pixel_size[0], 0.): | |
186 illegal_value(pixel_size, 'pixel size', 'detector file') | |
187 return(None) | |
188 else: | |
189 pixel_size = pixel_size[0]/self.lens_magnification | |
190 else: | |
191 illegal_value(pixel_size, 'pixel size', 'detector file') | |
192 return(None) | |
193 | |
194 return(pixel_size) | |
195 | |
196 @property | |
197 @cache | |
198 def dimensions(self): | |
199 pixels = self.config['detector'].get('pixels') | |
200 num_rows = pixels.get('rows') | |
201 if not is_int(num_rows, 1): | |
202 illegal_value(num_rows, 'rows', 'detector file') | |
203 return(None) | |
204 num_columns = pixels.get('columns') | |
205 if not is_int(num_columns, 1): | |
206 illegal_value(num_columns, 'columns', 'detector file') | |
207 return(None) | |
208 return(num_rows, num_columns) | |
209 | |
210 | |
211 class EDDDetectorConfig(YamlDetectorConfig): | |
212 def __init__(self, config_source): | |
213 validate_yaml_pars = ['num_bins', | |
214 'max_E', | |
215 # 'angle', # KLS leave this out for now -- I think it has to do with the relative geometry of sample, beam, and detector (not a property of the detector on its own), so may not belong here in the DetectorConfig object? | |
216 'tth_angle', | |
217 'slope', | |
218 'intercept'] | |
219 super().__init__(config_source, validate_yaml_pars=validate_yaml_pars) | |
220 | |
221 @property | |
222 @cache | |
223 def num_bins(self): | |
224 try: | |
225 num_bins = int(self.config['num_bins']) | |
226 if num_bins <= 0: | |
227 raise(ValueError) | |
228 else: | |
229 return(num_bins) | |
230 except: | |
231 illegal_value(self.config['num_bins'], 'num_bins') | |
232 @property | |
233 @cache | |
234 def max_E(self): | |
235 try: | |
236 max_E = float(self.config['max_E']) | |
237 if max_E <= 0: | |
238 raise(ValueError) | |
239 else: | |
240 return(max_E) | |
241 except: | |
242 illegal_value(self.config['max_E'], 'max_E') | |
243 return(None) | |
244 | |
245 @property | |
246 def bin_energies(self): | |
247 return(self.slope * np.linspace(0, self.max_E, self.num_bins, endpoint=False) + | |
248 self.intercept) | |
249 | |
250 @property | |
251 def tth_angle(self): | |
252 try: | |
253 return(float(self.config['tth_angle'])) | |
254 except: | |
255 illegal_value(tth_angle, 'tth_angle') | |
256 return(None) | |
257 @tth_angle.setter | |
258 def tth_angle(self, value): | |
259 try: | |
260 self._config['tth_angle'] = float(value) | |
261 except: | |
262 illegal_value(value, 'tth_angle') | |
263 | |
264 @property | |
265 def slope(self): | |
266 try: | |
267 return(float(self.config['slope'])) | |
268 except: | |
269 illegal_value(slope, 'slope') | |
270 return(None) | |
271 @slope.setter | |
272 def slope(self, value): | |
273 try: | |
274 self._config['slope'] = float(value) | |
275 except: | |
276 illegal_value(value, 'slope') | |
277 | |
278 @property | |
279 def intercept(self): | |
280 try: | |
281 return(float(self.config['intercept'])) | |
282 except: | |
283 illegal_value(intercept, 'intercept') | |
284 return(None) | |
285 @intercept.setter | |
286 def intercept(self, value): | |
287 try: | |
288 self._config['intercept'] = float(value) | |
289 except: | |
290 illegal_value(value, 'intercept') | |
291 | |
292 | |
293 # class HexrdDetectorConfig(YamlDetectorConfig): | |
294 # def __init__(self, config_source, detector_names=[]): | |
295 # self.detector_names = detector_names | |
296 # validate_yaml_pars_each_detector = [*[f'buffer:{i}' for i in range(2)], | |
297 # 'distortion:function_name', | |
298 # *[f'distortion:parameters:{i}' for i in range(6)], | |
299 # 'pixels:columns', | |
300 # 'pixels:rows', | |
301 # *['pixels:size:%i' % i for i in range(2)], | |
302 # 'saturation_level', | |
303 # *[f'transform:tilt:{i}' for i in range(3)], | |
304 # *[f'transform:translation:{i}' for i in range(3)]] | |
305 # validate_yaml_pars = [] | |
306 # for detector_name in self.detector_names: | |
307 # validate_yaml_pars += [f'detectors:{detector_name}:{par}' for par in validate_yaml_pars_each_detector] | |
308 | |
309 # super().__init__(config_source, validate_yaml_pars=validate_yaml_pars) | |
310 | |
311 # def validate(self): | |
312 # yaml_valid = YamlDetectorConfig.validate(self) | |
313 # if not yaml_valid: | |
314 # return(False) | |
315 # else: | |
316 # hedm_instrument = HEDMInstrument(instrument_config=self.config) | |
317 # for detector_name in self.detector_names: | |
318 # if detector_name in hedm_instrument.detectors: | |
319 # if isinstance(hedm_instrument.detectors[detector_name], PlanarDetector): | |
320 # continue | |
321 # else: | |
322 # return(False) | |
323 # else: | |
324 # return(False) | |
325 # return(True) | |
326 | |
327 # class SAXSWAXSDetectorConfig(DetectorConfig): | |
328 # def __init__(self, config_source): | |
329 # super().__init__(config_source) | |
330 | |
331 # @property | |
332 # def ai(self): | |
333 # return(self.config) | |
334 # @ai.setter | |
335 # def ai(self, value): | |
336 # if isinstance(value, pyFAI.azimuthalIntegrator.AzimuthalIntegrator): | |
337 # self.config = ai | |
338 # else: | |
339 # illegal_value(value, 'azimuthal integrator') | |
340 | |
341 # # pyFAI will perform its own error-checking for the mask attribute. | |
342 # mask = property(self.ai.get_mask, self.ai,set_mask) | |
343 | |
344 | |
345 |