ChessAnalysisPipeline 0.0.12__py3-none-any.whl → 0.0.13__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/tomo/processor.py CHANGED
@@ -26,7 +26,6 @@ from CHAP.utils.general import (
26
26
  select_image_indices,
27
27
  select_roi_1d,
28
28
  select_roi_2d,
29
- quick_plot,
30
29
  quick_imshow,
31
30
  )
32
31
  from CHAP.utils.fit import Fit
@@ -119,6 +118,7 @@ class TomoCHESSMapConverter(Processor):
119
118
 
120
119
  # Local modules
121
120
  from CHAP.common.models.map import MapConfig
121
+ from CHAP.utils.general import index_nearest
122
122
 
123
123
  darkfield = get_nxroot(data, 'darkfield')
124
124
  brightfield = get_nxroot(data, 'brightfield')
@@ -383,7 +383,6 @@ class TomoCHESSMapConverter(Processor):
383
383
  thetas = np.asarray(tomofields.data.rotation_angles)
384
384
  #RV num_image = len(tomofields.data.rotation_angles)
385
385
  assert len(thetas) > 2
386
- from CHAP.utils.general import index_nearest
387
386
  delta_theta = thetas[1]-thetas[0]
388
387
  if thetas[-1]-thetas[0] > 180-delta_theta:
389
388
  image_end = index_nearest(thetas, thetas[0]+180)
@@ -441,8 +440,8 @@ class TomoDataProcessor(Processor):
441
440
 
442
441
  def process(
443
442
  self, data, interactive=False, reduce_data=False,
444
- find_center=False, reconstruct_data=False, combine_data=False,
445
- output_folder='.', save_figs='no', **kwargs):
443
+ find_center=False, calibrate_center=False, reconstruct_data=False,
444
+ combine_data=False, output_folder='.', save_figs='no', **kwargs):
446
445
  """
447
446
  Process the input map or configuration with the step specific
448
447
  instructions and return either a dictionary or a
@@ -457,9 +456,12 @@ class TomoDataProcessor(Processor):
457
456
  :param reduce_data: Generate reduced tomography images,
458
457
  defaults to False.
459
458
  :type reduce_data: bool, optional
460
- :param find_center: Find the calibrated center axis info,
459
+ :param find_center: Generate calibrated center axis info,
461
460
  defaults to False.
462
461
  :type find_center: bool, optional
462
+ :param calibrate_center: Calibrate the rotation axis,
463
+ defaults to False.
464
+ :type calibrate_center: bool, optional
463
465
  :param reconstruct_data: Reconstruct the tomography data,
464
466
  defaults to False.
465
467
  :type reconstruct_data: bool, optional
@@ -494,6 +496,9 @@ class TomoDataProcessor(Processor):
494
496
  raise ValueError(f'Invalid parameter reduce_data ({reduce_data})')
495
497
  if not isinstance(find_center, bool):
496
498
  raise ValueError(f'Invalid parameter find_center ({find_center})')
499
+ if not isinstance(calibrate_center, bool):
500
+ raise ValueError(
501
+ f'Invalid parameter calibrate_center ({calibrate_center})')
497
502
  if not isinstance(reconstruct_data, bool):
498
503
  raise ValueError(
499
504
  f'Invalid parameter reconstruct_data ({reconstruct_data})')
@@ -529,14 +534,36 @@ class TomoDataProcessor(Processor):
529
534
 
530
535
  nxsetconfig(memory=100000)
531
536
 
537
+ # Calibrate the rotation axis
538
+ if calibrate_center:
539
+ if (reduce_data or find_center
540
+ or reconstruct_data or reconstruct_data_config is not None
541
+ or combine_data or combine_data_config is not None):
542
+ self._logger.warning('Ignoring any step specific instructions '
543
+ 'during center calibration')
544
+ if nxroot is None:
545
+ raise RuntimeError('Map info required to calibrate the '
546
+ 'rotation axis')
547
+ if find_center_config is None:
548
+ find_center_config = TomoFindCenterConfig()
549
+ calibrate_center_rows = True
550
+ else:
551
+ calibrate_center_rows = find_center_config.center_rows
552
+ if calibrate_center_rows == None:
553
+ calibrate_center_rows = True
554
+ nxroot, calibrate_center_rows = tomo.reduce_data(
555
+ nxroot, reduce_data_config, calibrate_center_rows)
556
+ return tomo.find_centers(
557
+ nxroot, find_center_config, calibrate_center_rows)
558
+
532
559
  # Reduce tomography images
533
560
  if reduce_data or reduce_data_config is not None:
534
561
  if nxroot is None:
535
562
  raise RuntimeError('Map info required to reduce the '
536
563
  'tomography images')
537
- nxroot = tomo.reduce_data(nxroot, reduce_data_config)
564
+ nxroot, _ = tomo.reduce_data(nxroot, reduce_data_config)
538
565
 
539
- # Find rotation axis centers for the tomography stacks
566
+ # Find calibrated center axis info for the tomography stacks
540
567
  center_config = None
541
568
  if find_center or find_center_config is not None:
542
569
  run_find_centers = False
@@ -668,7 +695,7 @@ class Tomo:
668
695
 
669
696
  def __init__(
670
697
  self, interactive=False, num_core=-1, output_folder='.',
671
- save_figs='no', test_mode=False):
698
+ save_figs='no'):
672
699
  """
673
700
  Initialize Tomo.
674
701
 
@@ -682,9 +709,6 @@ class Tomo:
682
709
  :param save_figs: Safe figures to file ('yes' or 'only') and/or
683
710
  display figures ('yes' or 'no'), defaults to 'no'.
684
711
  :type save_figs: Literal['yes', 'no', 'only'], optional
685
- :param test_mode: Run in test mode (non-interactively), defaults
686
- to False.
687
- :type test_mode: bool, optional
688
712
  :raises ValueError: Invalid input parameter.
689
713
  """
690
714
  # System modules
@@ -702,17 +726,7 @@ class Tomo:
702
726
  self._output_folder = os_path.abspath(output_folder)
703
727
  if not os_path.isdir(self._output_folder):
704
728
  mkdir(self._output_folder)
705
- if self._interactive:
706
- self._test_mode = False
707
- else:
708
- if not isinstance(test_mode, bool):
709
- raise ValueError(f'Invalid parameter test_mode ({test_mode})')
710
- self._test_mode = test_mode
711
729
  self._test_config = {}
712
- if self._test_mode:
713
- if save_figs != 'only':
714
- self._logger.warning('Ignoring save_figs in test mode')
715
- save_figs = 'only'
716
730
  if save_figs == 'only':
717
731
  self._save_only = True
718
732
  self._save_figs = True
@@ -738,7 +752,8 @@ class Tomo:
738
752
  f'of available processors and reduced to {cpu_count()}')
739
753
  self._num_core = cpu_count()
740
754
 
741
- def reduce_data(self, nxroot, tool_config=None):
755
+ def reduce_data(
756
+ self, nxroot, tool_config=None, calibrate_center_rows=False):
742
757
  """
743
758
  Reduced the tomography images.
744
759
 
@@ -770,7 +785,17 @@ class Tomo:
770
785
  img_row_bounds = None
771
786
  else:
772
787
  delta_theta = tool_config.delta_theta
773
- img_row_bounds = tool_config.img_row_bounds
788
+ img_row_bounds = tuple(tool_config.img_row_bounds)
789
+ if img_row_bounds is not None:
790
+ if (nxentry.instrument.source.attrs['station']
791
+ in ('id1a3', 'id3a')):
792
+ self._logger.warning('Ignoring parameter img_row_bounds '
793
+ 'for id1a3 and id3a')
794
+ img_row_bounds = None
795
+ elif calibrate_center_rows:
796
+ self._logger.warning('Ignoring parameter img_row_bounds '
797
+ 'during rotation axis calibration')
798
+ img_row_bounds = None
774
799
 
775
800
  image_key = nxentry.instrument.detector.get('image_key', None)
776
801
  if image_key is None or 'data' not in nxentry.instrument.detector:
@@ -798,7 +823,7 @@ class Tomo:
798
823
  if delta_theta is not None:
799
824
  delta_theta = None
800
825
  self._logger.warning(
801
- 'Ignore delta_theta when an image mask is used')
826
+ 'Ignoring delta_theta when an image mask is used')
802
827
  np.random.seed(0)
803
828
  image_mask = np.where(np.random.rand(
804
829
  len(thetas)) < drop_fraction/100, 0, 1).astype(bool)
@@ -820,20 +845,32 @@ class Tomo:
820
845
  self._logger.debug(f'image_mask = {image_mask}')
821
846
  reduced_data.image_mask = image_mask
822
847
  thetas = thetas[image_mask]
823
- self._logger.debug(f'thetas = {thetas}')
824
- reduced_data.rotation_angle = thetas
825
- reduced_data.rotation_angle.units = 'degrees'
826
848
 
827
- # Set vertical detector bounds for image stack
849
+ # Set vertical detector bounds for image stack or rotation
850
+ # axis calibration rows
828
851
  img_row_bounds = self._set_detector_bounds(
829
852
  nxentry, reduced_data, image_key, thetas[0],
830
- img_row_bounds=img_row_bounds)
853
+ img_row_bounds, calibrate_center_rows)
831
854
  self._logger.info(f'img_row_bounds = {img_row_bounds}')
855
+ if calibrate_center_rows:
856
+ calibrate_center_rows = tuple(sorted(img_row_bounds))
857
+ img_row_bounds = None
858
+ if img_row_bounds is None:
859
+ tbf_shape = np.asarray(reduced_data.data.bright_field).shape
860
+ img_row_bounds = (0, tbf_shape[0])
832
861
  reduced_data.img_row_bounds = img_row_bounds
