ChessAnalysisPipeline 0.0.13__py3-none-any.whl → 0.0.14__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.

Potentially problematic release.


This version of ChessAnalysisPipeline might be problematic. Click here for more details.

CHAP/common/reader.py CHANGED
@@ -12,7 +12,6 @@ from os.path import (
12
12
  splitext,
13
13
  )
14
14
  from sys import modules
15
- from time import time
16
15
 
17
16
  # Third party modules
18
17
  import numpy as np
@@ -40,7 +39,7 @@ class BinaryFileReader(Reader):
40
39
  class H5Reader(Reader):
41
40
  """Reader for h5 files.
42
41
  """
43
- def read(self, filename, h5path='/'):
42
+ def read(self, filename, h5path='/', idx=None):
44
43
  """Return the data object stored at `h5path` in an h5-file.
45
44
 
46
45
  :param filename: The name of the h5-file to read from.
@@ -55,6 +54,8 @@ class H5Reader(Reader):
55
54
  from h5py import File
56
55
 
57
56
  data = File(filename, 'r')[h5path]
57
+ if idx is not None:
58
+ data = data[tuple(idx)]
58
59
  return data
59
60
 
60
61
 
@@ -142,8 +143,8 @@ class MapReader(Reader):
142
143
  attrs={'long_name': f'{dim.label} ({dim.units})',
143
144
  'data_type': dim.data_type,
144
145
  'local_name': dim.name})
145
- if map_config.map_type == 'structured':
146
- nxentry.data.attrs[f'{dim.label}_indices'] = i
146
+ # if map_config.map_type == 'structured':
147
+ # nxentry.data.attrs[f'{dim.label}_indices'] = i
147
148
 
148
149
  # Create empty NXfields for all scalar data present in the
149
150
  # provided map configuration
@@ -167,15 +168,13 @@ class MapReader(Reader):
167
168
  # Create empty NXfields of appropriate shape for raw
168
169
  # detector data
169
170
  for detector_name in detector_names:
171
+ if not isinstance(detector_name, str):
172
+ detector_name = str(detector_name)
170
173
  detector_data = map_config.get_detector_data(
171
174
  detector_name, (0,) * len(map_config.shape))
172
175
  nxentry.data[detector_name] = NXfield(value=np.zeros(
173
176
  (*map_config.shape, *detector_data.shape)),
174
177
  dtype=detector_data.dtype)
175
- # data_shape = list(map_config.shape)+list(detector_data.shape)
176
- # nxentry.data[detector_name] = NXfield(
177
- # value=np.zeros(data_shape), shape=data_shape,
178
- # dtype=detector_data.dtype)
179
178
 
180
179
  # Read and fill in maps of raw data
181
180
  if len(map_config.all_scalar_data) > 0 or len(detector_names) > 0:
@@ -184,6 +183,8 @@ class MapReader(Reader):
184
183
  nxentry.data[data.label][map_index] = map_config.get_value(
185
184
  data, map_index)
186
185
  for detector_name in detector_names:
186
+ if not isinstance(detector_name, str):
187
+ detector_name = str(detector_name)
187
188
  nxentry.data[detector_name][map_index] = \
188
189
  map_config.get_detector_data(detector_name, map_index)
189
190
 
CHAP/common/writer.py CHANGED
@@ -136,7 +136,7 @@ class ExtractArchiveWriter(Writer):
136
136
  and write the extracted archive to files.
137
137
 
138
138
  :param data: The data to write to archive.
139
- :type data: CHAP.pipeline.PipelineData
139
+ :type data: list[PipelineData]
140
140
  :param filename: The name of the directory to write the archive
141
141
  files to.
142
142
  :type filename: str
@@ -162,7 +162,7 @@ class FileTreeWriter(Writer):
162
162
  directory tree stuctured like the NeXus tree.
163
163
 
164
164
  :param data: The data to write to disk.
165
- :type data: CHAP.pipeline.PipelineData
165
+ :type data: list[PipelineData]
166
166
  :param outputdir: The name of the directory to write to.
167
167
  :type outputdir: str
168
168
  :param force_overwrite: Flag to allow data to be overwritten
@@ -211,7 +211,7 @@ class MatplotlibAnimationWriter(Writer):
211
211
  contained in `data` to file.
212
212
 
213
213
  :param data: The matplotlib animation.
214
- :type data: CHAP.pipeline.PipelineData
214
+ :type data: list[PipelineData]
215
215
  :param filename: The name of the file to write to.
216
216
  :type filename: str
217
217
  :param fps: Movie frame rate (frames per second),
@@ -224,7 +224,7 @@ class MatplotlibAnimationWriter(Writer):
224
224
  extension = os_path.splitext(filename)[1]
225
225
  if not extension:
226
226
  data.save(f'{filename}.gif', fps=fps)
227
- elif extension in '.gif':
227
+ elif extension == '.gif':
228
228
  data.save(filename, fps=fps)
229
229
  elif extension == '.mp4':
230
230
  data.save(filename, writer='ffmpeg', fps=fps)
@@ -239,7 +239,7 @@ class MatplotlibFigureWriter(Writer):
239
239
  file.
240
240
 
241
241
  :param data: The matplotlib figure
242
- :type data: CHAP.pipeline.PipelineData
242
+ :type data: list[PipelineData]
243
243
  :param filename: The name of the file to write to.
244
244
  :type filename: str
245
245
  :param savefig_kw: Keyword args to pass to
@@ -265,7 +265,7 @@ class NexusWriter(Writer):
265
265
  """Write the NeXus object contained in `data` to file.
