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/__init__.py +2 -0
- CHAP/common/__init__.py +6 -2
- CHAP/common/processor.py +245 -153
- CHAP/common/reader.py +160 -124
- CHAP/common/writer.py +150 -94
- CHAP/tomo/models.py +10 -9
- CHAP/tomo/processor.py +554 -353
- CHAP/utils/general.py +21 -11
- CHAP/utils/scanparsers.py +78 -39
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/RECORD +15 -15
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/WHEEL +1 -1
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/top_level.txt +0 -0
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,
|
|
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:
|
|
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
|
|
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'
|
|
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(
|
|
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
|
-
'
|
|
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
|
|
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(
|
|
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
|
-
#
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
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
|
|
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
|
|
971
|
-
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(
|
|
980
|
-
|
|
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,
|
|
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
|
-
|
|
994
|
-
|
|
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] ==
|
|
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
|
|
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,:,
|
|
1015
|
-
|
|
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
|
-
|
|
1018
|
-
|
|
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 {
|
|
1023
|
-
self._logger.debug(f'center_row
|
|
1024
|
-
self._logger.debug(f'center_offset
|
|
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
|
-
|
|
1034
|
-
if
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
|
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
|
-
|
|
1147
|
-
x_bounds
|
|
1148
|
-
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
|
-
|
|
1394
|
-
|
|
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
|
-
[
|
|
1411
|
-
|
|
1412
|
-
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1716
|
-
img_row_bounds
|
|
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
|
-
#
|
|
1780
|
+
# Set initial image bounds or rotation calibration rows
|
|
1760
1781
|
tbf = np.asarray(reduced_data.data.bright_field)
|
|
1761
|
-
if
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
-
#
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
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
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
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
|
-
|
|
1814
|
-
|
|
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=
|
|
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
|
-
|
|
1840
|
-
os_path.join(
|
|
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(
|
|
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
|
|
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
|
-
#
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
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
|
|
2001
|
-
if not
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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}
|
|
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(
|
|
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 Vo
|
|
2229
|
+
f'Finding center using Nghia Vo\'s method took {time()-t0:.2f} '
|
|
2148
2230
|
'seconds')
|
|
2149
|
-
|
|
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 Vo
|
|
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
|
-
#
|
|
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}
|
|
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
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
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
|
|
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
|
|
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,
|
|
2204
|
-
|
|
2205
|
-
if
|
|
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
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2321
|
-
((False,
|
|
2322
|
-
|
|
2323
|
-
|
|
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
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
f'{
|
|
2387
|
-
|
|
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.
|
|
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 =
|
|
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
|
|
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.
|
|
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])))
|