833
862
  reduced_data.img_row_bounds.units = 'pixels'
863
+ reduced_data.img_row_bounds.attrs['long_name'] = \
864
+ 'image row boundaries in detector frame of reference'
865
+
866
+ # Store rotation angles for image stacks
867
+ self._logger.debug(f'thetas = {thetas}')
868
+ reduced_data.rotation_angle = thetas
869
+ reduced_data.rotation_angle.units = 'degrees'
834
870
 
835
871
  # Generate reduced tomography fields
836
- reduced_data = self._gen_tomo(nxentry, reduced_data, image_key)
872
+ reduced_data = self._gen_tomo(
873
+ nxentry, reduced_data, image_key, calibrate_center_rows)
837
874
 
838
875
  # Create a copy of the input Nexus object and remove raw and
839
876
  # any existing reduced data
@@ -866,9 +903,9 @@ class Tomo:
866
903
  nxentry.reduced_data.rotation_angle, name='rotation_angle')
867
904
  nxentry.data.attrs['signal'] = 'reduced_data'
868
905
 
869
- return nxroot
906
+ return nxroot, calibrate_center_rows
870
907
 
871
- def find_centers(self, nxroot, tool_config):
908
+ def find_centers(self, nxroot, tool_config, calibrate_center_rows=False):
872
909
  """
873
910
  Find the calibrated center axis info
874
911
 
@@ -895,45 +932,24 @@ class Tomo:
895
932
  nxentry = nxroot[nxroot.attrs['default']]
896
933
  else:
897
934
  raise ValueError(f'Invalid parameter nxroot ({nxroot})')
898
- center_rows = tool_config.center_rows
899
- center_stack_index = tool_config.center_stack_index
900
- if (center_stack_index is not None
901
- and (not isinstance(center_stack_index, int)
902
- or center_stack_index < 0)):
903
- raise ValueError(
904
- 'Invalid parameter center_stack_index '
905
- f'({center_stack_index})')
906
935
 
907
936
  # Check if reduced data is available
908
937
  if ('reduced_data' not in nxentry
909
938
  or 'reduced_data' not in nxentry.data):
910
939
  raise ValueError(f'Unable to find valid reduced data in {nxentry}.')
911
940
 
912
- # Get full bright field
913
- tbf = np.asarray(nxentry.reduced_data.data.bright_field)
914
- tbf_shape = tbf.shape
915
-
916
- # Get image bounds
917
- img_row_bounds = tuple(
918
- nxentry.reduced_data.get('img_row_bounds', (0, tbf_shape[0])))
919
- img_column_bounds = tuple(
920
- nxentry.reduced_data.get('img_column_bounds', (0, tbf_shape[1])))
921
-
922
- # Select the image stack to calibrate the center axis
941
+ # Select the image stack to find the calibrated center axis
923
942
  # reduced data axes order: stack,theta,row,column
924
943
  # Note: Nexus can't follow a link if the data it points to is
925
944
  # too big get the data from the actual place, not from
926
945
  # nxentry.data
927
946
  num_tomo_stacks = nxentry.reduced_data.data.tomo_fields.shape[0]
928
- img_shape = nxentry.reduced_data.data.bright_field.shape
929
- num_row = int(img_row_bounds[1] - img_row_bounds[0])
930
947
  if num_tomo_stacks == 1:
931
948
  center_stack_index = 0
932
- default = 'n'
933
949
  else:
934
- if self._test_mode:
935
- # Convert input value to offset 0
936
- center_stack_index = self._test_config['center_stack_index']
950
+ center_stack_index = tool_config.center_stack_index
951
+ if calibrate_center_rows:
952
+ center_stack_index = int(num_tomo_stacks/2)
937
953
  elif self._interactive:
938
954
  if center_stack_index is None:
939
955
  center_stack_index = input_int(
@@ -945,44 +961,45 @@ class Tomo:
945
961
  center_stack_index = int(num_tomo_stacks/2)
946
962
  self._logger.warning(
947
963
  'center_stack_index unspecified, use stack '
948
- f'{center_stack_index} to find centers')
949
- default = 'y'
964
+ f'{center_stack_index} to find center axis info')
950
965
 
951
966
  # Get thetas (in degrees)
952
967
  thetas = np.asarray(nxentry.reduced_data.rotation_angle)
953
968
 
954
- # Get effective pixel_size
955
- if 'zoom_perc' in nxentry.reduced_data:
956
- eff_pixel_size = float(
957
- 100. * (nxentry.instrument.detector.row_pixel_size
958
- / nxentry.reduced_data.attrs['zoom_perc']))
959
- else:
960
- eff_pixel_size = float(nxentry.instrument.detector.row_pixel_size)
961
-
962
- # Get cross sectional diameter
963
- cross_sectional_dim = img_shape[1]*eff_pixel_size
964
- self._logger.debug(f'cross_sectional_dim = {cross_sectional_dim}')
965
-
966
- # Determine center offset at sample row boundaries
967
- self._logger.info('Determine center offset at sample row boundaries')
968
-
969
969
  # Select center rows
970
- if self._test_mode:
971
- center_rows = tuple(self._test_config['center_rows'])
970
+ if calibrate_center_rows:
971
+ center_rows = calibrate_center_rows
972
+ offset_center_rows = (0, 1)
972
973
  else:
973
974
  # Third party modules
974
975
  import matplotlib.pyplot as plt
975
976
 
977
+ # Get full bright field
978
+ tbf = np.asarray(nxentry.reduced_data.data.bright_field)
979
+ tbf_shape = tbf.shape
980
+
981
+ # Get image bounds
982
+ img_row_bounds = nxentry.reduced_data.get(
983
+ 'img_row_bounds', (0, tbf_shape[0]))
984
+ img_row_bounds = (int(img_row_bounds[0]), int(img_row_bounds[1]))
985
+ img_column_bounds = nxentry.reduced_data.get(
986
+ 'img_column_bounds', (0, tbf_shape[1]))
987
+ img_column_bounds = (
988
+ int(img_column_bounds[0]), int(img_column_bounds[1]))
989
+
990
+ center_rows = tool_config.center_rows
976
991
  if center_rows is None:
977
992
  if num_tomo_stacks == 1:
978
993
  # Add a small margin to avoid edge effects
979
- offset = min(5, int(0.1*num_row))
980
- center_rows = (offset, num_row-1-offset)
994
+ offset = min(
995
+ 5, int(0.1*(img_row_bounds[1] - img_row_bounds[0])))
996
+ center_rows = (
997
+ img_row_bounds[0]+offset, img_row_bounds[1]-1-offset)
981
998
  else:
982
999
  if not self._interactive:
983
1000
  self._logger.warning('center_rows unspecified, find '
984
1001
  'centers at reduced data bounds')
985
- center_rows = (0, num_row-1)
1002
+ center_rows = (img_row_bounds[0], img_row_bounds[1]-1)
986
1003
  fig, center_rows = select_image_indices(
987
1004
  nxentry.reduced_data.data.tomo_fields[
988
1005
  center_stack_index,0,:,:],
@@ -990,13 +1007,18 @@ class Tomo:
990
1007
  b=tbf[img_row_bounds[0]:img_row_bounds[1],
991
1008
  img_column_bounds[0]:img_column_bounds[1]],
992
1009
  preselected_indices=center_rows,
993
- title='Select or adjust two detector image row indices to '
994
- f'find center axis (in range [0, {num_row-1}])',
1010
+ axis_index_offset=img_row_bounds[0],
1011
+ title='Select two detector image row indices to find center '
1012
+ f'axis (in range [{img_row_bounds[0]}, '
1013
+ f'{img_row_bounds[1]-1}])',
995
1014
  title_a=r'Tomography image at $\theta$ = '
996
1015
  f'{round(thetas[0], 2)+0}',
997
1016
  title_b='Bright field', interactive=self._interactive)
998
- if center_rows[1] == num_row:
1017
+ if center_rows[1] == img_row_bounds[1]:
999
1018
  center_rows = (center_rows[0], center_rows[1]-1)
1019
+ offset_center_rows = (
1020
+ center_rows[0] - img_row_bounds[0],
1021
+ center_rows[1] - img_row_bounds[0])
1000
1022
  # Plot results
1001
1023
  if self._save_figs:
1002
1024
  fig.savefig(
@@ -1004,24 +1026,41 @@ class Tomo:
1004
1026
  self._output_folder, 'center_finding_rows.png'))
1005
1027
  plt.close()
1006
1028
 
1029
+ # Get effective pixel_size
1030
+ if 'zoom_perc' in nxentry.reduced_data:
1031
+ eff_pixel_size = float(
1032
+ 100. * (nxentry.instrument.detector.row_pixel_size
1033
+ / nxentry.reduced_data.attrs['zoom_perc']))
1034
+ else:
1035
+ eff_pixel_size = float(nxentry.instrument.detector.row_pixel_size)
1036
+ self._logger.debug(f'eff_pixel_size = {eff_pixel_size}')
1037
+
1038
+ # Get cross sectional diameter
1039
+ cross_sectional_dim = \
1040
+ eff_pixel_size * nxentry.reduced_data.data.bright_field.shape[1]
1041
+ self._logger.debug(f'cross_sectional_dim = {cross_sectional_dim}')
1042
+
1007
1043
  # Find the center offsets at each of the center rows
1044
+ prev_center_offset = None
1008
1045
  center_offsets = []
1009
- for i, center_row in enumerate(center_rows):
1046
+ for row, offset_row in zip(center_rows, offset_center_rows):
1010
1047
  t0 = time()
1011
1048
  center_offsets.append(
1012
1049
  self._find_center_one_plane(
1013
1050
  nxentry.reduced_data.data.tomo_fields[
1014
- center_stack_index,:,center_row,:],
1015
- center_row, thetas, eff_pixel_size, cross_sectional_dim,
1051
+ center_stack_index,:,offset_row,:],
1052
+ row, thetas, eff_pixel_size, cross_sectional_dim,
1016
1053
  path=self._output_folder, num_core=self._num_core,
1017
- search_range=tool_config.search_range,
1018
- search_step=tool_config.search_step,
1054
+ center_offset_min=tool_config.center_offset_min,
1055
+ center_offset_max=tool_config.center_offset_max,
1019
1056
  gaussian_sigma=tool_config.gaussian_sigma,
1020
- ring_width=tool_config.ring_width))
1057
+ ring_width=tool_config.ring_width,
1058
+ prev_center_offset=prev_center_offset))
1021
1059
  self._logger.info(
1022
- f'Finding center {i} took {time()-t0:.2f} seconds')
1023
- self._logger.debug(f'center_row {i} = {center_rows[i]:.2f}')
1024
- self._logger.debug(f'center_offset {i} = {center_offsets[i]:.2f}')
1060
+ f'Finding center row {row} took {time()-t0:.2f} seconds')
1061
+ self._logger.debug(f'center_row = {row:.2f}')
1062
+ self._logger.debug(f'center_offset = {center_offsets[-1]:.2f}')
1063
+ prev_center_offset = center_offsets[-1]
1025
1064
 
1026
1065
  center_config = {
1027
1066
  'center_rows': list(center_rows),
@@ -1029,12 +1068,14 @@ class Tomo:
1029
1068
  }
1030
1069
  if num_tomo_stacks > 1:
1031
1070
  center_config['center_stack_index'] = center_stack_index
1032
-
1033
- # Save test data to file
1034
- if self._test_mode:
1035
- with open(f'{self._output_folder}/center_config.yaml', 'w',
1036
- encoding='utf8') as f:
1037
- safe_dump(center_config, f)
1071
+ if tool_config.center_offset_min is not None:
1072
+ center_config['center_offset_min'] = tool_config.center_offset_min
1073
+ if tool_config.center_offset_max is not None:
1074
+ center_config['center_offset_max'] = tool_config.center_offset_max
1075
+ if tool_config.gaussian_sigma is not None:
1076
+ center_config['gaussian_sigma'] = tool_config.gaussian_sigma
1077
+ if tool_config.ring_width is not None:
1078
+ center_config['ring_width'] = tool_config.ring_width
1038
1079
 
1039
1080
  return center_config
1040
1081
 
@@ -1080,7 +1121,7 @@ class Tomo:
1080
1121
  # Create an NXprocess to store image reconstruction (meta)data
1081
1122
  nxprocess = NXprocess()
1082
1123
 
1083
- # Get rotation axis rows and centers
1124
+ # Get calibrated center axis rows and centers
1084
1125
  center_rows = center_info.get('center_rows')
1085
1126
  center_offsets = center_info.get('center_offsets')
1086
1127
  if center_rows is None or center_offsets is None:
@@ -1106,6 +1147,9 @@ class Tomo:
1106
1147
  tomo_stacks = np.asarray(nxentry.reduced_data.data.tomo_fields)
1107
1148
  num_tomo_stacks = tomo_stacks.shape[0]
1108
1149
  tomo_recon_stacks = num_tomo_stacks*[np.array([])]
1150
+ img_row_bounds = tuple(nxentry.reduced_data.get(
1151
+ 'img_row_bounds', (0, tomo_stacks.shape[2])))
1152
+ center_rows -= img_row_bounds[0]
1109
1153
  for i in range(num_tomo_stacks):
1110
1154
  # Convert reduced data stack from theta,row,column to
1111
1155
  # row,theta,column
@@ -1143,14 +1187,9 @@ class Tomo:
1143
1187
  # Resize the reconstructed tomography data
1144
1188
  # reconstructed data order in each stack: row/-z,y,x
1145
1189
  tomo_recon_shape = tomo_recon_stacks[0].shape
1146
- if self._test_mode:
1147
- x_bounds = tuple(self._test_config.get('x_bounds'))
1148
- y_bounds = tuple(self._test_config.get('y_bounds'))
1149
- z_bounds = (0, tomo_recon_shape[0])
1150
- else:
1151
- x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
1152
- tomo_recon_stacks, x_bounds=tool_config.x_bounds,
1153
- y_bounds=tool_config.y_bounds, z_bounds=tool_config.z_bounds)
1190
+ x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
1191
+ tomo_recon_stacks, x_bounds=tool_config.x_bounds,
1192
+ y_bounds=tool_config.y_bounds, z_bounds=tool_config.z_bounds)
1154
1193
  if x_bounds is None:
1155
1194
  x_range = (0, tomo_recon_shape[2])
1156
1195
  x_slice = int(x_range[1]/2)
@@ -1247,14 +1286,6 @@ class Tomo:
1247
1286
  title=f'recon {res_title} z={z[z_index]:.4f}',
1248
1287
  origin='lower', extent=extent, path=self._output_folder,
1249
1288
  save_fig=True, save_only=True)
1250
-
1251
- # Save test data to file
1252
- # reconstructed data order in each stack: z,y,x
1253
- if self._test_mode:
1254
- np.savetxt(
1255
- f'{self._output_folder}/recon_stack.txt',
1256
- tomo_recon_stacks[z_slice-z_range[0],:,:],
1257
- fmt='%.6e')
1258
1289
  else:
1259
1290
  # Plot a few reconstructed image slices
1260
1291
  if self._save_figs:
@@ -1276,15 +1307,6 @@ class Tomo:
1276
1307
  title=title, path=self._output_folder,
1277
1308
  save_fig=True, save_only=True)
1278
1309
 
1279
- # Save test data to file
1280
- # reconstructed data order in each stack: row/-z,y,x
1281
- if self._test_mode:
1282
- for i in range(tomo_recon_shape[0]):
1283
- np.savetxt(
1284
- f'{self._output_folder}/recon_stack_{i}.txt',
1285
- tomo_recon_stacks[i,z_slice-z_range[0],:,:],
1286
- fmt='%.6e')
1287
-
1288
1310
  # Add image reconstruction to reconstructed data NXprocess
1289
1311
  # reconstructed data order:
1290
1312
  # - for one stack: z,y,x
@@ -1295,15 +1317,24 @@ class Tomo:
1295
1317
  nxprocess[k] = v
1296
1318
  if k == 'center_rows' or k == 'center_offsets':
1297
1319
  nxprocess[k].units = 'pixels'
1320
+ if k == 'center_rows':
1321
+ nxprocess[k].attrs['long_name'] = \
1322
+ 'center row indices in detector frame of reference'
1298
1323
  if x_bounds is not None:
1299
1324
  nxprocess.x_bounds = x_bounds
1300
1325
  nxprocess.x_bounds.units = 'pixels'
1326
+ nxprocess.x_bounds.attrs['long_name'] = \
1327
+ 'x range indices in reduced data frame of reference'
1301
1328
  if y_bounds is not None:
1302
1329
  nxprocess.y_bounds = y_bounds
1303
1330
  nxprocess.y_bounds.units = 'pixels'
1331
+ nxprocess.y_bounds.attrs['long_name'] = \
1332
+ 'y range indices in reduced data frame of reference'
1304
1333
  if z_bounds is not None:
1305
1334
  nxprocess.z_bounds = z_bounds
1306
1335
  nxprocess.z_bounds.units = 'pixels'
1336
+ nxprocess.z_bounds.attrs['long_name'] = \
1337
+ 'z range indices in reduced data frame of reference'
1307
1338
  nxprocess.data.attrs['signal'] = 'reconstructed_data'
1308
1339
  if num_tomo_stacks == 1:
1309
1340
  nxprocess.data.reconstructed_data = tomo_recon_stack
@@ -1390,8 +1421,11 @@ class Tomo:
1390
1421
  # (meta)data
1391
1422
  nxprocess = NXprocess()
1392
1423
 
1393
- num_tomo_stacks = \
1394
- nxentry.reconstructed_data.data.reconstructed_data.shape[0]
1424
+ if nxentry.reconstructed_data.data.reconstructed_data.ndim == 3:
1425
+ num_tomo_stacks = 1
1426
+ else:
1427
+ num_tomo_stacks = \
1428
+ nxentry.reconstructed_data.data.reconstructed_data.shape[0]
1395
1429
  if num_tomo_stacks == 1:
1396
1430
  self._logger.info('Only one stack available: leaving combine_data')
1397
1431
  return nxroot
@@ -1407,19 +1441,16 @@ class Tomo:
1407
1441
  tomo_recon_combined = \
1408
1442
  nxentry.reconstructed_data.data.reconstructed_data[0,:,:,:]
1409
1443
  tomo_recon_combined = np.concatenate(
1410
- [nxentry.reconstructed_data.data.reconstructed_data[i,:,:,:]
1411
- for i in range(num_tomo_stacks-1,0,-1)]
1412
- + [tomo_recon_combined])
1444
+ [tomo_recon_combined]
1445
+ + [nxentry.reconstructed_data.data.reconstructed_data[i,:,:,:]
1446
+ for i in range(1, num_tomo_stacks)])
1413
1447
  self._logger.info(
1414
1448
  f'Combining the reconstructed stacks took {time()-t0:.2f} seconds')
1449
+ tomo_shape = tomo_recon_combined.shape
1415
1450
 
1416
1451
  # Resize the combined tomography data stacks
1417
1452
  # combined data order: row/-z,y,x
1418
- if self._test_mode:
1419
- x_bounds = None
1420
- y_bounds = None
1421
- z_bounds = tuple(self._test_config.get('z_bounds'))
1422
- elif self._interactive:
1453
+ if self._interactive:
1423
1454
  x_bounds, y_bounds, z_bounds = self._resize_reconstructed_data(
1424
1455
  tomo_recon_combined, combine_data=True)
1425
1456
  else:
@@ -1428,41 +1459,41 @@ class Tomo:
1428
1459
  self._logger.warning(
1429
1460
  'x_bounds unspecified, reconstruct data for full x-range')
1430
1461
  elif not is_int_pair(
1431
- x_bounds, ge=0, le=tomo_recon_combined.shape[2]):
1462
+ x_bounds, ge=0, le=tomo_shape[2]):
1432
1463
  raise ValueError(f'Invalid parameter x_bounds ({x_bounds})')
1433
1464
  y_bounds = tool_config.y_bounds
1434
1465
  if y_bounds is None:
1435
1466
  self._logger.warning(
1436
1467
  'y_bounds unspecified, reconstruct data for full y-range')
1437
1468
  elif not is_int_pair(
1438
- y_bounds, ge=0, le=tomo_recon_combined.shape[1]):
1469
+ y_bounds, ge=0, le=tomo_shape[1]):
1439
1470
  raise ValueError(f'Invalid parameter y_bounds ({y_bounds})')
1440
1471
  z_bounds = tool_config.z_bounds
1441
1472
  if z_bounds is None:
1442
1473
  self._logger.warning(
1443
1474
  'z_bounds unspecified, reconstruct data for full z-range')
1444
1475
  elif not is_int_pair(
1445
- z_bounds, ge=0, le=tomo_recon_combined.shape[0]):
1476
+ z_bounds, ge=0, le=tomo_shape[0]):
1446
1477
  raise ValueError(f'Invalid parameter z_bounds ({z_bounds})')
1447
1478
  if x_bounds is None:
1448
- x_range = (0, tomo_recon_combined.shape[2])
1479
+ x_range = (0, tomo_shape[2])
1449
1480
  x_slice = int(x_range[1]/2)
1450
1481
  else:
1451
1482
  x_range = (min(x_bounds), max(x_bounds))
1452
1483
  x_slice = int((x_bounds[0]+x_bounds[1]) / 2)
1453
1484
  if y_bounds is None:
1454
- y_range = (0, tomo_recon_combined.shape[1])
1485
+ y_range = (0, tomo_shape[1])
1455
1486
  y_slice = int(y_range[1]/2)
1456
1487
  else:
1457
1488
  y_range = (min(y_bounds), max(y_bounds))
1458
1489
  y_slice = int((y_bounds[0]+y_bounds[1]) / 2)
1459
1490
  if z_bounds is None:
1460
- z_range = (0, tomo_recon_combined.shape[0])
1491
+ z_range = (0, tomo_shape[0])
1461
1492
  z_slice = int(z_range[1]/2)
1462
1493
  else:
1463
1494
  z_range = (min(z_bounds), max(z_bounds))
1464
1495
  z_slice = int((z_bounds[0]+z_bounds[1]) / 2)
1465
- z_dim_org = tomo_recon_combined.shape[0]
1496
+ z_dim_org = tomo_shape[0]
1466
1497
  tomo_recon_combined = tomo_recon_combined[
1467
1498
  z_range[0]:z_range[1],y_range[0]:y_range[1],x_range[0]:x_range[1]]
1468
1499
 
@@ -1472,6 +1503,7 @@ class Tomo:
1472
1503
  # Here x is to the right, y along the beam direction
1473
1504
  # and z upwards in the lab frame of reference
1474
1505
  tomo_recon_combined = np.flip(tomo_recon_combined, 0)
1506
+ tomo_shape = tomo_recon_combined.shape
1475
1507
  z_range = (z_dim_org-z_range[1], z_dim_org-z_range[0])
1476
1508
 
1477
1509
  # Get coordinate axes
@@ -1507,7 +1539,7 @@ class Tomo:
1507
1539
  y[-1],
1508
1540
  z[0],
1509
1541
  z[-1])
1510
- x_slice = int(tomo_recon_combined.shape[2]/2)
1542
+ x_slice = int(tomo_shape[2]/2)
1511
1543
  quick_imshow(
1512
1544
  tomo_recon_combined[:,:,x_slice],
1513
1545
  title=f'recon combined x={x[x_slice]:.4f}', origin='lower',
@@ -1518,7 +1550,7 @@ class Tomo:
1518
1550
  x[-1],
1519
1551
  z[0],
1520
1552
  z[-1])
1521
- y_slice = int(tomo_recon_combined.shape[1]/2)
1553
+ y_slice = int(tomo_shape[1]/2)
1522
1554
  quick_imshow(
1523
1555
  tomo_recon_combined[:,y_slice,:],
1524
1556
  title=f'recon combined y={y[y_slice]:.4f}', origin='lower',
@@ -1529,34 +1561,32 @@ class Tomo:
1529
1561
  x[-1],
1530
1562
  y[0],
1531
1563
  y[-1])
1532
- z_slice = int(tomo_recon_combined.shape[0]/2)
1564
+ z_slice = int(tomo_shape[0]/2)
1533
1565
  quick_imshow(
1534
1566
  tomo_recon_combined[z_slice,:,:],
1535
1567
  title=f'recon combined z={z[z_slice]:.4f}', origin='lower',
1536
1568
  extent=extent, path=self._output_folder, save_fig=True,
1537
1569
  save_only=True)
1538
1570
 
1539
- # Save test data to file
1540
- # combined data order: z,y,x
1541
- if self._test_mode:
1542
- z_slice = int(tomo_recon_combined.shape[0]/2)
1543
- np.savetxt(
1544
- f'{self._output_folder}/recon_combined.txt',
1545
- tomo_recon_combined[z_slice,:,:], fmt='%.6e')
1546
-
1547
1571
  # Add image reconstruction to reconstructed data NXprocess
1548
1572
  # combined data order: z,y,x
1549
1573
  nxprocess.data = NXdata()
1550
1574
  nxprocess.attrs['default'] = 'data'
1551
- if x_bounds is not None:
1575
+ if x_bounds is not None and x_bounds != (0, tomo_shape[2]):
1552
1576
  nxprocess.x_bounds = x_bounds
1553
1577
  nxprocess.x_bounds.units = 'pixels'
1554
- if y_bounds is not None:
1578
+ nxprocess.x_bounds.attrs['long_name'] = \
1579
+ 'x range indices in reconstructed data frame of reference'
1580
+ if y_bounds is not None and y_bounds != (0, tomo_shape[1]):
1555
1581
  nxprocess.y_bounds = y_bounds
1556
1582
  nxprocess.y_bounds.units = 'pixels'
1557
- if z_bounds is not None:
1583
+ nxprocess.y_bounds.attrs['long_name'] = \
1584
+ 'y range indices in reconstructed data frame of reference'
1585
+ if z_bounds is not None and z_bounds != (0, tomo_shape[0]):
1558
1586
  nxprocess.z_bounds = z_bounds
1559
1587
  nxprocess.z_bounds.units = 'pixels'
1588
+ nxprocess.z_bounds.attrs['long_name'] = \
1589
+ 'z range indices in reconstructed data frame of reference'
1560
1590
  nxprocess.data.combined_data = tomo_recon_combined
1561
1591
  nxprocess.data.attrs['signal'] = 'combined_data'
1562
1592
  nxprocess.data.attrs['axes'] = ['z', 'y', 'x']
@@ -1689,12 +1719,6 @@ class Tomo:
1689
1719
  else:
1690
1720
  raise RuntimeError(f'Invalid tbf_stack shape ({tbf_stack.shape})')
1691
1721
 
1692
- # Subtract dark field
1693
- if 'data' in reduced_data and 'dark_field' in reduced_data.data:
1694
- tbf -= np.asarray(reduced_data.data.dark_field)
1695
- else:
1696
- self._logger.warning('Dark field unavailable')
1697
-
1698
1722
  # Set any non-positive values to one
1699
1723
  # (avoid negative bright field values for spikes in dark field)
1700
1724
  tbf[tbf < 1] = 1
@@ -1712,8 +1736,9 @@ class Tomo:
1712
1736
 
1713
1737
  return reduced_data
1714
1738
 
1715
- def _set_detector_bounds(self, nxentry, reduced_data, image_key, theta,
1716
- img_row_bounds=None):
1739
+ def _set_detector_bounds(
1740
+ self, nxentry, reduced_data, image_key, theta, img_row_bounds,
1741
+ calibrate_center_rows):
1717
1742
  """