266
266
 
267
267
  :param data: The data to write to file.
268
- :type data: CHAP.pipeline.PipelineData
268
+ :type data: list[PipelineData]
269
269
  :param filename: The name of the file to write to.
270
270
  :param force_overwrite: Flag to allow data in `filename` to be
271
271
  overwritten if it already exists, defaults to `False`.
@@ -275,7 +275,21 @@ class NexusWriter(Writer):
275
275
  :return: The data written to file.
276
276
  :rtype: nexusformat.nexus.NXobject
277
277
  """
278
+ from nexusformat.nexus import (
279
+ NXentry,
280
+ NXroot,
281
+ )
278
282
  data = self.unwrap_pipelinedata(data)[-1]
283
+ nxclass = data.nxclass
284
+ nxname = data.nxname
285
+ if nxclass == 'NXentry':
286
+ data = NXroot(data)
287
+ data[nxname].set_default()
288
+ elif nxclass != 'NXroot':
289
+ data = NXroot(NXentry(data))
290
+ if nxclass == 'NXdata':
291
+ data.entry[nxname].set_default()
292
+ data.entry.set_default()
279
293
  write_nexus(data, filename, force_overwrite)
280
294
 
281
295
  return data
CHAP/edd/__init__.py CHANGED
@@ -3,8 +3,10 @@ processing workflows.
3
3
  """
4
4
  # from CHAP.edd.reader import
5
5
  from CHAP.edd.processor import (DiffractionVolumeLengthProcessor,
6
+ LatticeParameterRefinementProcessor,
6
7
  MCACeriaCalibrationProcessor,
7
8
  MCADataProcessor,
9
+ MCAEnergyCalibrationProcessor,
8
10
  StrainAnalysisProcessor)
9
11
  # from CHAP.edd.writer import
10
12
 
CHAP/edd/models.py CHANGED
@@ -38,43 +38,70 @@ class MCAElementConfig(BaseModel):
38
38
  :type detector_name: str
39
39
  :ivar num_bins: Number of MCA channels.
40
40
  :type num_bins: int, optional
41
- :ivar include_bin_ranges: List of MCA channel index ranges whose
42
- data should be included after applying a mask (the bounds are
43
- inclusive), defaults to `[]`
44
- :type include_bin_ranges: list[[int, int]], optional
41
+ :ivar include_energy_ranges: List of MCA channel energy ranges
42
+ whose data should be included after applying a mask (the
43
+ bounds are inclusive), defaults to `[[50,150]]`
44
+ :type include_energy_ranges: list[[float, float]], optional
45
45
  """
46
46
  detector_name: constr(strip_whitespace=True, min_length=1) = 'mca1'
47
47
  num_bins: Optional[conint(gt=0)]
48
- include_bin_ranges: conlist(
48
+ max_energy_kev: confloat(gt=0) = 200
49
+ include_energy_ranges: conlist(
49
50
  min_items=1,
50
51
  item_type=conlist(
51
- item_type=conint(ge=0),
52
+ item_type=confloat(ge=0),
52
53
  min_items=2,
53
- max_items=2)) = []
54
+ max_items=2)) = [[50,150]]
54
55
 
55
- @validator('include_bin_ranges', each_item=True)
56
- def validate_include_bin_range(cls, value, values):
57
- """Ensure that no bin ranges are outside the boundary of the
56
+ @validator('include_energy_ranges', each_item=True)
57
+ def validate_include_energy_range(cls, value, values):
58
+ """Ensure that no energy ranges are outside the boundary of the
58
59
  detector.
59
60
 
60
- :param value: Field value to validate (`include_bin_ranges`).
61
+ :param value: Field value to validate (`include_energy_ranges`).
61
62
  :type values: dict
62
63
  :param values: Dictionary of previously validated field values.
63
64
  :type values: dict
64
- :return: The validated value of `include_bin_ranges`.
65
+ :return: The validated value of `include_energy_ranges`.
65
66
  :rtype: dict
66
67
  """
