changeset 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 009d2d45ac79
files msnc_tools.py tomo.py tomo_find_center.xml tomo_macros.xml tomo_reconstruct.xml tomo_setup.py tomo_setup.xml
diffstat 7 files changed, 204 insertions(+), 115 deletions(-) [+]
line wrap: on
line diff
--- a/msnc_tools.py	Tue Apr 19 19:11:59 2022 +0000
+++ b/msnc_tools.py	Thu Apr 21 14:21:38 2022 +0000
@@ -266,6 +266,7 @@
             illegal_value('files[0]', files[0], 'loadImageStack')
             return img_stack
         t0 = time()
+        logging.info(f'Loading {files[0]}')
         with h5py.File(files[0], 'r') as f:
             shape = f['entry/instrument/detector/data'].shape
             if len(shape) != 3:
@@ -290,6 +291,13 @@
         illegal_value('filetype', filetype, 'findImageRange')
     return img_stack
 
+def combine_tiffs_in_h5(files, num_imgs, h5_filename):
+    img_stack = loadImageStack(files, 'tif', 0, num_imgs)
+    with h5py.File(h5_filename, 'w') as f:
+        f.create_dataset('entry/instrument/detector/data', data=img_stack)
+    del img_stack
+    return [h5_filename]
+
 def clearFig(title):
     if not isinstance(title, str):
         illegal_value('title', title, 'clearFig')
@@ -337,6 +345,8 @@
         if show_grid:
             ax = plt.gca()
             ax.grid(color=grid_color, linewidth=grid_linewidth)
+        if title != 'quick_imshow':
+            plt.title = title
         plt.savefig(path)
         plt.close(fig=title)
         #plt.imsave(f'{title}.png', a, **kwargs)
@@ -347,6 +357,8 @@
         if show_grid:
             ax = plt.gca()
             ax.grid(color=grid_color, linewidth=grid_linewidth)
+        if title != 'quick_imshow':
+            plt.title = title
         if save_fig:
             plt.savefig(path)
         plt.pause(1)
@@ -394,6 +406,8 @@
         if show_grid:
             ax = plt.gca()
             ax.grid(color='k')#, linewidth=1)
+        if title != 'quick_plot':
+            plt.title = title
         plt.savefig(path)
         plt.close(fig=title)
     else:
@@ -407,6 +421,8 @@
         if show_grid:
             ax = plt.gca()
             ax.grid(color='k')#, linewidth=1)
+        if title != 'quick_plot':
+            plt.title = title
         if save_fig:
             plt.savefig(path)
         plt.pause(1)
@@ -588,7 +604,7 @@
         mod = RectangleModel(form=form)
     pars = mod.guess(y, x=x)
     out  = mod.fit(y, pars, x=x)
-    #print(out.fit_report())
+    #print(f'fit_report:\n{out.fit_report()}')
     #quickPlot((x,y),(x,out.best_fit))
     return out.best_values
 
--- a/tomo.py	Tue Apr 19 19:11:59 2022 +0000
+++ b/tomo.py	Thu Apr 21 14:21:38 2022 +0000
@@ -20,16 +20,36 @@
     pass
 import argparse
 import numpy as np
-import numexpr as ne
+try:
+    import numexpr as ne
+except:
+    pass
 import multiprocessing as mp
-import scipy.ndimage as spi
-import tomopy
+try:
+    import scipy.ndimage as spi
+except:
+    pass
+try:
+    import tomopy
+except:
+    pass
 from time import time
-from skimage.transform import iradon
-from skimage.restoration import denoise_tv_chambolle
+try:
+    from skimage.transform import iradon
+except:
+    pass
+try:
+    from skimage.restoration import denoise_tv_chambolle
+except:
+    pass
 
 import msnc_tools as msnc
 
+# the following tomopy routines don't run with more than 24 cores on Galaxy-Dev
+#   - tomopy.find_center_vo
+#   - tomopy.prep.stripe.remove_stripe_fw
+num_core_tomopy_limit = 24
+
 class set_numexpr_threads:
 
     def __init__(self, num_core):
@@ -630,7 +650,7 @@
 
         return
 
-    def _genDark(self, tdf_files, dark_field_pngname):
+    def _genDark(self, tdf_files):
         """Generate dark field.
         """
         # Load the dark field images
@@ -651,13 +671,13 @@
         logging.debug(f'tdf_mean = {tdf_mean}')
         np.nan_to_num(self.tdf, copy=False, nan=tdf_mean, posinf=tdf_mean, neginf=0.)
         if self.galaxy_flag:
-            msnc.quickImshow(self.tdf, title='dark field', name=dark_field_pngname,
+            msnc.quickImshow(self.tdf, title='dark field', path='setup_pngs',
                     save_fig=True, save_only=True)
         elif not self.test_mode:
             msnc.quickImshow(self.tdf, title='dark field', path=self.output_folder,
                     save_fig=self.save_plots, save_only=self.save_plots_only)
 
-    def _genBright(self, tbf_files, bright_field_pngname):
+    def _genBright(self, tbf_files):
         """Generate bright field.
         """
         # Load the bright field images
@@ -685,13 +705,13 @@
         else:
             logging.warning('Dark field unavailable')
         if self.galaxy_flag:
-            msnc.quickImshow(self.tbf, title='bright field', name=bright_field_pngname,
+            msnc.quickImshow(self.tbf, title='bright field', path='setup_pngs',
                     save_fig=True, save_only=True)
         elif not self.test_mode:
             msnc.quickImshow(self.tbf, title='bright field', path=self.output_folder,
                     save_fig=self.save_plots, save_only=self.save_plots_only)
 
-    def _setDetectorBounds(self, tomo_stack_files, tomo_field_pngname, detectorbounds_pngname):
+    def _setDetectorBounds(self, tomo_stack_files):
         """Set vertical detector bounds for image stack.
         """
         preprocess = self.config.get('preprocess')
@@ -729,35 +749,64 @@
             for i in range(2, num_tomo_stacks):
                 delta_z = min(delta_z, stacks[i]['ref_height']-stacks[i-1]['ref_height'])
             logging.debug(f'delta_z = {delta_z}')
-            num_x_min = int(delta_z/pixel_size)+1
+            num_x_min = int((delta_z-0.5*pixel_size)/pixel_size)
             logging.debug(f'num_x_min = {num_x_min}')
             if num_x_min > self.tbf.shape[0]:
                 logging.warning('Image bounds and pixel size prevent seamless stacking')
-                num_x_min = self.tbf.shape[0]
+                num_x_min = None
 
         # Select image bounds
         if self.galaxy_flag:
-            if num_x_min is None or num_x_min >= self.tbf.shape[0]:
-                img_x_bounds = [0, self.tbf.shape[0]]
-            else:
-                margin = int(num_x_min/10)
-                offset = min(0, int((self.tbf.shape[0]-num_x_min)/2-margin))
-                img_x_bounds = [offset, num_x_min+offset+2*margin]
-            tomo_stack = msnc.loadImageStack(tomo_stack_files[0], self.config['data_filetype'],
-                stacks[0]['img_offset'], 1)
-            x_sum = np.sum(tomo_stack[0,:,:], 1)
+            x_sum = np.sum(self.tbf, 1)
             x_sum_min = x_sum.min()
             x_sum_max = x_sum.max()
-            title = f'tomography image at theta={self.config["theta_range"]["start"]}'
-            msnc.quickImshow(tomo_stack[0,:,:], title=title, name=tomo_field_pngname,
-                    save_fig=True, save_only=True, show_grid=True)
+            x_low = 0
+            x_upp = x_sum.size
+            if num_x_min is not None:
+                fit = msnc.fitStep(y=x_sum, model='rectangle', form='atan')
+                x_low = fit.get('center1', None)
+                x_upp = fit.get('center2', None)
+                sig_low = fit.get('sigma1', None)
+                sig_upp = fit.get('sigma2', None)
+                if (x_low is not None and x_upp is not None and sig_low is not None and
+                        sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and
+                        (sig_low+sig_upp)/(x_upp-x_low) < 0.1):
+                    if num_tomo_stacks == 1 or num_x_min is None:
+                        x_low = int(x_low-(x_upp-x_low)/10)
+                        x_upp = int(x_upp+(x_upp-x_low)/10)
+                    else:
+                        x_low = int((x_low+x_upp)/2-num_x_min/2)
+                        x_upp = x_low+num_x_min
+                    if x_low < 0:
+                        x_low = 0
+                    if x_upp > x_sum.size:
+                        x_upp = x_sum.size
+                else:
+                    x_low = 0
+                    x_upp = x_sum.size
             msnc.quickPlot((range(x_sum.size), x_sum),
-                    ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'),
-                    ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum_min, x_sum_max], 'r-'),
-                    title='sum over theta and y', name=detectorbounds_pngname,
-                    save_fig=True, save_only=True, show_grid=True)
+                    ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'),
+                    ([x_upp-1, x_upp-1], [x_sum_min, x_sum_max], 'r-'),
+                    title=f'sum bright field over theta/y (row bounds: [{x_low}, {x_upp}])',
+                    path='setup_pngs', name='detectorbounds.png', save_fig=True, save_only=True,
+                    show_grid=True)
+            for i,stack in enumerate(stacks):
+                tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
+                    stack['img_offset'], 1)
+                tomo_stack = tomo_stack[0,:,:]
+                if num_x_min is not None:
+                    tomo_stack_max = tomo_stack.max()
+                    tomo_stack[x_low,:] = tomo_stack_max
+                    tomo_stack[x_upp-1,:] = tomo_stack_max
+                title = f'tomography image at theta={self.config["theta_range"]["start"]}'
+                msnc.quickImshow(tomo_stack, title=title, path='setup_pngs',
+                        name=f'tomo_{stack["index"]}.png', save_fig=True, save_only=True,
+                        show_grid=True)
+                del tomo_stack
             
             # Update config and save to file