1718
1743
  Set vertical detector bounds for each image stack.Right now the
1719
1744
  range is the same for each set in the image stack.
@@ -1724,15 +1749,11 @@ class Tomo:
1724
1749
  # Local modules
1725
1750
  from CHAP.utils.general import is_index_range
1726
1751
 
1727
- if self._test_mode:
1728
- return tuple(self._test_config['img_row_bounds'])
1729
-
1730
1752
  # Get the first tomography image and the reference heights
1731
1753
  image_mask = reduced_data.get('image_mask')
1732
1754
  if image_mask is None:
1733
1755
  first_image_index = 0
1734
1756
  else:
1735
- raise RuntimeError('image_mask not tested yet')
1736
1757
  image_mask = np.asarray(image_mask)
1737
1758
  first_image_index = int(np.argmax(image_mask))
1738
1759
  field_indices_all = [
@@ -1756,79 +1777,105 @@ class Tomo:
1756
1777
  raise RuntimeError('Unable to load the tomography images '
1757
1778
  f'for stack {i}')
1758
1779
 
1759
- # Select image bounds
1780
+ # Set initial image bounds or rotation calibration rows
1760
1781
  tbf = np.asarray(reduced_data.data.bright_field)
1761
- if nxentry.instrument.source.attrs['station'] in ('id1a3', 'id3a'):
1762
- pixel_size = float(nxentry.instrument.detector.row_pixel_size)
1763
- # Try to get a fit from the bright field
1764
- row_sum = np.sum(tbf, 1)
1765
- fit = Fit.fit_data(
1766
- row_sum, 'rectangle', x=np.array(range(len(row_sum))),
1767
- form='atan', guess=True)
1768
- parameters = fit.best_values
1769
- row_low_fit = parameters.get('center1', None)
1770
- row_upp_fit = parameters.get('center2', None)
1771
- sig_low = parameters.get('sigma1', None)
1772
- sig_upp = parameters.get('sigma2', None)
1773
- have_fit = (fit.success and row_low_fit is not None
1774
- and row_upp_fit is not None and sig_low is not None
1775
- and sig_upp is not None
1776
- and 0 <= row_low_fit < row_upp_fit <= row_sum.size
1777
- and (sig_low+sig_upp) / (row_upp_fit-row_low_fit) < 0.1)
1778
- if num_tomo_stacks == 1:
1779
- if have_fit:
1780
- delta_z = (row_upp_fit-row_low_fit) * pixel_size
1782
+ if (not isinstance(calibrate_center_rows, bool)
1783
+ and is_int_pair(calibrate_center_rows)):
1784
+ img_row_bounds = calibrate_center_rows
1785
+ else:
1786
+ if nxentry.instrument.source.attrs['station'] in ('id1a3', 'id3a'):
1787
+ pixel_size = float(nxentry.instrument.detector.row_pixel_size)
1788
+ # Try to get a fit from the bright field
1789
+ row_sum = np.sum(tbf, 1)
1790
+ fit = Fit.fit_data(
1791
+ row_sum, 'rectangle', x=np.array(range(len(row_sum))),
1792
+ form='atan', guess=True)
1793
+ parameters = fit.best_values
1794
+ row_low_fit = parameters.get('center1', None)
1795
+ row_upp_fit = parameters.get('center2', None)
1796
+ sig_low = parameters.get('sigma1', None)
1797
+ sig_upp = parameters.get('sigma2', None)
1798
+ have_fit = (fit.success and row_low_fit is not None
1799
+ and row_upp_fit is not None and sig_low is not None
1800
+ and sig_upp is not None
1801
+ and 0 <= row_low_fit < row_upp_fit <= row_sum.size
1802
+ and (sig_low+sig_upp) / (row_upp_fit-row_low_fit) < 0.1)
1803
+ if num_tomo_stacks == 1:
1804
+ if have_fit:
1805
+ # Add a pixel margin for roundoff effects in fit
1806
+ row_low_fit += 1
1807
+ row_upp_fit -= 1
1808
+ delta_z = (row_upp_fit-row_low_fit) * pixel_size
1809
+ else:
1810
+ # Set a default range of 1 mm
1811
+ # RV can we get this from the slits?
1812
+ delta_z = 1.0
1781
1813
  else:
1782
- # Set a default range of 1 mm
1783
- # RV can we get this from the slits?
1784
- delta_z = 1.0
1785
- else:
1786
- # Get the default range from the reference heights
1787
- delta_z = z_translation_levels[1]-z_translation_levels[0]
1788
- for i in range(2, num_tomo_stacks):
1789
- delta_z = min(
1790
- delta_z,
1791
- z_translation_levels[i]-z_translation_levels[i-1])
1792
- self._logger.debug(f'delta_z = {delta_z}')
1793
- num_row_min = int((delta_z + 0.5*pixel_size) / pixel_size)
1794
- if num_row_min > tbf.shape[0]:
1795
- self._logger.warning(
1796
- 'Image bounds and pixel size prevent seamless '
1797
- 'stacking')
1798
- row_low = 0
1799
- row_upp = tbf.shape[0]
1800
- else:
1801
- self._logger.debug(f'num_row_min = {num_row_min}')
1802
- if have_fit:
1803
- # Center the default range relative to the fitted
1804
- # window
1805
- row_low = int((row_low_fit+row_upp_fit-num_row_min) / 2)
1806
- row_upp = row_low+num_row_min
1814
+ # Get the default range from the reference heights
1815
+ delta_z = z_translation_levels[1]-z_translation_levels[0]
1816
+ for i in range(2, num_tomo_stacks):
1817
+ delta_z = min(
1818
+ delta_z,
1819
+ z_translation_levels[i]-z_translation_levels[i-1])
1820
+ self._logger.debug(f'delta_z = {delta_z}')
1821
+ num_row_min = int((delta_z + 0.5*pixel_size) / pixel_size)
1822
+ if num_row_min > tbf.shape[0]:
1823
+ self._logger.warning(
1824
+ 'Image bounds and pixel size prevent seamless '
1825
+ 'stacking')
1826
+ row_low = 0
1827
+ row_upp = tbf.shape[0]
1807
1828
  else:
1808
- # Center the default range
1809
- row_low = int((tbf.shape[0]-num_row_min) / 2)
1810
- row_upp = row_low+num_row_min
1811
- img_row_bounds = (row_low, row_upp)
1829
+ self._logger.debug(f'num_row_min = {num_row_min}')
1830
+ if have_fit:
1831
+ # Center the default range relative to the fitted
1832
+ # window
1833
+ row_low = int((row_low_fit+row_upp_fit-num_row_min) / 2)
1834
+ row_upp = row_low+num_row_min
1835
+ else:
1836
+ # Center the default range
1837
+ row_low = int((tbf.shape[0]-num_row_min) / 2)
1838
+ row_upp = row_low+num_row_min
1839
+ img_row_bounds = (row_low, row_upp)
1840
+ if calibrate_center_rows:
1841
+ # Add a small margin to avoid edge effects
1842
+ offset = int(min(5, 0.1*(row_upp-row_low)))
1843
+ img_row_bounds = (row_low+offset, row_upp-1-offset)
1844
+ else:
1845
+ if num_tomo_stacks > 1:
1846
+ raise NotImplementedError(
1847
+ 'Selecting image bounds or calibrating rotation axis '
1848
+ 'for multiple stacks on FMB')
1849
+ # For FMB: use the first tomography image to select range
1850
+ # RV revisit if they do tomography with multiple stacks
1851
+ if img_row_bounds is None and not self._interactive:
1852
+ if calibrate_center_rows:
1853
+ self._logger.warning(
1854
+ 'calibrate_center_rows unspecified, find rotation '
1855
+ 'axis at detector bounds (with a small margin)')
1856
+ # Add a small margin to avoid edge effects
1857
+ offset = min(5, 0.1*first_image.shape[0])
1858
+ img_row_bounds = (
1859
+ offset, first_image.shape[0]-1-offset)
1860
+ else:
1861
+ self._logger.warning(
1862
+ 'img_row_bounds unspecified, reduce data for '
1863
+ 'entire detector range')
1864
+ img_row_bounds = (0, first_image.shape[0])
1865
+ if calibrate_center_rows:
1866
+ title='Select two detector image row indices to '\
1867
+ 'calibrate rotation axis (in range '\
1868
+ f'[0, {first_image.shape[0]-1}])'
1812
1869
  else:
1813
- if num_tomo_stacks > 1:
1814
- raise NotImplementedError(
1815
- 'Selecting image bounds for multiple stacks on FMB')
1816
- # For FMB: use the first tomography image to select range
1817
- # RV revisit if they do tomography with multiple stacks
1818
- if img_row_bounds is None:
1819
- if not self._interactive:
1820
- self._logger.warning(
1821
- 'img_row_bounds unspecified, reduce data for entire '
1822
- 'detector range')
1823
- img_row_bounds = (0, first_image.shape[0])
1870
+ title='Select detector image row bounds for data '\
1871
+ f'reduction (in range [0, {first_image.shape[0]}])'
1824
1872
  fig, img_row_bounds = select_image_indices(
1825
1873
  first_image, 0, b=tbf, preselected_indices=img_row_bounds,
1826
- title='Select or adjust detector image row bounds for data '
1827
- f'reduction (in range {[0, first_image.shape[0]]})',
1874
+ title=title,
1828
1875
  title_a=r'Tomography image at $\theta$ = 'f'{round(theta, 2)+0}',
1829
1876
  title_b='Bright field',
1830
1877
  interactive=self._interactive)
1831
- if (num_tomo_stacks > 1
1878
+ if not calibrate_center_rows and (num_tomo_stacks > 1
1832
1879
  and (img_row_bounds[1]-img_row_bounds[0]+1)
1833
1880
  < int((delta_z - 0.5*pixel_size) / pixel_size)):
1834
1881
  self._logger.warning(
@@ -1836,8 +1883,12 @@ class Tomo:
1836
1883
 
1837
1884
  # Plot results
1838
1885
  if self._save_figs:
1839
- fig.savefig(
1840
- os_path.join(self._output_folder, 'detector_image_bounds.png'))
1886
+ if calibrate_center_rows:
1887
+ fig.savefig(os_path.join(
1888
+ self._output_folder, 'rotation_calibration_rows.png'))
1889
+ else:
1890
+ fig.savefig(os_path.join(
1891
+ self._output_folder, 'detector_image_bounds.png'))
1841
1892
  plt.close()
1842
1893
 
1843
1894
  return img_row_bounds
@@ -1881,9 +1932,6 @@ class Tomo:
1881
1932
  # Local modules
1882
1933
  from CHAP.utils.general import index_nearest
1883
1934
 
1884
- if self._test_mode:
1885
- return tuple(self._test_config['delta_theta'])
1886
-
1887
1935
  # if input_yesno(
1888
1936
  # '\nDo you want to zoom in to reduce memory '
1889
1937
  # 'requirement (y/n)?', 'n'):
@@ -1916,38 +1964,58 @@ class Tomo:
1916
1964
 
1917
1965
  return zoom_perc, delta_theta
1918
1966
 
1919
- def _gen_tomo(self, nxentry, reduced_data, image_key):
1967
+ def _gen_tomo(
1968
+ self, nxentry, reduced_data, image_key, calibrate_center_rows):
1920
1969
  """Generate tomography fields."""
1921
1970
  # Third party modules
1922
1971
  from numexpr import evaluate
1923
1972
  from scipy.ndimage import zoom
1924
1973
 
1925
- # Get full bright field
1974
+ # Get dark field
1975
+ if 'dark_field' in reduced_data.data:
1976
+ tdf = np.asarray(reduced_data.data.dark_field)
1977
+ else:
1978
+ self._logger.warning('Dark field unavailable')
1979
+ tdf = None
1980
+
1981
+ # Get bright field
1926
1982
  tbf = np.asarray(reduced_data.data.bright_field)
1927
1983
  tbf_shape = tbf.shape
1928
1984
 
1985
+ # Subtract dark field
1986
+ if tdf is not None:
1987
+ try:
1988
+ with SetNumexprThreads(self._num_core):
1989
+ evaluate('tbf-tdf', out=tbf)
1990
+ except TypeError as e:
1991
+ sys_exit(
1992
+ f'\nA {type(e).__name__} occured while subtracting '
1993
+ 'the dark field with num_expr.evaluate()'
1994
+ '\nTry reducing the detector range'
1995
+ f'\n(currently img_row_bounds = {img_row_bounds}, and '
1996
+ f'img_column_bounds = {img_column_bounds})\n')
1997
+
1929
1998
  # Get image bounds
1930
- img_row_bounds = tuple(
1931
- reduced_data.get('img_row_bounds', (0, tbf_shape[0])))
1999
+ img_row_bounds = tuple(reduced_data.get('img_row_bounds'))
1932
2000
  img_column_bounds = tuple(
1933
2001
  reduced_data.get('img_column_bounds', (0, tbf_shape[1])))
1934
2002
 
1935
- # Get resized dark field
1936
- if 'dark_field' in reduced_data.data:
1937
- tdf = np.asarray(
1938
- reduced_data.data.dark_field)[
2003
+ # Check if this run is a rotation axis calibration
2004
+ # and resize dark and bright fields accordingly
2005
+ if calibrate_center_rows:
2006
+ if tdf is not None:
2007
+ tdf = tdf[calibrate_center_rows,:]
2008
+ tbf = tbf[calibrate_center_rows,:]
2009
+ else:
2010
+ if (img_row_bounds != (0, tbf.shape[0])
2011
+ or img_column_bounds != (0, tbf.shape[1])):
2012
+ if tdf is not None:
2013
+ tdf = tdf[
2014
+ img_row_bounds[0]:img_row_bounds[1],
2015
+ img_column_bounds[0]:img_column_bounds[1]]
2016
+ tbf = tbf[
1939
2017
  img_row_bounds[0]:img_row_bounds[1],
1940
2018
  img_column_bounds[0]:img_column_bounds[1]]
1941
- else:
1942
- self._logger.warning('Dark field unavailable')
1943
- tdf = None
1944
-
1945
- # Resize bright field
1946
- if (img_row_bounds != (0, tbf.shape[0])
1947
- or img_column_bounds != (0, tbf.shape[1])):
1948
- tbf = tbf[
1949
- img_row_bounds[0]:img_row_bounds[1],
1950
- img_column_bounds[0]:img_column_bounds[1]]
1951
2019
 
1952
2020
  # Get thetas (in degrees)
1953
2021
  thetas = np.asarray(reduced_data.rotation_angle)
@@ -1968,11 +2036,14 @@ class Tomo:
1968
2036
  nxentry.sample.z_translation)[field_indices_all]
1969
2037
  z_translation_levels = sorted(list(set(z_translation_all)))
1970
2038
  num_tomo_stacks = len(z_translation_levels)
2039
+ if calibrate_center_rows:
2040
+ center_stack_index = int(num_tomo_stacks/2)
1971
2041
  tomo_stacks = num_tomo_stacks*[np.array([])]
1972
2042
  horizontal_shifts = []
1973
2043
  vertical_shifts = []
1974
- tomo_stacks = []
1975
2044
  for i, z_translation in enumerate(z_translation_levels):
2045
+ if calibrate_center_rows and i != center_stack_index:
2046
+ continue
1976
2047
  try:
1977
2048
  field_indices = [
1978
2049
  field_indices_all[index]
@@ -1997,28 +2068,35 @@ class Tomo:
1997
2068
  except:
1998
2069
  raise RuntimeError('Unable to load the tomography images '
1999
2070
  f'for stack {i}')
2000
- tomo_stacks.append(tomo_stack)
2001
- if not i:
2002
- tomo_stack_shape = tomo_stack.shape
2003
- else:
2004
- assert tomo_stack_shape == tomo_stack.shape
2071
+ tomo_stacks[i] = tomo_stack
2072
+ if not calibrate_center_rows:
2073
+ if not i:
2074
+ tomo_stack_shape = tomo_stack.shape
2075
+ else:
2076
+ assert tomo_stack_shape == tomo_stack.shape
2005
2077
 
2006
2078
  row_pixel_size = float(nxentry.instrument.detector.row_pixel_size)
2007
2079
  column_pixel_size = float(
2008
2080
  nxentry.instrument.detector.column_pixel_size)
2009
- reduced_tomo_stacks = []
2081
+ reduced_tomo_stacks = num_tomo_stacks*[np.array([])]
2082
+ tomo_stack_shape = None
2010
2083
  for i, tomo_stack in enumerate(tomo_stacks):
2084
+ if not tomo_stack.size:
2085
+ continue
2011
2086
  # Resize the tomography images
2012
2087
  # Right now the range is the same for each set in the stack
2013
- assert len(thetas) == tomo_stack.shape[0]
2014
- if (img_row_bounds != (0, tomo_stack.shape[1])
2015
- or img_column_bounds != (0, tomo_stack.shape[2])):
2016
- tomo_stack = tomo_stack[
2017
- :,img_row_bounds[0]:img_row_bounds[1],
2018
- img_column_bounds[0]:img_column_bounds[1]].astype(
2088
+ if calibrate_center_rows:
2089
+ tomo_stack = tomo_stack[:,calibrate_center_rows,:].astype(
2019
2090
  'float64', copy=False)
2020
2091
  else:
2021
- tomo_stack = tomo_stack.astype('float64', copy=False)
2092
+ if (img_row_bounds != (0, tomo_stack.shape[1])
2093
+ or img_column_bounds != (0, tomo_stack.shape[2])):
2094
+ tomo_stack = tomo_stack[
2095
+ :,img_row_bounds[0]:img_row_bounds[1],
2096
+ img_column_bounds[0]:img_column_bounds[1]].astype(
2097
+ 'float64', copy=False)
2098
+ else:
2099
+ tomo_stack = tomo_stack.astype('float64', copy=False)
2022
2100
 
2023
2101
  # Subtract dark field
2024
2102
  if tdf is not None:
@@ -2059,7 +2137,7 @@ class Tomo:
2059
2137
 
2060
2138
  # Downsize tomography stack to smaller size
2061
2139
  tomo_stack = tomo_stack.astype('float32', copy=False)
2062
- if not self._test_mode and (self._save_figs or self._save_only):
2140
+ if self._save_figs or self._save_only:
2063
2141
  theta = round(thetas[0], 2)
2064
2142
  if len(tomo_stacks) == 1:
2065
2143
  title = r'Reduced data, $\theta$ = 'f'{theta}'
@@ -2081,24 +2159,24 @@ class Tomo:
2081
2159
  tomo_zoom_list.append(tomo_zoom)
2082
2160
  tomo_stack = np.stack(tomo_zoom_list)
2083
2161
  self._logger.info(f'Zooming in took {time()-t0:.2f} seconds')
2162
+ title = f'red stack {zoom_perc}p theta ' \
2163
+ f'{round(thetas[0], 2)+0}'
2164
+ quick_imshow(
2165
+ tomo_stack[0,:,:], title=title,
2166
+ path=self._output_folder, save_fig=self._save_figs,
2167
+ save_only=self._save_only, block=self._block)
2084
2168
  del tomo_zoom_list
2085
- if not self._test_mode:
2086
- title = f'red stack {zoom_perc}p theta ' \
2087
- f'{round(thetas[0], 2)+0}'
2088
- quick_imshow(
2089
- tomo_stack[0,:,:], title=title,
2090
- path=self._output_folder, save_fig=self._save_figs,
2091
- save_only=self._save_only, block=self._block)
2092
-
2093
- # Save test data to file
2094
- if self._test_mode:
2095
- row_index = int(tomo_stack.shape[1]/2)
2096
- np.savetxt(
2097
- f'{self._output_folder}/red_stack_{i}.txt',
2098
- tomo_stack[:,row_index,:], fmt='%.6e')
2099
2169
 
2100
2170
  # Combine resized stacks
2101
- reduced_tomo_stacks.append(tomo_stack)
2171
+ reduced_tomo_stacks[i] = tomo_stack
2172
+ if tomo_stack_shape is None:
2173
+ tomo_stack_shape = tomo_stack.shape
2174
+ else:
2175
+ assert tomo_stack_shape == tomo_stack.shape
2176
+
2177
+ for i, stack in enumerate(reduced_tomo_stacks):
2178
+ if not stack.size:
2179
+ reduced_tomo_stacks[i] = np.zeros(tomo_stack_shape)
2102
2180
 
2103
2181
  # Add tomo field info to reduced data NXprocess
2104
2182
  reduced_data.x_translation = np.asarray(horizontal_shifts)
@@ -2116,98 +2194,195 @@ class Tomo:
2116
2194
 
2117
2195
  def _find_center_one_plane(
2118
2196
  self, sinogram, row, thetas, eff_pixel_size, cross_sectional_dim,
2119
- path=None, num_core=1, search_range=None, search_step=None,
2120
- gaussian_sigma=None, ring_width=None):
2197
+ path=None, num_core=1, center_offset_min=-50, center_offset_max=50,
2198
+ gaussian_sigma=None, ring_width=None, prev_center_offset=None):
2121
2199
  """Find center for a single tomography plane."""
2122
2200
  # Third party modules
2123
2201
  import matplotlib.pyplot as plt
2124
2202
  from tomopy import find_center_vo
2125
2203
 
2204
+ # sinogram index order: theta,column
2205
+ sinogram = np.asarray(sinogram)
2126
2206
  if not gaussian_sigma:
2127
2207
  gaussian_sigma = None
2128
2208
  if not ring_width:
2129
2209
  ring_width = None
2130
- # Try automatic center finding routines for initial value
2131
- # sinogram index order: theta,column
2132
- # need column,theta for iradon, so take transpose
2133
- sinogram = np.asarray(sinogram)
2134
- sinogram_t = sinogram.T
2135
- center = sinogram.shape[1]/2
2136
2210
 
2137
- # Try using Nghia Vos method
2211
+ # Try Nghia Vo's method to get the default center offset
2138
2212
  t0 = time()
2213
+ if center_offset_min is None:
2214
+ center_offset_min = -50
2215
+ if center_offset_max is None:
2216
+ center_offset_max = 50
2139
2217
  if num_core > NUM_CORE_TOMOPY_LIMIT:
2140
2218
  self._logger.debug(
2141
- f'Running find_center_vo on {NUM_CORE_TOMOPY_LIMIT} cores ...')
2219
+ f'Running find_center_vo on {NUM_CORE_TOMOPY_LIMIT} '
2220
+ 'cores ...')
2142
2221
  tomo_center = find_center_vo(
2143
- sinogram, ncore=NUM_CORE_TOMOPY_LIMIT)
2222
+ sinogram, ncore=NUM_CORE_TOMOPY_LIMIT, smin=center_offset_min,
2223
+ smax=center_offset_max)
2144
2224
  else:
2145
- tomo_center = find_center_vo(sinogram, ncore=num_core)
2225
+ tomo_center = find_center_vo(
2226
+ sinogram, ncore=num_core, smin=center_offset_min,
2227
+ smax=center_offset_max)
2146
2228
  self._logger.info(
2147
- f'Finding center using Nghia Vos method took {time()-t0:.2f} '
2229
+ f'Finding center using Nghia Vo\'s method took {time()-t0:.2f} '
2148
2230
  'seconds')
2149
- center_offset_vo = float(tomo_center-center)
2231
+ center_offset_range = sinogram.shape[1]/2
2232
+ center_offset_vo = float(tomo_center-center_offset_range)
2150
2233
  self._logger.info(
2151
- f'Center at row {row} using Nghia Vos method = '
2234
+ f'Center at row {row} using Nghia Vo\'s method = '
2152
2235
  f'{center_offset_vo:.2f}')
2153
2236
 
2237
+ center_offset_default = None
2154
2238
  if self._interactive or self._save_figs:
2155
2239
 
2156
- # Reconstruct the plane for Nghia Vo’s center offset
2240
+ # Need column,theta for iradon, so take transpose
2241
+ sinogram_t = sinogram.T
2242
+
2243
+ # Reconstruct the plane for the Nghia Vo's center offset
2157
2244
  t0 = time()
2158
2245
  recon_plane = self._reconstruct_one_plane(
2159
2246
  sinogram_t, center_offset_vo, thetas, eff_pixel_size,
2160
2247
  cross_sectional_dim, False, num_core, gaussian_sigma,
2161
2248
  ring_width)
2162
2249
  self._logger.info(
2163
- f'Reconstructing row {row} took {time()-t0:.2f} seconds')
2250
+ f'Reconstructing row {row} with center at '
2251
+ f'{center_offset_vo} took {time()-t0:.2f} seconds')
2252
+
2164
2253
  recon_edges = [self._get_edges_one_plane(recon_plane)]
2165
- fig, accept, selected_center_offset = self._select_center_offset(
2166
- recon_edges, row, center_offset_vo)
2167
- # Plot results
2168
- if self._save_figs:
2169
- fig.savefig(
2170
- os_path.join(
2171
- self._output_folder,
2172
- f'edges_default_center_row_{row}.png'))
2173
- plt.close()
2254
+ if (not self._interactive or prev_center_offset is None
2255
+ or prev_center_offset == center_offset_vo):
2256
+ fig, accept, center_offset_default = \
2257
+ self._select_center_offset(
2258
+ recon_edges, row, center_offset_vo, vo=True)
2259
+ # Plot results
2260
+ if self._save_figs:
2261
+ fig.savefig(
2262
+ os_path.join(
2263
+ self._output_folder,
2264
+ f'edges_center_row_{row}_vo.png'))
2265
+ plt.close()
2174
2266
 
2267
+ if self._interactive:
2268
+
2269
+ if center_offset_default is None:
2270
+ # Reconstruct the plane at the previous center offset
2271
+ t0 = time()
2272
+ recon_plane = self._reconstruct_one_plane(
2273
+ sinogram_t, prev_center_offset, thetas, eff_pixel_size,
2274
+ cross_sectional_dim, False, num_core, gaussian_sigma,
2275
+ ring_width)
2276
+ self._logger.info(
2277
+ f'Reconstructing row {row} with center at '
2278
+ f'{prev_center_offset} took {time()-t0:.2f} seconds')
2279
+
2280
+ recon_edges.insert(0, self._get_edges_one_plane(recon_plane))
2281
+ fig, accept, center_offset_default = \
2282
+ self._select_center_offset(
2283
+ recon_edges, row,
2284
+ (prev_center_offset, center_offset_vo),
2285
+ vo=True, include_all_bad=True)
2286
+ # Plot results
2287
+ if self._save_figs:
2288
+ fig.savefig(
2289
+ os_path.join(
2290
+ self._output_folder,
2291
+ f'edges_center_row_{row}_'
2292
+ f'{prev_center_offset}_{center_offset_vo}.png'))
2293
+ plt.close()
2294
+
2295
+ if center_offset_default == center_offset_vo:
2296
+ recon_edges.pop(0)
2297
+ fig, accept, _ = self._select_center_offset(
2298
+ recon_edges, row, center_offset_vo, vo=True)
2299
+ # Plot results
2300
+ if self._save_figs:
2301
+ fig.savefig(
2302
+ os_path.join(
2303
+ self._output_folder,
2304
+ f'edges_center_row_{row}_vo.png'))
2305
+ plt.close()
2306
+ else:
2307
+ recon_edges.pop()
2308
+ if center_offset_default == prev_center_offset:
2309
+ fig, accept, _ = self._select_center_offset(
2310
+ recon_edges, row, prev_center_offset, vo=False)
2311
+ # Plot results
2312
+ if self._save_figs:
2313
+ fig.savefig(
2314
+ os_path.join(
2315
+ self._output_folder,
2316
+ f'edges_center_row_{row}_'
2317
+ f'{prev_center_offset}.png'))
2318
+ plt.close()
2319
+ else:
2320
+ center_offset_default = prev_center_offset
2175
2321
 
2176
2322
  # Perform center finding search
2177
- center_offsets = [center_offset_vo]
2178
- step_size = 4
2179
- indices = 3*[-1]
2180
2323
  prev_index = None
2181
2324
  up = True
2325
+ include_all_bad = True
2326
+ selected_center_offset = center_offset_default
2327
+ center_offsets = [center_offset_default]
2328
+ step_size = 4
2329
+ if not accept:
2330
+ max_step_size = min(
2331
+ center_offset_range+center_offset_default,
2332
+ center_offset_range-center_offset_default-1)
2333
+ max_step_size = 1 << int(np.log2(max_step_size))-1
2334
+ step_size = input_int(
2335
+ '\nEnter the intial step size in the center '
2336
+ 'calibration search (will be truncated to the nearest '
2337
+ 'lower power of 2)', ge=2, le=max_step_size,
2338
+ default=4)
2339
+ step_size = 1 << int(np.log2(step_size))
2182
2340
  while not accept and step_size:
2183
- selected_center_offset = round(selected_center_offset)
2184
- preselected_offsets = (
2185
- selected_center_offset-step_size,
2186
- selected_center_offset,
2187
- selected_center_offset+step_size)
2341
+ if selected_center_offset == 'all bad':
2342
+ selected_center_offset = round(center_offset_default)
2343
+ preselected_offsets = (
2344
+ selected_center_offset-step_size,
2345
+ selected_center_offset+step_size)
2346
+ else:
2347
+ selected_center_offset = round(center_offset_default)
2348
+ preselected_offsets = (
2349
+ selected_center_offset-step_size,
2350
+ selected_center_offset,
2351
+ selected_center_offset+step_size)
2352
+ indices = []
2188
2353
  for i, preselected_offset in enumerate(preselected_offsets):
2189
2354
  if preselected_offset in center_offsets:
2190
- indices[i] = center_offsets.index(preselected_offset)
2355
+ indices.append(
2356
+ center_offsets.index(preselected_offset))
2191
2357
  else:
2192
2358
  recon_plane = self._reconstruct_one_plane(
2193
2359
  sinogram_t, preselected_offset, thetas,
2194
2360
  eff_pixel_size, cross_sectional_dim, False,
2195
2361
  num_core, gaussian_sigma, ring_width)
2196
- indices[i] = len(center_offsets)
2362
+ indices.append(len(center_offsets))
2197
2363
  center_offsets.append(preselected_offset)
2198
2364
  recon_edges.append(
2199
2365
  self._get_edges_one_plane(recon_plane))
2200
2366
  fig, accept, selected_center_offset = \
2201
2367
  self._select_center_offset(
2202
2368
  [recon_edges[i] for i in indices],
2203
- row, preselected_offsets, center_offset_vo)
2204
- index = preselected_offsets.index(selected_center_offset)
2205
- if index != 1 and prev_index in (None, index) and up:
2369
+ row, preselected_offsets,
2370
+ include_all_bad=include_all_bad)
2371
+ if selected_center_offset == 'all bad':
2206
2372
  step_size *=2