67
- num_bins = values.get('num_bins')
68
- if num_bins is not None:
69
- value[1] = min(value[1], num_bins-1)
70
- if value[0] >= value[1]:
71
- raise ValueError('Invalid bin range in include_bin_ranges '
72
- f'({value})')
68
+ max_energy_kev = values.get('max_energy_kev')
69
+ value.sort()
70
+ if value[1] > max_energy_kev:
71
+ value[1] = max_energy_kev
73
72
  return value
74
73
 
74
+ @property
75
+ def include_bin_ranges(self):
76
+ """Return the value of `include_energy_ranges` represented in
77
+ terms of channel indices instead of channel energies.
78
+ """
79
+ from CHAP.utils.general import index_nearest_down, index_nearest_upp
80
+
81
+ include_bin_ranges = []
82
+ energies = np.linspace(0, self.max_energy_kev, self.num_bins)
83
+ for e_min, e_max in self.include_energy_ranges:
84
+ include_bin_ranges.append(
85
+ [index_nearest_down(energies, e_min),
86
+ index_nearest_upp(energies, e_max)])
87
+ return include_bin_ranges
88
+
89
+ def get_energy_ranges(self, bin_ranges):
90
+ """Given a list of channel index ranges, return the
91
+ correspongin list of channel energy ranges.
92
+
93
+ :param bin_ranges: A list of channel bin ranges to convert to
94
+ energy ranges.
95
+ :type bin_ranges: list[list[int]]
96
+ :returns: Energy ranges
97
+ :rtype: list[list[float]]
98
+ """
99
+ energies = np.linspace(0, self.max_energy_kev, self.num_bins)
100
+ return [[energies[i] for i in range_] for range_ in bin_ranges]
101
+
75
102
  def mca_mask(self):
76
103
  """Get a boolean mask array to use on this MCA element's data.
77
- Note that the bounds of self.include_bin_ranges are inclusive.
104
+ Note that the bounds of self.include_energy_ranges are inclusive.
78
105
 
79
106
  :return: Boolean mask array.
80
107
  :rtype: numpy.ndarray
@@ -94,9 +121,9 @@ class MCAElementConfig(BaseModel):
94
121
  :rtype: dict
95
122
  """
96
123
  d = super().dict(*args, **kwargs)
97
- d['include_bin_ranges'] = [
98
- list(d['include_bin_ranges'][i]) \
99
- for i in range(len(d['include_bin_ranges']))]
124
+ d['include_energy_ranges'] = [
125
+ [float(energy) for energy in d['include_energy_ranges'][i]]
126
+ for i in range(len(d['include_energy_ranges']))]
100
127
  return d
101
128
 
102
129
 
@@ -172,7 +199,7 @@ class MCAScanDataConfig(BaseModel):
172
199
  def validate_detectors(cls, values):