+            img_x_bounds = [x_low, x_upp]
+            logging.debug(f'img_x_bounds: {img_x_bounds}')
             if preprocess is None:
                 self.cf.config['preprocess'] = {'img_x_bounds' : img_x_bounds}
             else:
@@ -832,16 +881,24 @@
                         f'upper bound = {img_x_bounds[1]} (exclusive)]')
                 use_bounds =  pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True)
             if use_bounds == 'no':
+                use_fit = 'no'
                 fit = msnc.fitStep(y=x_sum, model='rectangle', form='atan')
                 x_low = fit.get('center1', None)
                 x_upp = fit.get('center2', None)
-                if (x_low is not None and x_low >= 0 and x_upp is not None and
-                        x_low < x_upp < x_sum.size):
-                    x_low = int(x_low-(x_upp-x_low)/10)
+                sig_low = fit.get('sigma1', None)
+                sig_upp = fit.get('sigma2', None)
+                if (x_low is not None and x_upp is not None and sig_low is not None and
+                        sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and
+                        (sig_low+sig_upp)/(x_upp-x_low) < 0.1):
+                    if num_tomo_stacks == 1 or num_x_min is None:
+                        x_low = int(x_low-(x_upp-x_low)/10)
+                        x_upp = int(x_upp+(x_upp-x_low)/10)
+                    else:
+                        x_low = int((x_low+x_upp)/2-num_x_min/2)
+                        x_upp = x_low+num_x_min
                     if x_low < 0:
                         x_low = 0
-                    x_upp = int(x_upp+(x_upp-x_low)/10)
-                    if x_upp >= x_sum.size:
+                    if x_upp > x_sum.size:
                         x_upp = x_sum.size
                     tmp = np.copy(self.tbf)
                     tmp_max = tmp.max()
@@ -854,7 +911,8 @@
                             ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'),
                             ([x_upp, x_upp], [x_sum_min, x_sum_max], 'r-'),
                             title='sum over theta and y')
-                    print(f'lower bound = {x_low} (inclusive)\nupper bound = {x_upp} (exclusive)]')
+                    print(f'lower bound = {x_low} (inclusive)')
+                    print(f'upper bound = {x_upp} (exclusive)]')
                     use_fit =  pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True)
                 if use_fit == 'no':
                     img_x_bounds = msnc.selectArrayBounds(x_sum, img_x_bounds[0], img_x_bounds[1],
@@ -863,11 +921,29 @@
                     img_x_bounds = [x_low, x_upp]
                 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min:
                     logging.warning('Image bounds and pixel size prevent seamless stacking')
-                msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]),
-                        x_sum[img_x_bounds[0]:img_x_bounds[1]],
+                #msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]),
+                #        x_sum[img_x_bounds[0]:img_x_bounds[1]],
+                #        title='sum over theta and y', path=self.output_folder,
+                #        save_fig=self.save_plots, save_only=True)
+                msnc.quickPlot((range(x_sum.size), x_sum),
+                        ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'),
+                        ([img_x_bounds[1], img_x_bounds[1]], [x_sum_min, x_sum_max], 'r-'),
                         title='sum over theta and y', path=self.output_folder,
                         save_fig=self.save_plots, save_only=True)
             del x_sum
+            for i,stack in enumerate(stacks):
+                tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
+                    stack['img_offset'], 1)
+                tomo_stack = tomo_stack[0,:,:]
+                if num_x_min is not None:
+                    tomo_stack_max = tomo_stack.max()
+                    tomo_stack[img_x_bounds[0],:] = tomo_stack_max
+                    tomo_stack[img_x_bounds[1]-1,:] = tomo_stack_max
+                title = f'tomography image at theta={self.config["theta_range"]["start"]}'
+                msnc.quickImshow(tomo_stack, title=title, path='setup_pngs',
+                        name=f'tomo_{stack["index"]}.png', save_fig=self.save_plots, save_only=True,
+                        show_grid=True)
+                del tomo_stack
         logging.debug(f'img_x_bounds: {img_x_bounds}')
 
         if self.save_plots_only:
@@ -1148,9 +1224,9 @@
         recon_clean = np.expand_dims(recon_sinogram, axis=0)
         del recon_sinogram
         t1 = time()
-        logging.info(f'running tomopy.misc.corr.remove_ring on {num_core} cores ...')
+        logging.debug(f'running remove_ring on {num_core} cores ...')
         recon_clean = tomopy.misc.corr.remove_ring(recon_clean, rwidth=17, ncore=num_core)
-        logging.info(f'... tomopy.misc.corr.remove_ring took {time()-t1:.2f} seconds!')
+        logging.debug(f'... remove_ring took {time()-t1:.2f} seconds!')
         logging.debug(f'filtering and removing ring artifact took {time()-t0:.2f} seconds!')
         return recon_clean
 
@@ -1177,7 +1253,7 @@
         """Find center for a single tomography plane.
         """
         if self.galaxy_flag:
-            assert(galaxy_param)
+            assert(isinstance(galaxy_param, dict))
             if not os.path.exists('find_center_pngs'):
                 os.mkdir('find_center_pngs')
         # sinogram index order: theta,column
@@ -1187,13 +1263,13 @@
 
         # try automatic center finding routines for initial value
         t0 = time()
-        if num_core > 24:
-            logging.info('running tomopy.find_center_vo on 24 cores ...')
-            tomo_center = tomopy.find_center_vo(sinogram, ncore=24)
+        if num_core > num_core_tomopy_limit:
+            logging.debug(f'running find_center_vo on {num_core_tomopy_limit} cores ...')
+            tomo_center = tomopy.find_center_vo(sinogram, ncore=num_core_tomopy_limit)
         else:
-            logging.info(f'running tomopy.find_center_vo on {num_core} cores ...')
+            logging.debug(f'running find_center_vo on {num_core} cores ...')
             tomo_center = tomopy.find_center_vo(sinogram, ncore=num_core)
-        logging.info(f'... tomopy.find_center_vo took {time()-t0:.2f} seconds!')
+        logging.debug(f'... find_center_vo took {time()-t0:.2f} seconds!')
         center_offset_vo = tomo_center-center
         if self.test_mode:
             logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}')
@@ -1202,10 +1278,10 @@
         elif self.galaxy_flag:
             logging.info(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}')
             t0 = time()
-            logging.info(f'running self._reconstructOnePlane on {num_core} cores ...')
+            logging.debug(f'running _reconstructOnePlane on {num_core} cores ...')
             recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg,
                     eff_pixel_size, cross_sectional_dim, False, num_core)
-            logging.info(f'... self._reconstructOnePlane took {time()-t0:.2f} seconds!')
+            logging.debug(f'... _reconstructOnePlane took {time()-t0:.2f} seconds!')
             title = f'edges row{row} center offset{center_offset_vo:.2f} Vo'
             self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs')
             del recon_plane
@@ -1279,12 +1355,11 @@
             for center_offset in center_offsets:
                 if center_offset == center_offset_vo:
                     continue
-                logging.info(f'center_offset = {center_offset:.2f}')
                 t0 = time()
-                logging.info(f'running self._reconstructOnePlane on {num_core} cores ...')
+                logging.debug(f'running _reconstructOnePlane on {num_core} cores ...')
                 recon_plane = self._reconstructOnePlane(sinogram_T, center_offset+center,
                         thetas_deg, eff_pixel_size, cross_sectional_dim, False, num_core)
-                logging.info(f'... self._reconstructOnePlane took {time()-t0:.2f} seconds!')
+                logging.debug(f'... _reconstructOnePlane took {time()-t0:.2f} seconds!')
                 title = f'edges row{row} center_offset{center_offset:.2f}'
                 if self.galaxy_flag:
                     self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs')
@@ -1335,25 +1410,26 @@
         centers += tomo_stack.shape[2]/2
         if True:
             t0 = time()
-            if num_core > 24:
-                logging.info(f'running tomopy.prep.stripe.remove_stripe_fw on 24 cores ...')
+            if num_core > num_core_tomopy_limit:
+                logging.debug('running remove_stripe_fw on {num_core_tomopy_limit} cores ...')
                 tomo_stack = tomopy.prep.stripe.remove_stripe_fw(
-                        tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma, ncore=24)
+                        tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma,
+                        ncore=num_core_tomopy_limit)
             else:
-                logging.info(f'running tomopy.prep.stripe.remove_stripe_fw on {num_core} cores ...')
+                logging.debug(f'running remove_stripe_fw on {num_core} cores ...')
                 tomo_stack = tomopy.prep.stripe.remove_stripe_fw(
                         tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma, ncore=num_core)
-            logging.info(f'... tomopy.prep.stripe.remove_stripe_fw took {time()-t0:.2f} seconds!')
+            logging.debug(f'... tomopy.prep.stripe.remove_stripe_fw took {time()-t0:.2f} seconds!')
         else:
             tomo_stack = tomo_stack[row_bounds[0]:row_bounds[1]]
-        logging.info('performing initial reconstruction')
+        logging.debug('performing initial reconstruction')
         t0 = time()
-        logging.info(f'running tomopy.recon on {num_core} cores ...')
+        logging.debug(f'running recon on {num_core} cores ...')
         tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers, sinogram_order=True,
                 algorithm=algorithm, ncore=num_core)
-        logging.info(f'... tomopy.recon took {time()-t0:.2f} seconds!')
+        logging.debug(f'... recon took {time()-t0:.2f} seconds!')
         if run_secondary_sirt and secondary_iter > 0:
-            logging.info(f'running {secondary_iter} secondary iterations')
+            logging.debug(f'running {secondary_iter} secondary iterations')
             #options = {'method':'SIRT_CUDA', 'proj_type':'cuda', 'num_iter':secondary_iter}
             #RV: doesn't work for me: "Error: CUDA error 803: system has unsupported display driver /
             #                          cuda driver combination."
@@ -1363,20 +1439,20 @@
             options = {'method':'SART', 'proj_type':'linear', 'MinConstraint': 0,
                     'num_iter':secondary_iter}
             t0 = time()
-            logging.info(f'running tomopy.recon on {num_core} cores ...')
+            logging.debug(f'running recon on {num_core} cores ...')
             tomo_recon_stack  = tomopy.recon(tomo_stack, thetas, centers,
                     init_recon=tomo_recon_stack, options=options, sinogram_order=True,
                     algorithm=tomopy.astra, ncore=num_core)
-            logging.info(f'... tomopy.recon took {time()-t0:.2f} seconds!')
+            logging.debug(f'... recon took {time()-t0:.2f} seconds!')
         if True:
             t0 = time()
-            logging.info(f'running tomopy.misc.corr.remove_ring on {num_core} cores ...')
+            logging.debug(f'running remove_ring on {num_core} cores ...')
             tomopy.misc.corr.remove_ring(tomo_recon_stack, rwidth=rwidth, out=tomo_recon_stack,
                     ncore=num_core)
-            logging.info(f'... tomopy.misc.corr.remove_ring took {time()-t0:.2f} seconds!')
+            logging.debug(f'... remove_ring took {time()-t0:.2f} seconds!')
         return tomo_recon_stack
 
-    def findImageFiles(self):
+    def findImageFiles(self, tiff_to_h5_flag = False):
         """Find all available image files.
         """
         self.is_valid = True
@@ -1407,6 +1483,10 @@
                     self.is_valid = False
         logging.info(f'Number of dark field images = {dark_field["num"]}')
         logging.info(f'Dark field image start index = {dark_field["img_start"]}')
+        if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
+            dark_files = msnc.combine_tiffs_in_h5(dark_files, num_imgs,
+                    f'{self.config["work_folder"]}/dark_field.h5')
+            dark_field['data_path'] = dark_files[0]
 
         # Find bright field images
         bright_field = self.config['bright_field']
@@ -1431,6 +1511,10 @@
                 self.is_valid = False
         logging.info(f'Number of bright field images = {bright_field["num"]}')
         logging.info(f'Bright field image start index = {bright_field["img_start"]}')
+        if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
+            bright_files = msnc.combine_tiffs_in_h5(bright_files, num_imgs,
+                    f'{self.config["work_folder"]}/bright_field.h5')
+            bright_field['data_path'] = bright_files[0]
 
         # Find tomography images
         tomo_stack_files = []
@@ -1457,10 +1541,16 @@
                     self.is_valid = False
             logging.info(f'Number of tomography images for set {index} = {stack["num"]}')
             logging.info(f'Tomography set {index} image start index = {stack["img_start"]}')
+            if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
+                tomo_files = msnc.combine_tiffs_in_h5(tomo_files, num_imgs,
+                        f'{self.config["work_folder"]}/tomo_field_{index}.h5')
+                stack['data_path'] = tomo_files[0]
             tomo_stack_files.append(tomo_files)
             del tomo_files
 
         # Safe updated config