2207
2373
  else:
2208
- step_size = int(step_size/2)
2209
- up = False
2210
- prev_index = index
2374
+ include_all_bad = False
2375
+ index = preselected_offsets.index(selected_center_offset)
2376
+ if index != 1 and prev_index in (None, index) and up:
2377
+ step_size *=2
2378
+ else:
2379
+ step_size = int(step_size/2)
2380
+ up = False
2381
+ prev_index = index
2382
+ if step_size > max_step_size:
2383
+ self._logger.warning('Exceeding maximum step size'
2384
+ f'of {max_step_size}')
2385
+ step_size = max_step_size
2211
2386
  # Plot results
2212
2387
  if self._save_figs:
2213
2388
  fig.savefig(
@@ -2216,6 +2391,8 @@ class Tomo:
2216
2391
  f'edges_center_{row}_{min(preselected_offsets)}_'\
2217
2392
  f'{max(preselected_offsets)}.png'))
2218
2393
  plt.close()
2394
+ del sinogram_t
2395
+ del recon_plane
2219
2396
 
2220
2397
  # Select center location
2221
2398
  if self._interactive:
@@ -2223,10 +2400,6 @@ class Tomo:
2223
2400
  else:
2224
2401
  center_offset = center_offset_vo
2225
2402
 
2226
- del sinogram_t
2227
- if recon_plane is not None:
2228
- del recon_plane
2229
-
2230
2403
  return float(center_offset)
