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