173
200
  """Fill in values for _scanparser / _parfile (if applicable).
174
201
  Fill in each detector's num_bins field, if needed.
175
- Check each detector's include_bin_ranges field against the
202
+ Check each detector's include_energy_ranges field against the
176
203
  flux file, if available.
177
204
 
178
205
  :param values: Dictionary of previously validated field values.
@@ -212,20 +239,21 @@ class MCAScanDataConfig(BaseModel):
212
239
  )
213
240
  flux = np.loadtxt(flux_file)
214
241
  flux_file_energies = flux[:,0]/1.e3
215
- energy_range = (flux_file_energies.min(), flux_file_energies.max())
242
+ flux_e_min = flux_file_energies.min()
243
+ flux_e_max = flux_file_energies.max()
216
244
  for detector in detectors:
217
245
  mca_bin_energies = np.linspace(
218
246
  0, detector.max_energy_kev, detector.num_bins)
219
- e_min = index_nearest_upp(mca_bin_energies, energy_range[0])
220
- e_max = index_nearest_down(mca_bin_energies, energy_range[1])
221
- for i, (min_, max_) in enumerate(
222
- deepcopy(detector.include_bin_ranges)):
223
- if min_ < e_min or max_ > e_max:
224
- bin_range = [max(min_, e_min), min(max_, e_max)]
225
- print(f'WARNING: include_bin_ranges[{i}] out of range '
226
- f'({detector.include_bin_ranges[i]}): adjusted '
227
- f'to {bin_range}')
228
- detector.include_bin_ranges[i] = bin_range
247
+ for i, (det_e_min, det_e_max) in enumerate(
248
+ deepcopy(detector.include_energy_ranges)):
249
+ if det_e_min < flux_e_min or det_e_max > flux_e_max:
250
+ energy_range = [min(det_e_min, flux_e_min),
251
+ max(det_e_max, flux_e_max)]
252
+ print(
253
+ f'WARNING: include_energy_ranges[{i}] out of range'
254
+ f' ({detector.include_energy_ranges[i]}): adjusted'
255
+ f' to {energy_range}')
256
+ detector.include_energy_ranges[i] = energy_range
229
257
 
230
258
  return values
231
259
 
@@ -381,6 +409,8 @@ class MCAElementCalibrationConfig(MCAElementConfig):
381
409
  :ivar hkl_indices: List of unique HKL indices to fit peaks for in
382
410
  the calibration routine, defaults to `[]`.
383
411
  :type hkl_indices: list[int], optional
412
+ :ivar background: Background model for peak fitting.
413
+ :type background: str, list[str], optional
384
414
  :ivar tth_initial_guess: Initial guess for 2&theta,
385
415
  defaults to `5.0`.
386
416
  :type tth_initial_guess: float, optional
@@ -394,7 +424,7 @@ class MCAElementCalibrationConfig(MCAElementConfig):
394
424
  :type tth_calibrated: float, optional
395
425
  :ivar slope_calibrated: Calibrated value for detector channel
396
426
  energy correction linear slope.
397
- :type slope_calibrated: float, optional
427
+ :type slope_calibrated: float, optional
398
428
  :ivar intercept_calibrated: Calibrated value for detector channel
399
429
  energy correction y-intercept.
400
430
  :type intercept_calibrated: float, optional
@@ -403,6 +433,7 @@ class MCAElementCalibrationConfig(MCAElementConfig):
403
433
  tth_max: confloat(gt=0, allow_inf_nan=False) = 90.0
404
434
  hkl_tth_tol: confloat(gt=0, allow_inf_nan=False) = 0.15
405
435
  hkl_indices: Optional[conlist(item_type=conint(ge=0), min_items=1)] = []
436
+ background: Optional[Union[str, list]]
406
437
  tth_initial_guess: confloat(gt=0, le=tth_max, allow_inf_nan=False) = 5.0
407
438
  slope_initial_guess: float = 1.0
408
439
  intercept_initial_guess: float = 0.0
@@ -435,10 +466,19 @@ class MCAElementDiffractionVolumeLengthConfig(MCAElementConfig):
435
466
  :ivar dvl_measured: Placeholder for the measured diffraction
436
467
  volume length before writing the data to file.
437
468
  :type dvl_measured: float, optional
469
+ :ivar fit_amplitude: Placeholder for amplitude of the gaussian fit.
470
+ :type fit_amplitude: float, optional
471
+ :ivar fit_center: Placeholder for center of the gaussian fit.
472
+ :type fit_center: float, optional
473
+ :ivar fit_sigma: Placeholder for sigma of the gaussian fit.
474
+ :type fit_sigma: float, optional
438
475
  """
439
476
  measurement_mode: Optional[Literal['manual', 'auto']] = 'auto'
440
477
  sigma_to_dvl_factor: Optional[Literal[3.5, 2.0, 4.0]] = 3.5
441
478
  dvl_measured: Optional[confloat(gt=0)] = None
479
+ fit_amplitude: Optional[float] = None
480
+ fit_center: Optional[float] = None
481
+ fit_sigma: Optional[float] = None
442
482
 
443
483
  def dict(self, *args, **kwargs):