2231
2404
 
2232
2405
  def _reconstruct_one_plane(
@@ -2306,8 +2479,8 @@ class Tomo:
2306
2479
  return denoise_tv_chambolle(recon_plane, weight=weight)[0]
2307
2480
 
2308
2481
  def _select_center_offset(
2309
- self, recon_edges, row, preselected_offsets,
2310
- center_offset_vo=None):
2482
+ self, recon_edges, row, preselected_offsets, vo=False,
2483
+ include_all_bad=False):
2311
2484
  """Select a center offset value from an "edges plot" image
2312
2485
  for a single reconstructed tomography data plane."""
2313
2486
  # Third party modules
@@ -2317,10 +2490,13 @@ class Tomo:
2317
2490
 
2318
2491
  def select_offset(offset):
2319
2492
  """Callback function for the "Select offset" input."""
2320
- selected_offset.append(
2321
- ((False,
2322
- preselected_offsets[preselected_offsets.index(
2323
- float(radio_btn.value_selected))])))
2493
+ if offset in ('both bad', 'all bad'):
2494
+ selected_offset.append(((False, 'all bad')))
2495
+ else:
2496
+ selected_offset.append(
2497
+ ((False,
2498
+ preselected_offsets[preselected_offsets.index(
2499
+ float(radio_btn.value_selected))])))
2324
2500
  plt.close()