+        if tiff_to_h5_flag:
+            self.config['data_filetype'] == 'h5'
         if self.is_valid:
             self.cf.saveFile(self.config_out)
 
@@ -1501,9 +1591,11 @@
             tbf_files = galaxy_param['tbf_files']
             tomo_stack_files = galaxy_param['tomo_stack_files']
             assert(num_tomo_stacks == len(tomo_stack_files))
+            if not os.path.exists('setup_pngs'):
+                os.mkdir('setup_pngs')
         else:
             if galaxy_param:
-                logging.warning('Ignoring galaxy_param in findCenters (only for Galaxy)')
+                logging.warning('Ignoring galaxy_param in genTomoStacks (only for Galaxy)')
                 galaxy_param = None
             tdf_files, tbf_files, tomo_stack_files = self.findImageFiles()
             if not self.is_valid:
@@ -1525,14 +1617,13 @@
 
             # Generate dark field
             if tdf_files:
-                self._genDark(tdf_files, galaxy_param['dark_field_pngname'])
+                self._genDark(tdf_files)
 
             # Generate bright field
-            self._genBright(tbf_files, galaxy_param['bright_field_pngname'])
+            self._genBright(tbf_files)
 
             # Set vertical detector bounds for image stack
-            self._setDetectorBounds(tomo_stack_files, galaxy_param['tomo_field_pngname'],
-                    galaxy_param['detectorbounds_pngname'])
+            self._setDetectorBounds(tomo_stack_files)
 
             # Set zoom and/or theta skip to reduce memory the requirement
             self._setZoomOrSkip()
@@ -1577,12 +1668,8 @@
         available_stacks = [stack['index'] for stack in stacks if stack.get('preprocessed', False)]
         logging.debug('Available stacks: {available_stacks}')
         if self.galaxy_flag:
-            assert(isinstance(galaxy_param, dict))
             row_bounds = galaxy_param['row_bounds']
             center_rows = galaxy_param['center_rows']
-            if center_rows is None:
-                logging.error('Missing center_rows entry in galaxy_param')
-                return
             center_type_selector = galaxy_param['center_type_selector']
             if center_type_selector:
                 if center_type_selector == 'vo':
@@ -1638,8 +1725,13 @@
                 raise OSError('Unable to load the required preprocessed tomography stack')
             assert(stacks[0].get('preprocessed', False) == True)
         elif self.galaxy_flag:
-            logging.error('CHECK/FIX FOR GALAXY')
-            #center_stack_index = stacks[int(num_tomo_stacks/2)]['index']
+            center_stack_index = stacks[int(num_tomo_stacks/2)]['index']
+            tomo_stack_index = available_stacks.index(center_stack_index)
+            center_stack = self.tomo_stacks[tomo_stack_index]
+            if not center_stack.size:
+                stacks[tomo_stack_index]['preprocessed'] = False
+                raise OSError('Unable to load the required preprocessed tomography stack')
+            assert(stacks[tomo_stack_index].get('preprocessed', False) == True)
         else:
             while True:
                 if not center_stack_index:
@@ -1675,6 +1767,10 @@
             row_bounds[0] = 0
         if row_bounds[1] == -1:
             row_bounds[1] = center_stack.shape[0]
+        if center_rows[0] == -1:
+            center_rows[0] = 0
+        if center_rows[1] == -1:
+            center_rows[1] = center_stack.shape[0]-1
         if not msnc.is_index_range(row_bounds, 0, center_stack.shape[0]):
             msnc.illegal_value('row_bounds', row_bounds)
             return
@@ -1718,14 +1814,11 @@
                  n1 = row_bounds[0]
                  n2 = row_bounds[1]
         else:
-            logging.error('CHECK/FIX FOR GALAXY')
             tomo_ref_heights = [stack['ref_height'] for stack in stacks]
             n1 = int((1.+(tomo_ref_heights[0]+center_stack.shape[0]*eff_pixel_size-
                 tomo_ref_heights[1])/eff_pixel_size)/2)
             n2 = center_stack.shape[0]-n1
         logging.debug(f'n1 = {n1}, n2 = {n2} (n2-n1) = {(n2-n1)*eff_pixel_size:.3f} mm')
-        if not center_stack.size:
-            RuntimeError('Center stack not loaded')
         if not self.test_mode and not self.galaxy_flag:
             tmp = center_stack[:,0,:]
             tmp_max = tmp.max()