444
484
  """Return a representation of this configuration in a
@@ -452,6 +492,8 @@ class MCAElementDiffractionVolumeLengthConfig(MCAElementConfig):
452
492
  d = super().dict(*args, **kwargs)
453
493
  if self.measurement_mode == 'manual':
454
494
  del d['sigma_to_dvl_factor']
495
+ for param in ('amplitude', 'center', 'sigma'):
496
+ d[f'fit_{param}'] = float(d[f'fit_{param}'])
455
497
  return d
456
498
 
457
499
 
@@ -460,10 +502,15 @@ class DiffractionVolumeLengthConfig(MCAScanDataConfig):
460
502
  volume length calculation for an EDD setup using a steel-foil
461
503
  raster scan.
462
504
 
505
+ :ivar sample_thickness: Thickness of scanned foil sample. Quantity
506
+ must be provided in the same units as the values of the
507
+ scanning motor.
508
+ :type sample_thickness: float
463
509
  :ivar detectors: Individual detector element DVL
464
510
  measurement configurations
465
511
  :type detectors: list[MCAElementDiffractionVolumeLengthConfig]
466
512
  """
513
+ sample_thickness: float
467
514
  detectors: conlist(min_items=1,
468
515
  item_type=MCAElementDiffractionVolumeLengthConfig)
469
516
 
@@ -481,18 +528,6 @@ class DiffractionVolumeLengthConfig(MCAScanDataConfig):
481
528
  scan_numbers=self._parfile.good_scan_numbers())
482
529
  return self.scanparser.spec_scan_motor_vals[0]
483
530
 
484
- @property
485
- def scanned_dim_lbl(self):
486
- """Return a label for plot axes corresponding to the scanned
487
- dimension.
488
-
489
- :return: Name of scanned motor.
490
- :rtype: str
491
- """
492
- if self._parfile is not None:
493
- return self.scan_column
494
- return self.scanparser.spec_scan_motor_mnes[0]
495
-
496
531
 
497
532
  class CeriaConfig(MaterialConfig):
498
533
  """Model for the sample material used in calibrations.
@@ -629,6 +664,8 @@ class MCAElementStrainAnalysisConfig(MCAElementConfig):
629
664
  :type hkl_indices: list[int], optional
630
665
  :ivar background: Background model for peak fitting.
631
666
  :type background: str, list[str], optional
667
+ :ivar num_proc: Number of processors used for peak fitting.
668
+ :type num_proc: int, optional
632
669
  :ivar peak_models: Peak model for peak fitting,
633
670
  defaults to `'gaussian'`.
634
671
  :type peak_models: Literal['gaussian', 'lorentzian']],
@@ -656,7 +693,8 @@ class MCAElementStrainAnalysisConfig(MCAElementConfig):
656
693
  tth_max: confloat(gt=0, allow_inf_nan=False) = 90.0
657
694
  hkl_tth_tol: confloat(gt=0, allow_inf_nan=False) = 0.15
658
695
  hkl_indices: Optional[conlist(item_type=conint(ge=0), min_items=1)] = []
659
- background: Optional[str]
696
+ background: Optional[Union[str, list]]
697
+ num_proc: Optional[conint(gt=0)] = os.cpu_count()
660
698
  peak_models: Union[
661
699
  conlist(item_type=Literal['gaussian', 'lorentzian'], min_items=1),
662
700
  Literal['gaussian', 'lorentzian']] = 'gaussian'
@@ -667,6 +705,13 @@ class MCAElementStrainAnalysisConfig(MCAElementConfig):
667
705
  tth_calibrated: Optional[confloat(gt=0, allow_inf_nan=False)]
668
706
  slope_calibrated: Optional[confloat(allow_inf_nan=False)]
669
707
  intercept_calibrated: Optional[confloat(allow_inf_nan=False)]
708
+ calibration_bin_ranges: Optional[
709
+ conlist(
710
+ min_items=1,
711
+ item_type=conlist(
712
+ item_type=conint(ge=0),
713
+ min_items=2,
714
+ max_items=2))]
670
715
  tth_file: Optional[FilePath]
671
716
  tth_map: Optional[np.ndarray] = None
672
717
 
@@ -696,6 +741,7 @@ class MCAElementStrainAnalysisConfig(MCAElementConfig):
696
741
  'intercept_calibrated', 'num_bins', 'max_energy_kev']
697
742
  for field in add_fields:
698
743
  setattr(self, field, getattr(calibration, field))
744
+ self.calibration_bin_ranges = calibration.include_bin_ranges
699
745
 
700
746
  def get_tth_map(self, map_config):
701
747
  """Return a map of 2&theta values to use -- may vary at each