2325
2501
 
2326
2502
  def reject(event):
@@ -2379,23 +2555,40 @@ class Tomo:
2379
2555
  if not i:
2380
2556
  ax.set_ylabel('y', fontsize='x-large')
2381
2557
 
2382
- if len(recon_edges) > 1:
2383
- fig_title = plt.figtext(
2384
- *title_pos,
2385
- f'Reconstructions for row {row} (default center offset: '
2386
- f'{center_offset_vo})',
2387
- **title_props)
2558
+ if num_plots == 1:
2559
+ if vo:
2560
+ fig_title = plt.figtext(
2561
+ *title_pos,
2562
+ f'Reconstruction for row {row} (Nghia Vo\'s center '
2563
+ f'offset: {preselected_offsets[0]})',
2564
+ **title_props)
2565
+ else:
2566
+ fig_title = plt.figtext(
2567
+ *title_pos,
2568
+ f'Reconstruction for row {row} (previous center offset: '
2569
+ f'{preselected_offsets[0]})',
2570
+ **title_props)
2571
+ fig_subtitle = plt.figtext(
2572
+ *subtitle_pos,
2573
+ 'Press "Accept" to accept this value or "Reject" to start a '
2574
+ 'center calibration search',
2575
+ **subtitle_props)
2576
+ else:
2577
+ if vo:
2578
+ fig_title = plt.figtext(
2579
+ *title_pos,
2580
+ f'Reconstruction for row {row} (previous center offset: '
2581
+ f'{preselected_offsets[0]}, Nghia Vo\'s center '
2582
+ f'offset: {preselected_offsets[1]})',
2583
+ **title_props)
2584
+ else:
2585
+ fig_title = plt.figtext(
2586
+ *title_pos, f'Reconstruction for row {row}', **title_props)
2388
2587
  fig_subtitle = plt.figtext(
2389
2588
  *subtitle_pos,
2390
2589
  'Select the best offset or press "Accept" to accept the '
2391
2590
  f'default value of {preselected_offsets[1]}',
2392
2591
  **subtitle_props)
