comparison tomo.py @ 41:ef5c2f7b49ec draft

"planemo upload for repository https://github.com/rolfverberg/galaxytools commit 0d207c80e38a6019595ebe178f5678372b75f3e7"
author rv43
date Thu, 21 Apr 2022 14:21:38 +0000
parents fa94fe25ca46
children 0b9ce9489ecc
comparison
equal deleted inserted replaced
40:fa94fe25ca46 41:ef5c2f7b49ec
18 import pyinputplus as pyip 18 import pyinputplus as pyip
19 except: 19 except:
20 pass 20 pass
21 import argparse 21 import argparse
22 import numpy as np 22 import numpy as np
23 import numexpr as ne 23 try:
24 import numexpr as ne
25 except:
26 pass
24 import multiprocessing as mp 27 import multiprocessing as mp
25 import scipy.ndimage as spi 28 try:
26 import tomopy 29 import scipy.ndimage as spi
30 except:
31 pass
32 try:
33 import tomopy
34 except:
35 pass
27 from time import time 36 from time import time
28 from skimage.transform import iradon 37 try:
29 from skimage.restoration import denoise_tv_chambolle 38 from skimage.transform import iradon
39 except:
40 pass
41 try:
42 from skimage.restoration import denoise_tv_chambolle
43 except:
44 pass
30 45
31 import msnc_tools as msnc 46 import msnc_tools as msnc
47
48 # the following tomopy routines don't run with more than 24 cores on Galaxy-Dev
49 # - tomopy.find_center_vo
50 # - tomopy.prep.stripe.remove_stripe_fw
51 num_core_tomopy_limit = 24
32 52
33 class set_numexpr_threads: 53 class set_numexpr_threads:
34 54
35 def __init__(self, num_core): 55 def __init__(self, num_core):
36 cpu_count = mp.cpu_count() 56 cpu_count = mp.cpu_count()
628 if self.is_valid: 648 if self.is_valid:
629 self.cf.saveFile(self.config_out) 649 self.cf.saveFile(self.config_out)
630 650
631 return 651 return
632 652
633 def _genDark(self, tdf_files, dark_field_pngname): 653 def _genDark(self, tdf_files):
634 """Generate dark field. 654 """Generate dark field.
635 """ 655 """
636 # Load the dark field images 656 # Load the dark field images
637 logging.debug('Loading dark field...') 657 logging.debug('Loading dark field...')
638 dark_field = self.config['dark_field'] 658 dark_field = self.config['dark_field']
649 tdf_mean = np.nanmean(self.tdf) 669 tdf_mean = np.nanmean(self.tdf)
650 logging.debug(f'tdf_cutoff = {tdf_cutoff}') 670 logging.debug(f'tdf_cutoff = {tdf_cutoff}')
651 logging.debug(f'tdf_mean = {tdf_mean}') 671 logging.debug(f'tdf_mean = {tdf_mean}')
652 np.nan_to_num(self.tdf, copy=False, nan=tdf_mean, posinf=tdf_mean, neginf=0.) 672 np.nan_to_num(self.tdf, copy=False, nan=tdf_mean, posinf=tdf_mean, neginf=0.)
653 if self.galaxy_flag: 673 if self.galaxy_flag:
654 msnc.quickImshow(self.tdf, title='dark field', name=dark_field_pngname, 674 msnc.quickImshow(self.tdf, title='dark field', path='setup_pngs',
655 save_fig=True, save_only=True) 675 save_fig=True, save_only=True)
656 elif not self.test_mode: 676 elif not self.test_mode:
657 msnc.quickImshow(self.tdf, title='dark field', path=self.output_folder, 677 msnc.quickImshow(self.tdf, title='dark field', path=self.output_folder,
658 save_fig=self.save_plots, save_only=self.save_plots_only) 678 save_fig=self.save_plots, save_only=self.save_plots_only)
659 679
660 def _genBright(self, tbf_files, bright_field_pngname): 680 def _genBright(self, tbf_files):
661 """Generate bright field. 681 """Generate bright field.
662 """ 682 """
663 # Load the bright field images 683 # Load the bright field images
664 logging.debug('Loading bright field...') 684 logging.debug('Loading bright field...')
665 bright_field = self.config['bright_field'] 685 bright_field = self.config['bright_field']
683 if self.tdf.size: 703 if self.tdf.size:
684 self.tbf -= self.tdf 704 self.tbf -= self.tdf
685 else: 705 else:
686 logging.warning('Dark field unavailable') 706 logging.warning('Dark field unavailable')
687 if self.galaxy_flag: 707 if self.galaxy_flag:
688 msnc.quickImshow(self.tbf, title='bright field', name=bright_field_pngname, 708 msnc.quickImshow(self.tbf, title='bright field', path='setup_pngs',
689 save_fig=True, save_only=True) 709 save_fig=True, save_only=True)
690 elif not self.test_mode: 710 elif not self.test_mode:
691 msnc.quickImshow(self.tbf, title='bright field', path=self.output_folder, 711 msnc.quickImshow(self.tbf, title='bright field', path=self.output_folder,
692 save_fig=self.save_plots, save_only=self.save_plots_only) 712 save_fig=self.save_plots, save_only=self.save_plots_only)
693 713
694 def _setDetectorBounds(self, tomo_stack_files, tomo_field_pngname, detectorbounds_pngname): 714 def _setDetectorBounds(self, tomo_stack_files):
695 """Set vertical detector bounds for image stack. 715 """Set vertical detector bounds for image stack.
696 """ 716 """
697 preprocess = self.config.get('preprocess') 717 preprocess = self.config.get('preprocess')
698 if preprocess is None: 718 if preprocess is None:
699 img_x_bounds = [None, None] 719 img_x_bounds = [None, None]
727 if num_tomo_stacks > 1: 747 if num_tomo_stacks > 1:
728 delta_z = stacks[1]['ref_height']-stacks[0]['ref_height'] 748 delta_z = stacks[1]['ref_height']-stacks[0]['ref_height']
729 for i in range(2, num_tomo_stacks): 749 for i in range(2, num_tomo_stacks):
730 delta_z = min(delta_z, stacks[i]['ref_height']-stacks[i-1]['ref_height']) 750 delta_z = min(delta_z, stacks[i]['ref_height']-stacks[i-1]['ref_height'])
731 logging.debug(f'delta_z = {delta_z}') 751 logging.debug(f'delta_z = {delta_z}')
732 num_x_min = int(delta_z/pixel_size)+1 752 num_x_min = int((delta_z-0.5*pixel_size)/pixel_size)
733 logging.debug(f'num_x_min = {num_x_min}') 753 logging.debug(f'num_x_min = {num_x_min}')
734 if num_x_min > self.tbf.shape[0]: 754 if num_x_min > self.tbf.shape[0]:
735 logging.warning('Image bounds and pixel size prevent seamless stacking') 755 logging.warning('Image bounds and pixel size prevent seamless stacking')
736 num_x_min = self.tbf.shape[0] 756 num_x_min = None
737 757
738 # Select image bounds 758 # Select image bounds
739 if self.galaxy_flag: 759 if self.galaxy_flag:
740 if num_x_min is None or num_x_min >= self.tbf.shape[0]: 760 x_sum = np.sum(self.tbf, 1)
741 img_x_bounds = [0, self.tbf.shape[0]]
742 else:
743 margin = int(num_x_min/10)
744 offset = min(0, int((self.tbf.shape[0]-num_x_min)/2-margin))
745 img_x_bounds = [offset, num_x_min+offset+2*margin]
746 tomo_stack = msnc.loadImageStack(tomo_stack_files[0], self.config['data_filetype'],
747 stacks[0]['img_offset'], 1)
748 x_sum = np.sum(tomo_stack[0,:,:], 1)
749 x_sum_min = x_sum.min() 761 x_sum_min = x_sum.min()
750 x_sum_max = x_sum.max() 762 x_sum_max = x_sum.max()
751 title = f'tomography image at theta={self.config["theta_range"]["start"]}' 763 x_low = 0
752 msnc.quickImshow(tomo_stack[0,:,:], title=title, name=tomo_field_pngname, 764 x_upp = x_sum.size
753 save_fig=True, save_only=True, show_grid=True) 765 if num_x_min is not None:
766 fit = msnc.fitStep(y=x_sum, model='rectangle', form='atan')
767 x_low = fit.get('center1', None)
768 x_upp = fit.get('center2', None)
769 sig_low = fit.get('sigma1', None)
770 sig_upp = fit.get('sigma2', None)
771 if (x_low is not None and x_upp is not None and sig_low is not None and
772 sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and
773 (sig_low+sig_upp)/(x_upp-x_low) < 0.1):
774 if num_tomo_stacks == 1 or num_x_min is None:
775 x_low = int(x_low-(x_upp-x_low)/10)
776 x_upp = int(x_upp+(x_upp-x_low)/10)
777 else:
778 x_low = int((x_low+x_upp)/2-num_x_min/2)
779 x_upp = x_low+num_x_min
780 if x_low < 0:
781 x_low = 0
782 if x_upp > x_sum.size:
783 x_upp = x_sum.size
784 else:
785 x_low = 0
786 x_upp = x_sum.size
754 msnc.quickPlot((range(x_sum.size), x_sum), 787 msnc.quickPlot((range(x_sum.size), x_sum),
755 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'), 788 ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'),
756 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum_min, x_sum_max], 'r-'), 789 ([x_upp-1, x_upp-1], [x_sum_min, x_sum_max], 'r-'),
757 title='sum over theta and y', name=detectorbounds_pngname, 790 title=f'sum bright field over theta/y (row bounds: [{x_low}, {x_upp}])',
758 save_fig=True, save_only=True, show_grid=True) 791 path='setup_pngs', name='detectorbounds.png', save_fig=True, save_only=True,
792 show_grid=True)
793 for i,stack in enumerate(stacks):
794 tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
795 stack['img_offset'], 1)
796 tomo_stack = tomo_stack[0,:,:]
797 if num_x_min is not None:
798 tomo_stack_max = tomo_stack.max()
799 tomo_stack[x_low,:] = tomo_stack_max
800 tomo_stack[x_upp-1,:] = tomo_stack_max
801 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
802 msnc.quickImshow(tomo_stack, title=title, path='setup_pngs',
803 name=f'tomo_{stack["index"]}.png', save_fig=True, save_only=True,
804 show_grid=True)
805 del tomo_stack
759 806
760 # Update config and save to file 807 # Update config and save to file
808 img_x_bounds = [x_low, x_upp]
809 logging.debug(f'img_x_bounds: {img_x_bounds}')
761 if preprocess is None: 810 if preprocess is None:
762 self.cf.config['preprocess'] = {'img_x_bounds' : img_x_bounds} 811 self.cf.config['preprocess'] = {'img_x_bounds' : img_x_bounds}
763 else: 812 else:
764 preprocess['img_x_bounds'] = img_x_bounds 813 preprocess['img_x_bounds'] = img_x_bounds
765 self.cf.saveFile(self.config_out) 814 self.cf.saveFile(self.config_out)
830 title='sum over theta and y') 879 title='sum over theta and y')
831 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+ 880 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+
832 f'upper bound = {img_x_bounds[1]} (exclusive)]') 881 f'upper bound = {img_x_bounds[1]} (exclusive)]')
833 use_bounds = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) 882 use_bounds = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True)
834 if use_bounds == 'no': 883 if use_bounds == 'no':
884 use_fit = 'no'
835 fit = msnc.fitStep(y=x_sum, model='rectangle', form='atan') 885 fit = msnc.fitStep(y=x_sum, model='rectangle', form='atan')
836 x_low = fit.get('center1', None) 886 x_low = fit.get('center1', None)
837 x_upp = fit.get('center2', None) 887 x_upp = fit.get('center2', None)
838 if (x_low is not None and x_low >= 0 and x_upp is not None and 888 sig_low = fit.get('sigma1', None)
839 x_low < x_upp < x_sum.size): 889 sig_upp = fit.get('sigma2', None)
840 x_low = int(x_low-(x_upp-x_low)/10) 890 if (x_low is not None and x_upp is not None and sig_low is not None and
891 sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and
892 (sig_low+sig_upp)/(x_upp-x_low) < 0.1):
893 if num_tomo_stacks == 1 or num_x_min is None:
894 x_low = int(x_low-(x_upp-x_low)/10)
895 x_upp = int(x_upp+(x_upp-x_low)/10)
896 else:
897 x_low = int((x_low+x_upp)/2-num_x_min/2)
898 x_upp = x_low+num_x_min
841 if x_low < 0: 899 if x_low < 0:
842 x_low = 0 900 x_low = 0
843 x_upp = int(x_upp+(x_upp-x_low)/10) 901 if x_upp > x_sum.size:
844 if x_upp >= x_sum.size:
845 x_upp = x_sum.size 902 x_upp = x_sum.size
846 tmp = np.copy(self.tbf) 903 tmp = np.copy(self.tbf)
847 tmp_max = tmp.max() 904 tmp_max = tmp.max()
848 tmp[x_low,:] = tmp_max 905 tmp[x_low,:] = tmp_max
849 tmp[x_upp-1,:] = tmp_max 906 tmp[x_upp-1,:] = tmp_max
852 del tmp 909 del tmp
853 msnc.quickPlot((range(x_sum.size), x_sum), 910 msnc.quickPlot((range(x_sum.size), x_sum),
854 ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'), 911 ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'),
855 ([x_upp, x_upp], [x_sum_min, x_sum_max], 'r-'), 912 ([x_upp, x_upp], [x_sum_min, x_sum_max], 'r-'),
856 title='sum over theta and y') 913 title='sum over theta and y')
857 print(f'lower bound = {x_low} (inclusive)\nupper bound = {x_upp} (exclusive)]') 914 print(f'lower bound = {x_low} (inclusive)')
915 print(f'upper bound = {x_upp} (exclusive)]')
858 use_fit = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) 916 use_fit = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True)
859 if use_fit == 'no': 917 if use_fit == 'no':
860 img_x_bounds = msnc.selectArrayBounds(x_sum, img_x_bounds[0], img_x_bounds[1], 918 img_x_bounds = msnc.selectArrayBounds(x_sum, img_x_bounds[0], img_x_bounds[1],
861 num_x_min, 'sum over theta and y') 919 num_x_min, 'sum over theta and y')
862 else: 920 else:
863 img_x_bounds = [x_low, x_upp] 921 img_x_bounds = [x_low, x_upp]
864 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min: 922 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min:
865 logging.warning('Image bounds and pixel size prevent seamless stacking') 923 logging.warning('Image bounds and pixel size prevent seamless stacking')
866 msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]), 924 #msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]),
867 x_sum[img_x_bounds[0]:img_x_bounds[1]], 925 # x_sum[img_x_bounds[0]:img_x_bounds[1]],
926 # title='sum over theta and y', path=self.output_folder,
927 # save_fig=self.save_plots, save_only=True)
928 msnc.quickPlot((range(x_sum.size), x_sum),
929 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'),
930 ([img_x_bounds[1], img_x_bounds[1]], [x_sum_min, x_sum_max], 'r-'),
868 title='sum over theta and y', path=self.output_folder, 931 title='sum over theta and y', path=self.output_folder,
869 save_fig=self.save_plots, save_only=True) 932 save_fig=self.save_plots, save_only=True)
870 del x_sum 933 del x_sum
934 for i,stack in enumerate(stacks):
935 tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
936 stack['img_offset'], 1)
937 tomo_stack = tomo_stack[0,:,:]
938 if num_x_min is not None:
939 tomo_stack_max = tomo_stack.max()
940 tomo_stack[img_x_bounds[0],:] = tomo_stack_max
941 tomo_stack[img_x_bounds[1]-1,:] = tomo_stack_max
942 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
943 msnc.quickImshow(tomo_stack, title=title, path='setup_pngs',
944 name=f'tomo_{stack["index"]}.png', save_fig=self.save_plots, save_only=True,
945 show_grid=True)
946 del tomo_stack
871 logging.debug(f'img_x_bounds: {img_x_bounds}') 947 logging.debug(f'img_x_bounds: {img_x_bounds}')
872 948
873 if self.save_plots_only: 949 if self.save_plots_only:
874 msnc.clearFig('bright field') 950 msnc.clearFig('bright field')
875 msnc.clearFig('sum over theta and y') 951 msnc.clearFig('sum over theta and y')
1146 # recon_sinogram = filters.gaussian(recon_sinogram, 3.0) 1222 # recon_sinogram = filters.gaussian(recon_sinogram, 3.0)
1147 recon_sinogram = spi.gaussian_filter(recon_sinogram, 0.5) 1223 recon_sinogram = spi.gaussian_filter(recon_sinogram, 0.5)
1148 recon_clean = np.expand_dims(recon_sinogram, axis=0) 1224 recon_clean = np.expand_dims(recon_sinogram, axis=0)
1149 del recon_sinogram 1225 del recon_sinogram
1150 t1 = time() 1226 t1 = time()
1151 logging.info(f'running tomopy.misc.corr.remove_ring on {num_core} cores ...') 1227 logging.debug(f'running remove_ring on {num_core} cores ...')
1152 recon_clean = tomopy.misc.corr.remove_ring(recon_clean, rwidth=17, ncore=num_core) 1228 recon_clean = tomopy.misc.corr.remove_ring(recon_clean, rwidth=17, ncore=num_core)
1153 logging.info(f'... tomopy.misc.corr.remove_ring took {time()-t1:.2f} seconds!') 1229 logging.debug(f'... remove_ring took {time()-t1:.2f} seconds!')
1154 logging.debug(f'filtering and removing ring artifact took {time()-t0:.2f} seconds!') 1230 logging.debug(f'filtering and removing ring artifact took {time()-t0:.2f} seconds!')
1155 return recon_clean 1231 return recon_clean
1156 1232
1157 def _plotEdgesOnePlane(self, recon_plane, title, path=None, weight=0.001): 1233 def _plotEdgesOnePlane(self, recon_plane, title, path=None, weight=0.001):
1158 # RV parameters for the denoise, gaussian, and ring removal will be different for different feature sizes 1234 # RV parameters for the denoise, gaussian, and ring removal will be different for different feature sizes
1175 def _findCenterOnePlane(self, sinogram, row, thetas_deg, eff_pixel_size, cross_sectional_dim, 1251 def _findCenterOnePlane(self, sinogram, row, thetas_deg, eff_pixel_size, cross_sectional_dim,
1176 tol=0.1, num_core=1, galaxy_param=None): 1252 tol=0.1, num_core=1, galaxy_param=None):
1177 """Find center for a single tomography plane. 1253 """Find center for a single tomography plane.
1178 """ 1254 """
1179 if self.galaxy_flag: 1255 if self.galaxy_flag:
1180 assert(galaxy_param) 1256 assert(isinstance(galaxy_param, dict))
1181 if not os.path.exists('find_center_pngs'): 1257 if not os.path.exists('find_center_pngs'):
1182 os.mkdir('find_center_pngs') 1258 os.mkdir('find_center_pngs')
1183 # sinogram index order: theta,column 1259 # sinogram index order: theta,column
1184 # need index order column,theta for iradon, so take transpose 1260 # need index order column,theta for iradon, so take transpose
1185 sinogram_T = sinogram.T 1261 sinogram_T = sinogram.T
1186 center = sinogram.shape[1]/2 1262 center = sinogram.shape[1]/2
1187 1263
1188 # try automatic center finding routines for initial value 1264 # try automatic center finding routines for initial value
1189 t0 = time() 1265 t0 = time()
1190 if num_core > 24: 1266 if num_core > num_core_tomopy_limit:
1191 logging.info('running tomopy.find_center_vo on 24 cores ...') 1267 logging.debug(f'running find_center_vo on {num_core_tomopy_limit} cores ...')
1192 tomo_center = tomopy.find_center_vo(sinogram, ncore=24) 1268 tomo_center = tomopy.find_center_vo(sinogram, ncore=num_core_tomopy_limit)
1193 else: 1269 else:
1194 logging.info(f'running tomopy.find_center_vo on {num_core} cores ...') 1270 logging.debug(f'running find_center_vo on {num_core} cores ...')
1195 tomo_center = tomopy.find_center_vo(sinogram, ncore=num_core) 1271 tomo_center = tomopy.find_center_vo(sinogram, ncore=num_core)
1196 logging.info(f'... tomopy.find_center_vo took {time()-t0:.2f} seconds!') 1272 logging.debug(f'... find_center_vo took {time()-t0:.2f} seconds!')
1197 center_offset_vo = tomo_center-center 1273 center_offset_vo = tomo_center-center
1198 if self.test_mode: 1274 if self.test_mode:
1199 logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') 1275 logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}')
1200 del sinogram_T 1276 del sinogram_T
1201 return float(center_offset_vo) 1277 return float(center_offset_vo)
1202 elif self.galaxy_flag: 1278 elif self.galaxy_flag:
1203 logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') 1279 logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}')
1204 t0 = time() 1280 t0 = time()
1205 logging.info(f'running self._reconstructOnePlane on {num_core} cores ...') 1281 logging.debug(f'running _reconstructOnePlane on {num_core} cores ...')
1206 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, 1282 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg,
1207 eff_pixel_size, cross_sectional_dim, False, num_core) 1283 eff_pixel_size, cross_sectional_dim, False, num_core)
1208 logging.info(f'... self._reconstructOnePlane took {time()-t0:.2f} seconds!') 1284 logging.debug(f'... _reconstructOnePlane took {time()-t0:.2f} seconds!')
1209 title = f'edges row{row} center offset{center_offset_vo:.2f} Vo' 1285 title = f'edges row{row} center offset{center_offset_vo:.2f} Vo'
1210 self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs') 1286 self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs')
1211 del recon_plane 1287 del recon_plane
1212 if not galaxy_param['center_type_selector']: 1288 if not galaxy_param['center_type_selector']:
1213 del sinogram_T 1289 del sinogram_T
1277 num_center_offset = 1+int((center_offset_upp-center_offset_low)/center_offset_step) 1353 num_center_offset = 1+int((center_offset_upp-center_offset_low)/center_offset_step)
1278 center_offsets = np.linspace(center_offset_low, center_offset_upp, num_center_offset) 1354 center_offsets = np.linspace(center_offset_low, center_offset_upp, num_center_offset)
1279 for center_offset in center_offsets: 1355 for center_offset in center_offsets:
1280 if center_offset == center_offset_vo: 1356 if center_offset == center_offset_vo:
1281 continue 1357 continue
1282 logging.info(f'center_offset = {center_offset:.2f}')
1283 t0 = time() 1358 t0 = time()
1284 logging.info(f'running self._reconstructOnePlane on {num_core} cores ...') 1359 logging.debug(f'running _reconstructOnePlane on {num_core} cores ...')
1285 recon_plane = self._reconstructOnePlane(sinogram_T, center_offset+center, 1360 recon_plane = self._reconstructOnePlane(sinogram_T, center_offset+center,
1286 thetas_deg, eff_pixel_size, cross_sectional_dim, False, num_core) 1361 thetas_deg, eff_pixel_size, cross_sectional_dim, False, num_core)
1287 logging.info(f'... self._reconstructOnePlane took {time()-t0:.2f} seconds!') 1362 logging.debug(f'... _reconstructOnePlane took {time()-t0:.2f} seconds!')
1288 title = f'edges row{row} center_offset{center_offset:.2f}' 1363 title = f'edges row{row} center_offset{center_offset:.2f}'
1289 if self.galaxy_flag: 1364 if self.galaxy_flag:
1290 self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs') 1365 self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs')
1291 else: 1366 else:
1292 self._plotEdgesOnePlane(recon_plane, title) 1367 self._plotEdgesOnePlane(recon_plane, title)
1333 raise ValueError('center_offsets dimension mismatch in reconstructOneTomoStack') 1408 raise ValueError('center_offsets dimension mismatch in reconstructOneTomoStack')
1334 centers = center_offsets 1409 centers = center_offsets
1335 centers += tomo_stack.shape[2]/2 1410 centers += tomo_stack.shape[2]/2
1336 if True: 1411 if True:
1337 t0 = time() 1412 t0 = time()
1338 if num_core > 24: 1413 if num_core > num_core_tomopy_limit:
1339 logging.info(f'running tomopy.prep.stripe.remove_stripe_fw on 24 cores ...') 1414 logging.debug('running remove_stripe_fw on {num_core_tomopy_limit} cores ...')
1340 tomo_stack = tomopy.prep.stripe.remove_stripe_fw( 1415 tomo_stack = tomopy.prep.stripe.remove_stripe_fw(
1341 tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma, ncore=24) 1416 tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma,
1417 ncore=num_core_tomopy_limit)
1342 else: 1418 else:
1343 logging.info(f'running tomopy.prep.stripe.remove_stripe_fw on {num_core} cores ...') 1419 logging.debug(f'running remove_stripe_fw on {num_core} cores ...')
1344 tomo_stack = tomopy.prep.stripe.remove_stripe_fw( 1420 tomo_stack = tomopy.prep.stripe.remove_stripe_fw(
1345 tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma, ncore=num_core) 1421 tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma, ncore=num_core)
1346 logging.info(f'... tomopy.prep.stripe.remove_stripe_fw took {time()-t0:.2f} seconds!') 1422 logging.debug(f'... tomopy.prep.stripe.remove_stripe_fw took {time()-t0:.2f} seconds!')
1347 else: 1423 else:
1348 tomo_stack = tomo_stack[row_bounds[0]:row_bounds[1]] 1424 tomo_stack = tomo_stack[row_bounds[0]:row_bounds[1]]
1349 logging.info('performing initial reconstruction') 1425 logging.debug('performing initial reconstruction')
1350 t0 = time() 1426 t0 = time()
1351 logging.info(f'running tomopy.recon on {num_core} cores ...') 1427 logging.debug(f'running recon on {num_core} cores ...')
1352 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers, sinogram_order=True, 1428 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers, sinogram_order=True,
1353 algorithm=algorithm, ncore=num_core) 1429 algorithm=algorithm, ncore=num_core)
1354 logging.info(f'... tomopy.recon took {time()-t0:.2f} seconds!') 1430 logging.debug(f'... recon took {time()-t0:.2f} seconds!')
1355 if run_secondary_sirt and secondary_iter > 0: 1431 if run_secondary_sirt and secondary_iter > 0:
1356 logging.info(f'running {secondary_iter} secondary iterations') 1432 logging.debug(f'running {secondary_iter} secondary iterations')
1357 #options = {'method':'SIRT_CUDA', 'proj_type':'cuda', 'num_iter':secondary_iter} 1433 #options = {'method':'SIRT_CUDA', 'proj_type':'cuda', 'num_iter':secondary_iter}
1358 #RV: doesn't work for me: "Error: CUDA error 803: system has unsupported display driver / 1434 #RV: doesn't work for me: "Error: CUDA error 803: system has unsupported display driver /
1359 # cuda driver combination." 1435 # cuda driver combination."
1360 #options = {'method':'SIRT', 'proj_type':'linear', 'MinConstraint': 0, 'num_iter':secondary_iter} 1436 #options = {'method':'SIRT', 'proj_type':'linear', 'MinConstraint': 0, 'num_iter':secondary_iter}
1361 #SIRT did not finish while running overnight 1437 #SIRT did not finish while running overnight
1362 #options = {'method':'SART', 'proj_type':'linear', 'num_iter':secondary_iter} 1438 #options = {'method':'SART', 'proj_type':'linear', 'num_iter':secondary_iter}
1363 options = {'method':'SART', 'proj_type':'linear', 'MinConstraint': 0, 1439 options = {'method':'SART', 'proj_type':'linear', 'MinConstraint': 0,
1364 'num_iter':secondary_iter} 1440 'num_iter':secondary_iter}
1365 t0 = time() 1441 t0 = time()
1366 logging.info(f'running tomopy.recon on {num_core} cores ...') 1442 logging.debug(f'running recon on {num_core} cores ...')
1367 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers, 1443 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers,
1368 init_recon=tomo_recon_stack, options=options, sinogram_order=True, 1444 init_recon=tomo_recon_stack, options=options, sinogram_order=True,
1369 algorithm=tomopy.astra, ncore=num_core) 1445 algorithm=tomopy.astra, ncore=num_core)
1370 logging.info(f'... tomopy.recon took {time()-t0:.2f} seconds!') 1446 logging.debug(f'... recon took {time()-t0:.2f} seconds!')
1371 if True: 1447 if True:
1372 t0 = time() 1448 t0 = time()
1373 logging.info(f'running tomopy.misc.corr.remove_ring on {num_core} cores ...') 1449 logging.debug(f'running remove_ring on {num_core} cores ...')
1374 tomopy.misc.corr.remove_ring(tomo_recon_stack, rwidth=rwidth, out=tomo_recon_stack, 1450 tomopy.misc.corr.remove_ring(tomo_recon_stack, rwidth=rwidth, out=tomo_recon_stack,
1375 ncore=num_core) 1451 ncore=num_core)
1376 logging.info(f'... tomopy.misc.corr.remove_ring took {time()-t0:.2f} seconds!') 1452 logging.debug(f'... remove_ring took {time()-t0:.2f} seconds!')
1377 return tomo_recon_stack 1453 return tomo_recon_stack
1378 1454
1379 def findImageFiles(self): 1455 def findImageFiles(self, tiff_to_h5_flag = False):
1380 """Find all available image files. 1456 """Find all available image files.
1381 """ 1457 """
1382 self.is_valid = True 1458 self.is_valid = True
1383 1459
1384 # Find dark field images 1460 # Find dark field images
1405 logging.error('Inconsistent image start index for dark field images') 1481 logging.error('Inconsistent image start index for dark field images')
1406 if dark_field['data_path']: 1482 if dark_field['data_path']:
1407 self.is_valid = False 1483 self.is_valid = False
1408 logging.info(f'Number of dark field images = {dark_field["num"]}') 1484 logging.info(f'Number of dark field images = {dark_field["num"]}')
1409 logging.info(f'Dark field image start index = {dark_field["img_start"]}') 1485 logging.info(f'Dark field image start index = {dark_field["img_start"]}')
1486 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
1487 dark_files = msnc.combine_tiffs_in_h5(dark_files, num_imgs,
1488 f'{self.config["work_folder"]}/dark_field.h5')
1489 dark_field['data_path'] = dark_files[0]
1410 1490
1411 # Find bright field images 1491 # Find bright field images
1412 bright_field = self.config['bright_field'] 1492 bright_field = self.config['bright_field']
1413 img_start, num_imgs, bright_files = msnc.findImageFiles( 1493 img_start, num_imgs, bright_files = msnc.findImageFiles(
1414 bright_field['data_path'], self.config['data_filetype'], 'bright field') 1494 bright_field['data_path'], self.config['data_filetype'], 'bright field')
1429 if img_start_old < img_start: 1509 if img_start_old < img_start:
1430 logging.warning('Inconsistent image start index for bright field images') 1510 logging.warning('Inconsistent image start index for bright field images')
1431 self.is_valid = False 1511 self.is_valid = False
1432 logging.info(f'Number of bright field images = {bright_field["num"]}') 1512 logging.info(f'Number of bright field images = {bright_field["num"]}')
1433 logging.info(f'Bright field image start index = {bright_field["img_start"]}') 1513 logging.info(f'Bright field image start index = {bright_field["img_start"]}')
1514 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
1515 bright_files = msnc.combine_tiffs_in_h5(bright_files, num_imgs,
1516 f'{self.config["work_folder"]}/bright_field.h5')
1517 bright_field['data_path'] = bright_files[0]
1434 1518
1435 # Find tomography images 1519 # Find tomography images
1436 tomo_stack_files = [] 1520 tomo_stack_files = []
1437 for stack in self.config['stack_info']['stacks']: 1521 for stack in self.config['stack_info']['stacks']:
1438 index = stack['index'] 1522 index = stack['index']
1455 if img_start_old < img_start: 1539 if img_start_old < img_start:
1456 logging.warning('Inconsistent image start index for tomography images') 1540 logging.warning('Inconsistent image start index for tomography images')
1457 self.is_valid = False 1541 self.is_valid = False
1458 logging.info(f'Number of tomography images for set {index} = {stack["num"]}') 1542 logging.info(f'Number of tomography images for set {index} = {stack["num"]}')
1459 logging.info(f'Tomography set {index} image start index = {stack["img_start"]}') 1543 logging.info(f'Tomography set {index} image start index = {stack["img_start"]}')
1544 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
1545 tomo_files = msnc.combine_tiffs_in_h5(tomo_files, num_imgs,
1546 f'{self.config["work_folder"]}/tomo_field_{index}.h5')
1547 stack['data_path'] = tomo_files[0]
1460 tomo_stack_files.append(tomo_files) 1548 tomo_stack_files.append(tomo_files)
1461 del tomo_files 1549 del tomo_files
1462 1550
1463 # Safe updated config 1551 # Safe updated config
1552 if tiff_to_h5_flag:
1553 self.config['data_filetype'] == 'h5'
1464 if self.is_valid: 1554 if self.is_valid:
1465 self.cf.saveFile(self.config_out) 1555 self.cf.saveFile(self.config_out)
1466 1556
1467 return dark_files, bright_files, tomo_stack_files 1557 return dark_files, bright_files, tomo_stack_files
1468 1558
1499 assert(isinstance(galaxy_param, dict)) 1589 assert(isinstance(galaxy_param, dict))
1500 tdf_files = galaxy_param['tdf_files'] 1590 tdf_files = galaxy_param['tdf_files']
1501 tbf_files = galaxy_param['tbf_files'] 1591 tbf_files = galaxy_param['tbf_files']
1502 tomo_stack_files = galaxy_param['tomo_stack_files'] 1592 tomo_stack_files = galaxy_param['tomo_stack_files']
1503 assert(num_tomo_stacks == len(tomo_stack_files)) 1593 assert(num_tomo_stacks == len(tomo_stack_files))
1594 if not os.path.exists('setup_pngs'):
1595 os.mkdir('setup_pngs')
1504 else: 1596 else:
1505 if galaxy_param: 1597 if galaxy_param:
1506 logging.warning('Ignoring galaxy_param in findCenters (only for Galaxy)') 1598 logging.warning('Ignoring galaxy_param in genTomoStacks (only for Galaxy)')
1507 galaxy_param = None 1599 galaxy_param = None
1508 tdf_files, tbf_files, tomo_stack_files = self.findImageFiles() 1600 tdf_files, tbf_files, tomo_stack_files = self.findImageFiles()
1509 if not self.is_valid: 1601 if not self.is_valid:
1510 return 1602 return
1511 for i,stack in enumerate(stacks): 1603 for i,stack in enumerate(stacks):
1523 if not self.is_valid: 1615 if not self.is_valid:
1524 return 1616 return
1525 1617
1526 # Generate dark field 1618 # Generate dark field
1527 if tdf_files: 1619 if tdf_files:
1528 self._genDark(tdf_files, galaxy_param['dark_field_pngname']) 1620 self._genDark(tdf_files)
1529 1621
1530 # Generate bright field 1622 # Generate bright field
1531 self._genBright(tbf_files, galaxy_param['bright_field_pngname']) 1623 self._genBright(tbf_files)
1532 1624
1533 # Set vertical detector bounds for image stack 1625 # Set vertical detector bounds for image stack
1534 self._setDetectorBounds(tomo_stack_files, galaxy_param['tomo_field_pngname'], 1626 self._setDetectorBounds(tomo_stack_files)
1535 galaxy_param['detectorbounds_pngname'])
1536 1627
1537 # Set zoom and/or theta skip to reduce memory the requirement 1628 # Set zoom and/or theta skip to reduce memory the requirement
1538 self._setZoomOrSkip() 1629 self._setZoomOrSkip()
1539 1630
1540 # Generate tomography fields 1631 # Generate tomography fields
1575 logging.debug('Find centers for tomography stacks') 1666 logging.debug('Find centers for tomography stacks')
1576 stacks = self.config['stack_info']['stacks'] 1667 stacks = self.config['stack_info']['stacks']
1577 available_stacks = [stack['index'] for stack in stacks if stack.get('preprocessed', False)] 1668 available_stacks = [stack['index'] for stack in stacks if stack.get('preprocessed', False)]
1578 logging.debug('Available stacks: {available_stacks}') 1669 logging.debug('Available stacks: {available_stacks}')
1579 if self.galaxy_flag: 1670 if self.galaxy_flag:
1580 assert(isinstance(galaxy_param, dict))
1581 row_bounds = galaxy_param['row_bounds'] 1671 row_bounds = galaxy_param['row_bounds']
1582 center_rows = galaxy_param['center_rows'] 1672 center_rows = galaxy_param['center_rows']
1583 if center_rows is None:
1584 logging.error('Missing center_rows entry in galaxy_param')
1585 return
1586 center_type_selector = galaxy_param['center_type_selector'] 1673 center_type_selector = galaxy_param['center_type_selector']
1587 if center_type_selector: 1674 if center_type_selector:
1588 if center_type_selector == 'vo': 1675 if center_type_selector == 'vo':
1589 set_center = None 1676 set_center = None
1590 elif center_type_selector == 'user': 1677 elif center_type_selector == 'user':
1636 if not center_stack.size: 1723 if not center_stack.size:
1637 stacks[0]['preprocessed'] = False 1724 stacks[0]['preprocessed'] = False
1638 raise OSError('Unable to load the required preprocessed tomography stack') 1725 raise OSError('Unable to load the required preprocessed tomography stack')
1639 assert(stacks[0].get('preprocessed', False) == True) 1726 assert(stacks[0].get('preprocessed', False) == True)
1640 elif self.galaxy_flag: 1727 elif self.galaxy_flag:
1641 logging.error('CHECK/FIX FOR GALAXY') 1728 center_stack_index = stacks[int(num_tomo_stacks/2)]['index']
1642 #center_stack_index = stacks[int(num_tomo_stacks/2)]['index'] 1729 tomo_stack_index = available_stacks.index(center_stack_index)
1730 center_stack = self.tomo_stacks[tomo_stack_index]
1731 if not center_stack.size:
1732 stacks[tomo_stack_index]['preprocessed'] = False
1733 raise OSError('Unable to load the required preprocessed tomography stack')
1734 assert(stacks[tomo_stack_index].get('preprocessed', False) == True)
1643 else: 1735 else:
1644 while True: 1736 while True:
1645 if not center_stack_index: 1737 if not center_stack_index:
1646 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get ' 1738 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get '
1647 f'rotation axis centers {available_stacks}: ') 1739 f'rotation axis centers {available_stacks}: ')
1673 row_bounds = [0, center_stack.shape[0]] 1765 row_bounds = [0, center_stack.shape[0]]
1674 if row_bounds[0] == -1: 1766 if row_bounds[0] == -1:
1675 row_bounds[0] = 0 1767 row_bounds[0] = 0
1676 if row_bounds[1] == -1: 1768 if row_bounds[1] == -1:
1677 row_bounds[1] = center_stack.shape[0] 1769 row_bounds[1] = center_stack.shape[0]
1770 if center_rows[0] == -1:
1771 center_rows[0] = 0
1772 if center_rows[1] == -1:
1773 center_rows[1] = center_stack.shape[0]-1
1678 if not msnc.is_index_range(row_bounds, 0, center_stack.shape[0]): 1774 if not msnc.is_index_range(row_bounds, 0, center_stack.shape[0]):
1679 msnc.illegal_value('row_bounds', row_bounds) 1775 msnc.illegal_value('row_bounds', row_bounds)
1680 return 1776 return
1681 1777
1682 # Set thetas (in degrees) 1778 # Set thetas (in degrees)
1716 title=f'center stack theta={theta_start}') 1812 title=f'center stack theta={theta_start}')
1717 else: 1813 else:
1718 n1 = row_bounds[0] 1814 n1 = row_bounds[0]
1719 n2 = row_bounds[1] 1815 n2 = row_bounds[1]
1720 else: 1816 else:
1721 logging.error('CHECK/FIX FOR GALAXY')
1722 tomo_ref_heights = [stack['ref_height'] for stack in stacks] 1817 tomo_ref_heights = [stack['ref_height'] for stack in stacks]
1723 n1 = int((1.+(tomo_ref_heights[0]+center_stack.shape[0]*eff_pixel_size- 1818 n1 = int((1.+(tomo_ref_heights[0]+center_stack.shape[0]*eff_pixel_size-
1724 tomo_ref_heights[1])/eff_pixel_size)/2) 1819 tomo_ref_heights[1])/eff_pixel_size)/2)
1725 n2 = center_stack.shape[0]-n1 1820 n2 = center_stack.shape[0]-n1
1726 logging.debug(f'n1 = {n1}, n2 = {n2} (n2-n1) = {(n2-n1)*eff_pixel_size:.3f} mm') 1821 logging.debug(f'n1 = {n1}, n2 = {n2} (n2-n1) = {(n2-n1)*eff_pixel_size:.3f} mm')
1727 if not center_stack.size:
1728 RuntimeError('Center stack not loaded')
1729 if not self.test_mode and not self.galaxy_flag: 1822 if not self.test_mode and not self.galaxy_flag:
1730 tmp = center_stack[:,0,:] 1823 tmp = center_stack[:,0,:]
1731 tmp_max = tmp.max() 1824 tmp_max = tmp.max()
1732 tmp[n1,:] = tmp_max 1825 tmp[n1,:] = tmp_max
1733 tmp[n2-1,:] = tmp_max 1826 tmp[n2-1,:] = tmp_max
1916 def reconstructTomoStacks(self, galaxy_param=None, num_core=None): 2009 def reconstructTomoStacks(self, galaxy_param=None, num_core=None):
1917 """Reconstruct tomography stacks. 2010 """Reconstruct tomography stacks.
1918 """ 2011 """
1919 if num_core is None: 2012 if num_core is None:
1920 num_core = self.num_core 2013 num_core = self.num_core
1921 logging.info(f'num_core available = {num_core}') 2014 logging.info(f'num_core = {num_core}')
1922 #if num_core > 24:
1923 # num_core = 24
1924 logging.info(f'num_core used = {num_core}')
1925 if self.galaxy_flag: 2015 if self.galaxy_flag:
1926 assert(galaxy_param) 2016 assert(galaxy_param)
1927 if not os.path.exists('center_slice_pngs'): 2017 if not os.path.exists('center_slice_pngs'):
1928 os.mkdir('center_slice_pngs') 2018 os.mkdir('center_slice_pngs')
1929 logging.debug('Reconstruct tomography stacks') 2019 logging.debug('Reconstruct tomography stacks')
2014 continue 2104 continue
2015 assert(0 <= lower_row < upper_row < self.tomo_stacks[i].shape[0]) 2105 assert(0 <= lower_row < upper_row < self.tomo_stacks[i].shape[0])
2016 center_offsets = [lower_center_offset-lower_row*center_slope, 2106 center_offsets = [lower_center_offset-lower_row*center_slope,
2017 upper_center_offset+(self.tomo_stacks[i].shape[0]-1-upper_row)*center_slope] 2107 upper_center_offset+(self.tomo_stacks[i].shape[0]-1-upper_row)*center_slope]
2018 t0 = time() 2108 t0 = time()
2019 logging.info(f'running self._reconstructOneTomoStack on {num_core} cores ...') 2109 logging.debug(f'running _reconstructOneTomoStack on {num_core} cores ...')
2020 self.tomo_recon_stacks[i]= self._reconstructOneTomoStack(self.tomo_stacks[i], 2110 self.tomo_recon_stacks[i]= self._reconstructOneTomoStack(self.tomo_stacks[i],
2021 thetas, center_offsets=center_offsets, sigma=0.1, num_core=num_core, 2111 thetas, center_offsets=center_offsets, sigma=0.1, num_core=num_core,
2022 algorithm='gridrec', run_secondary_sirt=True, secondary_iter=25) 2112 algorithm='gridrec', run_secondary_sirt=True, secondary_iter=25)
2023 logging.info(f'... self._reconstructOneTomoStack took {time()-t0:.2f} seconds!') 2113 logging.debug(f'... _reconstructOneTomoStack took {time()-t0:.2f} seconds!')
2024 #logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!') 2114 logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!')
2025 if self.galaxy_flag: 2115 if self.galaxy_flag:
2026 x_slice = int(self.tomo_stacks[i].shape[0]/2) 2116 x_slice = int(self.tomo_stacks[i].shape[0]/2)
2027 title = f'{basetitle} {index} xslice{x_slice}' 2117 title = f'{basetitle} {index} xslice{x_slice}'
2028 msnc.quickImshow(self.tomo_recon_stacks[i][x_slice,:,:], title=title, 2118 msnc.quickImshow(self.tomo_recon_stacks[i][x_slice,:,:], title=title,
2029 path='center_slice_pngs', save_fig=True, save_only=True) 2119 path='center_slice_pngs', save_fig=True, save_only=True)
2219 z_bounds = [0, tomo_recon_combined.shape[0]] 2309 z_bounds = [0, tomo_recon_combined.shape[0]]
2220 else: 2310 else:
2221 z_bounds = msnc.selectArrayBounds(tomosum, title='recon combined sum xy') 2311 z_bounds = msnc.selectArrayBounds(tomosum, title='recon combined sum xy')
2222 if z_bounds[0] != 0 or z_bounds[1] != tomo_recon_combined.shape[0]: 2312 if z_bounds[0] != 0 or z_bounds[1] != tomo_recon_combined.shape[0]:
2223 tomo_recon_combined = tomo_recon_combined[z_bounds[0]:z_bounds[1],:,:] 2313 tomo_recon_combined = tomo_recon_combined[z_bounds[0]:z_bounds[1],:,:]
2224 logging.info(f'tomo_recon_combined.shape = {tomo_recon_combined.shape}')
2225 if self.save_plots_only: 2314 if self.save_plots_only:
2226 msnc.clearFig('recon combined sum xy') 2315 msnc.clearFig('recon combined sum xy')
2227 2316
2228 # Plot center slices 2317 # Plot center slices
2229 msnc.quickImshow(tomo_recon_combined[int(tomo_recon_combined.shape[0]/2),:,:], 2318 msnc.quickImshow(tomo_recon_combined[int(tomo_recon_combined.shape[0]/2),:,:],