@@ -1918,10 +2011,7 @@
         """
         if num_core is None:
             num_core = self.num_core
-        logging.info(f'num_core available = {num_core}')
-        #if num_core > 24:
-        #    num_core = 24
-        logging.info(f'num_core used = {num_core}')
+        logging.info(f'num_core = {num_core}')
         if self.galaxy_flag:
             assert(galaxy_param)
             if not os.path.exists('center_slice_pngs'):
@@ -2016,12 +2106,12 @@
             center_offsets = [lower_center_offset-lower_row*center_slope,
                     upper_center_offset+(self.tomo_stacks[i].shape[0]-1-upper_row)*center_slope]
             t0 = time()
-            logging.info(f'running self._reconstructOneTomoStack on {num_core} cores ...')
+            logging.debug(f'running _reconstructOneTomoStack on {num_core} cores ...')
             self.tomo_recon_stacks[i]= self._reconstructOneTomoStack(self.tomo_stacks[i],
                     thetas, center_offsets=center_offsets, sigma=0.1, num_core=num_core,
                     algorithm='gridrec', run_secondary_sirt=True, secondary_iter=25)
-            logging.info(f'... self._reconstructOneTomoStack took {time()-t0:.2f} seconds!')
-            #logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!')
+            logging.debug(f'... _reconstructOneTomoStack took {time()-t0:.2f} seconds!')
+            logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!')
             if self.galaxy_flag:
                 x_slice = int(self.tomo_stacks[i].shape[0]/2) 
                 title = f'{basetitle} {index} xslice{x_slice}'
@@ -2221,7 +2311,6 @@
             z_bounds = msnc.selectArrayBounds(tomosum, title='recon combined sum xy')
         if z_bounds[0] != 0 or z_bounds[1] != tomo_recon_combined.shape[0]:
             tomo_recon_combined = tomo_recon_combined[z_bounds[0]:z_bounds[1],:,:]
-        logging.info(f'tomo_recon_combined.shape = {tomo_recon_combined.shape}')
         if self.save_plots_only:
             msnc.clearFig('recon combined sum xy')
 
--- a/tomo_find_center.xml	Tue Apr 19 19:11:59 2022 +0000
+++ b/tomo_find_center.xml	Thu Apr 21 14:21:38 2022 +0000
@@ -1,4 +1,4 @@
-<tool id="tomo_find_center" name="Tomo Find Center Axis" version="0.1.1" python_template_version="3.9">
+<tool id="tomo_find_center" name="Tomo Find Center Axis" version="0.1.2" python_template_version="3.9">
     <description>Find the center axis for a tomography reconstruction</description>
     <macros>
         <import>tomo_macros.xml</import>
@@ -30,8 +30,8 @@
             <param name="row_bound_upp" type="integer" value="-1" label="Upper bound"/>
         </section>
         <section name="recon_centers" title="Reconstruction rows to establish center axis">
-            <param name="lower_row" type="integer" optional="true" label="Lower row"/>
-            <param name="upper_row" type="integer" optional="true" label="Upper row"/>
+            <param name="lower_row" type="integer" value="-1" label="Lower row"/>
+            <param name="upper_row" type="integer" value="-1" label="Upper row"/>
         </section>
         <conditional name="set">
             <param name="set_selector" type="select" label="Reconstruct slices for a set of center positions?">
@@ -58,6 +58,7 @@
         <collection name="recon_center_set" type="list" label="Recontructed slices at various centers">
             <discover_datasets pattern="__name_and_ext__" directory="find_center_pngs"/>
         </collection>
+        <data name="output_config" format="yaml" label="Output config find center" from_work_dir="output_config.yaml"/>
     </outputs>
     <help><![CDATA[
         Find the center axis for a tomography reconstruction.
--- a/tomo_macros.xml	Tue Apr 19 19:11:59 2022 +0000
+++ b/tomo_macros.xml	Thu Apr 21 14:21:38 2022 +0000
@@ -19,7 +19,6 @@
         <param name="config" type='data' format='yaml' optional='false' label="Input config"/>
     </xml>
     <xml name="common_outputs">
-        <data name="output_config" format="yaml" label="Output config" from_work_dir="output_config.yaml"/>
         <data name="log" format="txt" label="Log"/>
     </xml>
 </macros>
--- a/tomo_reconstruct.xml	Tue Apr 19 19:11:59 2022 +0000
+++ b/tomo_reconstruct.xml	Thu Apr 21 14:21:38 2022 +0000
@@ -1,4 +1,4 @@
-<tool id="tomo_reconstruct" name="Tomo Reconstruction" version="0.1.0" python_template_version="3.9">
+<tool id="tomo_reconstruct" name="Tomo Reconstruction" version="0.1.1" python_template_version="3.9">
     <description>Perform a tomography reconstruction</description>
     <macros>
         <import>tomo_macros.xml</import>
@@ -28,6 +28,7 @@
         <collection name="recon_slices" type="list" label="Recontructed slices midway in each dimension">
             <discover_datasets pattern="__name_and_ext__" directory="center_slice_pngs"/>
         </collection>
+        <data name="output_config" format="yaml" label="Output config reconstruction" from_work_dir="output_config.yaml"/>
     </outputs>
     <help><![CDATA[
         Reconstruct tomography images.
--- a/tomo_setup.py	Tue Apr 19 19:11:59 2022 +0000
+++ b/tomo_setup.py	Thu Apr 21 14:21:38 2022 +0000
@@ -27,14 +27,6 @@
             help='Input config')
     parser.add_argument('--theta_range',
             help='Theta range (lower bound, upper bound, number of angles)')
-    parser.add_argument('--dark',
-            help='Dark field')
-    parser.add_argument('--bright',
-            help='Bright field')
-    parser.add_argument('--tomo',
-            help='First tomography image')
-    parser.add_argument('--detectorbounds',
-            help='Detector bounds')
     parser.add_argument('--output_config',
             help='Output config')
     parser.add_argument('--output_data',
@@ -60,10 +52,6 @@
 
     logging.debug(f'config = {args.config}')
     logging.debug(f'theta_range = {args.theta_range.split()}')
-    logging.debug(f'dark = {args.dark}')
-    logging.debug(f'bright = {args.bright}')
-    logging.debug(f'tomo = {args.tomo}')
-    logging.debug(f'detectorbounds = {args.detectorbounds}')
     logging.debug(f'output_config = {args.output_config}')
     logging.debug(f'output_data = {args.output_data}')
     logging.debug(f'log = {args.log}')
@@ -162,9 +150,7 @@
 
     # Preprocess the image files
     galaxy_param = {'tdf_files' : tdf_files[0], 'tbf_files' : tbf_files[0],
-            'tomo_stack_files' : tomo_stack_files, 'dark_field_pngname' : args.dark,
-            'bright_field_pngname' : args.bright, 'tomo_field_pngname' : args.tomo,
-            'detectorbounds_pngname' : args.detectorbounds, 'output_name' : args.output_data}
+            'tomo_stack_files' : tomo_stack_files, 'output_name' : args.output_data}
     tomo.genTomoStacks(galaxy_param)
     if not tomo.is_valid:
         IOError('Unable to load all required image files.')
--- a/tomo_setup.xml	Tue Apr 19 19:11:59 2022 +0000
+++ b/tomo_setup.xml	Thu Apr 21 14:21:38 2022 +0000
@@ -1,19 +1,16 @@
-<tool id="tomo_setup" name="Tomo Setup" version="0.1.1" python_template_version="3.9">
+<tool id="tomo_setup" name="Tomo Setup" version="0.1.2" python_template_version="3.9">
     <description>Preprocess tomography images</description>
     <macros>
         <import>tomo_macros.xml</import>
     </macros>
     <expand macro="requirements" />
     <command detect_errors="exit_code"><![CDATA[
+        mkdir setup_pngs;
         cp '$inputfiles' inputfiles.txt &&
         $__tool_directory__/tomo_setup.py
         -i inputfiles.txt
         -c '$config'
         --theta_range '$thetas.theta_start $thetas.theta_end $thetas.num_thetas'
-        --dark 'dark_field.png'
-        --bright 'bright_field.png'
-        --tomo 'tomo.png'
-        --detectorbounds 'detectorbounds.png'
         --output_data 'output_data.npz'
         --output_config 'output_config.yaml'
         -l '$log'
@@ -44,10 +41,10 @@
     <outputs>
         <expand macro="common_outputs"/>
         <data name="inputfiles" format="txt" label="Input files" from_work_dir="inputfiles.txt" hidden="true"/>
-        <data name="dark_field" format="png" label="Dark field" from_work_dir="dark_field.png"/>
-        <data name="bright_field" format="png" label="Bright field" from_work_dir="bright_field.png"/>
-        <data name="tomo" format="png" label="First tomography image" from_work_dir="tomo.png"/>
-        <data name="detectorbounds" format="png" label="Detector bounds" from_work_dir="detectorbounds.png"/>
+        <collection name="setup_pngs" type="list" label="Tomo setup images">
+            <discover_datasets pattern="__name_and_ext__" directory="setup_pngs"/>
+        </collection>
+        <data name="output_config" format="yaml" label="Output config setup" from_work_dir="output_config.yaml"/>
         <data name="output_data" format="npz" label="Preprocessed tomography data" from_work_dir="output_data.npz"/>
     </outputs>
     <help><![CDATA[