2393
- else:
2394
- fig_title = plt.figtext(
2395
- *title_pos,
2396
- 'Press "Accept" to accept this value or "Reject" to start a '
2397
- 'center calibration search',
2398
- **title_props)
2399
2592
 
2400
2593
  if not self._interactive:
2401
2594
 
@@ -2403,7 +2596,7 @@ class Tomo:
2403
2596
 
2404
2597
  else:
2405
2598
 
2406
- fig.subplots_adjust(bottom=0.2)
2599
+ fig.subplots_adjust(bottom=0.25, top=0.85)
2407
2600
 
2408
2601
  if num_plots == 1:
2409
2602
 
@@ -2419,9 +2612,16 @@ class Tomo:
2419
2612
  select_text = plt.figtext(
2420
2613
  0.225, 0.175, 'Select offset', fontsize='x-large',
2421
2614
  horizontalalignment='center', verticalalignment='center')
2615
+ if include_all_bad:
2616
+ if num_plots == 2:
2617
+ labels = (*preselected_offsets, 'both bad')
2618
+ else:
2619
+ labels = (*preselected_offsets, 'all bad')
2620
+ else:
2621
+ labels = preselected_offsets
2422
2622
  radio_btn = RadioButtons(
2423
2623
  plt.axes([0.175, 0.05, 0.1, 0.1]),
2424
- labels = preselected_offsets, active=1)
2624
+ labels = labels, active=1)
2425
2625
  radio_cid = radio_btn.on_clicked(select_offset)
