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 |
