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

Files changed (38) hide show
  1. CHAP/__init__.py +1 -1
  2. CHAP/common/__init__.py +13 -0
  3. CHAP/common/models/integration.py +29 -26
  4. CHAP/common/models/map.py +395 -224
  5. CHAP/common/processor.py +1725 -93
  6. CHAP/common/reader.py +265 -28
  7. CHAP/common/writer.py +191 -18
  8. CHAP/edd/__init__.py +9 -2
  9. CHAP/edd/models.py +886 -665
  10. CHAP/edd/processor.py +2592 -936
  11. CHAP/edd/reader.py +889 -0
  12. CHAP/edd/utils.py +846 -292
  13. CHAP/foxden/__init__.py +6 -0
  14. CHAP/foxden/processor.py +42 -0
  15. CHAP/foxden/writer.py +65 -0
  16. CHAP/giwaxs/__init__.py +8 -0
  17. CHAP/giwaxs/models.py +100 -0
  18. CHAP/giwaxs/processor.py +520 -0
  19. CHAP/giwaxs/reader.py +5 -0
  20. CHAP/giwaxs/writer.py +5 -0
  21. CHAP/pipeline.py +48 -10
  22. CHAP/runner.py +161 -72
  23. CHAP/tomo/models.py +31 -29
  24. CHAP/tomo/processor.py +169 -118
  25. CHAP/utils/__init__.py +1 -0
  26. CHAP/utils/fit.py +1292 -1315
  27. CHAP/utils/general.py +411 -53
  28. CHAP/utils/models.py +594 -0
  29. CHAP/utils/parfile.py +10 -2
  30. ChessAnalysisPipeline-0.0.16.dist-info/LICENSE +60 -0
  31. {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/METADATA +1 -1
  32. ChessAnalysisPipeline-0.0.16.dist-info/RECORD +62 -0
  33. {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/WHEEL +1 -1
  34. CHAP/utils/scanparsers.py +0 -1431
  35. ChessAnalysisPipeline-0.0.14.dist-info/LICENSE +0 -21
  36. ChessAnalysisPipeline-0.0.14.dist-info/RECORD +0 -54
  37. {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/entry_points.txt +0 -0
  38. {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/top_level.txt +0 -0
CHAP/tomo/processor.py CHANGED
@@ -8,7 +8,6 @@ Description: Module for Processors used only by tomography experiments
8
8
  """
9
9
 
10
10
  # System modules
11
- from os import mkdir
12
11
  from os import path as os_path
13
12
  from sys import exit as sys_exit
14
13
  from time import time
@@ -31,7 +30,6 @@ from CHAP.utils.general import (
31
30
  quick_imshow,
32
31
  nxcopy,
33
32
  )
34
- from CHAP.utils.fit import Fit
35
33
  from CHAP.processor import Processor
36
34
  from CHAP.reader import main
37
35
 
@@ -148,6 +146,8 @@ class TomoCHESSMapConverter(Processor):
148
146
 
149
147
  # Validate map
150
148
  map_config = MapConfig(**loads(str(tomofields.map_config)))
149
+ assert len(map_config.spec_scans) == 1
150
+ num_tomo_stack = len(map_config.spec_scans[0].scan_numbers)
151
151
 
152
152
  # Check available independent dimensions
153
153
  independent_dimensions = tomofields.data.attrs['axes']
@@ -167,8 +167,6 @@ class TomoCHESSMapConverter(Processor):
167
167
  f'({rotation_angle_data_type})')
168
168
  matched_dimensions.pop(matched_dimensions.index('rotation_angles'))
169
169
  if 'x_translation' in independent_dimensions:
170
- x_translation_index = \
171
- tomofields.data.axes.index('x_translation')
172
170
  x_translation_data_type = \
173
171
  tomofields.data.x_translation.attrs['data_type']
174
172
  x_translation_name = \
@@ -180,8 +178,6 @@ class TomoCHESSMapConverter(Processor):
180
178
  else:
181
179
  x_translation_data_type = None
182
180
  if 'z_translation' in independent_dimensions:
183
- z_translation_index = \
184
- tomofields.data.axes.index('z_translation')
185
181
  z_translation_data_type = \
186
182
  tomofields.data.z_translation.attrs['data_type']
187
183
  z_translation_name = \
@@ -226,17 +222,12 @@ class TomoCHESSMapConverter(Processor):
226
222
 
227
223
  # Add an NXdetector to the NXinstrument
228
224
  # (do not fill in data fields yet)
225
+ detector_names = list(np.asarray(tomofields.detector_names, dtype=str))
229
226
  detector_prefix = detector_config.prefix
230
- detectors = list(
231
- set(tomofields.data.entries) - set(independent_dimensions))
232
- if detector_prefix not in detectors:
233
- raise ValueError(f'Data for detector {detector_prefix} is '
234
- f'unavailable (available detectors: {detectors})')
235
- tomo_stacks = tomofields.data[detector_prefix]
236
- tomo_stack_shape = tomo_stacks.shape
237
- assert len(tomo_stack_shape) == 2+len(independent_dimensions)
238
- assert tomo_stack_shape[-2] == detector_config.rows
239
- assert tomo_stack_shape[-1] == detector_config.columns
227
+ if detector_prefix not in detector_names:
228
+ raise ValueError(
229
+ f'Data for detector {detector_prefix} is unavailable '
230
+ f'(available detectors: {detector_names})')
240
231
  nxdetector = NXdetector()
241
232
  nxinstrument.detector = nxdetector
242
233
  nxdetector.local_name = detector_prefix
@@ -274,10 +265,9 @@ class TomoCHESSMapConverter(Processor):
274
265
  x_translations = []
275
266
  z_translations = []
276
267
  if darkfield is not None:
277
- nxentry.dark_field_config = darkfield.spec_config
268
+ nxentry.dark_field_config = darkfield.config
278
269
  for scan_name, scan in darkfield.spec_scans.items():
279
270
  for scan_number, nxcollection in scan.items():
280
- scan_columns = loads(str(nxcollection.scan_columns))
281
271
  data_shape = nxcollection.data[detector_prefix].shape
282
272
  assert len(data_shape) == 3
283
273
  assert data_shape[1] == detector_config.rows
@@ -286,7 +276,7 @@ class TomoCHESSMapConverter(Processor):
286
276
  image_keys += num_image*[2]
287
277
  sequence_numbers += list(range(num_image))
288
278
  image_stacks.append(
289
- nxcollection.data[detector_prefix])
279
+ nxcollection.data[detector_prefix].nxdata)
290
280
  rotation_angles += num_image*[0.0]
291
281
  if (x_translation_data_type == 'spec_motor' or
292
282
  z_translation_data_type == 'spec_motor'):
@@ -314,10 +304,9 @@ class TomoCHESSMapConverter(Processor):
314
304
  num_image*[smb_pars[z_translation_name]]
315
305
 
316
306
  # Collect bright field data
317
- nxentry.bright_field_config = brightfield.spec_config
307
+ nxentry.bright_field_config = brightfield.config
318
308
  for scan_name, scan in brightfield.spec_scans.items():
319
309
  for scan_number, nxcollection in scan.items():
320
- scan_columns = loads(str(nxcollection.scan_columns))
321
310
  data_shape = nxcollection.data[detector_prefix].shape
322
311
  assert len(data_shape) == 3
323
312
  assert data_shape[1] == detector_config.rows
@@ -326,7 +315,7 @@ class TomoCHESSMapConverter(Processor):
326
315
  image_keys += num_image*[1]
327
316
  sequence_numbers += list(range(num_image))
328
317
  image_stacks.append(
329
- nxcollection.data[detector_prefix])
318
+ nxcollection.data[detector_prefix].nxdata)
330
319
  rotation_angles += num_image*[0.0]
331
320
  if (x_translation_data_type == 'spec_motor' or
332
321
  z_translation_data_type == 'spec_motor'):
@@ -354,65 +343,46 @@ class TomoCHESSMapConverter(Processor):
354
343
  num_image*[smb_pars[z_translation_name]]
355
344
 
356
345
  # Collect tomography fields data
357
- if x_translation_data_type is None:
358
- x_trans = [0.0]
359
- if z_translation_data_type is None:
360
- z_trans = [0.0]
361
- tomo_stacks = np.reshape(tomo_stacks, (1,1,*tomo_stacks.shape))
362
- else:
363
- z_trans = tomofields.data.z_translation.nxdata
364
- # if len(list(tomofields.data.z_translation)):
365
- # z_trans = list(tomofields.data.z_translation)
366
- # else:
367
- # z_trans = [float(tomofields.data.z_translation)]
368
- if rotation_angles_index < z_translation_index:
369
- tomo_stacks = np.swapaxes(
370
- tomo_stacks, rotation_angles_index,
371
- z_translation_index)
372
- tomo_stacks = np.expand_dims(tomo_stacks, z_translation_index)
373
- elif z_translation_data_type is None:
374
- z_trans = [0.0]
375
- if rotation_angles_index < x_translation_index:
376
- tomo_stacks = np.swapaxes(
377
- tomo_stacks, rotation_angles_index, x_translation_index)
378
- tomo_stacks = np.expand_dims(tomo_stacks, 0)
379
- else:
380
- x_trans = tomofields.data.x_translation.nxdata
381
- z_trans = tomofields.data.z_translation.nxdata
382
- #if tomofields.data.x_translation.size > 1:
383
- # x_trans = list(tomofields.data.x_translation)
384
- #else:
385
- # x_trans = [float(tomofields.data.x_translation)]
386
- #if len(list(tomofields.data.z_translation)):
387
- # z_trans = list(tomofields.data.z_translation)
388
- #else:
389
- # z_trans = [float(tomofields.data.z_translation)]
390
- if (rotation_angles_index
391
- < max(x_translation_index, z_translation_index)):
392
- tomo_stacks = np.swapaxes(
393
- tomo_stacks, rotation_angles_index,
394
- max(x_translation_index, z_translation_index))
395
- if x_translation_index < z_translation_index:
396
- tomo_stacks = np.swapaxes(
397
- tomo_stacks, x_translation_index, z_translation_index)
346
+ tomo_stacks = tomofields.data.detector_data.nxdata[
347
+ detector_names.index(detector_prefix)]
348
+ tomo_stack_shape = tomo_stacks.shape
349
+ assert len(tomo_stack_shape) == 3
350
+ assert tomo_stack_shape[-2] == detector_config.rows
351
+ assert tomo_stack_shape[-1] == detector_config.columns
352
+ assert not tomo_stack_shape[0] % num_tomo_stack
398
353
  # Restrict to 180 degrees set of data for now to match old code
399
- thetas = tomofields.data.rotation_angles.nxdata
400
- assert len(thetas) > 2
401
- delta_theta = thetas[1]-thetas[0]
402
- if thetas[-1]-thetas[0] > 180-delta_theta:
403
- image_end = index_nearest(thetas, thetas[0]+180)
354
+ thetas_stacks = tomofields.data.rotation_angles.nxdata
355
+ num_theta = tomo_stack_shape[0] // num_tomo_stack
356
+ assert num_theta > 2
357
+ thetas = thetas_stacks[0:num_theta]
358
+ delta_theta = thetas[1] - thetas[0]
359
+ if thetas[num_theta-1] - thetas[0] > 180 - delta_theta:
360
+ image_end = index_nearest(thetas, thetas[0] + 180)
404
361
  else:
405
- image_end = len(thetas)
362
+ image_end = thetas.size
406
363
  thetas = thetas[:image_end]
407
- num_image = len(thetas)
408
- for i, z in enumerate(z_trans):
409
- for j, x in enumerate(x_trans):
410
- image_keys += num_image*[0]
411
- sequence_numbers += list(range(num_image))
412
- image_stacks.append(tomo_stacks[i,j,:image_end,:,:])
413
- rotation_angles += list(thetas)
414
- x_translations += num_image*[x]
415
- z_translations += num_image*[z]
364
+ num_image = thetas.size
365
+ n_start = 0
366
+ image_keys += num_tomo_stack * num_image * [0]
367
+ sequence_numbers += num_tomo_stack * list(range(num_image))
368
+ if x_translation_data_type is None:
369
+ x_translations += num_tomo_stack * num_image * [0.0]
370
+ if z_translation_data_type is None:
371
+ z_translations += num_tomo_stack * num_image * [0.0]
372
+ for i in range(num_tomo_stack):
373
+ image_stacks.append(tomo_stacks[n_start:n_start+num_image])
374
+ if not np.array_equal(
375
+ thetas, thetas_stacks[n_start:n_start+num_image]):
376
+ raise RuntimeError(
377
+ 'Inconsistent thetas among tomography image stacks')
378
+ rotation_angles += list(thetas)
379
+ if x_translation_data_type is not None:
380
+ x_translations += list(
381
+ tomofields.data.x_translation[n_start:n_start+num_image])
382
+ if z_translation_data_type is not None:
383
+ z_translations += list(
384
+ tomofields.data.z_translation[n_start:n_start+num_image])
385
+ n_start += num_theta
416
386
 
417
387
  # Add image data to NXdetector
418
388
  nxinstrument.detector.image_key = image_keys
@@ -458,7 +428,7 @@ class TomoDataProcessor(Processor):
458
428
  for tomographic image reduction.
459
429
  :type data: list[PipelineData]
460
430
  :param outputdir: Output folder name, defaults to '.'.
461
- :type outputdir:: str, optional
431
+ :type outputdir: str, optional
462
432
  :param interactive: Allows for user interactions,
463
433
  defaults to False.
464
434
  :type interactive: bool, optional
@@ -666,7 +636,7 @@ class Tomo:
666
636
  :param num_core: Number of processors.
667
637
  :type num_core: int
668
638
  :param outputdir: Output folder name, defaults to '.'.
669
- :type outputdir:: str, optional
639
+ :type outputdir: str, optional
670
640
  :param save_figs: Safe figures to file ('yes' or 'only') and/or
671
641
  display figures ('yes' or 'no'), defaults to 'no'.
672
642
  :type save_figs: Literal['yes', 'no', 'only'], optional
@@ -1395,28 +1365,28 @@ class Tomo:
1395
1365
 
1396
1366
  # Resize the combined tomography data stacks
1397
1367
  # - combined axis data order: row/-z,y,x
1398
- if self._interactive:
1368
+ if self._interactive or self._save_figs:
1399
1369
  x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
1400
1370
  tomo_recon_combined, combine_data=True)
1401
1371
  else:
1402
1372
  x_bounds = tool_config.x_bounds
1403
1373
  if x_bounds is None:
1404
1374
  self._logger.warning(
1405
- 'x_bounds unspecified, reconstruct data for full x-range')
1375
+ 'x_bounds unspecified, combine data for full x-range')
1406
1376
  elif not is_int_pair(
1407
1377
  x_bounds, ge=0, le=tomo_shape[2]):
1408
1378
  raise ValueError(f'Invalid parameter x_bounds ({x_bounds})')
1409
1379
  y_bounds = tool_config.y_bounds
1410
1380
  if y_bounds is None:
1411
1381
  self._logger.warning(
1412
- 'y_bounds unspecified, reconstruct data for full y-range')
1382
+ 'y_bounds unspecified, combine data for full y-range')
1413
1383
  elif not is_int_pair(
1414
1384
  y_bounds, ge=0, le=tomo_shape[1]):
1415
1385
  raise ValueError(f'Invalid parameter y_bounds ({y_bounds})')
1416
1386
  z_bounds = tool_config.z_bounds
1417
1387
  if z_bounds is None:
1418
1388
  self._logger.warning(
1419
- 'z_bounds unspecified, reconstruct data for full z-range')
1389
+ 'z_bounds unspecified, combine data for full z-range')
1420
1390
  elif not is_int_pair(
1421
1391
  z_bounds, ge=0, le=tomo_shape[0]):
1422
1392
  raise ValueError(f'Invalid parameter z_bounds ({z_bounds})')
@@ -1706,18 +1676,46 @@ class Tomo:
1706
1676
  img_row_bounds = calibrate_center_rows
1707
1677
  else:
1708
1678
  if nxentry.instrument.source.attrs['station'] in ('id1a3', 'id3a'):
1679
+ # System modules
1680
+ from sys import float_info
1681
+
1682
+ # Third party modules
1683
+ from nexusformat.nexus import (
1684
+ NXdata,
1685
+ NXfield,
1686
+ )
1687
+
1688
+ # Local modules
1689
+ from CHAP.utils.fit import FitProcessor
1690
+
1709
1691
  pixel_size = float(nxentry.instrument.detector.row_pixel_size)
1710
1692
  # Try to get a fit from the bright field
1711
1693
  row_sum = np.sum(tbf, 1)
1712
- fit = Fit.fit_data(
1713
- row_sum, 'rectangle', x=np.array(range(len(row_sum))),
1714
- form='atan', guess=True)
1715
- parameters = fit.best_values
1694
+ num = len(row_sum)
1695
+ fit = FitProcessor()
1696
+ model = {'model': 'rectangle',
1697
+ 'parameters': [
1698
+ {'name': 'amplitude',
1699
+ 'value': row_sum.max()-row_sum.min(),
1700
+ 'min': 0.0},
1701
+ {'name': 'center1', 'value': 0.25*num,
1702
+ 'min': 0.0, 'max': num},
1703
+ {'name': 'sigma1', 'value': num/7.0,
1704
+ 'min': float_info.min},
1705
+ {'name': 'center2', 'value': 0.75*num,
1706
+ 'min': 0.0, 'max': num},
1707
+ {'name': 'sigma2', 'value': num/7.0,
1708
+ 'min': float_info.min}]}
1709
+ bounds_fit = fit.process(
1710
+ NXdata(NXfield(row_sum, 'y'),
1711
+ NXfield(np.array(range(num)), 'x')),
1712
+ {'models': [model], 'method': 'trf'})
1713
+ parameters = bounds_fit.best_values
1716
1714
  row_low_fit = parameters.get('center1', None)
1717
1715
  row_upp_fit = parameters.get('center2', None)
1718
1716
  sig_low = parameters.get('sigma1', None)
1719
1717
  sig_upp = parameters.get('sigma2', None)
1720
- have_fit = (fit.success and row_low_fit is not None
1718
+ have_fit = (bounds_fit.success and row_low_fit is not None
1721
1719
  and row_upp_fit is not None and sig_low is not None
1722
1720
  and sig_upp is not None
1723
1721
  and 0 <= row_low_fit < row_upp_fit <= row_sum.size
@@ -1787,7 +1785,7 @@ class Tomo:
1787
1785
  if calibrate_center_rows:
1788
1786
  title='Select two detector image row indices to '\
1789
1787
  'calibrate rotation axis (in range '\
1790
- f'[0, {first_image.shape[0]}])'
1788
+ f'[0, {first_image.shape[0]-1}])'
1791
1789
  else:
1792
1790
  title='Select detector image row bounds for data '\
1793
1791
  f'reduction (in range [0, {first_image.shape[0]}])'
@@ -2052,7 +2050,7 @@ class Tomo:
2052
2050
  evaluate('-log(tomo_stack)', out=tomo_stack)
2053
2051
 
2054
2052
  # Get rid of nans/infs that may be introduced by normalization
2055
- np.where(np.isfinite(tomo_stack), tomo_stack, 0.)
2053
+ tomo_stack = np.where(np.isfinite(tomo_stack), tomo_stack, 0.)
2056
2054
 
2057
2055
  # Downsize tomography stack to smaller size
2058
2056
  tomo_stack = tomo_stack.astype('float32', copy=False)
@@ -2452,8 +2450,9 @@ class Tomo:
2452
2450
  f'{selected_center_offset:.2f}.png'))
2453
2451
  plt.close()
2454
2452
 
2453
+ del recon_planes
2454
+
2455
2455
  del sinogram
2456
- del recon_planes
2457
2456
 
2458
2457
  # Return the center location
2459
2458
  if self._interactive:
@@ -2834,16 +2833,16 @@ class Tomo:
2834
2833
  # Selecting x an y bounds (in z-plane)
2835
2834
  if x_bounds is None:
2836
2835
  if not self._interactive:
2837
- self._logger.warning('x_bounds unspecified, reconstruct '
2838
- 'data for full x-range')
2836
+ self._logger.warning('x_bounds unspecified, use data for '
2837
+ 'full x-range')
2839
2838
  x_bounds = (0, tomo_recon_stacks[0].shape[2])
2840
2839
  elif not is_int_pair(
2841
2840
  x_bounds, ge=0, le=tomo_recon_stacks[0].shape[2]):
2842
2841
  raise ValueError(f'Invalid parameter x_bounds ({x_bounds})')
2843
2842
  if y_bounds is None:
2844
2843
  if not self._interactive:
2845
- self._logger.warning('y_bounds unspecified, reconstruct '
2846
- 'data for full y-range')
2844
+ self._logger.warning('y_bounds unspecified, use data for '
2845
+ 'full y-range')
2847
2846
  y_bounds = (0, tomo_recon_stacks[0].shape[1])
2848
2847
  elif not is_int_pair(
2849
2848
  y_bounds, ge=0, le=tomo_recon_stacks[0].shape[1]):
@@ -2865,11 +2864,20 @@ class Tomo:
2865
2864
  tomosum = 0
2866
2865
  for i in range(num_tomo_stacks):
2867
2866
  tomosum = tomosum + np.sum(tomo_recon_stacks[i], axis=0)
2868
- fig, roi = select_roi_2d(
2867
+ if self._save_figs:
2868
+ if combine_data:
2869
+ filename = os_path.join(
2870
+ self._outputdir, 'combined_data_xy_roi.png')
2871
+ else:
2872
+ filename = os_path.join(
2873
+ self._outputdir, 'reconstructed_data_xy_roi.png')
2874
+ else:
2875
+ filename = None
2876
+ roi = select_roi_2d(
2869
2877
  tomosum, preselected_roi=preselected_roi,
2870
2878
  title_a='Reconstructed data summed over z',
2871
2879
  row_label='y', column_label='x',
2872
- interactive=self._interactive)
2880
+ interactive=self._interactive, filename=filename)
2873
2881
  if roi is None:
2874
2882
  x_bounds = (0, tomo_recon_stacks[0].shape[2])
2875
2883
  y_bounds = (0, tomo_recon_stacks[0].shape[1])
@@ -2878,12 +2886,6 @@ class Tomo:
2878
2886
  y_bounds = (int(roi[2]), int(roi[3]))
2879
2887
  self._logger.debug(f'x_bounds = {x_bounds}')
2880
2888
  self._logger.debug(f'y_bounds = {y_bounds}')
2881
- # Plot results
2882
- if self._save_figs:
2883
- fig.savefig(
2884
- os_path.join(
2885
- self._outputdir, 'reconstructed_data_xy_roi.png'))
2886
- plt.close()
2887
2889
 
2888
2890
  # Selecting z bounds (in xy-plane)
2889
2891
  # (only valid for a single image stack or when combining a stack)
@@ -2905,17 +2907,20 @@ class Tomo:
2905
2907
  tomosum = 0
2906
2908
  for i in range(num_tomo_stacks):
2907
2909
  tomosum = tomosum + np.sum(tomo_recon_stacks[i], axis=(1,2))
2908
- fig, z_bounds = select_roi_1d(
2910
+ if self._save_figs:
2911
+ if combine_data:
2912
+ filename = os_path.join(
2913
+ self._outputdir, 'combined_data_z_roi.png')
2914
+ else:
2915
+ filename = os_path.join(
2916
+ self._outputdir, 'reconstructed_data_z_roi.png')
2917
+ else:
2918
+ filename = None
2919
+ z_bounds = select_roi_1d(
2909
2920
  tomosum, preselected_roi=z_bounds,
2910
2921
  xlabel='z', ylabel='Reconstructed data summed over x and y',
2911
- interactive=self._interactive)
2922
+ interactive=self._interactive, filename=filename)
2912
2923
  self._logger.debug(f'z_bounds = {z_bounds}')
2913
- # Plot results
2914
- if self._save_figs:
2915
- fig.savefig(
2916
- os_path.join(
2917
- self._outputdir, 'reconstructed_data_z_roi.png'))
2918
- plt.close()
2919
2924
 
2920
2925
  return x_bounds, y_bounds, z_bounds
2921
2926
 
@@ -2957,6 +2962,9 @@ class TomoSimFieldProcessor(Processor):
2957
2962
  sample_size = config.sample_size
2958
2963
  if len(sample_size) == 1:
2959
2964
  sample_size = (sample_size[0], sample_size[0])
2965
+ if sample_type == 'hollow_pyramid' and len(sample_size) != 3:
2966
+ raise ValueError('Invalid combindation of sample_type '
2967
+ f'({sample_type}) and sample_size ({sample_size}')
2960
2968
  wall_thickness = config.wall_thickness
2961
2969
  mu = config.mu
2962
2970
  theta_step = config.theta_step
@@ -2993,15 +3001,22 @@ class TomoSimFieldProcessor(Processor):
2993
3001
 
2994
3002
  # Get the number of horizontal stacks bases on the diagonal
2995
3003
  # of the square and for now don't allow more than one
2996
- num_tomo_stack = 1 + int((sample_size[1]*np.sqrt(2)-pixel_size[1])
2997
- / (detector_size[1]*pixel_size[1]))
3004
+ if (sample_size) == 3:
3005
+ num_tomo_stack = 1 + int(
3006
+ (max(sample_size[1:2])*np.sqrt(2)-pixel_size[1])
3007
+ / (detector_size[1]*pixel_size[1]))
3008
+ else:
3009
+ num_tomo_stack = 1 + int((sample_size[1]*np.sqrt(2)-pixel_size[1])
3010
+ / (detector_size[1]*pixel_size[1]))
2998
3011
  if num_tomo_stack > 1:
2999
3012
  raise ValueError('Sample is too wide for the detector')
3000
3013
 
3001
3014
  # Create the x-ray path length through a solid square
3002
3015
  # crosssection for a set of rotation angles.
3003
- path_lengths_solid = self._create_pathlength_solid_square(
3004
- sample_size[1], thetas, pixel_size[1], detector_size[1])
3016
+ path_lengths_solid = None
3017
+ if sample_type != 'hollow_pyramid':
3018
+ path_lengths_solid = self._create_pathlength_solid_square(
3019
+ sample_size[1], thetas, pixel_size[1], detector_size[1])
3005
3020
 
3006
3021
  # Create the x-ray path length through a hollow square
3007
3022
  # crosssection for a set of rotation angles.
@@ -3027,7 +3042,12 @@ class TomoSimFieldProcessor(Processor):
3027
3042
  num_theta = len(thetas)
3028
3043
  vertical_shifts = []
3029
3044
  tomo_fields_stack = []
3030
- img_dim = (len(img_row_coords), path_lengths_solid.shape[1])
3045
+ len_img_y = (detector_size[1]+1)//2
3046
+ if len_img_y%2:
3047
+ len_img_y = 2*len_img_y - 1
3048
+ else:
3049
+ len_img_y = 2*len_img_y
3050
+ img_dim = (len(img_row_coords), len_img_y)
3031
3051
  intensities_solid = None
3032
3052
  intensities_hollow = None
3033
3053
  for n in range(num_tomo_stack):
@@ -3044,6 +3064,37 @@ class TomoSimFieldProcessor(Processor):
3044
3064
  beam_intensity * np.exp(-mu*path_lengths_hollow)
3045
3065
  for n in range(num_theta):
3046
3066
  tomo_field[n,:,:] = intensities_hollow[n]
3067
+ elif sample_type == 'hollow_pyramid':
3068
+ outer_indices = \
3069
+ np.where(abs(img_row_coords) <= sample_size[0]/2)[0]
3070
+ inner_indices = np.where(
3071
+ abs(img_row_coords) < sample_size[0]/2 - wall_thickness)[0]
3072
+ wall_indices = list(set(outer_indices)-set(inner_indices))
3073
+ ratio = abs(sample_size[1]-sample_size[2])/sample_size[0]
3074
+ baselength = max(sample_size[1:2])
3075
+ for i in wall_indices:
3076
+ path_lengths_solid = self._create_pathlength_solid_square(
3077
+ baselength - ratio*(
3078
+ img_row_coords[i] + 0.5*sample_size[0]),
3079
+ thetas, pixel_size[1], detector_size[1])
3080
+ intensities_solid = \
3081
+ beam_intensity * np.exp(-mu*path_lengths_solid)
3082
+ for n in range(num_theta):
3083
+ tomo_field[n,i] = intensities_solid[n]
3084
+ for i in inner_indices:
3085
+ path_lengths_hollow = (
3086
+ self._create_pathlength_solid_square(
3087
+ baselength - ratio*(
3088
+ img_row_coords[i] + 0.5*sample_size[0]),
3089
+ thetas, pixel_size[1], detector_size[1])
3090
+ - self._create_pathlength_solid_square(
3091
+ baselength - 2*wall_thickness - ratio*(
3092
+ img_row_coords[i] + 0.5*sample_size[0]),
3093
+ thetas, pixel_size[1], detector_size[1]))
3094
+ intensities_hollow = \
3095
+ beam_intensity * np.exp(-mu*path_lengths_hollow)
3096
+ for n in range(num_theta):
3097
+ tomo_field[n,i] = intensities_hollow[n]
3047
3098
  else:
3048
3099
  intensities_solid = \
3049
3100
  beam_intensity * np.exp(-mu*path_lengths_solid)
@@ -3136,7 +3187,7 @@ class TomoSimFieldProcessor(Processor):
3136
3187
  """
3137
3188
  # Get the column coordinates
3138
3189
  img_y_coords = pixel_size * (0.5 * (1 - detector_size%2)
3139
- + np.asarray(range(int(0.5 * (detector_size+1)))))
3190
+ + np.asarray(range((detector_size+1)//2)))
3140
3191
 
3141
3192
  # Get the path lenghts for position column coordinates
3142
3193
  lengths = np.zeros((len(thetas), len(img_y_coords)), dtype=np.float64)
CHAP/utils/__init__.py CHANGED
@@ -2,3 +2,4 @@
2
2
  CHESS scan data, collecting interactive user input, and finding
3
3
  lattice properties of materials (among others).
4
4
  """
5
+ from CHAP.utils.fit import FitProcessor