2426
2626
 
2427
2627
  # Setup "Accept" button
@@ -2446,9 +2646,11 @@ class Tomo:
2446
2646
  fig_title.remove()
2447
2647
  else:
2448
2648
  fig_title.set_in_layout(True)
2449
- fig_subtitle.remove()
2450
2649
  select_text.remove()
2650
+ fig_subtitle.remove()
2451
2651
  fig.tight_layout(rect=(0, 0, 1, 0.95))
2652
+ if not selected_offset and num_plots == 1:
2653
+ selected_offset.append((True, preselected_offsets[0]))
2452
2654
 
2453
2655
  return fig, *selected_offset[0]
2454
2656
 
@@ -2507,7 +2709,6 @@ class Tomo:
2507
2709
  # RV prep.stripe.remove_stripe_fw seems flawed for hollow brick
2508
2710
  # accross multiple stacks
2509
2711
  if remove_stripe_sigma is not None and remove_stripe_sigma:
2510
- self._logger.warning('Ignoring remove_stripe_sigma')
2511
2712
  if num_core > NUM_CORE_TOMOPY_LIMIT:
2512
2713
  tomo_stack = prep.stripe.remove_stripe_fw(
2513
2714
  tomo_stack, sigma=remove_stripe_sigma,
@@ -3070,9 +3271,9 @@ class TomoBrightFieldProcessor(Processor):
3070
3271
  dtype=np.int64)
3071
3272
  bright_field = np.concatenate((dummy_fields, bright_field))
3072
3273
  num_image += num_dummy_start
3073
- # Add 10% to slit size to make the bright beam slightly taller
3274
+ # Add 20% to slit size to make the bright beam slightly taller
3074
3275
  # than the vertical displacements between stacks
3075
- slit_size = 1.10*source.slit_size
3276
+ slit_size = 1.2*source.slit_size
3076
3277
  if slit_size < float(detector.row_pixel_size*detector_size[0]):
3077
3278
  img_row_coords = float(detector.row_pixel_size) \
3078
3279
  * (0.5 + np.asarray(range(int(detector_size[0])))