comparison tomo.py @ 1:e4778148df6b draft

"planemo upload for repository https://github.com/rolfverberg/galaxytools commit 7bc44bd6a5a7d503fc4d6f6fb67b3a04a631430b"
author rv43
date Thu, 31 Mar 2022 18:24:16 +0000
parents cb1b0d757704
children b8977c98800b
comparison
equal deleted inserted replaced
0:cb1b0d757704 1:e4778148df6b
16 import io 16 import io
17 try: 17 try:
18 import pyinputplus as pyip 18 import pyinputplus as pyip
19 except: 19 except:
20 pass 20 pass
21 import argparse
21 import numpy as np 22 import numpy as np
22 import numexpr as ne 23 import numexpr as ne
23 import multiprocessing as mp 24 import multiprocessing as mp
24 import scipy.ndimage as spi 25 import scipy.ndimage as spi
25 import tomopy 26 import tomopy
29 30
30 import msnc_tools as msnc 31 import msnc_tools as msnc
31 32
32 class set_numexpr_threads: 33 class set_numexpr_threads:
33 34
34 def __init__(self, nthreads): 35 def __init__(self, num_core):
35 cpu_count = mp.cpu_count() 36 cpu_count = mp.cpu_count()
36 if nthreads is None or nthreads > cpu_count: 37 if num_core is None or num_core < 1 or num_core > cpu_count:
37 self.n = cpu_count 38 self.num_core = cpu_count
38 else: 39 else:
39 self.n = nthreads 40 self.num_core = num_core
40 41
41 def __enter__(self): 42 def __enter__(self):
42 self.oldn = ne.set_num_threads(self.n) 43 self.num_core_org = ne.set_num_threads(self.num_core)
43 44
44 def __exit__(self, exc_type, exc_value, traceback): 45 def __exit__(self, exc_type, exc_value, traceback):
45 ne.set_num_threads(self.oldn) 46 ne.set_num_threads(self.num_core_org)
46 47
47 class ConfigTomo(msnc.Config): 48 class ConfigTomo(msnc.Config):
48 """Class for processing a config file. 49 """Class for processing a config file.
49 """ 50 """
50 51
208 is_valid = self._validate_yaml() 209 is_valid = self._validate_yaml()
209 elif self.suffix == '.txt': 210 elif self.suffix == '.txt':
210 is_valid = self._validate_txt() 211 is_valid = self._validate_txt()
211 else: 212 else:
212 logging.error(f'Undefined or illegal config file extension: {self.suffix}') 213 logging.error(f'Undefined or illegal config file extension: {self.suffix}')
214 is_valid = False
213 215
214 # Find tomography bright field images file/folder 216 # Find tomography bright field images file/folder
215 if self.tdf_data_path: 217 if self.tdf_data_path:
216 if self.data_filetype == 'h5': 218 if self.data_filetype == 'h5':
217 if isinstance(self.tdf_data_path, str): 219 if isinstance(self.tdf_data_path, str):
376 class Tomo: 378 class Tomo:
377 """Processing tomography data with misalignment. 379 """Processing tomography data with misalignment.
378 """ 380 """
379 381
380 def __init__(self, config_file=None, config_dict=None, config_out=None, output_folder='.', 382 def __init__(self, config_file=None, config_dict=None, config_out=None, output_folder='.',
381 log_level='INFO', log_stream='tomo.log', galaxy_flag=False, test_mode=False): 383 log_level='INFO', log_stream='tomo.log', galaxy_flag=False, test_mode=False,
384 num_core=-1):
382 """Initialize with optional config input file or dictionary 385 """Initialize with optional config input file or dictionary
383 """ 386 """
384 self.ncore = mp.cpu_count() 387 self.num_core = None
385 self.config_out = config_out 388 self.config_out = config_out
386 self.output_folder = output_folder 389 self.output_folder = output_folder
387 self.galaxy_flag = galaxy_flag 390 self.galaxy_flag = galaxy_flag
388 self.test_mode = test_mode 391 self.test_mode = test_mode
389 self.save_plots = True # Make input argument? 392 self.save_plots = True # Make input argument?
419 log_stream = f'{output_folder}/{log_stream}' 422 log_stream = f'{output_folder}/{log_stream}'
420 if not isinstance(galaxy_flag, bool): 423 if not isinstance(galaxy_flag, bool):
421 raise ValueError(f'Invalid galaxy_flag input {galaxy_flag} {type(galaxy_flag)}') 424 raise ValueError(f'Invalid galaxy_flag input {galaxy_flag} {type(galaxy_flag)}')
422 if not isinstance(test_mode, bool): 425 if not isinstance(test_mode, bool):
423 raise ValueError(f'Invalid test_mode input {test_mode} {type(test_mode)}') 426 raise ValueError(f'Invalid test_mode input {test_mode} {type(test_mode)}')
427 if not isinstance(num_core, int) or num_core < -1 or num_core == 0:
428 raise ValueError(f'Invalid num_core input {num_core} {type(num_core)}')
429 if num_core == -1:
430 self.num_core = mp.cpu_count()
431 else:
432 self.num_core = num_core
424 433
425 # Set log configuration 434 # Set log configuration
426 logging_format = '%(asctime)s : %(levelname)s - %(module)s : %(funcName)s - %(message)s' 435 logging_format = '%(asctime)s : %(levelname)s - %(module)s : %(funcName)s - %(message)s'
427 if self.test_mode: 436 if self.test_mode:
428 self.save_plots_only = True 437 self.save_plots_only = True
429 if isinstance(log_stream, str): 438 if isinstance(log_stream, str):
430 logging.basicConfig(filename=f'{log_stream}', filemode='w', 439 logging.basicConfig(filename=f'{log_stream}', filemode='w',
431 format=logging_format, level=logging.WARNING, force=True) 440 format=logging_format, level=logging.INFO, force=True)
441 #format=logging_format, level=logging.WARNING, force=True)
432 elif isinstance(log_stream, io.TextIOWrapper): 442 elif isinstance(log_stream, io.TextIOWrapper):
433 logging.basicConfig(filemode='w', format=logging_format, level=logging.WARNING, 443 #logging.basicConfig(filemode='w', format=logging_format, level=logging.WARNING,
444 logging.basicConfig(filemode='w', format=logging_format, level=logging.INFO,
434 stream=log_stream, force=True) 445 stream=log_stream, force=True)
435 else: 446 else:
436 raise ValueError(f'Invalid log_stream: {log_stream}') 447 raise ValueError(f'Invalid log_stream: {log_stream}')
437 logging.warning('Ignoring log_level argument in test mode') 448 logging.warning('Ignoring log_level argument in test mode')
438 else: 449 else:
461 self.config_out = f'{self.output_folder}/config.yaml' 472 self.config_out = f'{self.output_folder}/config.yaml'
462 elif (self.config_out is os.path.basename(self.config_out) and 473 elif (self.config_out is os.path.basename(self.config_out) and
463 not os.path.isabs(self.config_out)): 474 not os.path.isabs(self.config_out)):
464 self.config_out = f'{self.output_folder}/{self.config_out}' 475 self.config_out = f'{self.output_folder}/{self.config_out}'
465 476
466 logging.info(f'ncore = {self.ncore}') 477 # Create config object and load config file
478 self.cf = ConfigTomo(config_file, config_dict)
479 if not self.cf.load_flag:
480 self.is_valid = False
481 return
482
483 if self.galaxy_flag:
484 self.num_core = 1 #RV can I set this? mp.cpu_count()
485 assert(self.output_folder == '.')
486 assert(self.test_mode is False)
487 self.save_plots = True
488 self.save_plots_only = True
489 else:
490 # Input validation is already performed during link_data_to_galaxy
491
492 # Check config file parameters
493 self.is_valid = self.cf.validate()
494
495 # Load detector info file
496 df = msnc.Detector(self.cf.config['detector']['id'])
497
498 # Check detector info file parameters
499 if df.validate():
500 pixel_size = df.getPixelSize()
501 num_rows, num_columns = df.getDimensions()
502 if not pixel_size or not num_rows or not num_columns:
503 self.is_valid = False
504 else:
505 pixel_size = None
506 num_rows = None
507 num_columns = None
508 self.is_valid = False
509
510 # Update config
511 self.cf.config['detector']['pixel_size'] = pixel_size
512 self.cf.config['detector']['rows'] = num_rows
513 self.cf.config['detector']['columns'] = num_columns
514 logging.debug(f'pixel_size = self.cf.config["detector"]["pixel_size"]')
515 logging.debug(f'num_rows: {self.cf.config["detector"]["rows"]}')
516 logging.debug(f'num_columns: {self.cf.config["detector"]["columns"]}')
517
518 # Safe config to file
519 if self.is_valid:
520 self.cf.saveFile(self.config_out)
521
522 # Initialize shortcut to config
523 self.config = self.cf.config
524
525 # Initialize tomography stack
526 num_tomo_stacks = self.config['stack_info']['num']
527 if num_tomo_stacks:
528 self.tomo_stacks = [np.array([]) for _ in range(num_tomo_stacks)]
529 self.tomo_recon_stacks = [np.array([]) for _ in range(num_tomo_stacks)]
530
531 logging.info(f'num_core = {self.num_core}')
467 logging.debug(f'config_file = {config_file}') 532 logging.debug(f'config_file = {config_file}')
468 logging.debug(f'config_dict = {config_dict}') 533 logging.debug(f'config_dict = {config_dict}')
469 logging.debug(f'config_out = {self.config_out}') 534 logging.debug(f'config_out = {self.config_out}')
470 logging.debug(f'output_folder = {self.output_folder}') 535 logging.debug(f'output_folder = {self.output_folder}')
471 logging.debug(f'log_stream = {log_stream}') 536 logging.debug(f'log_stream = {log_stream}')
472 logging.debug(f'log_level = {log_level}') 537 logging.debug(f'log_level = {log_level}')
473 logging.debug(f'galaxy_flag = {self.galaxy_flag}') 538 logging.debug(f'galaxy_flag = {self.galaxy_flag}')
474 logging.debug(f'test_mode = {self.test_mode}') 539 logging.debug(f'test_mode = {self.test_mode}')
475
476 # Create config object and load config file
477 self.cf = ConfigTomo(config_file, config_dict)
478 if not self.cf.load_flag:
479 self.is_valid = False
480 return
481
482 if self.galaxy_flag:
483 self.ncore = 1 #RV can I set this? mp.cpu_count()
484 assert(self.output_folder == '.')
485 assert(self.test_mode is False)
486 self.save_plots = True
487 self.save_plots_only = True
488 else:
489 # Input validation is already performed during link_data_to_galaxy
490
491 # Check config file parameters
492 self.is_valid = self.cf.validate()
493
494 # Load detector info file
495 df = msnc.Detector(self.cf.config['detector']['id'])
496
497 # Check detector info file parameters
498 if df.validate():
499 pixel_size = df.getPixelSize()
500 num_rows, num_columns = df.getDimensions()
501 if not pixel_size or not num_rows or not num_columns:
502 self.is_valid = False
503 else:
504 pixel_size = None
505 num_rows = None
506 num_columns = None
507 self.is_valid = False
508
509 # Update config
510 self.cf.config['detector']['pixel_size'] = pixel_size
511 self.cf.config['detector']['rows'] = num_rows
512 self.cf.config['detector']['columns'] = num_columns
513 logging.debug(f'pixel_size = self.cf.config["detector"]["pixel_size"]')
514 logging.debug(f'num_rows: {self.cf.config["detector"]["rows"]}')
515 logging.debug(f'num_columns: {self.cf.config["detector"]["columns"]}')
516
517 # Safe config to file
518 if self.is_valid:
519 self.cf.saveFile(self.config_out)
520
521 # Initialize shortcut to config
522 self.config = self.cf.config
523
524 # Initialize tomography stack
525 num_tomo_stacks = self.config['stack_info']['num']
526 if num_tomo_stacks:
527 self.tomo_stacks = [np.array([]) for _ in range(num_tomo_stacks)]
528 self.tomo_recon_stacks = [np.array([]) for _ in range(num_tomo_stacks)]
529
530 logging.debug(f'save_plots = {self.save_plots}') 540 logging.debug(f'save_plots = {self.save_plots}')
531 logging.debug(f'save_plots_only = {self.save_plots_only}') 541 logging.debug(f'save_plots_only = {self.save_plots_only}')
532 542
533 def _selectImageRanges(self, available_stacks=None): 543 def _selectImageRanges(self, available_stacks=None):
534 """Select image files to be included in analysis. 544 """Select image files to be included in analysis.
580 if not self.test_mode: 590 if not self.test_mode:
581 img_start, img_offset, num_imgs = msnc.selectImageRange(img_start, img_offset, 591 img_start, img_offset, num_imgs = msnc.selectImageRange(img_start, img_offset,
582 num_imgs, 'bright field') 592 num_imgs, 'bright field')
583 if img_start < 0 or num_imgs < 1: 593 if img_start < 0 or num_imgs < 1:
584 logging.error('Unable to find suitable bright field images') 594 logging.error('Unable to find suitable bright field images')
585 if bright_field['data_path']: 595 self.is_valid = False
586 self.is_valid = False
587 bright_field['img_start'] = img_start 596 bright_field['img_start'] = img_start
588 bright_field['img_offset'] = img_offset 597 bright_field['img_offset'] = img_offset
589 bright_field['num'] = num_imgs 598 bright_field['num'] = num_imgs
590 logging.debug(f'Bright field image start index: {bright_field["img_start"]}') 599 logging.debug(f'Bright field image start index: {bright_field["img_start"]}')
591 logging.debug(f'Bright field image offset: {bright_field["img_offset"]}') 600 logging.debug(f'Bright field image offset: {bright_field["img_offset"]}')
610 stack['img_offset'] = img_offset 619 stack['img_offset'] = img_offset
611 stack['num'] = num_imgs 620 stack['num'] = num_imgs
612 logging.debug(f'Tomography stack {index} image start index: {stack["img_start"]}') 621 logging.debug(f'Tomography stack {index} image start index: {stack["img_start"]}')
613 logging.debug(f'Tomography stack {index} image offset: {stack["img_offset"]}') 622 logging.debug(f'Tomography stack {index} image offset: {stack["img_offset"]}')
614 logging.debug(f'Number of tomography images for stack {index}: {stack["num"]}') 623 logging.debug(f'Number of tomography images for stack {index}: {stack["num"]}')
615 available_stacks[i] = True
616 624
617 # Safe updated config to file 625 # Safe updated config to file
618 if self.is_valid: 626 if self.is_valid:
619 self.cf.saveFile(self.config_out) 627 self.cf.saveFile(self.config_out)
620 628
687 preprocess = self.config.get('preprocess') 695 preprocess = self.config.get('preprocess')
688 if preprocess is None: 696 if preprocess is None:
689 img_x_bounds = [None, None] 697 img_x_bounds = [None, None]
690 else: 698 else:
691 img_x_bounds = preprocess.get('img_x_bounds', [0, self.tbf.shape[0]]) 699 img_x_bounds = preprocess.get('img_x_bounds', [0, self.tbf.shape[0]])
700 if img_x_bounds[0] is not None and img_x_bounds[1] is not None:
701 if img_x_bounds[0] < 0:
702 msnc.illegal_value('img_x_bounds[0]', img_x_bounds[0], 'config file')
703 img_x_bounds[0] = 0
704 if not msnc.is_index_range(img_x_bounds, 0, self.tbf.shape[0]):
705 msnc.illegal_value('img_x_bounds[1]', img_x_bounds[1], 'config file')
706 img_x_bounds[1] = self.tbf.shape[0]
692 if self.test_mode: 707 if self.test_mode:
693 # Update config and save to file 708 # Update config and save to file
694 if preprocess is None: 709 if preprocess is None:
695 self.cf.config['preprocess'] = {'img_x_bounds' : [0, self.tbf.shape[0]]} 710 self.cf.config['preprocess'] = {'img_x_bounds' : [0, self.tbf.shape[0]]}
696 else: 711 else:
761 tomo_or_bright = 0 776 tomo_or_bright = 0
762 if tomo_or_bright: 777 if tomo_or_bright:
763 x_sum = np.sum(tomo_stack[0,:,:], 1) 778 x_sum = np.sum(tomo_stack[0,:,:], 1)
764 use_bounds = 'no' 779 use_bounds = 'no'
765 if img_x_bounds[0] is not None and img_x_bounds[1] is not None: 780 if img_x_bounds[0] is not None and img_x_bounds[1] is not None:
766 if img_x_bounds[0] < 0:
767 msnc.illegal_value('img_x_bounds[0]', img_x_bounds[0], 'config file')
768 img_x_bounds[0] = 0
769 if not img_x_bounds[0] < img_x_bounds[1] <= x_sum.size:
770 msnc.illegal_value('img_x_bounds[1]', img_x_bounds[1], 'config file')
771 img_x_bounds[1] = x_sum.size
772 tomo_tmp = tomo_stack[0,:,:]
773 tomo_tmp[img_x_bounds[0],:] = tomo_stack[0,:,:].max()
774 tomo_tmp[img_x_bounds[1]-1,:] = tomo_stack[0,:,:].max()
775 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
776 msnc.quickImshow(tomo_stack[0,:,:], title=title)
777 msnc.quickPlot((range(x_sum.size), x_sum), 781 msnc.quickPlot((range(x_sum.size), x_sum),
778 ([img_x_bounds[0], img_x_bounds[0]], [x_sum.min(), x_sum.max()], 'r-'), 782 ([img_x_bounds[0], img_x_bounds[0]], [x_sum.min(), x_sum.max()], 'r-'),
779 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum.min(), x_sum.max()], 'r-'), 783 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum.min(), x_sum.max()], 'r-'),
780 title='sum over theta and y') 784 title='sum over theta and y')
781 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+ 785 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+
785 img_x_bounds = msnc.selectImageBounds(tomo_stack[0,:,:], 0, 789 img_x_bounds = msnc.selectImageBounds(tomo_stack[0,:,:], 0,
786 img_x_bounds[0], img_x_bounds[1], num_x_min, 790 img_x_bounds[0], img_x_bounds[1], num_x_min,
787 f'tomography image at theta={self.config["theta_range"]["start"]}') 791 f'tomography image at theta={self.config["theta_range"]["start"]}')
788 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min: 792 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min:
789 logging.warning('Image bounds and pixel size prevent seamless stacking') 793 logging.warning('Image bounds and pixel size prevent seamless stacking')
790 tomo_tmp = tomo_stack[0,:,:]
791 tomo_tmp[img_x_bounds[0],:] = tomo_stack[0,:,:].max()
792 tomo_tmp[img_x_bounds[1]-1,:] = tomo_stack[0,:,:].max()
793 title = f'tomography image at theta={self.config["theta_range"]["start"]}' 794 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
794 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder, 795 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder,
795 save_fig=self.save_plots, save_only=True) 796 save_fig=self.save_plots, save_only=True)
796 msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]), 797 msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]),
797 x_sum[img_x_bounds[0]:img_x_bounds[1]], 798 x_sum[img_x_bounds[0]:img_x_bounds[1]],
799 save_fig=self.save_plots, save_only=True) 800 save_fig=self.save_plots, save_only=True)
800 else: 801 else:
801 x_sum = np.sum(self.tbf, 1) 802 x_sum = np.sum(self.tbf, 1)
802 use_bounds = 'no' 803 use_bounds = 'no'
803 if img_x_bounds[0] is not None and img_x_bounds[1] is not None: 804 if img_x_bounds[0] is not None and img_x_bounds[1] is not None:
804 if img_x_bounds[0] < 0:
805 msnc.illegal_value('img_x_bounds[0]', img_x_bounds[0], 'config file')
806 img_x_bounds[0] = 0
807 if not img_x_bounds[0] < img_x_bounds[1] <= x_sum.size:
808 msnc.illegal_value('img_x_bounds[1]', img_x_bounds[1], 'config file')
809 img_x_bounds[1] = x_sum.size
810 msnc.quickPlot((range(x_sum.size), x_sum), 805 msnc.quickPlot((range(x_sum.size), x_sum),
811 ([img_x_bounds[0], img_x_bounds[0]], [x_sum.min(), x_sum.max()], 'r-'), 806 ([img_x_bounds[0], img_x_bounds[0]], [x_sum.min(), x_sum.max()], 'r-'),
812 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum.min(), x_sum.max()], 'r-'), 807 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum.min(), x_sum.max()], 'r-'),
813 title='sum over theta and y') 808 title='sum over theta and y')
814 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+ 809 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+
971 966
972 preprocess = self.config.get('preprocess') 967 preprocess = self.config.get('preprocess')
973 if preprocess is None: 968 if preprocess is None:
974 img_x_bounds = [0, self.tbf.shape[0]] 969 img_x_bounds = [0, self.tbf.shape[0]]
975 img_y_bounds = [0, self.tbf.shape[1]] 970 img_y_bounds = [0, self.tbf.shape[1]]
976 zoom_perc = preprocess['zoom_perc'] 971 zoom_perc = 100
977 num_theta_skip = preprocess['num_theta_skip'] 972 num_theta_skip = 0
978 else: 973 else:
979 img_x_bounds = preprocess.get('img_x_bounds', [0, self.tbf.shape[0]]) 974 img_x_bounds = preprocess.get('img_x_bounds', [0, self.tbf.shape[0]])
980 img_y_bounds = preprocess.get('img_y_bounds', [0, self.tbf.shape[1]]) 975 img_y_bounds = preprocess.get('img_y_bounds', [0, self.tbf.shape[1]])
981 zoom_perc = 100 976 zoom_perc = preprocess.get('zoom_perc', 100)
982 num_theta_skip = 0 977 num_theta_skip = preprocess.get('num_theta_skip', 0)
983 978
984 if self.tdf.size: 979 if self.tdf.size:
985 tdf = self.tdf[img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]] 980 tdf = self.tdf[img_x_bounds[0]:img_x_bounds[1],img_y_bounds[0]:img_y_bounds[1]]
986 else: 981 else:
987 logging.warning('Dark field unavailable') 982 logging.warning('Dark field unavailable')
993 # Check if stack is already loaded or available 988 # Check if stack is already loaded or available
994 if self.tomo_stacks[i].size or available_stacks[i]: 989 if self.tomo_stacks[i].size or available_stacks[i]:
995 continue 990 continue
996 991
997 # Load a stack of tomography images 992 # Load a stack of tomography images
993 index = stack['index']
998 t0 = time() 994 t0 = time()
999 tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'], 995 tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
1000 stack['img_offset'], self.config['theta_range']['num'], num_theta_skip, 996 stack['img_offset'], self.config['theta_range']['num'], num_theta_skip,
1001 img_x_bounds, img_y_bounds) 997 img_x_bounds, img_y_bounds)
1002 tomo_stack = tomo_stack.astype('float64') 998 tomo_stack = tomo_stack.astype('float64')
1003 logging.debug(f'loading took {time()-t0:.2f} seconds!') 999 logging.debug(f'loading stack {index} took {time()-t0:.2f} seconds!')
1004 1000
1005 # Subtract dark field 1001 # Subtract dark field
1006 if self.tdf.size: 1002 if self.tdf.size:
1007 t0 = time() 1003 t0 = time()
1008 with set_numexpr_threads(self.ncore): 1004 with set_numexpr_threads(self.num_core):
1009 ne.evaluate('tomo_stack-tdf', out=tomo_stack) 1005 ne.evaluate('tomo_stack-tdf', out=tomo_stack)
1010 logging.debug(f'subtracting dark field took {time()-t0:.2f} seconds!') 1006 logging.debug(f'subtracting dark field took {time()-t0:.2f} seconds!')
1011 1007
1012 # Normalize 1008 # Normalize
1013 t0 = time() 1009 t0 = time()
1014 with set_numexpr_threads(self.ncore): 1010 with set_numexpr_threads(self.num_core):
1015 ne.evaluate('tomo_stack/tbf', out=tomo_stack, truediv=True) 1011 ne.evaluate('tomo_stack/tbf', out=tomo_stack, truediv=True)
1016 logging.debug(f'normalizing took {time()-t0:.2f} seconds!') 1012 logging.debug(f'normalizing took {time()-t0:.2f} seconds!')
1017 1013
1018 # Remove non-positive values and linearize data 1014 # Remove non-positive values and linearize data
1019 t0 = time() 1015 t0 = time()
1020 cutoff = 1.e-6 1016 cutoff = 1.e-6
1021 with set_numexpr_threads(self.ncore): 1017 with set_numexpr_threads(self.num_core):
1022 ne.evaluate('where(tomo_stack<cutoff, cutoff, tomo_stack)', out=tomo_stack) 1018 ne.evaluate('where(tomo_stack<cutoff, cutoff, tomo_stack)', out=tomo_stack)
1023 with set_numexpr_threads(self.ncore): 1019 with set_numexpr_threads(self.num_core):
1024 ne.evaluate('-log(tomo_stack)', out=tomo_stack) 1020 ne.evaluate('-log(tomo_stack)', out=tomo_stack)
1025 logging.debug('removing non-positive values and linearizing data took '+ 1021 logging.debug('removing non-positive values and linearizing data took '+
1026 f'{time()-t0:.2f} seconds!') 1022 f'{time()-t0:.2f} seconds!')
1027 1023
1028 # Get rid of nans/infs that may be introduced by normalization 1024 # Get rid of nans/infs that may be introduced by normalization
1031 logging.debug(f'remove nans/infs took {time()-t0:.2f} seconds!') 1027 logging.debug(f'remove nans/infs took {time()-t0:.2f} seconds!')
1032 1028
1033 # Downsize tomography stack to smaller size 1029 # Downsize tomography stack to smaller size
1034 tomo_stack = tomo_stack.astype('float32') 1030 tomo_stack = tomo_stack.astype('float32')
1035 if not self.galaxy_flag: 1031 if not self.galaxy_flag:
1036 index = stack['index']
1037 title = f'red stack fullres {index}' 1032 title = f'red stack fullres {index}'
1038 if not self.test_mode: 1033 if not self.test_mode:
1039 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder, 1034 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder,
1040 save_fig=self.save_plots, save_only=self.save_plots_only) 1035 save_fig=self.save_plots, save_only=self.save_plots_only)
1041 if zoom_perc != 100: 1036 if zoom_perc != 100:
1051 if not self.galaxy_flag: 1046 if not self.galaxy_flag:
1052 title = f'red stack {zoom_perc}p {index}' 1047 title = f'red stack {zoom_perc}p {index}'
1053 if not self.test_mode: 1048 if not self.test_mode:
1054 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder, 1049 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder,
1055 save_fig=self.save_plots, save_only=self.save_plots_only) 1050 save_fig=self.save_plots, save_only=self.save_plots_only)
1056 1051
1057 # Convert tomography stack from theta,row,column to row,theta,column 1052 # Convert tomography stack from theta,row,column to row,theta,column
1058 tomo_stack = np.swapaxes(tomo_stack, 0, 1) 1053 tomo_stack = np.swapaxes(tomo_stack, 0, 1)
1059 1054
1060 # Save tomography stack to file 1055 # Save tomography stack to file
1061 if not self.galaxy_flag: 1056 if not self.galaxy_flag:
1062 if not self.test_mode: 1057 if not self.test_mode:
1063 self._saveTomo('red stack', tomo_stack, index) 1058 self._saveTomo('red stack', tomo_stack, index)
1064 else: 1059 else:
1065 np.savetxt(self.output_folder+f'red_stack_{index}.txt', 1060 np.savetxt(f'{self.output_folder}/red_stack_{index}.txt',
1066 tomo_stack[0,:,:], fmt='%.6e') 1061 tomo_stack[0,:,:], fmt='%.6e')
1067 1062
1068 # Combine stacks 1063 # Combine stacks
1069 t0 = time() 1064 t0 = time()
1070 self.tomo_stacks[i] = tomo_stack 1065 self.tomo_stacks[i] = tomo_stack
1071 logging.debug(f'combining nstack took {time()-t0:.2f} seconds!') 1066 logging.debug(f'combining nstack took {time()-t0:.2f} seconds!')
1072 1067
1073 # Update config and save to file 1068 # Update config and save to file
1074 stack['preprocessed'] = True 1069 stack['preprocessed'] = True
1070 stack.pop('reconstructed', 'reconstructed not found')
1071 find_center = self.config.get('find_center')
1072 if find_center:
1073 if self.test_mode:
1074 find_center.pop('completed', 'completed not found')
1075 find_center.pop('lower_center_offset', 'lower_center_offset not found')
1076 find_center.pop('upper_center_offset', 'upper_center_offset not found')
1077 else:
1078 if find_center.get('center_stack_index', -1) == index:
1079 self.config.pop('find_center')
1075 self.cf.saveFile(self.config_out) 1080 self.cf.saveFile(self.config_out)
1076 1081
1077 if self.tdf.size: 1082 if self.tdf.size:
1078 del tdf 1083 del tdf
1079 del tbf 1084 del tbf
1093 logging.debug(f'sinogram range = [{two_offset+dist_from_edge}, {-dist_from_edge}]') 1098 logging.debug(f'sinogram range = [{two_offset+dist_from_edge}, {-dist_from_edge}]')
1094 sinogram = tomo_plane_T[two_offset+dist_from_edge:-dist_from_edge,:] 1099 sinogram = tomo_plane_T[two_offset+dist_from_edge:-dist_from_edge,:]
1095 else: 1100 else:
1096 logging.debug(f'sinogram range = [{dist_from_edge}, {two_offset-dist_from_edge}]') 1101 logging.debug(f'sinogram range = [{dist_from_edge}, {two_offset-dist_from_edge}]')
1097 sinogram = tomo_plane_T[dist_from_edge:two_offset-dist_from_edge,:] 1102 sinogram = tomo_plane_T[dist_from_edge:two_offset-dist_from_edge,:]
1098 if plot_sinogram: 1103 if plot_sinogram and not self.test_mode:
1099 msnc.quickImshow(sinogram.T, f'sinogram center offset{center_offset:.2f}', 1104 msnc.quickImshow(sinogram.T, f'sinogram center offset{center_offset:.2f}',
1100 path=self.output_folder, save_fig=self.save_plots, 1105 path=self.output_folder, save_fig=self.save_plots,
1101 save_only=self.save_plots_only, aspect='auto') 1106 save_only=self.save_plots_only, aspect='auto')
1102 1107
1103 # Inverting sinogram 1108 # Inverting sinogram
1139 center = sinogram.shape[1]/2 1144 center = sinogram.shape[1]/2
1140 1145
1141 # try automatic center finding routines for initial value 1146 # try automatic center finding routines for initial value
1142 tomo_center = tomopy.find_center_vo(sinogram) 1147 tomo_center = tomopy.find_center_vo(sinogram)
1143 center_offset_vo = tomo_center-center 1148 center_offset_vo = tomo_center-center
1144 print(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}') 1149 if not self.test_mode:
1150 print(f'Center at row {row} using Nghia Vo’s method = {center_offset_vo:.2f}')
1145 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, 1151 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg,
1146 eff_pixel_size, cross_sectional_dim, False) 1152 eff_pixel_size, cross_sectional_dim, False)
1147 base_name=f'edges row{row} center_offset_vo{center_offset_vo:.2f}' 1153 if not self.test_mode:
1148 self._plotEdgesOnePlane(recon_plane, base_name) 1154 base_name=f'edges row{row} center_offset_vo{center_offset_vo:.2f}'
1149 if pyip.inputYesNo('Try finding center using phase correlation (y/[n])? ', 1155 self._plotEdgesOnePlane(recon_plane, base_name)
1150 blank=True) == 'yes': 1156 use_phase_corr = 'no'
1157 if not self.test_mode:
1158 use_phase_corr = pyip.inputYesNo('Try finding center using phase correlation '+
1159 '(y/[n])? ', blank=True)
1160 if use_phase_corr == 'yes':
1151 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=0.1, 1161 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=0.1,
1152 rotc_guess=tomo_center) 1162 rotc_guess=tomo_center)
1153 error = 1. 1163 error = 1.
1154 while error > tol: 1164 while error > tol:
1155 prev = tomo_center 1165 prev = tomo_center
1160 print(f'Center at row {row} using phase correlation = {center_offset:.2f}') 1170 print(f'Center at row {row} using phase correlation = {center_offset:.2f}')
1161 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, 1171 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg,
1162 eff_pixel_size, cross_sectional_dim, False) 1172 eff_pixel_size, cross_sectional_dim, False)
1163 base_name=f'edges row{row} center_offset{center_offset:.2f}' 1173 base_name=f'edges row{row} center_offset{center_offset:.2f}'
1164 self._plotEdgesOnePlane(recon_plane, base_name) 1174 self._plotEdgesOnePlane(recon_plane, base_name)
1165 if pyip.inputYesNo('Accept a center location ([y]) or continue search (n)? ', 1175 accept_center = 'yes'
1166 blank=True) != 'no': 1176 if not self.test_mode:
1177 accept_center = pyip.inputYesNo('Accept a center location ([y]) or continue '+
1178 'search (n)? ', blank=True)
1179 if accept_center != 'no':
1167 del sinogram_T 1180 del sinogram_T
1168 del recon_plane 1181 del recon_plane
1169 center_offset = pyip.inputNum( 1182 if self.test_mode:
1170 f' Enter chosen center offset [{-int(center)}, {int(center)}] '+
1171 f'([{center_offset_vo}])): ', blank=True)
1172 if center_offset == '':
1173 center_offset = center_offset_vo 1183 center_offset = center_offset_vo
1184 else:
1185 center_offset = pyip.inputNum(
1186 f' Enter chosen center offset [{-int(center)}, {int(center)}] '+
1187 f'([{center_offset_vo}])): ', blank=True)
1188 if center_offset == '':
1189 center_offset = center_offset_vo
1174 return float(center_offset) 1190 return float(center_offset)
1175 1191
1176 while True: 1192 while True:
1177 center_offset_low = pyip.inputInt('\nEnter lower bound for center offset '+ 1193 center_offset_low = pyip.inputInt('\nEnter lower bound for center offset '+
1178 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center)) 1194 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center))
1200 center_offset = pyip.inputNum(f' Enter chosen center offset '+ 1216 center_offset = pyip.inputNum(f' Enter chosen center offset '+
1201 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center)) 1217 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center))
1202 return float(center_offset) 1218 return float(center_offset)
1203 1219
1204 def _reconstructOneTomoStack(self, tomo_stack, thetas, row_bounds=None, 1220 def _reconstructOneTomoStack(self, tomo_stack, thetas, row_bounds=None,
1205 center_offsets=[], sigma=0.1, rwidth=30, ncore=1, algorithm='gridrec', 1221 center_offsets=[], sigma=0.1, rwidth=30, num_core=1, algorithm='gridrec',
1206 run_secondary_sirt=False, secondary_iter=100): 1222 run_secondary_sirt=False, secondary_iter=100):
1207 """reconstruct a single tomography stack. 1223 """reconstruct a single tomography stack.
1208 """ 1224 """
1209 # stack order: row,theta,column 1225 # stack order: row,theta,column
1210 # thetas must be in radians 1226 # thetas must be in radians
1215 # https://tomopy.readthedocs.io/en/latest/api/tomopy.misc.corr.html 1231 # https://tomopy.readthedocs.io/en/latest/api/tomopy.misc.corr.html
1216 # RV: Add an option to do (extra) secondary iterations later or to do some sort of convergence test? 1232 # RV: Add an option to do (extra) secondary iterations later or to do some sort of convergence test?
1217 if row_bounds is None: 1233 if row_bounds is None:
1218 row_bounds = [0, tomo_stack.shape[0]] 1234 row_bounds = [0, tomo_stack.shape[0]]
1219 else: 1235 else:
1220 if not (0 <= row_bounds[0] <= row_bounds[1] <= tomo_stack.shape[0]): 1236 if not msnc.is_index_range(row_bounds, 0, tomo_stack.shape[0]):
1221 raise ValueError('Illegal row bounds in reconstructOneTomoStack') 1237 raise ValueError('Illegal row bounds in reconstructOneTomoStack')
1222 if thetas.size != tomo_stack.shape[1]: 1238 if thetas.size != tomo_stack.shape[1]:
1223 raise ValueError('theta dimension mismatch in reconstructOneTomoStack') 1239 raise ValueError('theta dimension mismatch in reconstructOneTomoStack')
1224 if not len(center_offsets): 1240 if not len(center_offsets):
1225 centers = np.zeros((row_bounds[1]-row_bounds[0])) 1241 centers = np.zeros((row_bounds[1]-row_bounds[0]))
1230 if center_offsets.size != row_bounds[1]-row_bounds[0]: 1246 if center_offsets.size != row_bounds[1]-row_bounds[0]:
1231 raise ValueError('center_offsets dimension mismatch in reconstructOneTomoStack') 1247 raise ValueError('center_offsets dimension mismatch in reconstructOneTomoStack')
1232 centers = center_offsets 1248 centers = center_offsets
1233 centers += tomo_stack.shape[2]/2 1249 centers += tomo_stack.shape[2]/2
1234 if True: 1250 if True:
1235 tomo_stack = tomopy.prep.stripe.remove_stripe_fw(tomo_stack[row_bounds[0]:row_bounds[1]], 1251 tomo_stack = tomopy.prep.stripe.remove_stripe_fw(
1236 sigma=sigma, ncore=ncore) 1252 tomo_stack[row_bounds[0]:row_bounds[1]], sigma=sigma, ncore=num_core)
1237 else: 1253 else:
1238 tomo_stack = tomo_stack[row_bounds[0]:row_bounds[1]] 1254 tomo_stack = tomo_stack[row_bounds[0]:row_bounds[1]]
1239 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers, sinogram_order=True, 1255 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers, sinogram_order=True,
1240 algorithm=algorithm, ncore=ncore) 1256 algorithm=algorithm, ncore=num_core)
1241 if run_secondary_sirt and secondary_iter > 0: 1257 if run_secondary_sirt and secondary_iter > 0:
1242 #options = {'method':'SIRT_CUDA', 'proj_type':'cuda', 'num_iter':secondary_iter} 1258 #options = {'method':'SIRT_CUDA', 'proj_type':'cuda', 'num_iter':secondary_iter}
1243 #RV: doesn't work for me: "Error: CUDA error 803: system has unsupported display driver / 1259 #RV: doesn't work for me: "Error: CUDA error 803: system has unsupported display driver /
1244 # cuda driver combination." 1260 # cuda driver combination."
1245 #options = {'method':'SIRT', 'proj_type':'linear', 'MinConstraint': 0, 'num_iter':secondary_iter} 1261 #options = {'method':'SIRT', 'proj_type':'linear', 'MinConstraint': 0, 'num_iter':secondary_iter}
1246 #SIRT did not finish while running overnight 1262 #SIRT did not finish while running overnight
1247 #options = {'method':'SART', 'proj_type':'linear', 'num_iter':secondary_iter} 1263 #options = {'method':'SART', 'proj_type':'linear', 'num_iter':secondary_iter}
1248 options = {'method':'SART', 'proj_type':'linear', 'MinConstraint': 0, 'num_iter':secondary_iter} 1264 options = {'method':'SART', 'proj_type':'linear', 'MinConstraint': 0,
1249 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers, init_recon=tomo_recon_stack, 1265 'num_iter':secondary_iter}
1250 options=options, sinogram_order=True, algorithm=tomopy.astra, ncore=ncore) 1266 tomo_recon_stack = tomopy.recon(tomo_stack, thetas, centers,
1267 init_recon=tomo_recon_stack, options=options, sinogram_order=True,
1268 algorithm=tomopy.astra, ncore=num_core)
1251 if True: 1269 if True:
1252 tomopy.misc.corr.remove_ring(tomo_recon_stack, rwidth=rwidth, out=tomo_recon_stack) 1270 tomopy.misc.corr.remove_ring(tomo_recon_stack, rwidth=rwidth, out=tomo_recon_stack)
1253 return tomo_recon_stack 1271 return tomo_recon_stack
1254 1272
1255 def findImageFiles(self): 1273 def findImageFiles(self):
1263 dark_field['data_path'], self.config['data_filetype'], 'dark field') 1281 dark_field['data_path'], self.config['data_filetype'], 'dark field')
1264 if img_start < 0 or num_imgs < 1: 1282 if img_start < 0 or num_imgs < 1:
1265 logging.error('Unable to find suitable dark field images') 1283 logging.error('Unable to find suitable dark field images')
1266 if dark_field['data_path']: 1284 if dark_field['data_path']:
1267 self.is_valid = False 1285 self.is_valid = False
1268 dark_field['num'] = num_imgs 1286 img_start_old = dark_field.get('img_start')
1269 dark_field['img_start'] = img_start 1287 num_imgs_old = dark_field.get('num')
1288 if num_imgs_old is None:
1289 dark_field['num'] = num_imgs
1290 else:
1291 if num_imgs_old > num_imgs:
1292 logging.error('Inconsistent number of availaible dark field images')
1293 if dark_field['data_path']:
1294 self.is_valid = False
1295 if img_start_old is None:
1296 dark_field['img_start'] = img_start
1297 else:
1298 if img_start_old < img_start:
1299 logging.error('Inconsistent image start index for dark field images')
1300 if dark_field['data_path']:
1301 self.is_valid = False
1270 logging.info(f'Number of dark field images = {dark_field["num"]}') 1302 logging.info(f'Number of dark field images = {dark_field["num"]}')
1271 logging.info(f'Dark field image start index = {dark_field["img_start"]}') 1303 logging.info(f'Dark field image start index = {dark_field["img_start"]}')
1272 1304
1273 # Find bright field images 1305 # Find bright field images
1274 bright_field = self.config['bright_field'] 1306 bright_field = self.config['bright_field']
1275 img_start, num_imgs, bright_files = msnc.findImageFiles( 1307 img_start, num_imgs, bright_files = msnc.findImageFiles(
1276 bright_field['data_path'], self.config['data_filetype'], 'bright field') 1308 bright_field['data_path'], self.config['data_filetype'], 'bright field')
1277 if img_start < 0 or num_imgs < 1: 1309 if img_start < 0 or num_imgs < 1:
1278 logging.error('Unable to find suitable bright field images') 1310 logging.error('Unable to find suitable bright field images')
1279 self.is_valid = False 1311 self.is_valid = False
1280 bright_field['num'] = num_imgs 1312 img_start_old = bright_field.get('img_start')
1281 bright_field['img_start'] = img_start 1313 num_imgs_old = bright_field.get('num')
1314 if num_imgs_old is None:
1315 bright_field['num'] = num_imgs
1316 else:
1317 if num_imgs_old > num_imgs:
1318 logging.error('Inconsistent number of availaible bright field images')
1319 self.is_valid = False
1320 if img_start_old is None:
1321 bright_field['img_start'] = img_start
1322 else:
1323 if img_start_old < img_start:
1324 logging.warning('Inconsistent image start index for bright field images')
1325 self.is_valid = False
1282 logging.info(f'Number of bright field images = {bright_field["num"]}') 1326 logging.info(f'Number of bright field images = {bright_field["num"]}')
1283 logging.info(f'Bright field image start index = {bright_field["img_start"]}') 1327 logging.info(f'Bright field image start index = {bright_field["img_start"]}')
1284 1328
1285 # Find tomography images 1329 # Find tomography images
1286 tomo_stack_files = [] 1330 tomo_stack_files = []
1289 img_start, num_imgs, tomo_files = msnc.findImageFiles( 1333 img_start, num_imgs, tomo_files = msnc.findImageFiles(
1290 stack['data_path'], self.config['data_filetype'], f'tomography set {index}') 1334 stack['data_path'], self.config['data_filetype'], f'tomography set {index}')
1291 if img_start < 0 or num_imgs < 1: 1335 if img_start < 0 or num_imgs < 1:
1292 logging.error('Unable to find suitable tomography images') 1336 logging.error('Unable to find suitable tomography images')
1293 self.is_valid = False 1337 self.is_valid = False
1294 stack['num'] = num_imgs 1338 img_start_old = stack.get('img_start')
1295 stack['img_start'] = img_start 1339 num_imgs_old = stack.get('num')
1340 if num_imgs_old is None:
1341 stack['num'] = num_imgs
1342 else:
1343 if num_imgs_old > num_imgs:
1344 logging.error('Inconsistent number of availaible tomography images')
1345 self.is_valid = False
1346 if img_start_old is None:
1347 stack['img_start'] = img_start
1348 else:
1349 if img_start_old < img_start:
1350 logging.warning('Inconsistent image start index for tomography images')
1351 self.is_valid = False
1296 logging.info(f'Number of tomography images for set {index} = {stack["num"]}') 1352 logging.info(f'Number of tomography images for set {index} = {stack["num"]}')
1297 logging.info(f'Tomography set {index} image start index = {stack["img_start"]}') 1353 logging.info(f'Tomography set {index} image start index = {stack["img_start"]}')
1298 tomo_stack_files.append(tomo_files) 1354 tomo_stack_files.append(tomo_files)
1299 del tomo_files 1355 del tomo_files
1300 1356
1408 """Find rotation axis centers for the tomography stacks. 1464 """Find rotation axis centers for the tomography stacks.
1409 """ 1465 """
1410 logging.debug('Find centers for tomography stacks') 1466 logging.debug('Find centers for tomography stacks')
1411 stacks = self.config['stack_info']['stacks'] 1467 stacks = self.config['stack_info']['stacks']
1412 available_stacks = [stack['index'] for stack in stacks if stack.get('preprocessed', False)] 1468 available_stacks = [stack['index'] for stack in stacks if stack.get('preprocessed', False)]
1413 logging.debug('Avaliable stacks: {available_stacks}') 1469 logging.debug('Available stacks: {available_stacks}')
1414 1470
1415 # Check for valid available center stack index 1471 # Check for valid available center stack index
1416 find_center = self.config.get('find_center') 1472 find_center = self.config.get('find_center')
1473 center_stack_index = None
1417 if find_center and 'center_stack_index' in find_center: 1474 if find_center and 'center_stack_index' in find_center:
1418 center_stack_index = find_center['center_stack_index'] 1475 center_stack_index = find_center['center_stack_index']
1419 if (not isinstance(center_stack_index, int) or 1476 if (not isinstance(center_stack_index, int) or
1420 center_stack_index not in available_stacks): 1477 center_stack_index not in available_stacks):
1421 msnc.illegal_value('center_stack_index', center_stack_index, 'config file') 1478 msnc.illegal_value('center_stack_index', center_stack_index, 'config file')
1422 else: 1479 else:
1423 if self.test_mode: 1480 if self.test_mode:
1424 find_center['completed'] = True 1481 assert(find_center.get('completed', False) == False)
1425 self.cf.saveFile(self.config_out) 1482 else:
1426 return 1483 print('\nFound calibration center offset info for stack '+
1427 print('\nFound calibration center offset info for stack '+ 1484 f'{center_stack_index}')
1428 f'{center_stack_index}') 1485 if (pyip.inputYesNo('Do you want to use this again (y/n)? ') == 'yes' and
1429 if pyip.inputYesNo('Do you want to use this again (y/n)? ') == 'yes': 1486 find_center.get('completed', False) == True):
1430 find_center['completed'] = True 1487 return
1431 self.cf.saveFile(self.config_out)
1432 return
1433 1488
1434 # Load the required preprocessed stack 1489 # Load the required preprocessed stack
1435 # preprocessed stack order: row,theta,column 1490 # preprocessed stack order: row,theta,column
1436 num_tomo_stacks = self.config['stack_info']['num'] 1491 num_tomo_stacks = self.config['stack_info']['num']
1437 assert(len(stacks) == num_tomo_stacks) 1492 assert(len(stacks) == num_tomo_stacks)
1438 assert(len(self.tomo_stacks) == num_tomo_stacks) 1493 assert(len(self.tomo_stacks) == num_tomo_stacks)
1439 if num_tomo_stacks == 1: 1494 if num_tomo_stacks == 1:
1440 center_stack_index = stacks[0]['index'] 1495 if not center_stack_index:
1496 center_stack_index = stacks[0]['index']
1497 elif center_stack_index != stacks[0]['index']:
1498 raise ValueError(f'Inconsistent center_stack_index {center_stack_index}')
1441 if not self.tomo_stacks[0].size: 1499 if not self.tomo_stacks[0].size:
1442 self.tomo_stacks[0], available = self._loadTomo('red stack', center_stack_index, 1500 self.tomo_stacks[0], available = self._loadTomo('red stack', center_stack_index,
1443 required=True) 1501 required=True)
1444 center_stack = self.tomo_stacks[0] 1502 center_stack = self.tomo_stacks[0]
1445 if not center_stack.size: 1503 if not center_stack.size:
1446 logging.error('Unable to load the required preprocessed tomography stack') 1504 stacks[0]['preprocessed'] = False
1447 return 1505 raise OSError('Unable to load the required preprocessed tomography stack')
1448 assert(stacks[0].get('preprocessed', False) == True) 1506 assert(stacks[0].get('preprocessed', False) == True)
1449 else: 1507 else:
1450 while True: 1508 while True:
1451 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get ' 1509 if not center_stack_index:
1452 f'rotation axis centers {available_stacks}: ') 1510 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get '
1511 f'rotation axis centers {available_stacks}: ')
1453 while center_stack_index not in available_stacks: 1512 while center_stack_index not in available_stacks:
1454 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get ' 1513 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get '
1455 f'rotation axis centers {available_stacks}: ') 1514 f'rotation axis centers {available_stacks}: ')
1456 tomo_stack_index = available_stacks.index(center_stack_index) 1515 tomo_stack_index = available_stacks.index(center_stack_index)
1457 if not self.tomo_stacks[tomo_stack_index].size: 1516 if not self.tomo_stacks[tomo_stack_index].size:
1458 self.tomo_stacks[tomo_stack_index], available = self._loadTomo( 1517 self.tomo_stacks[tomo_stack_index], available = self._loadTomo(
1459 'red stack', center_stack_index, required=True) 1518 'red stack', center_stack_index, required=True)
1460 center_stack = self.tomo_stacks[tomo_stack_index] 1519 center_stack = self.tomo_stacks[tomo_stack_index]
1461 if not center_stack.size: 1520 if not center_stack.size:
1521 stacks[tomo_stack_index]['preprocessed'] = False
1462 logging.error(f'Unable to load the {center_stack_index}th '+ 1522 logging.error(f'Unable to load the {center_stack_index}th '+
1463 'preprocessed tomography stack, pick another one') 1523 'preprocessed tomography stack, pick another one')
1464 else: 1524 else:
1465 break 1525 break
1466 assert(stacks[tomo_stack_index].get('preprocessed', False) == True) 1526 assert(stacks[tomo_stack_index].get('preprocessed', False) == True)
1467 if find_center is None: 1527 if find_center is None:
1468 self.config['find_center'] = {'center_stack_index' : center_stack_index} 1528 self.config['find_center'] = {'center_stack_index' : center_stack_index}
1469 find_center = self.config['find_center'] 1529 find_center = self.config['find_center']
1530 else:
1531 find_center['center_stack_index'] = center_stack_index
1470 1532
1471 # Set thetas (in degrees) 1533 # Set thetas (in degrees)
1472 theta_range = self.config['theta_range'] 1534 theta_range = self.config['theta_range']
1473 theta_start = theta_range['start'] 1535 theta_start = theta_range['start']
1474 theta_end = theta_range['end'] 1536 theta_end = theta_range['end']
1486 logging.debug(f'eff_pixel_size = {eff_pixel_size}') 1548 logging.debug(f'eff_pixel_size = {eff_pixel_size}')
1487 tomo_ref_heights = [stack['ref_height'] for stack in stacks] 1549 tomo_ref_heights = [stack['ref_height'] for stack in stacks]
1488 if num_tomo_stacks == 1: 1550 if num_tomo_stacks == 1:
1489 n1 = 0 1551 n1 = 0
1490 height = center_stack.shape[0]*eff_pixel_size 1552 height = center_stack.shape[0]*eff_pixel_size
1491 if pyip.inputYesNo('\nDo you want to reconstruct the full samply height '+ 1553 if not self.test_mode and pyip.inputYesNo('\nDo you want to reconstruct the full '+
1492 f'({height:.3f} mm) (y/n)? ') == 'no': 1554 f'sample height ({height:.3f} mm) (y/n)? ') == 'no':
1493 height = pyip.inputNum('\nEnter the desired reconstructed sample height '+ 1555 height = pyip.inputNum('\nEnter the desired reconstructed sample height '+
1494 f'in mm [0, {height:.3f}]: ', min=0, max=height) 1556 f'in mm [0, {height:.3f}]: ', min=0, max=height)
1495 n1 = int(0.5*(center_stack.shape[0]-height/eff_pixel_size)) 1557 n1 = int(0.5*(center_stack.shape[0]-height/eff_pixel_size))
1496 else: 1558 else:
1497 n1 = int((1.+(tomo_ref_heights[0]+center_stack.shape[0]*eff_pixel_size- 1559 n1 = int((1.+(tomo_ref_heights[0]+center_stack.shape[0]*eff_pixel_size-
1498 tomo_ref_heights[1])/eff_pixel_size)/2) 1560 tomo_ref_heights[1])/eff_pixel_size)/2)
1499 n2 = center_stack.shape[0]-n1 1561 n2 = center_stack.shape[0]-n1
1500 logging.info(f'n1 = {n1}, n2 = {n2} (n2-n1) = {(n2-n1)*eff_pixel_size:.3f} mm') 1562 logging.info(f'n1 = {n1}, n2 = {n2} (n2-n1) = {(n2-n1)*eff_pixel_size:.3f} mm')
1501 if not center_stack.size: 1563 if not center_stack.size:
1502 RuntimeError('Center stack not loaded') 1564 RuntimeError('Center stack not loaded')
1503 msnc.quickImshow(center_stack[:,0,:], title=f'center stack theta={theta_start}', 1565 if not self.test_mode:
1504 path=self.output_folder, save_fig=self.save_plots, save_only=self.save_plots_only) 1566 msnc.quickImshow(center_stack[:,0,:], title=f'center stack theta={theta_start}',
1567 path=self.output_folder, save_fig=self.save_plots,
1568 save_only=self.save_plots_only)
1505 1569
1506 # Get cross sectional diameter in mm 1570 # Get cross sectional diameter in mm
1507 cross_sectional_dim = center_stack.shape[2]*eff_pixel_size 1571 cross_sectional_dim = center_stack.shape[2]*eff_pixel_size
1508 logging.debug(f'cross_sectional_dim = {cross_sectional_dim}') 1572 logging.debug(f'cross_sectional_dim = {cross_sectional_dim}')
1509 1573
1510 # Determine center offset at sample row boundaries 1574 # Determine center offset at sample row boundaries
1511 logging.info('Determine center offset at sample row boundaries') 1575 logging.info('Determine center offset at sample row boundaries')
1512 1576
1513 # Lower row center 1577 # Lower row center
1514 use_row = False 1578 use_row = 'no'
1515 use_center = False 1579 use_center = 'no'
1516 row = find_center.get('lower_row') 1580 row = find_center.get('lower_row')
1517 if msnc.is_int(row, n1, n2-2): 1581 if msnc.is_int(row, n1, n2-2):
1518 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') 1582 if self.test_mode:
1519 use_row = pyip.inputYesNo('\nCurrent row index for lower center = ' 1583 assert(row is not None)
1520 f'{row}, use this value (y/n)? ') 1584 use_row = 'yes'
1521 if self.save_plots_only: 1585 else:
1522 msnc.clearFig(f'theta={theta_start}')
1523 if use_row:
1524 center_offset = find_center.get('lower_center_offset')
1525 if msnc.is_num(center_offset):
1526 use_center = pyip.inputYesNo('Current lower center offset = '+
1527 f'{center_offset}, use this value (y/n)? ')
1528 if not use_center:
1529 if not use_row:
1530 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') 1586 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto')
1587 use_row = pyip.inputYesNo('\nCurrent row index for lower center = '
1588 f'{row}, use this value ([y]/n)? ', blank=True)
1589 if self.save_plots_only:
1590 msnc.clearFig(f'theta={theta_start}')
1591 if use_row != 'no':
1592 center_offset = find_center.get('lower_center_offset')
1593 if msnc.is_num(center_offset):
1594 use_center = pyip.inputYesNo('Current lower center offset = '+
1595 f'{center_offset}, use this value ([y]/n)? ', blank=True)
1596 if use_center == 'no':
1597 if use_row == 'no':
1598 if not self.test_mode:
1599 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}',
1600 aspect='auto')
1531 row = pyip.inputInt('\nEnter row index to find lower center '+ 1601 row = pyip.inputInt('\nEnter row index to find lower center '+
1532 f'[[{n1}], {n2-2}]: ', min=n1, max=n2-2, blank=True) 1602 f'[[{n1}], {n2-2}]: ', min=n1, max=n2-2, blank=True)
1533 if row == '': 1603 if row == '':
1534 row = n1 1604 row = n1
1535 if self.save_plots_only: 1605 if self.save_plots_only:
1545 find_center['lower_center_offset'] = center_offset 1615 find_center['lower_center_offset'] = center_offset
1546 self.cf.saveFile(self.config_out) 1616 self.cf.saveFile(self.config_out)
1547 lower_row = row 1617 lower_row = row
1548 1618
1549 # Upper row center 1619 # Upper row center
1550 use_row = False 1620 use_row = 'no'
1551 use_center = False 1621 use_center = 'no'
1552 row = find_center.get('upper_row') 1622 row = find_center.get('upper_row')
1553 if msnc.is_int(row, lower_row+1, n2-1): 1623 if msnc.is_int(row, lower_row+1, n2-1):
1554 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') 1624 if self.test_mode:
1555 use_row = pyip.inputYesNo('\nCurrent row index for upper center = ' 1625 assert(row is not None)
1556 f'{row}, use this value (y/n)? ') 1626 use_row = 'yes'
1557 if self.save_plots_only: 1627 else:
1558 msnc.clearFig(f'theta={theta_start}')
1559 if use_row:
1560 center_offset = find_center.get('upper_center_offset')
1561 if msnc.is_num(center_offset):
1562 use_center = pyip.inputYesNo('Current upper center offset = '+
1563 f'{center_offset}, use this value (y/n)? ')
1564 if not use_center:
1565 if not use_row:
1566 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') 1628 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto')
1629 use_row = pyip.inputYesNo('\nCurrent row index for upper center = '
1630 f'{row}, use this value ([y]/n)? ', blank=True)
1631 if self.save_plots_only:
1632 msnc.clearFig(f'theta={theta_start}')
1633 if use_row != 'no':
1634 center_offset = find_center.get('upper_center_offset')
1635 if msnc.is_num(center_offset):
1636 use_center = pyip.inputYesNo('Current upper center offset = '+
1637 f'{center_offset}, use this value ([y]/n)? ', blank=True)
1638 if use_center == 'no':
1639 if use_row == 'no':
1640 if not self.test_mode:
1641 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}',
1642 aspect='auto')
1567 row = pyip.inputInt('\nEnter row index to find upper center '+ 1643 row = pyip.inputInt('\nEnter row index to find upper center '+
1568 f'[{lower_row+1}, [{n2-1}]]: ', min=lower_row+1, max=n2-1, blank=True) 1644 f'[{lower_row+1}, [{n2-1}]]: ', min=lower_row+1, max=n2-1, blank=True)
1569 if row == '': 1645 if row == '':
1570 row = n2-1 1646 row = n2-1
1571 if self.save_plots_only: 1647 if self.save_plots_only:
1658 1734
1659 def reconstructTomoStacks(self): 1735 def reconstructTomoStacks(self):
1660 """Reconstruct tomography stacks. 1736 """Reconstruct tomography stacks.
1661 """ 1737 """
1662 logging.debug('Reconstruct tomography stacks') 1738 logging.debug('Reconstruct tomography stacks')
1739 stacks = self.config['stack_info']['stacks']
1740 assert(len(self.tomo_stacks) == self.config['stack_info']['num'])
1741 assert(len(self.tomo_stacks) == len(stacks))
1742 assert(len(self.tomo_recon_stacks) == len(stacks))
1663 1743
1664 # Get rotation axis rows and centers 1744 # Get rotation axis rows and centers
1665 find_center = self.config['find_center'] 1745 find_center = self.config['find_center']
1666 lower_row = find_center.get('lower_row') 1746 lower_row = find_center.get('lower_row')
1667 if lower_row is None: 1747 if lower_row is None:
1709 if stack.get('reconstructed', False): 1789 if stack.get('reconstructed', False):
1710 self.tomo_recon_stacks[i], available = self._loadTomo('recon stack', index) 1790 self.tomo_recon_stacks[i], available = self._loadTomo('recon stack', index)
1711 if self.tomo_recon_stacks[i].size or available: 1791 if self.tomo_recon_stacks[i].size or available:
1712 if self.tomo_stacks[i].size: 1792 if self.tomo_stacks[i].size:
1713 self.tomo_stacks[i] = np.array([]) 1793 self.tomo_stacks[i] = np.array([])
1794 assert(stack.get('preprocessed', False) == True)
1714 assert(stack.get('reconstructed', False) == True) 1795 assert(stack.get('reconstructed', False) == True)
1715 continue 1796 continue
1716 else: 1797 else:
1717 stack['reconstructed'] = False 1798 stack['reconstructed'] = False
1718 if not self.tomo_stacks[i].size: 1799 if not self.tomo_stacks[i].size:
1719 self.tomo_stacks[i], available = self._loadTomo('red stack', index, 1800 self.tomo_stacks[i], available = self._loadTomo('red stack', index,
1720 required=True) 1801 required=True)
1721 if not self.tomo_stacks[i].size: 1802 if not self.tomo_stacks[i].size:
1722 logging.error(f'Unable to load tomography stack {index} for reconstruction') 1803 logging.error(f'Unable to load tomography stack {index} for reconstruction')
1804 stack[i]['preprocessed'] = False
1723 load_error = True 1805 load_error = True
1724 continue 1806 continue
1725 assert(0 <= lower_row < upper_row < self.tomo_stacks[i].shape[0]) 1807 assert(0 <= lower_row < upper_row < self.tomo_stacks[i].shape[0])
1726 center_offsets = [lower_center_offset-lower_row*center_slope, 1808 center_offsets = [lower_center_offset-lower_row*center_slope,
1727 upper_center_offset+(self.tomo_stacks[i].shape[0]-1-upper_row)*center_slope] 1809 upper_center_offset+(self.tomo_stacks[i].shape[0]-1-upper_row)*center_slope]
1728 t0 = time() 1810 t0 = time()
1729 self.tomo_recon_stacks[i]= self._reconstructOneTomoStack(self.tomo_stacks[i], 1811 self.tomo_recon_stacks[i]= self._reconstructOneTomoStack(self.tomo_stacks[i],
1730 thetas, center_offsets=center_offsets, sigma=0.1, ncore=self.ncore, 1812 thetas, center_offsets=center_offsets, sigma=0.1, num_core=self.num_core,
1731 algorithm='gridrec', run_secondary_sirt=True, secondary_iter=25) 1813 algorithm='gridrec', run_secondary_sirt=True, secondary_iter=25)
1732 logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!') 1814 logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!')
1733 if not self.test_mode: 1815 if not self.test_mode:
1734 row_slice = int(self.tomo_stacks[i].shape[0]/2) 1816 row_slice = int(self.tomo_stacks[i].shape[0]/2)
1735 title = f'{basetitle} {index} slice{row_slice}' 1817 title = f'{basetitle} {index} slice{row_slice}'
1747 # self.tomo_recon_stacks[i][row_slice,:,:], fmt='%.6e') 1829 # self.tomo_recon_stacks[i][row_slice,:,:], fmt='%.6e')
1748 self.tomo_stacks[i] = np.array([]) 1830 self.tomo_stacks[i] = np.array([])
1749 1831
1750 # Update config and save to file 1832 # Update config and save to file
1751 stack['reconstructed'] = True 1833 stack['reconstructed'] = True
1834 combine_stacks = self.config.get('combine_stacks')
1835 if combine_stacks and index in combine_stacks.get('stacks', []):
1836 combine_stacks['stacks'].remove(index)
1752 self.cf.saveFile(self.config_out) 1837 self.cf.saveFile(self.config_out)
1753 1838
1754 def combineTomoStacks(self): 1839 def combineTomoStacks(self):
1755 """Combine the reconstructed tomography stacks. 1840 """Combine the reconstructed tomography stacks.
1756 """ 1841 """
1757 # stack order: stack,row(z),x,y 1842 # stack order: stack,row(z),x,y
1758 logging.debug('Combine reconstructed tomography stacks') 1843 logging.debug('Combine reconstructed tomography stacks')
1759 # Load any unloaded reconstructed stacks 1844 # Load any unloaded reconstructed stacks
1760 stacks = self.config['stack_info']['stacks'] 1845 stack_info = self.config['stack_info']
1846 stacks = stack_info['stacks']
1761 for i,stack in enumerate(stacks): 1847 for i,stack in enumerate(stacks):
1848 available = False
1762 if not self.tomo_recon_stacks[i].size and stack.get('reconstructed', False): 1849 if not self.tomo_recon_stacks[i].size and stack.get('reconstructed', False):
1763 self.tomo_recon_stacks[i], available = self._loadTomo('recon stack', 1850 self.tomo_recon_stacks[i], available = self._loadTomo('recon stack',
1764 stack['index'], required=True) 1851 stack['index'], required=True)
1765 if not self.tomo_recon_stacks[i].size or not available: 1852 if not self.tomo_recon_stacks[i].size:
1766 logging.error(f'Unable to load reconstructed stack {stack["index"]}') 1853 logging.error(f'Unable to load reconstructed stack {stack["index"]}')
1854 stack['reconstructed'] = False
1767 return 1855 return
1768 if i: 1856 if i:
1769 if (self.tomo_recon_stacks[i].shape[1] != self.tomo_recon_stacks[0].shape[1] or 1857 if (self.tomo_recon_stacks[i].shape[1] != self.tomo_recon_stacks[0].shape[1] or
1770 self.tomo_recon_stacks[i].shape[1] != self.tomo_recon_stacks[0].shape[1]): 1858 self.tomo_recon_stacks[i].shape[1] != self.tomo_recon_stacks[0].shape[1]):
1771 logging.error('Incompatible reconstructed tomography stack dimensions') 1859 logging.error('Incompatible reconstructed tomography stack dimensions')
1775 row_bounds = self.config['find_center']['row_bounds'] 1863 row_bounds = self.config['find_center']['row_bounds']
1776 if not msnc.is_index_range(row_bounds, 0, self.tomo_recon_stacks[0].shape[0]): 1864 if not msnc.is_index_range(row_bounds, 0, self.tomo_recon_stacks[0].shape[0]):
1777 msnc.illegal_value('row_bounds', row_bounds, 'config file') 1865 msnc.illegal_value('row_bounds', row_bounds, 'config file')
1778 return 1866 return
1779 1867
1780 # Selecting xy bounds 1868 # Selecting x bounds (in yz-plane)
1781 tomosum = 0 1869 tomosum = 0
1782 #RV FIX := 1870 #RV FIX :=
1783 #[tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,2)) for tomo_recon_stack in 1871 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,2)) for tomo_recon_stack in
1784 # self.tomo_recon_stacks] 1872 self.tomo_recon_stacks]
1785 combine_stacks =self.config.get('combine_stacks') 1873 combine_stacks = self.config.get('combine_stacks')
1786 if combine_stacks and 'x_bounds' in combine_stacks: 1874 if combine_stacks and 'x_bounds' in combine_stacks:
1787 x_bounds = combine_stacks['x_bounds'] 1875 x_bounds = combine_stacks['x_bounds']
1788 if not msnc.is_index_range(x_bounds, 0, self.tomo_recon_stacks[0].shape[1]): 1876 if not msnc.is_index_range(x_bounds, 0, self.tomo_recon_stacks[0].shape[1]):
1789 msnc.illegal_value('x_bounds', x_bounds, 'config file') 1877 msnc.illegal_value('x_bounds', x_bounds, 'config file')
1790 elif not self.test_mode: 1878 elif not self.test_mode:
1803 if pyip.inputYesNo('\nDo you want to change the image x-bounds (y/n)? ') == 'no': 1891 if pyip.inputYesNo('\nDo you want to change the image x-bounds (y/n)? ') == 'no':
1804 x_bounds = [0, self.tomo_recon_stacks[0].shape[1]] 1892 x_bounds = [0, self.tomo_recon_stacks[0].shape[1]]
1805 else: 1893 else:
1806 x_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum yz') 1894 x_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum yz')
1807 if False and self.test_mode: 1895 if False and self.test_mode:
1808 np.savetxt(self.output_folder+'recon_stack_sum_yz.txt', 1896 np.savetxt(f'{self.output_folder}/recon_stack_sum_yz.txt',
1809 tomosum[x_bounds[0]:x_bounds[1]], fmt='%.6e') 1897 tomosum[x_bounds[0]:x_bounds[1]], fmt='%.6e')
1810 if self.save_plots_only: 1898 if self.save_plots_only:
1811 msnc.clearFig('recon stack sum yz') 1899 msnc.clearFig('recon stack sum yz')
1900
1901 # Selecting y bounds (in xz-plane)
1812 tomosum = 0 1902 tomosum = 0
1813 #RV FIX := 1903 #RV FIX :=
1814 #[tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,1)) for tomo_recon_stack in 1904 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,1)) for tomo_recon_stack in
1815 # self.tomo_recon_stacks] 1905 self.tomo_recon_stacks]
1816 if combine_stacks and 'y_bounds' in combine_stacks: 1906 if combine_stacks and 'y_bounds' in combine_stacks:
1817 y_bounds = combine_stacks['y_bounds'] 1907 y_bounds = combine_stacks['y_bounds']
1818 if not msnc.is_index_range(x_bounds, 0, self.tomo_recon_stacks[0].shape[1]): 1908 if not msnc.is_index_range(y_bounds, 0, self.tomo_recon_stacks[0].shape[1]):
1819 msnc.illegal_value('y_bounds', y_bounds, 'config file') 1909 msnc.illegal_value('y_bounds', y_bounds, 'config file')
1820 elif not self.test_mode: 1910 elif not self.test_mode:
1821 msnc.quickPlot(tomosum, title='recon stack sum xz') 1911 msnc.quickPlot(tomosum, title='recon stack sum xz')
1822 if pyip.inputYesNo('\nCurrent image y-bounds: '+ 1912 if pyip.inputYesNo('\nCurrent image y-bounds: '+
1823 f'[{y_bounds[0]}, {y_bounds[1]}], use these values ([y]/n)? ', 1913 f'[{y_bounds[0]}, {y_bounds[1]}], use these values ([y]/n)? ',
1833 if pyip.inputYesNo('\nDo you want to change the image y-bounds (y/n)? ') == 'no': 1923 if pyip.inputYesNo('\nDo you want to change the image y-bounds (y/n)? ') == 'no':
1834 y_bounds = [0, self.tomo_recon_stacks[0].shape[1]] 1924 y_bounds = [0, self.tomo_recon_stacks[0].shape[1]]
1835 else: 1925 else:
1836 y_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum xz') 1926 y_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum xz')
1837 if False and self.test_mode: 1927 if False and self.test_mode:
1838 np.savetxt(self.output_folder+'recon_stack_sum_xz.txt', 1928 np.savetxt(f'{self.output_folder}/recon_stack_sum_xz.txt',
1839 tomosum[y_bounds[0]:y_bounds[1]], fmt='%.6e') 1929 tomosum[y_bounds[0]:y_bounds[1]], fmt='%.6e')
1840 if self.save_plots_only: 1930 if self.save_plots_only:
1841 msnc.clearFig('recon stack sum xz') 1931 msnc.clearFig('recon stack sum xz')
1842 1932
1843 # Combine reconstructed tomography stacks 1933 # Combine reconstructed tomography stacks
1844 logging.info(f'Combining reconstructed stacks ...') 1934 logging.info(f'Combining reconstructed stacks ...')
1845 t0 = time() 1935 t0 = time()
1846 num_tomo_stacks = self.config['stack_info']['num'] 1936 num_tomo_stacks = stack_info['num']
1847 if num_tomo_stacks == 1: 1937 if num_tomo_stacks == 1:
1848 low_bound = row_bounds[0] 1938 low_bound = row_bounds[0]
1849 else: 1939 else:
1850 low_bound = 0 1940 low_bound = 0
1851 tomo_recon_combined = self.tomo_recon_stacks[0][low_bound:row_bounds[1]:, 1941 tomo_recon_combined = self.tomo_recon_stacks[0][low_bound:row_bounds[1]:,
1858 if num_tomo_stacks > 1: 1948 if num_tomo_stacks > 1:
1859 tomo_recon_combined = np.concatenate([tomo_recon_combined]+ 1949 tomo_recon_combined = np.concatenate([tomo_recon_combined]+
1860 [self.tomo_recon_stacks[num_tomo_stacks-1][row_bounds[0]:, 1950 [self.tomo_recon_stacks[num_tomo_stacks-1][row_bounds[0]:,
1861 x_bounds[0]:x_bounds[1],y_bounds[0]:y_bounds[1]]]) 1951 x_bounds[0]:x_bounds[1],y_bounds[0]:y_bounds[1]]])
1862 logging.info(f'... done in {time()-t0:.2f} seconds!') 1952 logging.info(f'... done in {time()-t0:.2f} seconds!')
1953 combined_stacks = [stack['index'] for stack in stacks]
1954
1955 # Wrap up if in test_mode
1863 tomosum = np.sum(tomo_recon_combined, axis=(1,2)) 1956 tomosum = np.sum(tomo_recon_combined, axis=(1,2))
1864 if self.test_mode: 1957 if self.test_mode:
1865 zoom_perc = self.config['preprocess'].get('zoom_perc', 100) 1958 zoom_perc = self.config['preprocess'].get('zoom_perc', 100)
1866 filename = 'recon combined sum xy' 1959 filename = 'recon combined sum xy'
1867 if zoom_perc is None or zoom_perc == 100: 1960 if zoom_perc is None or zoom_perc == 100:
1870 filename += f' {zoom_perc}p.dat' 1963 filename += f' {zoom_perc}p.dat'
1871 msnc.quickPlot(tomosum, title='recon combined sum xy', 1964 msnc.quickPlot(tomosum, title='recon combined sum xy',
1872 path=self.output_folder, save_fig=self.save_plots, 1965 path=self.output_folder, save_fig=self.save_plots,
1873 save_only=self.save_plots_only) 1966 save_only=self.save_plots_only)
1874 if False: 1967 if False:
1875 np.savetxt(self.output_folder+'recon_combined_sum_xy.txt', 1968 np.savetxt(f'{self.output_folder}/recon_combined_sum_xy.txt',
1876 tomosum, fmt='%.6e') 1969 tomosum, fmt='%.6e')
1877 np.savetxt(self.output_folder+'recon_combined.txt', 1970 np.savetxt(f'{self.output_folder}/recon_combined.txt',
1878 tomo_recon_combined[int(tomo_recon_combined.shape[0]/2),:,:], fmt='%.6e') 1971 tomo_recon_combined[int(tomo_recon_combined.shape[0]/2),:,:], fmt='%.6e')
1879 combine_stacks =self.config.get('combine_stacks')
1880 1972
1881 # Update config and save to file 1973 # Update config and save to file
1882 if combine_stacks: 1974 if combine_stacks:
1883 combine_stacks['x_bounds'] = x_bounds 1975 combine_stacks['x_bounds'] = x_bounds
1884 combine_stacks['y_bounds'] = y_bounds 1976 combine_stacks['y_bounds'] = y_bounds
1885 else: 1977 combine_stacks['stacks'] = combined_stacks
1886 self.config['combine_stacks'] = {'x_bounds' : x_bounds, 'y_bounds' : y_bounds} 1978 else:
1979 self.config['combine_stacks'] = {'x_bounds' : x_bounds, 'y_bounds' : y_bounds,
1980 'stacks' : combined_stacks}
1887 self.cf.saveFile(self.config_out) 1981 self.cf.saveFile(self.config_out)
1888 return 1982 return
1983
1984 # Selecting z bounds (in xy-plane)
1889 msnc.quickPlot(tomosum, title='recon combined sum xy') 1985 msnc.quickPlot(tomosum, title='recon combined sum xy')
1890 if pyip.inputYesNo( 1986 if pyip.inputYesNo(
1891 '\nDo you want to change the image z-bounds (y/[n])? ', 1987 '\nDo you want to change the image z-bounds (y/[n])? ',
1892 blank=True) != 'yes': 1988 blank=True) != 'yes':
1893 z_bounds = [0, tomo_recon_combined.shape[0]] 1989 z_bounds = [0, tomo_recon_combined.shape[0]]
1913 path=self.output_folder, save_fig=self.save_plots, 2009 path=self.output_folder, save_fig=self.save_plots,
1914 save_only=self.save_plots_only) 2010 save_only=self.save_plots_only)
1915 2011
1916 # Save combined reconstructed tomo stacks 2012 # Save combined reconstructed tomo stacks
1917 base_name = 'recon combined' 2013 base_name = 'recon combined'
1918 combined_stacks = []
1919 for stack in stacks: 2014 for stack in stacks:
1920 base_name += f' {stack["index"]}' 2015 base_name += f' {stack["index"]}'
1921 combined_stacks.append(stack['index'])
1922 self._saveTomo(base_name, tomo_recon_combined) 2016 self._saveTomo(base_name, tomo_recon_combined)
1923 2017
1924 # Update config and save to file 2018 # Update config and save to file
1925 if combine_stacks: 2019 if combine_stacks:
1926 combine_stacks['x_bounds'] = x_bounds 2020 combine_stacks['x_bounds'] = x_bounds
1927 combine_stacks['y_bounds'] = y_bounds 2021 combine_stacks['y_bounds'] = y_bounds
2022 combine_stacks['z_bounds'] = z_bounds
1928 combine_stacks['stacks'] = combined_stacks 2023 combine_stacks['stacks'] = combined_stacks
1929 else: 2024 else:
1930 self.config['combine_stacks'] = {'x_bounds' : x_bounds, 'y_bounds' : y_bounds, 2025 self.config['combine_stacks'] = {'x_bounds' : x_bounds, 'y_bounds' : y_bounds,
1931 'stacks' : combined_stacks} 2026 'z_bounds' : z_bounds, 'stacks' : combined_stacks}
1932 self.cf.saveFile(self.config_out) 2027 self.cf.saveFile(self.config_out)
1933 2028
1934 def runTomo(config_file=None, config_dict=None, output_folder='.', log_level='INFO', 2029 def runTomo(config_file=None, config_dict=None, output_folder='.', log_level='INFO',
1935 test_mode=False): 2030 test_mode=False, num_core=-1):
1936 """Run a tomography analysis. 2031 """Run a tomography analysis.
1937 """ 2032 """
1938 # Instantiate Tomo object 2033 # Instantiate Tomo object
1939 tomo = Tomo(config_file=config_file, output_folder=output_folder, log_level=log_level, 2034 tomo = Tomo(config_file=config_file, output_folder=output_folder, log_level=log_level,
1940 test_mode=test_mode) 2035 test_mode=test_mode, num_core=num_core)
1941 if not tomo.is_valid: 2036 if not tomo.is_valid:
1942 raise ValueError('Invalid config and/or detector file provided.') 2037 raise ValueError('Invalid config and/or detector file provided.')
1943 2038
1944 # Preprocess the image files 2039 # Preprocess the image files
2040 assert(tomo.config['stack_info'])
1945 num_tomo_stacks = tomo.config['stack_info']['num'] 2041 num_tomo_stacks = tomo.config['stack_info']['num']
1946 assert(num_tomo_stacks == len(tomo.tomo_stacks)) 2042 assert(num_tomo_stacks == len(tomo.tomo_stacks))
1947 preprocess = tomo.config.get('preprocess', None)
1948 preprocessed_stacks = [] 2043 preprocessed_stacks = []
1949 if preprocess: 2044 if not tomo.test_mode:
1950 preprocessed_stacks = [stack['index'] for stack in tomo.config['stack_info']['stacks'] 2045 preprocess = tomo.config.get('preprocess', None)
1951 if stack.get('preprocessed', False)] 2046 if preprocess:
1952 if not len(preprocessed_stacks): 2047 preprocessed_stacks = [stack['index'] for stack in tomo.config['stack_info']['stacks']
2048 if stack.get('preprocessed', False)]
2049 if len(preprocessed_stacks) != num_tomo_stacks:
1953 tomo.genTomoStacks() 2050 tomo.genTomoStacks()
1954 if not tomo.is_valid: 2051 if not tomo.is_valid:
1955 IOError('Unable to load all required image files.') 2052 IOError('Unable to load all required image files.')
1956 find_center = tomo.config.get('find_center')
1957 if find_center and find_center.get('completed', False):
1958 center_stack_index = find_center['center_stack_index']
1959 if not center_stack_index in preprocessed_stacks:
1960 find_center['completed'] = False
1961 #RV FIX
1962 # tomo.config.pop('check_center', 'check_center not found')
1963 # combined_stacks = tomo.config.get('combined_stacks')
1964 # if combined_stacks:
1965 # combined_stacks['completed'] = False
1966 tomo.cf.saveFile(tomo.config_out) 2053 tomo.cf.saveFile(tomo.config_out)
1967 2054
1968 # Find centers 2055 # Find centers
1969 find_center = tomo.config.get('find_center') 2056 find_center = tomo.config.get('find_center')
1970 if find_center is None or not find_center.get('completed', False): 2057 if find_center is None or not find_center.get('completed', False):
1973 # Check centers 2060 # Check centers
1974 #if num_tomo_stacks > 1 and not tomo.config.get('check_centers', False): 2061 #if num_tomo_stacks > 1 and not tomo.config.get('check_centers', False):
1975 # tomo.checkCenters() 2062 # tomo.checkCenters()
1976 2063
1977 # Reconstruct tomography stacks 2064 # Reconstruct tomography stacks
1978 if len(tomo.config.get('reconstructed_stacks', [])) != tomo.config['stack_info']['num']: 2065 assert(tomo.config['stack_info']['stacks'])
2066 reconstructed_stacks = [stack['index'] for stack in tomo.config['stack_info']['stacks']
2067 if stack.get('reconstructed', False)]
2068 if len(reconstructed_stacks) != num_tomo_stacks:
1979 tomo.reconstructTomoStacks() 2069 tomo.reconstructTomoStacks()
1980 2070
1981 # Combine reconstructed tomography stacks 2071 # Combine reconstructed tomography stacks
1982 combined_stacks = tomo.config.get('combined_stacks') 2072 reconstructed_stacks = [stack['index'] for stack in tomo.config['stack_info']['stacks']
1983 if combined_stacks is None or not combined_stacks.get('completed', False): 2073 if stack.get('reconstructed', False)]
2074 combine_stacks = tomo.config.get('combine_stacks')
2075 if len(reconstructed_stacks) and (combine_stacks is None or
2076 combine_stacks.get('stacks') != reconstructed_stacks):
1984 tomo.combineTomoStacks() 2077 tomo.combineTomoStacks()
1985 2078
1986 #%%============================================================================ 2079 #%%============================================================================
1987 if __name__ == '__main__': 2080 if __name__ == '__main__':
2081
1988 # Parse command line arguments 2082 # Parse command line arguments
1989 arguments = sys.argv[1:] 2083 parser = argparse.ArgumentParser(
1990 config_file = None 2084 description='Tomography reconstruction')
1991 output_folder = '.' 2085 parser.add_argument('-c', '--config',
1992 log_level = 'INFO' 2086 default=None,
1993 test_mode = False 2087 help='Input config')
1994 try: 2088 parser.add_argument('-o', '--output_folder',
1995 opts, args = getopt.getopt(arguments,"hc:o:l:t") 2089 default='.',
1996 except getopt.GetoptError: 2090 help='Output folder')
1997 print('usage: tomo.py -c <config_file> -o <output_folder> -l <log_level> -t') 2091 parser.add_argument('-l', '--log_level',
1998 sys.exit(2) 2092 default='INFO',
1999 for opt, arg in opts: 2093 help='Log level')
2000 if opt == '-h': 2094 parser.add_argument('-t', '--test_mode',
2001 print('usage: tomo.py -c <config_file> -o <output_folder> -l <log_level> -t') 2095 action='store_true',
2002 sys.exit() 2096 default=False,
2003 elif opt in ("-c"): 2097 help='Test mode flag')
2004 config_file = arg 2098 parser.add_argument('--num_core',
2005 elif opt in ("-o"): 2099 default=-1,
2006 output_folder = arg 2100 help='Number of cores')
2007 elif opt in ("-l"): 2101 args = parser.parse_args()
2008 log_level = arg 2102
2009 elif opt in ("-t"): 2103 if args.config is None:
2010 test_mode = True
2011 if config_file is None:
2012 if os.path.isfile('config.yaml'): 2104 if os.path.isfile('config.yaml'):
2013 config_file = 'config.yaml' 2105 args.config = 'config.yaml'
2014 else: 2106 else:
2015 config_file = 'config.txt' 2107 args.config = 'config.txt'
2016 2108
2017 # Set basic log configuration 2109 # Set basic log configuration
2018 logging_format = '%(asctime)s : %(levelname)s - %(module)s : %(funcName)s - %(message)s' 2110 logging_format = '%(asctime)s : %(levelname)s - %(module)s : %(funcName)s - %(message)s'
2019 if not test_mode: 2111 if not args.test_mode:
2020 level = getattr(logging, log_level.upper(), None) 2112 level = getattr(logging, args.log_level.upper(), None)
2021 if not isinstance(level, int): 2113 if not isinstance(level, int):
2022 raise ValueError(f'Invalid log_level: {log_level}') 2114 raise ValueError(f'Invalid log_level: {args.log_level}')
2023 logging.basicConfig(format=logging_format, level=level, force=True, 2115 logging.basicConfig(format=logging_format, level=level, force=True,
2024 handlers=[logging.StreamHandler()]) 2116 handlers=[logging.StreamHandler()])
2025 2117
2026 logging.debug(f'config_file = {config_file}') 2118 logging.debug(f'config = {args.config}')
2027 logging.debug(f'output_folder = {output_folder}') 2119 logging.debug(f'output_folder = {args.output_folder}')
2028 logging.debug(f'log_level = {log_level}') 2120 logging.debug(f'log_level = {args.log_level}')
2029 logging.debug(f'test_mode = {test_mode}') 2121 logging.debug(f'test_mode = {args.test_mode}')
2122 logging.debug(f'num_core = {args.num_core}')
2030 2123
2031 # Run tomography analysis 2124 # Run tomography analysis
2032 runTomo(config_file=config_file, output_folder=output_folder, log_level=log_level, 2125 runTomo(config_file=args.config, output_folder=args.output_folder, log_level=args.log_level,
2033 test_mode=test_mode) 2126 test_mode=args.test_mode, num_core=args.num_core)
2034 2127
2035 #%%============================================================================ 2128 #%%============================================================================
2036 input('Press any key to continue') 2129 # input('Press any key to continue')
2037 #%%============================================================================ 2130 #%%============================================================================