Mercurial > repos > kls286 > chap_test_20230328
comparison CHAP/models/integration.py @ 0:cbbe42422d56 draft
planemo upload for repository https://github.com/CHESSComputing/ChessAnalysisPipeline/tree/galaxy commit 1401a7e1ae007a6bda260d147f9b879e789b73e0-dirty
| author | kls286 |
|---|---|
| date | Tue, 28 Mar 2023 15:07:30 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:cbbe42422d56 |
|---|---|
| 1 import copy | |
| 2 from functools import cache, lru_cache | |
| 3 import json | |
| 4 import logging | |
| 5 import os | |
| 6 from time import time | |
| 7 from typing import Literal, Optional | |
| 8 | |
| 9 # from multiprocessing.pool import ThreadPool | |
| 10 # from nexusformat.nexus import (NXdata, | |
| 11 # NXdetector, | |
| 12 # NXfield, | |
| 13 # NXprocess, | |
| 14 # NXroot) | |
| 15 import numpy as np | |
| 16 from pydantic import (BaseModel, | |
| 17 validator, | |
| 18 constr, | |
| 19 conlist, | |
| 20 conint, | |
| 21 confloat, | |
| 22 FilePath) | |
| 23 #import pyFAI, pyFAI.multi_geometry, pyFAI.units | |
| 24 from pyFAI import load as pyfai_load | |
| 25 from pyFAI.multi_geometry import MultiGeometry | |
| 26 from pyFAI.units import AZIMUTHAL_UNITS, RADIAL_UNITS | |
| 27 #from pyspec.file.tiff import TiffFile | |
| 28 | |
| 29 #from .map import MapConfig, SpecScans | |
| 30 | |
| 31 | |
| 32 class Detector(BaseModel): | |
| 33 """ | |
| 34 Detector class to represent a single detector used in the experiment. | |
| 35 | |
| 36 :param prefix: Prefix of the detector in the SPEC file. | |
| 37 :type prefix: str | |
| 38 :param poni_file: Path to the poni file. | |
| 39 :type poni_file: str | |
| 40 :param mask_file: Optional path to the mask file. | |
| 41 :type mask_file: str, optional | |
| 42 """ | |
| 43 prefix: constr(strip_whitespace=True, min_length=1) | |
| 44 poni_file: FilePath | |
| 45 mask_file: Optional[FilePath] | |
| 46 @validator('poni_file', allow_reuse=True) | |
| 47 def validate_poni_file(cls, poni_file): | |
| 48 """ | |
| 49 Validate the poni file by checking if it's a valid PONI file. | |
| 50 | |
| 51 :param poni_file: Path to the poni file. | |
| 52 :type poni_file: str | |
| 53 :raises ValueError: If poni_file is not a valid PONI file. | |
| 54 :returns: Absolute path to the poni file. | |
| 55 :rtype: str | |
| 56 """ | |
| 57 poni_file = os.path.abspath(poni_file) | |
| 58 try: | |
| 59 ai = azimuthal_integrator(poni_file) | |
| 60 except: | |
| 61 raise(ValueError(f'{poni_file} is not a valid PONI file')) | |
| 62 else: | |
| 63 return(poni_file) | |
| 64 @validator('mask_file', allow_reuse=True) | |
| 65 def validate_mask_file(cls, mask_file, values): | |
| 66 """ | |
| 67 Validate the mask file. If a mask file is provided, it checks if it's a valid TIFF file. | |
| 68 | |
| 69 :param mask_file: Path to the mask file. | |
| 70 :type mask_file: str or None | |
| 71 :param values: A dictionary of the Detector fields. | |
| 72 :type values: dict | |
| 73 :raises ValueError: If mask_file is provided and it's not a valid TIFF file. | |
| 74 :raises ValueError: If `'poni_file'` is not provided in `values`. | |
| 75 :returns: Absolute path to the mask file or None. | |
| 76 :rtype: str or None | |
| 77 """ | |
| 78 if mask_file is None: | |
| 79 return(mask_file) | |
| 80 else: | |
| 81 mask_file = os.path.abspath(mask_file) | |
| 82 poni_file = values.get('poni_file') | |
| 83 if poni_file is None: | |
| 84 raise(ValueError('Cannot validate mask file without a PONI file.')) | |
| 85 else: | |
| 86 try: | |
| 87 mask_array = get_mask_array(mask_file, poni_file) | |
| 88 except BaseException as e: | |
| 89 raise(ValueError(f'Unable to open {mask_file} as a TIFF file')) | |
| 90 else: | |
| 91 return(mask_file) | |
| 92 @property | |
| 93 def azimuthal_integrator(self): | |
| 94 return(azimuthal_integrator(self.poni_file)) | |
| 95 @property | |
| 96 def mask_array(self): | |
| 97 return(get_mask_array(self.mask_file, self.poni_file)) | |
| 98 | |
| 99 @cache | |
| 100 def azimuthal_integrator(poni_file:str): | |
| 101 if not isinstance(poni_file, str): | |
| 102 poni_file = str(poni_file) | |
| 103 return(pyfai_load(poni_file)) | |
| 104 @cache | |
| 105 def get_mask_array(mask_file:str, poni_file:str): | |
| 106 if mask_file is not None: | |
| 107 if not isinstance(mask_file, str): | |
| 108 mask_file = str(mask_file) | |
| 109 | |
| 110 from pyspec.file.tiff import TiffFile | |
| 111 with TiffFile(mask_file) as tiff: | |
| 112 mask_array = tiff.asarray() | |
| 113 else: | |
| 114 mask_array = np.zeros(azimuthal_integrator(poni_file).detector.shape) | |
| 115 return(mask_array) | |
| 116 | |
| 117 class IntegrationConfig(BaseModel): | |
| 118 """ | |
| 119 Class representing the configuration for a raw detector data integration. | |
| 120 | |
| 121 :ivar tool_type: type of integration tool; always set to "integration" | |
| 122 :type tool_type: str, optional | |
| 123 :ivar title: title of the integration | |
| 124 :type title: str | |
| 125 :ivar integration_type: type of integration, one of "azimuthal", "radial", or "cake" | |
| 126 :type integration_type: str | |
| 127 :ivar detectors: list of detectors used in the integration | |
| 128 :type detectors: List[Detector] | |
| 129 :ivar radial_units: radial units for the integration, defaults to `'q_A^-1'` | |
| 130 :type radial_units: str, optional | |
| 131 :ivar radial_min: minimum radial value for the integration range | |
| 132 :type radial_min: float, optional | |
| 133 :ivar radial_max: maximum radial value for the integration range | |
| 134 :type radial_max: float, optional | |
| 135 :ivar radial_npt: number of points in the radial range for the integration | |
| 136 :type radial_npt: int, optional | |
| 137 :ivar azimuthal_units: azimuthal units for the integration | |
| 138 :type azimuthal_units: str, optional | |
| 139 :ivar azimuthal_min: minimum azimuthal value for the integration range | |
| 140 :type azimuthal_min: float, optional | |
| 141 :ivar azimuthal_max: maximum azimuthal value for the integration range | |
| 142 :type azimuthal_max: float, optional | |
| 143 :ivar azimuthal_npt: number of points in the azimuthal range for the integration | |
| 144 :type azimuthal_npt: int, optional | |
| 145 :ivar error_model: error model for the integration, one of "poisson" or "azimuthal" | |
| 146 :type error_model: str, optional | |
| 147 """ | |
| 148 tool_type: Literal['integration'] = 'integration' | |
| 149 title: constr(strip_whitespace=True, min_length=1) | |
| 150 integration_type: Literal['azimuthal', 'radial', 'cake'] | |
| 151 detectors: conlist(item_type=Detector, min_items=1) | |
| 152 radial_units: str = 'q_A^-1' | |
| 153 radial_min: confloat(ge=0) | |
| 154 radial_max: confloat(gt=0) | |
| 155 radial_npt: conint(gt=0) = 1800 | |
| 156 azimuthal_units: str = 'chi_deg' | |
| 157 azimuthal_min: confloat(ge=-180) = -180 | |
| 158 azimuthal_max: confloat(le=360) = 180 | |
| 159 azimuthal_npt: conint(gt=0) = 3600 | |
| 160 error_model: Optional[Literal['poisson', 'azimuthal']] | |
| 161 sequence_index: Optional[conint(gt=0)] | |
| 162 @validator('radial_units', allow_reuse=True) | |
| 163 def validate_radial_units(cls, radial_units): | |
| 164 """ | |
| 165 Validate the radial units for the integration. | |
| 166 | |
| 167 :param radial_units: unvalidated radial units for the integration | |
| 168 :type radial_units: str | |
| 169 :raises ValueError: if radial units are not one of the recognized radial units | |
| 170 :return: validated radial units | |
| 171 :rtype: str | |
| 172 """ | |
| 173 if radial_units in RADIAL_UNITS.keys(): | |
| 174 return(radial_units) | |
| 175 else: | |
| 176 raise(ValueError(f'Invalid radial units: {radial_units}. Must be one of {", ".join(RADIAL_UNITS.keys())}')) | |
| 177 @validator('azimuthal_units', allow_reuse=True) | |
| 178 def validate_azimuthal_units(cls, azimuthal_units): | |
| 179 """ | |
| 180 Validate that `azimuthal_units` is one of the keys in the | |
| 181 `pyFAI.units.AZIMUTHAL_UNITS` dictionary. | |
| 182 | |
| 183 :param azimuthal_units: The string representing the unit to be validated. | |
| 184 :type azimuthal_units: str | |
| 185 :raises ValueError: If `azimuthal_units` is not one of the keys in `pyFAI.units.AZIMUTHAL_UNITS` | |
| 186 :return: The original supplied value, if is one of the keys in `pyFAI.units.AZIMUTHAL_UNITS`. | |
| 187 :rtype: str | |
| 188 """ | |
| 189 if azimuthal_units in AZIMUTHAL_UNITS.keys(): | |
| 190 return(azimuthal_units) | |
| 191 else: | |
| 192 raise(ValueError(f'Invalid azimuthal units: {azimuthal_units}. Must be one of {", ".join(AZIMUTHAL_UNITS.keys())}')) | |
| 193 def validate_range_max(range_name:str): | |
| 194 """Validate the maximum value of an integration range. | |
| 195 | |
| 196 :param range_name: The name of the integration range (e.g. radial, azimuthal). | |
| 197 :type range_name: str | |
| 198 :return: The callable that performs the validation. | |
| 199 :rtype: callable | |
| 200 """ | |
| 201 def _validate_range_max(cls, range_max, values): | |
| 202 """Check if the maximum value of the integration range is greater than its minimum value. | |
| 203 | |
| 204 :param range_max: The maximum value of the integration range. | |
| 205 :type range_max: float | |
| 206 :param values: The values of the other fields being validated. | |
| 207 :type values: dict | |
| 208 :raises ValueError: If the maximum value of the integration range is not greater than its minimum value. | |
| 209 :return: The validated maximum range value | |
| 210 :rtype: float | |
| 211 """ | |
| 212 range_min = values.get(f'{range_name}_min') | |
| 213 if range_min < range_max: | |
| 214 return(range_max) | |
| 215 else: | |
| 216 raise(ValueError(f'Maximum value of integration range must be greater than minimum value of integration range ({range_name}_min={range_min}).')) | |
| 217 return(_validate_range_max) | |
| 218 _validate_radial_max = validator('radial_max', allow_reuse=True)(validate_range_max('radial')) | |
| 219 _validate_azimuthal_max = validator('azimuthal_max', allow_reuse=True)(validate_range_max('azimuthal')) | |
| 220 def validate_for_map_config(self, map_config:BaseModel): | |
| 221 """ | |
| 222 Validate the existence of the detector data file for all scan points in `map_config`. | |
| 223 | |
| 224 :param map_config: The `MapConfig` instance to validate against. | |
| 225 :type map_config: MapConfig | |
| 226 :raises RuntimeError: If a detector data file could not be found for a scan point occurring in `map_config`. | |
| 227 :return: None | |
| 228 :rtype: None | |
| 229 """ | |
| 230 for detector in self.detectors: | |
| 231 for scans in map_config.spec_scans: | |
| 232 for scan_number in scans.scan_numbers: | |
| 233 scanparser = scans.get_scanparser(scan_number) | |
| 234 for scan_step_index in range(scanparser.spec_scan_npts): | |
| 235 # Make sure the detector data file exists for all scan points | |
| 236 try: | |
| 237 detector_data_file = scanparser.get_detector_data_file(detector.prefix, scan_step_index) | |
| 238 except: | |
| 239 raise(RuntimeError(f'Could not find data file for detector prefix {detector.prefix} on scan number {scan_number} in spec file {scans.spec_file}')) | |
| 240 def get_azimuthal_adjustments(self): | |
| 241 """To enable a continuous range of integration in the azimuthal direction | |
| 242 for radial and cake integration, obtain adjusted values for this | |
| 243 `IntegrationConfig`'s `azimuthal_min` and `azimuthal_max` values, the | |
| 244 angle amount by which those values were adjusted, and the proper location | |
| 245 of the discontinuity in the azimuthal direction. | |
| 246 | |
| 247 :return: Adjusted chi_min, adjusted chi_max, chi_offset, chi_discontinuity | |
| 248 :rtype: tuple[float,float,float,float] | |
| 249 """ | |
| 250 return(get_azimuthal_adjustments(self.azimuthal_min, self.azimuthal_max)) | |
| 251 def get_azimuthal_integrators(self): | |
| 252 """Get a list of `AzimuthalIntegrator`s that correspond to the detector | |
| 253 configurations in this instance of `IntegrationConfig`. | |
| 254 | |
| 255 The returned `AzimuthalIntegrator`s are (if need be) artificially rotated | |
| 256 in the azimuthal direction to achieve a continuous range of integration | |
| 257 in the azimuthal direction. | |
| 258 | |
| 259 :returns: A list of `AzimuthalIntegrator`s appropriate for use by this | |
| 260 `IntegrationConfig` tool | |
| 261 :rtype: list[pyFAI.azimuthalIntegrator.AzimuthalIntegrator] | |
| 262 """ | |
| 263 chi_min, chi_max, chi_offset, chi_disc = self.get_azimuthal_adjustments() | |
| 264 return(get_azimuthal_integrators(tuple([detector.poni_file for detector in self.detectors]), chi_offset=chi_offset)) | |
| 265 def get_multi_geometry_integrator(self): | |
| 266 """Get a `MultiGeometry` integrator suitable for use by this instance of | |
| 267 `IntegrationConfig`. | |
| 268 | |
| 269 :return: A `MultiGeometry` integrator | |
| 270 :rtype: pyFAI.multi_geometry.MultiGeometry | |
| 271 """ | |
| 272 poni_files = tuple([detector.poni_file for detector in self.detectors]) | |
| 273 radial_range = (self.radial_min, self.radial_max) | |
| 274 azimuthal_range = (self.azimuthal_min, self.azimuthal_max) | |
| 275 return(get_multi_geometry_integrator(poni_files, self.radial_units, radial_range, azimuthal_range)) | |
| 276 def get_azimuthally_integrated_data(self, spec_scans:BaseModel, scan_number:int, scan_step_index:int): | |
| 277 """Return azimuthally-integrated data for the scan step specified. | |
| 278 | |
| 279 :param spec_scans: An instance of `SpecScans` containing the scan step requested. | |
| 280 :type spec_scans: SpecScans | |
| 281 :param scan_number: The number of the scan containing the scan step requested. | |
| 282 :type scan_number: int | |
| 283 :param scan_step_index: The index of the scan step requested. | |
| 284 :type scan_step_index: int | |
| 285 :return: A 1D array of azimuthally-integrated raw detector intensities. | |
| 286 :rtype: np.ndarray | |
| 287 """ | |
| 288 detector_data = spec_scans.get_detector_data(self.detectors, scan_number, scan_step_index) | |
| 289 integrator = self.get_multi_geometry_integrator() | |
| 290 lst_mask = [detector.mask_array for detector in self.detectors] | |
| 291 result = integrator.integrate1d(detector_data, lst_mask=lst_mask, npt=self.radial_npt, error_model=self.error_model) | |
| 292 if result.sigma is None: | |
| 293 return(result.intensity) | |
| 294 else: | |
| 295 return(result.intensity, result.sigma) | |
| 296 def get_radially_integrated_data(self, spec_scans:BaseModel, scan_number:int, scan_step_index:int): | |
| 297 """Return radially-integrated data for the scan step specified. | |
| 298 | |
| 299 :param spec_scans: An instance of `SpecScans` containing the scan step requested. | |
| 300 :type spec_scans: SpecScans | |
| 301 :param scan_number: The number of the scan containing the scan step requested. | |
| 302 :type scan_number: int | |
| 303 :param scan_step_index: The index of the scan step requested. | |
| 304 :type scan_step_index: int | |
| 305 :return: A 1D array of radially-integrated raw detector intensities. | |
| 306 :rtype: np.ndarray | |
| 307 """ | |
| 308 # Handle idiosyncracies of azimuthal ranges in pyFAI | |
| 309 # Adjust chi ranges to get a continuous range of iintegrated data | |
| 310 chi_min, chi_max, chi_offset, chi_disc = self.get_azimuthal_adjustments() | |
| 311 # Perform radial integration on a detector-by-detector basis. | |
| 312 I_each_detector = [] | |
| 313 variance_each_detector = [] | |
| 314 integrators = self.get_azimuthal_integrators() | |
| 315 for i,(integrator,detector) in enumerate(zip(integrators,self.detectors)): | |
| 316 detector_data = spec_scans.get_detector_data([detector], scan_number, scan_step_index)[0] | |
| 317 result = integrator.integrate_radial(detector_data, self.azimuthal_npt, | |
| 318 unit=self.azimuthal_units, azimuth_range=(chi_min,chi_max), | |
| 319 radial_unit=self.radial_units, radial_range=(self.radial_min,self.radial_max), | |
| 320 mask=detector.mask_array) #, error_model=self.error_model) | |
| 321 I_each_detector.append(result.intensity) | |
| 322 if result.sigma is not None: | |
| 323 variance_each_detector.append(result.sigma**2) | |
| 324 # Add the individual detectors' integrated intensities together | |
| 325 I = np.nansum(I_each_detector, axis=0) | |
| 326 # Ignore data at values of chi for which there was no data | |
| 327 I = np.where(I==0, np.nan, I) | |
| 328 if len(I_each_detector) != len(variance_each_detector): | |
| 329 return(I) | |
| 330 else: | |
| 331 # Get the standard deviation of the summed detectors' intensities | |
| 332 sigma = np.sqrt(np.nansum(variance_each_detector, axis=0)) | |
| 333 return(I, sigma) | |
| 334 def get_cake_integrated_data(self, spec_scans:BaseModel, scan_number:int, scan_step_index:int): | |
| 335 """Return cake-integrated data for the scan step specified. | |
| 336 | |
| 337 :param spec_scans: An instance of `SpecScans` containing the scan step requested. | |
| 338 :type spec_scans: SpecScans | |
| 339 :param scan_number: The number of the scan containing the scan step requested. | |
| 340 :type scan_number: int | |
| 341 :param scan_step_index: The index of the scan step requested. | |
| 342 :type scan_step_index: int | |
| 343 :return: A 2D array of cake-integrated raw detector intensities. | |
| 344 :rtype: np.ndarray | |
| 345 """ | |
| 346 detector_data = spec_scans.get_detector_data(self.detectors, scan_number, scan_step_index) | |
| 347 integrator = self.get_multi_geometry_integrator() | |
| 348 lst_mask = [detector.mask_array for detector in self.detectors] | |
| 349 result = integrator.integrate2d(detector_data, lst_mask=lst_mask, | |
| 350 npt_rad=self.radial_npt, npt_azim=self.azimuthal_npt, | |
| 351 method='bbox', | |
| 352 error_model=self.error_model) | |
| 353 if result.sigma is None: | |
| 354 return(result.intensity) | |
| 355 else: | |
| 356 return(result.intensity, result.sigma) | |
| 357 def get_integrated_data(self, spec_scans:BaseModel, scan_number:int, scan_step_index:int): | |
| 358 """Return integrated data for the scan step specified. | |
| 359 | |
| 360 :param spec_scans: An instance of `SpecScans` containing the scan step requested. | |
| 361 :type spec_scans: SpecScans | |
| 362 :param scan_number: The number of the scan containing the scan step requested. | |
| 363 :type scan_number: int | |
| 364 :param scan_step_index: The index of the scan step requested. | |
| 365 :type scan_step_index: int | |
| 366 :return: An array of integrated raw detector intensities. | |
| 367 :rtype: np.ndarray | |
| 368 """ | |
| 369 if self.integration_type == 'azimuthal': | |
| 370 return(self.get_azimuthally_integrated_data(spec_scans, scan_number, scan_step_index)) | |
| 371 elif self.integration_type == 'radial': | |
| 372 return(self.get_radially_integrated_data(spec_scans, scan_number, scan_step_index)) | |
| 373 elif self.integration_type == 'cake': | |
| 374 return(self.get_cake_integrated_data(spec_scans, scan_number, scan_step_index)) | |
| 375 | |
| 376 @property | |
| 377 def integrated_data_coordinates(self): | |
| 378 """ | |
| 379 Return a dictionary of coordinate arrays for navigating the dimension(s) | |
| 380 of the integrated data produced by this instance of `IntegrationConfig`. | |
| 381 | |
| 382 :return: A dictionary with either one or two keys: 'azimuthal' and/or | |
| 383 'radial', each of which points to a 1-D `numpy` array of coordinate | |
| 384 values. | |
| 385 :rtype: dict[str,np.ndarray] | |
| 386 """ | |
| 387 if self.integration_type == 'azimuthal': | |
| 388 return(get_integrated_data_coordinates(radial_range=(self.radial_min,self.radial_max), | |
| 389 radial_npt=self.radial_npt)) | |
| 390 elif self.integration_type == 'radial': | |
| 391 return(get_integrated_data_coordinates(azimuthal_range=(self.azimuthal_min,self.azimuthal_max), | |
| 392 azimuthal_npt=self.azimuthal_npt)) | |
| 393 elif self.integration_type == 'cake': | |
| 394 return(get_integrated_data_coordinates(radial_range=(self.radial_min,self.radial_max), | |
| 395 radial_npt=self.radial_npt, | |
| 396 azimuthal_range=(self.azimuthal_min,self.azimuthal_max), | |
| 397 azimuthal_npt=self.azimuthal_npt)) | |
| 398 @property | |
| 399 def integrated_data_dims(self): | |
| 400 """Return a tuple of the coordinate labels for the integrated data | |
| 401 produced by this instance of `IntegrationConfig`. | |
| 402 """ | |
| 403 directions = list(self.integrated_data_coordinates.keys()) | |
| 404 dim_names = [getattr(self, f'{direction}_units') for direction in directions] | |
| 405 return(dim_names) | |
| 406 @property | |
| 407 def integrated_data_shape(self): | |
| 408 """Return a tuple representing the shape of the integrated data | |
| 409 produced by this instance of `IntegrationConfig` for a single scan step. | |
| 410 """ | |
| 411 return(tuple([len(coordinate_values) for coordinate_name,coordinate_values in self.integrated_data_coordinates.items()])) | |
| 412 | |
| 413 @cache | |
| 414 def get_azimuthal_adjustments(chi_min:float, chi_max:float): | |
| 415 """ | |
| 416 Fix chi discontinuity at 180 degrees and return the adjusted chi range, | |
| 417 offset, and discontinuty. | |
| 418 | |
| 419 If the discontinuity is crossed, obtain the offset to artificially rotate | |
| 420 detectors to achieve a continuous azimuthal integration range. | |
| 421 | |
| 422 :param chi_min: The minimum value of the azimuthal range. | |
| 423 :type chi_min: float | |
| 424 :param chi_max: The maximum value of the azimuthal range. | |
| 425 :type chi_max: float | |
| 426 :return: The following four values: the adjusted minimum value of the | |
| 427 azimuthal range, the adjusted maximum value of the azimuthal range, the | |
| 428 value by which the chi angle was adjusted, the position of the chi | |
| 429 discontinuity. | |
| 430 """ | |
| 431 # Fix chi discontinuity at 180 degrees for now. | |
| 432 chi_disc = 180 | |
| 433 # If the discontinuity is crossed, artificially rotate the detectors to | |
| 434 # achieve a continuous azimuthal integration range | |
| 435 if chi_min < chi_disc and chi_max > chi_disc: | |
| 436 chi_offset = chi_max - chi_disc | |
| 437 else: | |
| 438 chi_offset = 0 | |
| 439 return(chi_min-chi_offset, chi_max-chi_offset, chi_offset, chi_disc) | |
| 440 @cache | |
| 441 def get_azimuthal_integrators(poni_files:tuple, chi_offset=0): | |
| 442 """ | |
| 443 Return a list of `AzimuthalIntegrator` objects generated from PONI files. | |
| 444 | |
| 445 :param poni_files: Tuple of strings, each string being a path to a PONI file. : tuple | |
| 446 :type poni_files: tuple | |
| 447 :param chi_offset: The angle in degrees by which the `AzimuthalIntegrator` objects will be rotated, defaults to 0. | |
| 448 :type chi_offset: float, optional | |
| 449 :return: List of `AzimuthalIntegrator` objects | |
| 450 :rtype: list[pyFAI.azimuthalIntegrator.AzimuthalIntegrator] | |
| 451 """ | |
| 452 ais = [] | |
| 453 for poni_file in poni_files: | |
| 454 ai = copy.deepcopy(azimuthal_integrator(poni_file)) | |
| 455 ai.rot3 += chi_offset * np.pi/180 | |
| 456 ais.append(ai) | |
| 457 return(ais) | |
| 458 @cache | |
| 459 def get_multi_geometry_integrator(poni_files:tuple, radial_unit:str, radial_range:tuple, azimuthal_range:tuple): | |
| 460 """Return a `MultiGeometry` instance that can be used for azimuthal or cake | |
| 461 integration. | |
| 462 | |
| 463 :param poni_files: Tuple of PONI files that describe the detectors to be | |
| 464 integrated. | |
| 465 :type poni_files: tuple | |
| 466 :param radial_unit: Unit to use for radial integration range. | |
| 467 :type radial_unit: str | |
| 468 :param radial_range: Tuple describing the range for radial integration. | |
| 469 :type radial_range: tuple[float,float] | |
| 470 :param azimuthal_range:Tuple describing the range for azimuthal integration. | |
| 471 :type azimuthal_range: tuple[float,float] | |
| 472 :return: `MultiGeometry` instance that can be used for azimuthal or cake | |
| 473 integration. | |
| 474 :rtype: pyFAI.multi_geometry.MultiGeometry | |
| 475 """ | |
| 476 chi_min, chi_max, chi_offset, chi_disc = get_azimuthal_adjustments(*azimuthal_range) | |
| 477 ais = copy.deepcopy(get_azimuthal_integrators(poni_files, chi_offset=chi_offset)) | |
| 478 multi_geometry = MultiGeometry(ais, | |
| 479 unit=radial_unit, | |
| 480 radial_range=radial_range, | |
| 481 azimuth_range=(chi_min,chi_max), | |
| 482 wavelength=sum([ai.wavelength for ai in ais])/len(ais), | |
| 483 chi_disc=chi_disc) | |
| 484 return(multi_geometry) | |
| 485 @cache | |
| 486 def get_integrated_data_coordinates(azimuthal_range:tuple=None, azimuthal_npt:int=None, radial_range:tuple=None, radial_npt:int=None): | |
| 487 """ | |
| 488 Return a dictionary of coordinate arrays for the specified radial and/or | |
| 489 azimuthal integration ranges. | |
| 490 | |
| 491 :param azimuthal_range: Tuple specifying the range of azimuthal angles over | |
| 492 which to generate coordinates, in the format (min, max), defaults to | |
| 493 None. | |
| 494 :type azimuthal_range: tuple[float,float], optional | |
| 495 :param azimuthal_npt: Number of azimuthal coordinate points to generate, | |
| 496 defaults to None. | |
| 497 :type azimuthal_npt: int, optional | |
| 498 :param radial_range: Tuple specifying the range of radial distances over | |
| 499 which to generate coordinates, in the format (min, max), defaults to | |
| 500 None. | |
| 501 :type radial_range: tuple[float,float], optional | |
| 502 :param radial_npt: Number of radial coordinate points to generate, defaults | |
| 503 to None. | |
| 504 :type radial_npt: int, optional | |
| 505 :return: A dictionary with either one or two keys: 'azimuthal' and/or | |
| 506 'radial', each of which points to a 1-D `numpy` array of coordinate | |
| 507 values. | |
| 508 :rtype: dict[str,np.ndarray] | |
| 509 """ | |
| 510 integrated_data_coordinates = {} | |
| 511 if azimuthal_range is not None and azimuthal_npt is not None: | |
| 512 integrated_data_coordinates['azimuthal'] = np.linspace(*azimuthal_range, azimuthal_npt) | |
| 513 if radial_range is not None and radial_npt is not None: | |
| 514 integrated_data_coordinates['radial'] = np.linspace(*radial_range, radial_npt) | |
| 515 return(integrated_data_coordinates) |
