comparison tomo.py @ 49:26f99fdd8d61 draft

"planemo upload for repository https://github.com/rolfverberg/galaxytools commit 4f7738d02f4a3fd91373f43937ed311b6fe11a12"
author rv43
date Thu, 28 Jul 2022 16:05:24 +0000
parents 059819ea1f0e
children 75dd6e15f628
comparison
equal deleted inserted replaced
48:059819ea1f0e 49:26f99fdd8d61
12 import os 12 import os
13 import sys 13 import sys
14 import getopt 14 import getopt
15 import re 15 import re
16 import io 16 import io
17 try:
18 import pyinputplus as pyip
19 except:
20 pass
21 import argparse 17 import argparse
22 import numpy as np 18 import numpy as np
23 try: 19 try:
24 import numexpr as ne 20 import numexpr as ne
25 except: 21 except:
41 try: 37 try:
42 from skimage.restoration import denoise_tv_chambolle 38 from skimage.restoration import denoise_tv_chambolle
43 except: 39 except:
44 pass 40 pass
45 41
46 import msnc_tools as msnc 42 from detector import TomoDetectorConfig
43 from fit import Fit
44 #from msnctools.general import illegal_value, is_int, is_num, is_index_range, get_trailing_int, \
45 from general import illegal_value, is_int, is_num, is_index_range, get_trailing_int, \
46 input_int, input_num, input_yesno, input_menu, findImageFiles, loadImageStack, clearPlot, \
47 draw_mask_1d, quickPlot, clearImshow, quickImshow, combine_tiffs_in_h5, Config
48 from general import selectArrayBounds,selectImageRange, selectImageBounds
47 49
48 # the following tomopy routines don't run with more than 24 cores on Galaxy-Dev 50 # the following tomopy routines don't run with more than 24 cores on Galaxy-Dev
49 # - tomopy.find_center_vo 51 # - tomopy.find_center_vo
50 # - tomopy.prep.stripe.remove_stripe_fw 52 # - tomopy.prep.stripe.remove_stripe_fw
51 num_core_tomopy_limit = 24 53 num_core_tomopy_limit = 24
66 logging.debug(f'self.num_core={self.num_core}') 68 logging.debug(f'self.num_core={self.num_core}')
67 69
68 def __exit__(self, exc_type, exc_value, traceback): 70 def __exit__(self, exc_type, exc_value, traceback):
69 ne.set_num_threads(self.num_core_org) 71 ne.set_num_threads(self.num_core_org)
70 72
71 class ConfigTomo(msnc.Config): 73 class ConfigTomo(Config):
72 """Class for processing a config file. 74 """Class for processing a config file.
73 """ 75 """
74 76
75 def __init__(self, config_file=None, config_dict=None): 77 def __init__(self, config_file=None, config_dict=None):
76 super().__init__(config_file, config_dict) 78 super().__init__(config_file, config_dict)
94 # Find tomography bright field images file/folder 96 # Find tomography bright field images file/folder
95 self.tbf_data_path = self.config.get('tbf_data_path') 97 self.tbf_data_path = self.config.get('tbf_data_path')
96 98
97 # Check number of tomography image stacks 99 # Check number of tomography image stacks
98 self.num_tomo_stacks = self.config.get('num_tomo_stacks', 1) 100 self.num_tomo_stacks = self.config.get('num_tomo_stacks', 1)
99 if not msnc.is_int(self.num_tomo_stacks, 1): 101 if not is_int(self.num_tomo_stacks, 1):
100 self.num_tomo_stacks = None 102 self.num_tomo_stacks = None
101 msnc.illegal_value('num_tomo_stacks', self.num_tomo_stacks, 'config file') 103 illegal_value(self.num_tomo_stacks, 'num_tomo_stacks', 'config file')
102 return False 104 return False
103 logging.info(f'num_tomo_stacks = {self.num_tomo_stacks}') 105 logging.info(f'num_tomo_stacks = {self.num_tomo_stacks}')
104 106
105 # Find tomography images file/folders and stack parameters 107 # Find tomography images file/folders and stack parameters
106 tomo_data_paths_indices = sorted({key:value for key,value in self.config.items() 108 tomo_data_paths_indices = sorted({key:value for key,value in self.config.items()
107 if 'tomo_data_path' in key}.items()) 109 if 'tomo_data_path' in key}.items())
108 if len(tomo_data_paths_indices) != self.num_tomo_stacks: 110 if len(tomo_data_paths_indices) != self.num_tomo_stacks:
109 logging.error(f'Incorrect number of tomography data path names in config file') 111 logging.error(f'Incorrect number of tomography data path names in config file')
110 is_valid = False 112 is_valid = False
111 self.tomo_data_paths = [tomo_data_paths_indices[i][1] for i in range(self.num_tomo_stacks)] 113 self.tomo_data_paths = [tomo_data_paths_indices[i][1] for i in range(self.num_tomo_stacks)]
112 self.tomo_data_indices = [msnc.get_trailing_int(tomo_data_paths_indices[i][0]) 114 self.tomo_data_indices = [get_trailing_int(tomo_data_paths_indices[i][0])
113 if msnc.get_trailing_int(tomo_data_paths_indices[i][0]) else None 115 if get_trailing_int(tomo_data_paths_indices[i][0]) else None
114 for i in range(self.num_tomo_stacks)] 116 for i in range(self.num_tomo_stacks)]
115 tomo_ref_height_indices = sorted({key:value for key,value in self.config.items() 117 tomo_ref_height_indices = sorted({key:value for key,value in self.config.items()
116 if 'z_pos' in key}.items()) 118 if 'z_pos' in key}.items())
117 if self.num_tomo_stacks > 1 and len(tomo_ref_height_indices) != self.num_tomo_stacks: 119 if self.num_tomo_stacks > 1 and len(tomo_ref_height_indices) != self.num_tomo_stacks:
118 logging.error(f'Incorrect number of tomography reference heights in config file') 120 logging.error(f'Incorrect number of tomography reference heights in config file')
123 else: 125 else:
124 self.tomo_ref_heights = [0.0]*self.num_tomo_stacks 126 self.tomo_ref_heights = [0.0]*self.num_tomo_stacks
125 127
126 # Check tomo angle (theta) range 128 # Check tomo angle (theta) range
127 self.start_theta = self.config.get('start_theta', 0.) 129 self.start_theta = self.config.get('start_theta', 0.)
128 if not msnc.is_num(self.start_theta, 0.): 130 if not is_num(self.start_theta, 0.):
129 msnc.illegal_value('start_theta', self.start_theta, 'config file') 131 illegal_value(self.start_theta, 'start_theta', 'config file')
130 is_valid = False 132 is_valid = False
131 logging.debug(f'start_theta = {self.start_theta}') 133 logging.debug(f'start_theta = {self.start_theta}')
132 self.end_theta = self.config.get('end_theta', 180.) 134 self.end_theta = self.config.get('end_theta', 180.)
133 if not msnc.is_num(self.end_theta, self.start_theta): 135 if not is_num(self.end_theta, self.start_theta):
134 msnc.illegal_value('end_theta', self.end_theta, 'config file') 136 illegal_value(self.end_theta, 'end_theta', 'config file')
135 is_valid = False 137 is_valid = False
136 logging.debug(f'end_theta = {self.end_theta}') 138 logging.debug(f'end_theta = {self.end_theta}')
137 self.num_thetas = self.config.get('num_thetas') 139 self.num_thetas = self.config.get('num_thetas')
138 if not (self.num_thetas is None or msnc.is_int(self.num_thetas, 1)): 140 if not (self.num_thetas is None or is_int(self.num_thetas, 1)):
139 msnc.illegal_value('num_thetas', self.num_thetas, 'config file') 141 illegal_value(self.num_thetas, 'num_thetas', 'config file')
140 self.num_thetas = None 142 self.num_thetas = None
141 is_valid = False 143 is_valid = False
142 logging.debug(f'num_thetas = {self.num_thetas}') 144 logging.debug(f'num_thetas = {self.num_thetas}')
143 145
144 return is_valid 146 return is_valid
163 self.tbf_data_path = self.config['bright_field'].get('data_path') 165 self.tbf_data_path = self.config['bright_field'].get('data_path')
164 166
165 # Check number of tomography image stacks 167 # Check number of tomography image stacks
166 stack_info = self.config['stack_info'] 168 stack_info = self.config['stack_info']
167 self.num_tomo_stacks = stack_info.get('num', 1) 169 self.num_tomo_stacks = stack_info.get('num', 1)
168 if not msnc.is_int(self.num_tomo_stacks, 1): 170 if not is_int(self.num_tomo_stacks, 1):
169 self.num_tomo_stacks = None 171 self.num_tomo_stacks = None
170 msnc.illegal_value('stack_info:num', self.num_tomo_stacks, 'config file') 172 illegal_value(self.num_tomo_stacks, 'num_tomo_stacks', 'config file')
171 return False 173 return False
172 logging.info(f'num_tomo_stacks = {self.num_tomo_stacks}') 174 logging.info(f'num_tomo_stacks = {self.num_tomo_stacks}')
173 175
174 # Find tomography images file/folders and stack parameters 176 # Find tomography images file/folders and stack parameters
175 stacks = stack_info.get('stacks') 177 stacks = stack_info.get('stacks')
176 if stacks is None or len(stacks) is not self.num_tomo_stacks: 178 if stacks is None or len(stacks) is not self.num_tomo_stacks:
177 msnc.illegal_value('stack_info:stacks', stacks, 'config file') 179 illegal_value(stacks, 'stacks', 'config file')
178 return False 180 return False
179 self.tomo_data_paths = [] 181 self.tomo_data_paths = []
180 self.tomo_data_indices = [] 182 self.tomo_data_indices = []
181 self.tomo_ref_heights = [] 183 self.tomo_ref_heights = []
182 for stack in stacks: 184 for stack in stacks:
190 self.start_theta = 0. 192 self.start_theta = 0.
191 self.end_theta = 180. 193 self.end_theta = 180.
192 self.num_thetas = None 194 self.num_thetas = None
193 else: 195 else:
194 self.start_theta = theta_range.get('start', 0.) 196 self.start_theta = theta_range.get('start', 0.)
195 if not msnc.is_num(self.start_theta, 0.): 197 if not is_num(self.start_theta, 0.):
196 msnc.illegal_value('theta_range:start', self.start_theta, 'config file') 198 illegal_value(self.start_theta, 'theta_range:start', 'config file')
197 is_valid = False 199 is_valid = False
198 logging.debug(f'start_theta = {self.start_theta}') 200 logging.debug(f'start_theta = {self.start_theta}')
199 self.end_theta = theta_range.get('end', 180.) 201 self.end_theta = theta_range.get('end', 180.)
200 if not msnc.is_num(self.end_theta, self.start_theta): 202 if not is_num(self.end_theta, self.start_theta):
201 msnc.illegal_value('theta_range:end', self.end_theta, 'config file') 203 illegal_value(self.end_theta, 'theta_range:end', 'config file')
202 is_valid = False 204 is_valid = False
203 logging.debug(f'end_theta = {self.end_theta}') 205 logging.debug(f'end_theta = {self.end_theta}')
204 self.num_thetas = theta_range.get('num') 206 self.num_thetas = theta_range.get('num')
205 if self.num_thetas and not msnc.is_int(self.num_thetas, 1): 207 if self.num_thetas and not is_int(self.num_thetas, 1):
206 msnc.illegal_value('theta_range:num', self.num_thetas, 'config file') 208 illegal_value(self.num_thetas, 'theta_range:num', 'config file')
207 self.num_thetas = None 209 self.num_thetas = None
208 is_valid = False 210 is_valid = False
209 logging.debug(f'num_thetas = {self.num_thetas}') 211 logging.debug(f'num_thetas = {self.num_thetas}')
210 212
211 return is_valid 213 return is_valid
216 is_valid = True 218 is_valid = True
217 219
218 # Check work_folder (shared by both file formats) 220 # Check work_folder (shared by both file formats)
219 work_folder = os.path.abspath(self.config.get('work_folder', '')) 221 work_folder = os.path.abspath(self.config.get('work_folder', ''))
220 if not os.path.isdir(work_folder): 222 if not os.path.isdir(work_folder):
221 msnc.illegal_value('work_folder', work_folder, 'config file') 223 illegal_value(work_folder, 'work_folder', 'config file')
222 is_valid = False 224 is_valid = False
223 logging.info(f'work_folder: {work_folder}') 225 logging.info(f'work_folder: {work_folder}')
224 226
225 # Check data filetype (shared by both file formats) 227 # Check data filetype (shared by both file formats)
226 self.data_filetype = self.config.get('data_filetype', 'tif') 228 self.data_filetype = self.config.get('data_filetype', 'tif')
227 if not isinstance(self.data_filetype, str) or (self.data_filetype != 'tif' and 229 if not isinstance(self.data_filetype, str) or (self.data_filetype != 'tif' and
228 self.data_filetype != 'h5'): 230 self.data_filetype != 'h5'):
229 msnc.illegal_value('data_filetype', self.data_filetype, 'config file') 231 illegal_value(self.data_filetype, 'data_filetype', 'config file')
230 232
231 if self.suffix == '.yml' or self.suffix == '.yaml': 233 if self.suffix == '.yml' or self.suffix == '.yaml':
232 is_valid = self._validate_yaml() 234 is_valid = self._validate_yaml()
233 elif self.suffix == '.txt': 235 elif self.suffix == '.txt':
234 is_valid = self._validate_txt() 236 is_valid = self._validate_txt()
242 if isinstance(self.tdf_data_path, str): 244 if isinstance(self.tdf_data_path, str):
243 if not os.path.isabs(self.tdf_data_path): 245 if not os.path.isabs(self.tdf_data_path):
244 self.tdf_data_path = os.path.abspath( 246 self.tdf_data_path = os.path.abspath(
245 f'{work_folder}/{self.tdf_data_path}') 247 f'{work_folder}/{self.tdf_data_path}')
246 else: 248 else:
247 msnc.illegal_value('tdf_data_path', tdf_data_fil, 'config file') 249 illegal_value(tdf_data_fil, 'tdf_data_path', 'config file')
248 is_valid = False 250 is_valid = False
249 else: 251 else:
250 if isinstance(self.tdf_data_path, int): 252 if isinstance(self.tdf_data_path, int):
251 self.tdf_data_path = os.path.abspath( 253 self.tdf_data_path = os.path.abspath(
252 f'{work_folder}/{self.tdf_data_path}/nf') 254 f'{work_folder}/{self.tdf_data_path}/nf')
253 elif isinstance(self.tdf_data_path, str): 255 elif isinstance(self.tdf_data_path, str):
254 if not os.path.isabs(self.tdf_data_path): 256 if not os.path.isabs(self.tdf_data_path):
255 self.tdf_data_path = os.path.abspath( 257 self.tdf_data_path = os.path.abspath(
256 f'{work_folder}/{self.tdf_data_path}') 258 f'{work_folder}/{self.tdf_data_path}')
257 else: 259 else:
258 msnc.illegal_value('tdf_data_path', self.tdf_data_path, 'config file') 260 illegal_value(self.tdf_data_path, 'tdf_data_path', 'config file')
259 is_valid = False 261 is_valid = False
260 logging.info(f'dark field images path = {self.tdf_data_path}') 262 logging.info(f'dark field images path = {self.tdf_data_path}')
261 263
262 # Find tomography bright field images file/folder 264 # Find tomography bright field images file/folder
263 if self.tbf_data_path: 265 if self.tbf_data_path:
265 if isinstance(self.tbf_data_path, str): 267 if isinstance(self.tbf_data_path, str):
266 if not os.path.isabs(self.tbf_data_path): 268 if not os.path.isabs(self.tbf_data_path):
267 self.tbf_data_path = os.path.abspath( 269 self.tbf_data_path = os.path.abspath(
268 f'{work_folder}/{self.tbf_data_path}') 270 f'{work_folder}/{self.tbf_data_path}')
269 else: 271 else:
270 msnc.illegal_value('tbf_data_path', tbf_data_fil, 'config file') 272 illegal_value(tbf_data_fil, 'tbf_data_path', 'config file')
271 is_valid = False 273 is_valid = False
272 else: 274 else:
273 if isinstance(self.tbf_data_path, int): 275 if isinstance(self.tbf_data_path, int):
274 self.tbf_data_path = os.path.abspath( 276 self.tbf_data_path = os.path.abspath(
275 f'{work_folder}/{self.tbf_data_path}/nf') 277 f'{work_folder}/{self.tbf_data_path}/nf')
276 elif isinstance(self.tbf_data_path, str): 278 elif isinstance(self.tbf_data_path, str):
277 if not os.path.isabs(self.tbf_data_path): 279 if not os.path.isabs(self.tbf_data_path):
278 self.tbf_data_path = os.path.abspath( 280 self.tbf_data_path = os.path.abspath(
279 f'{work_folder}/{self.tbf_data_path}') 281 f'{work_folder}/{self.tbf_data_path}')
280 else: 282 else:
281 msnc.illegal_value('tbf_data_path', self.tbf_data_path, 'config file') 283 illegal_value(self.tbf_data_path, 'tbf_data_path', 'config file')
282 is_valid = False 284 is_valid = False
283 logging.info(f'bright field images path = {self.tbf_data_path}') 285 logging.info(f'bright field images path = {self.tbf_data_path}')
284 286
285 # Find tomography images file/folders and stack parameters 287 # Find tomography images file/folders and stack parameters
286 tomo_data_paths = [] 288 tomo_data_paths = []
291 if self.data_filetype == 'h5': 293 if self.data_filetype == 'h5':
292 if isinstance(data_path, str): 294 if isinstance(data_path, str):
293 if not os.path.isabs(data_path): 295 if not os.path.isabs(data_path):
294 data_path = os.path.abspath(f'{work_folder}/{data_path}') 296 data_path = os.path.abspath(f'{work_folder}/{data_path}')
295 else: 297 else:
296 msnc.illegal_value(f'stack_info:stacks:data_path', data_path, 'config file') 298 illegal_value(data_path, 'stack_info:stacks:data_path', 'config file')
297 is_valid = False 299 is_valid = False
298 data_path = None 300 data_path = None
299 else: 301 else:
300 if isinstance(data_path, int): 302 if isinstance(data_path, int):
301 data_path = os.path.abspath(f'{work_folder}/{data_path}/nf') 303 data_path = os.path.abspath(f'{work_folder}/{data_path}/nf')
302 elif isinstance(data_path, str): 304 elif isinstance(data_path, str):
303 if not os.path.isabs(data_path): 305 if not os.path.isabs(data_path):
304 data_path = os.path.abspath(f'{work_folder}/{data_path}') 306 data_path = os.path.abspath(f'{work_folder}/{data_path}')
305 else: 307 else:
306 msnc.illegal_value(f'stack_info:stacks:data_path', data_path, 'config file') 308 illegal_value(data_path, 'stack_info:stacks:data_path', 'config file')
307 is_valid = False 309 is_valid = False
308 data_path = None 310 data_path = None
309 tomo_data_paths.append(data_path) 311 tomo_data_paths.append(data_path)
310 if index is None: 312 if index is None:
311 if self.num_tomo_stacks > 1: 313 if self.num_tomo_stacks > 1:
313 is_valid = False 315 is_valid = False
314 index = None 316 index = None
315 else: 317 else:
316 index = 1 318 index = 1
317 elif not isinstance(index, int): 319 elif not isinstance(index, int):
318 msnc.illegal_value(f'stack_info:stacks:index', index, 'config file') 320 illegal_value(index, 'stack_info:stacks:index', 'config file')
319 is_valid = False 321 is_valid = False
320 index = None 322 index = None
321 tomo_data_indices.append(index) 323 tomo_data_indices.append(index)
322 if ref_height is None: 324 if ref_height is None:
323 if self.num_tomo_stacks > 1: 325 if self.num_tomo_stacks > 1:
324 logging.error('Missing stack_info:stacks:ref_height in config file') 326 logging.error('Missing stack_info:stacks:ref_height in config file')
325 is_valid = False 327 is_valid = False
326 ref_height = None 328 ref_height = None
327 else: 329 else:
328 ref_height = 0. 330 ref_height = 0.
329 elif not msnc.is_num(ref_height): 331 elif not is_num(ref_height):
330 msnc.illegal_value(f'stack_info:stacks:ref_height', ref_height, 'config file') 332 illegal_value(ref_height, 'stack_info:stacks:ref_height', 'config file')
331 is_valid = False 333 is_valid = False
332 ref_height = None 334 ref_height = None
333 # Set reference heights relative to first stack 335 # Set reference heights relative to first stack
334 if (len(tomo_ref_heights) and msnc.is_num(ref_height) and 336 if (len(tomo_ref_heights) and is_num(ref_height) and
335 msnc.is_num(tomo_ref_heights[0])): 337 is_num(tomo_ref_heights[0])):
336 ref_height = (round(ref_height-tomo_ref_heights[0], 3)) 338 ref_height = (round(ref_height-tomo_ref_heights[0], 3))
337 tomo_ref_heights.append(ref_height) 339 tomo_ref_heights.append(ref_height)
338 tomo_ref_heights[0] = 0.0 340 tomo_ref_heights[0] = 0.0
339 logging.info('tomography data paths:') 341 logging.info('tomography data paths:')
340 for i in range(self.num_tomo_stacks): 342 for i in range(self.num_tomo_stacks):
434 if path and not os.path.isdir(path): 436 if path and not os.path.isdir(path):
435 raise OSError(f'Invalid log_stream path') 437 raise OSError(f'Invalid log_stream path')
436 else: 438 else:
437 raise OSError(f'Invalid config_out input {config_out} {type(config_out)}') 439 raise OSError(f'Invalid config_out input {config_out} {type(config_out)}')
438 if not os.path.isdir(output_folder): 440 if not os.path.isdir(output_folder):
439 raise OSError(f'Invalid output_folder input {output_folder} {type(output_folder)}') 441 os.mkdir(os.path.abspath(output_folder))
440 if isinstance(log_stream, str): 442 if isinstance(log_stream, str):
441 path = os.path.split(log_stream)[0] 443 path = os.path.split(log_stream)[0]
442 if path and not os.path.isdir(path): 444 if path and not os.path.isdir(path):
443 raise OSError(f'Invalid log_stream path') 445 raise OSError(f'Invalid log_stream path')
444 if not os.path.isabs(path): 446 if not os.path.isabs(path):
513 515
514 # Check config file parameters 516 # Check config file parameters
515 self.is_valid = self.cf.validate() 517 self.is_valid = self.cf.validate()
516 518
517 # Load detector info file 519 # Load detector info file
518 df = msnc.Detector(self.cf.config['detector']['id']) 520 df = TomoDetectorConfig(self.cf.config['detector']['id'])
519 521
520 # Check detector info file parameters 522 # Check detector info file parameters
521 if df.validate(): 523 if df.valid:
522 pixel_size = df.getPixelSize() 524 pixel_size = df.pixel_size
523 num_rows, num_columns = df.getDimensions() 525 num_rows, num_columns = df.dimensions
524 if not pixel_size or not num_rows or not num_columns: 526 if not pixel_size or not num_rows or not num_columns:
525 self.is_valid = False 527 self.is_valid = False
526 else: 528 else:
527 pixel_size = None 529 pixel_size = None
528 num_rows = None 530 num_rows = None
575 available_stacks = [False]*stack_info['num'] 577 available_stacks = [False]*stack_info['num']
576 578
577 # Check number of tomography angles/thetas 579 # Check number of tomography angles/thetas
578 num_thetas = self.config['theta_range'].get('num') 580 num_thetas = self.config['theta_range'].get('num')
579 if num_thetas is None: 581 if num_thetas is None:
580 num_thetas = pyip.inputInt('\nEnter the number of thetas (>0): ', greaterThan=0) 582 num_thetas = input_int('\nEnter the number of thetas', 1)
581 elif not msnc.is_int(num_thetas, 0): 583 elif not is_int(num_thetas, 0):
582 msnc.illegal_value('num_thetas', num_thetas, 'config file') 584 illegal_value(num_thetas, 'num_thetas', 'config file')
583 self.is_valid = False 585 self.is_valid = False
584 return 586 return
585 self.config['theta_range']['num'] = num_thetas 587 self.config['theta_range']['num'] = num_thetas
586 logging.debug(f'num_thetas = {self.config["theta_range"]["num"]}') 588 logging.debug(f'num_thetas = {self.config["theta_range"]["num"]}')
587 589
589 dark_field = self.config['dark_field'] 591 dark_field = self.config['dark_field']
590 img_start = dark_field.get('img_start', -1) 592 img_start = dark_field.get('img_start', -1)
591 img_offset = dark_field.get('img_offset', -1) 593 img_offset = dark_field.get('img_offset', -1)
592 num_imgs = dark_field.get('num', 0) 594 num_imgs = dark_field.get('num', 0)
593 if not self.test_mode: 595 if not self.test_mode:
594 img_start, img_offset, num_imgs = msnc.selectImageRange(img_start, img_offset, 596 img_start, img_offset, num_imgs = selectImageRange(img_start, img_offset,
595 num_imgs, 'dark field') 597 num_imgs, 'dark field')
596 if img_start < 0 or num_imgs < 1: 598 if img_start < 0 or num_imgs < 1:
597 logging.error('Unable to find suitable dark field images') 599 logging.error('Unable to find suitable dark field images')
598 if dark_field['data_path']: 600 if dark_field['data_path']:
599 self.is_valid = False 601 self.is_valid = False
608 bright_field = self.config['bright_field'] 610 bright_field = self.config['bright_field']
609 img_start = bright_field.get('img_start', -1) 611 img_start = bright_field.get('img_start', -1)
610 img_offset = bright_field.get('img_offset', -1) 612 img_offset = bright_field.get('img_offset', -1)
611 num_imgs = bright_field.get('num', 0) 613 num_imgs = bright_field.get('num', 0)
612 if not self.test_mode: 614 if not self.test_mode:
613 img_start, img_offset, num_imgs = msnc.selectImageRange(img_start, img_offset, 615 img_start, img_offset, num_imgs = selectImageRange(img_start, img_offset,
614 num_imgs, 'bright field') 616 num_imgs, 'bright field')
615 if img_start < 0 or num_imgs < 1: 617 if img_start < 0 or num_imgs < 1:
616 logging.error('Unable to find suitable bright field images') 618 logging.error('Unable to find suitable bright field images')
617 self.is_valid = False 619 self.is_valid = False
618 bright_field['img_start'] = img_start 620 bright_field['img_start'] = img_start
630 index = stack['index'] 632 index = stack['index']
631 img_start = stack.get('img_start', -1) 633 img_start = stack.get('img_start', -1)
632 img_offset = stack.get('img_offset', -1) 634 img_offset = stack.get('img_offset', -1)
633 num_imgs = stack.get('num', 0) 635 num_imgs = stack.get('num', 0)
634 if not self.test_mode: 636 if not self.test_mode:
635 img_start, img_offset, num_imgs = msnc.selectImageRange(img_start, img_offset, 637 img_start, img_offset, num_imgs = selectImageRange(img_start, img_offset,
636 num_imgs, f'tomography stack {index}', num_thetas) 638 num_imgs, f'tomography stack {index}', num_thetas)
637 if img_start < 0 or num_imgs != num_thetas: 639 if img_start < 0 or num_imgs != num_thetas:
638 logging.error('Unable to find suitable tomography images') 640 logging.error('Unable to find suitable tomography images')
639 self.is_valid = False 641 self.is_valid = False
640 stack['img_start'] = img_start 642 stack['img_start'] = img_start
654 """Generate dark field. 656 """Generate dark field.
655 """ 657 """
656 # Load the dark field images 658 # Load the dark field images
657 logging.debug('Loading dark field...') 659 logging.debug('Loading dark field...')
658 dark_field = self.config['dark_field'] 660 dark_field = self.config['dark_field']
659 tdf_stack = msnc.loadImageStack(tdf_files, self.config['data_filetype'], 661 tdf_stack = loadImageStack(tdf_files, self.config['data_filetype'],
660 dark_field['img_offset'], dark_field['num']) 662 dark_field['img_offset'], dark_field['num'])
661 663
662 # Take median 664 # Take median
663 self.tdf = np.median(tdf_stack, axis=0) 665 self.tdf = np.median(tdf_stack, axis=0)
664 del tdf_stack 666 del tdf_stack
669 tdf_mean = np.nanmean(self.tdf) 671 tdf_mean = np.nanmean(self.tdf)
670 logging.debug(f'tdf_cutoff = {tdf_cutoff}') 672 logging.debug(f'tdf_cutoff = {tdf_cutoff}')
671 logging.debug(f'tdf_mean = {tdf_mean}') 673 logging.debug(f'tdf_mean = {tdf_mean}')
672 np.nan_to_num(self.tdf, copy=False, nan=tdf_mean, posinf=tdf_mean, neginf=0.) 674 np.nan_to_num(self.tdf, copy=False, nan=tdf_mean, posinf=tdf_mean, neginf=0.)
673 if self.galaxy_flag: 675 if self.galaxy_flag:
674 msnc.quickImshow(self.tdf, title='dark field', path='setup_pngs', 676 quickImshow(self.tdf, title='dark field', path='setup_pngs',
675 save_fig=True, save_only=True) 677 save_fig=True, save_only=True)
676 elif not self.test_mode: 678 elif not self.test_mode:
677 msnc.quickImshow(self.tdf, title='dark field', path=self.output_folder, 679 quickImshow(self.tdf, title='dark field', path=self.output_folder,
678 save_fig=self.save_plots, save_only=self.save_plots_only) 680 save_fig=self.save_plots, save_only=self.save_plots_only)
679 681
680 def _genBright(self, tbf_files): 682 def _genBright(self, tbf_files):
681 """Generate bright field. 683 """Generate bright field.
682 """ 684 """
683 # Load the bright field images 685 # Load the bright field images
684 logging.debug('Loading bright field...') 686 logging.debug('Loading bright field...')
685 bright_field = self.config['bright_field'] 687 bright_field = self.config['bright_field']
686 tbf_stack = msnc.loadImageStack(tbf_files, self.config['data_filetype'], 688 tbf_stack = loadImageStack(tbf_files, self.config['data_filetype'],
687 bright_field['img_offset'], bright_field['num']) 689 bright_field['img_offset'], bright_field['num'])
688 690
689 # Take median 691 # Take median
690 """Median or mean: It may be best to try the median because of some image 692 """Median or mean: It may be best to try the median because of some image
691 artifacts that arise due to crinkles in the upstream kapton tape windows 693 artifacts that arise due to crinkles in the upstream kapton tape windows
703 if self.tdf.size: 705 if self.tdf.size:
704 self.tbf -= self.tdf 706 self.tbf -= self.tdf
705 else: 707 else:
706 logging.warning('Dark field unavailable') 708 logging.warning('Dark field unavailable')
707 if self.galaxy_flag: 709 if self.galaxy_flag:
708 msnc.quickImshow(self.tbf, title='bright field', path='setup_pngs', 710 quickImshow(self.tbf, title='bright field', path='setup_pngs',
709 save_fig=True, save_only=True) 711 save_fig=True, save_only=True)
710 elif not self.test_mode: 712 elif not self.test_mode:
711 msnc.quickImshow(self.tbf, title='bright field', path=self.output_folder, 713 quickImshow(self.tbf, title='bright field', path=self.output_folder,
712 save_fig=self.save_plots, save_only=self.save_plots_only) 714 save_fig=self.save_plots, save_only=self.save_plots_only)
713 715
714 def _setDetectorBounds(self, tomo_stack_files): 716 def _setDetectorBounds(self, tomo_stack_files):
715 """Set vertical detector bounds for image stack. 717 """Set vertical detector bounds for image stack.
716 """ 718 """
719 img_x_bounds = [None, None] 721 img_x_bounds = [None, None]
720 else: 722 else:
721 img_x_bounds = preprocess.get('img_x_bounds', [0, self.tbf.shape[0]]) 723 img_x_bounds = preprocess.get('img_x_bounds', [0, self.tbf.shape[0]])
722 if img_x_bounds[0] is not None and img_x_bounds[1] is not None: 724 if img_x_bounds[0] is not None and img_x_bounds[1] is not None:
723 if img_x_bounds[0] < 0: 725 if img_x_bounds[0] < 0:
724 msnc.illegal_value('img_x_bounds[0]', img_x_bounds[0], 'config file') 726 illegal_value(img_x_bounds[0], 'preprocess:img_x_bounds[0]', 'config file')
725 img_x_bounds[0] = 0 727 img_x_bounds[0] = 0
726 if not msnc.is_index_range(img_x_bounds, 0, self.tbf.shape[0]): 728 if not is_index_range(img_x_bounds, 0, self.tbf.shape[0]):
727 msnc.illegal_value('img_x_bounds[1]', img_x_bounds[1], 'config file') 729 illegal_value(img_x_bounds[1], 'preprocess:img_x_bounds[1]', 'config file')
728 img_x_bounds[1] = self.tbf.shape[0] 730 img_x_bounds[1] = self.tbf.shape[0]
729 if self.test_mode: 731 if self.test_mode:
730 # Update config and save to file 732 # Update config and save to file
731 if preprocess is None: 733 if preprocess is None:
732 self.cf.config['preprocess'] = {'img_x_bounds' : [0, self.tbf.shape[0]]} 734 self.cf.config['preprocess'] = {'img_x_bounds' : [0, self.tbf.shape[0]]}
761 x_sum_min = x_sum.min() 763 x_sum_min = x_sum.min()
762 x_sum_max = x_sum.max() 764 x_sum_max = x_sum.max()
763 x_low = 0 765 x_low = 0
764 x_upp = x_sum.size 766 x_upp = x_sum.size
765 if num_x_min is not None: 767 if num_x_min is not None:
766 fit = msnc.fitStep(y=x_sum, model='rectangle', form='atan') 768 fit = Fit.fit_data(np.array(range(len(x_sum))), x_sum, models='rectangle',
767 x_low = fit.get('center1', None) 769 form='atan', guess=True)
768 x_upp = fit.get('center2', None) 770 parameters = fit.best_values
769 sig_low = fit.get('sigma1', None) 771 x_low = parameters.get('center1', None)
770 sig_upp = fit.get('sigma2', None) 772 x_upp = parameters.get('center2', None)
773 sig_low = parameters.get('sigma1', None)
774 sig_upp = parameters.get('sigma2', None)
771 if (x_low is not None and x_upp is not None and sig_low is not None and 775 if (x_low is not None and x_upp is not None and sig_low is not None and
772 sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and 776 sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and
773 (sig_low+sig_upp)/(x_upp-x_low) < 0.1): 777 (sig_low+sig_upp)/(x_upp-x_low) < 0.1):
774 if num_tomo_stacks == 1 or num_x_min is None: 778 if num_tomo_stacks == 1 or num_x_min is None:
775 x_low = int(x_low-(x_upp-x_low)/10) 779 x_low = int(x_low-(x_upp-x_low)/10)
782 if x_upp > x_sum.size: 786 if x_upp > x_sum.size:
783 x_upp = x_sum.size 787 x_upp = x_sum.size
784 else: 788 else:
785 x_low = 0 789 x_low = 0
786 x_upp = x_sum.size 790 x_upp = x_sum.size
787 msnc.quickPlot((range(x_sum.size), x_sum), 791 quickPlot((range(x_sum.size), x_sum),
788 ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'), 792 ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'),
789 ([x_upp-1, x_upp-1], [x_sum_min, x_sum_max], 'r-'), 793 ([x_upp-1, x_upp-1], [x_sum_min, x_sum_max], 'r-'),
790 title=f'sum bright field over theta/y (row bounds: [{x_low}, {x_upp}])', 794 title=f'sum bright field over theta/y (row bounds: [{x_low}, {x_upp}])',
791 path='setup_pngs', name='detectorbounds.png', save_fig=True, save_only=True, 795 path='setup_pngs', name='detectorbounds.png', save_fig=True, save_only=True,
792 show_grid=True) 796 show_grid=True)
793 for i,stack in enumerate(stacks): 797 for i,stack in enumerate(stacks):
794 tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'], 798 tomo_stack = loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
795 stack['img_offset'], 1) 799 stack['img_offset'], 1)
796 tomo_stack = tomo_stack[0,:,:] 800 tomo_stack = tomo_stack[0,:,:]
797 if num_x_min is not None: 801 if num_x_min is not None:
798 tomo_stack_max = tomo_stack.max() 802 tomo_stack_max = tomo_stack.max()
799 tomo_stack[x_low,:] = tomo_stack_max 803 tomo_stack[x_low,:] = tomo_stack_max
800 tomo_stack[x_upp-1,:] = tomo_stack_max 804 tomo_stack[x_upp-1,:] = tomo_stack_max
801 title = f'tomography image at theta={self.config["theta_range"]["start"]}' 805 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
802 msnc.quickImshow(tomo_stack, title=title, path='setup_pngs', 806 quickImshow(tomo_stack, title=title, path='setup_pngs',
803 name=f'tomo_{stack["index"]}.png', save_fig=True, save_only=True, 807 name=f'tomo_{stack["index"]}.png', save_fig=True, save_only=True,
804 show_grid=True) 808 show_grid=True)
805 del tomo_stack 809 del tomo_stack
806 810
807 # Update config and save to file 811 # Update config and save to file
815 del x_sum 819 del x_sum
816 return 820 return
817 821
818 # For one tomography stack only: load the first image 822 # For one tomography stack only: load the first image
819 title = None 823 title = None
820 msnc.quickImshow(self.tbf, title='bright field') 824 quickImshow(self.tbf, title='bright field')
821 if num_tomo_stacks == 1: 825 if num_tomo_stacks == 1:
822 tomo_stack = msnc.loadImageStack(tomo_stack_files[0], self.config['data_filetype'], 826 tomo_stack = loadImageStack(tomo_stack_files[0], self.config['data_filetype'],
823 stacks[0]['img_offset'], 1) 827 stacks[0]['img_offset'], 1)
824 title = f'tomography image at theta={self.config["theta_range"]["start"]}' 828 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
825 msnc.quickImshow(tomo_stack[0,:,:], title=title) 829 quickImshow(tomo_stack[0,:,:], title=title)
826 tomo_or_bright = pyip.inputNum('\nSelect image bounds from bright field (0) or '+ 830 tomo_or_bright = input_menu(['bright field', 'first tomography image'],
827 'from first tomography image (1): ', min=0, max=1) 831 header='\nSelect image bounds from')
828 else: 832 else:
829 print('\nSelect image bounds from bright field') 833 print('\nSelect image bounds from bright field')
830 tomo_or_bright = 0 834 tomo_or_bright = 0
831 if tomo_or_bright: 835 if tomo_or_bright:
832 x_sum = np.sum(tomo_stack[0,:,:], 1) 836 x_sum = np.sum(tomo_stack[0,:,:], 1)
833 use_bounds = 'no' 837 use_bounds = False
834 if img_x_bounds[0] is not None and img_x_bounds[1] is not None: 838 if img_x_bounds[0] is not None and img_x_bounds[1] is not None:
835 tmp = np.copy(tomo_stack[0,:,:]) 839 tmp = np.copy(tomo_stack[0,:,:])
836 tmp_max = tmp.max() 840 tmp_max = tmp.max()
837 tmp[img_x_bounds[0],:] = tmp_max 841 tmp[img_x_bounds[0],:] = tmp_max
838 tmp[img_x_bounds[1]-1,:] = tmp_max 842 tmp[img_x_bounds[1]-1,:] = tmp_max
839 title = f'tomography image at theta={self.config["theta_range"]["start"]}' 843 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
840 msnc.quickImshow(tmp, title=title) 844 quickImshow(tmp, title=title)
841 del tmp 845 del tmp
842 x_sum_min = x_sum.min() 846 x_sum_min = x_sum.min()
843 x_sum_max = x_sum.max() 847 x_sum_max = x_sum.max()
844 msnc.quickPlot((range(x_sum.size), x_sum), 848 quickPlot((range(x_sum.size), x_sum),
845 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'), 849 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'),
846 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum_min, x_sum_max], 'r-'), 850 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum_min, x_sum_max], 'r-'),
847 title='sum over theta and y') 851 title='sum over theta and y')
848 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+ 852 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+
849 f'upper bound = {img_x_bounds[1]} (exclusive)]') 853 f'upper bound = {img_x_bounds[1]} (exclusive)]')
850 use_bounds = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) 854 use_bounds = input_yesno('Accept these bounds (y/n)?', 'y')
851 if use_bounds == 'no': 855 if not use_bounds:
852 img_x_bounds = msnc.selectImageBounds(tomo_stack[0,:,:], 0, 856 img_x_bounds = list(selectImageBounds(tomo_stack[0,:,:], 0,
853 img_x_bounds[0], img_x_bounds[1], num_x_min, 857 img_x_bounds[0], img_x_bounds[1], num_x_min,
854 f'tomography image at theta={self.config["theta_range"]["start"]}') 858 f'tomography image at theta={self.config["theta_range"]["start"]}'))
855 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min: 859 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min:
856 logging.warning('Image bounds and pixel size prevent seamless stacking') 860 logging.warning('Image bounds and pixel size prevent seamless stacking')
857 title = f'tomography image at theta={self.config["theta_range"]["start"]}' 861 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
858 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder, 862 quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder,
859 save_fig=self.save_plots, save_only=True) 863 save_fig=self.save_plots, save_only=True)
860 msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]), 864 quickPlot(range(img_x_bounds[0], img_x_bounds[1]),
861 x_sum[img_x_bounds[0]:img_x_bounds[1]], 865 x_sum[img_x_bounds[0]:img_x_bounds[1]],
862 title='sum over theta and y', path=self.output_folder, 866 title='sum over theta and y', path=self.output_folder,
863 save_fig=self.save_plots, save_only=True) 867 save_fig=self.save_plots, save_only=True)
864 else: 868 else:
865 x_sum = np.sum(self.tbf, 1) 869 x_sum = np.sum(self.tbf, 1)
866 x_sum_min = x_sum.min() 870 x_sum_min = x_sum.min()
867 x_sum_max = x_sum.max() 871 x_sum_max = x_sum.max()
868 use_bounds = 'no' 872 use_bounds = False
869 if img_x_bounds[0] is not None and img_x_bounds[1] is not None: 873 if img_x_bounds[0] is not None and img_x_bounds[1] is not None:
870 tmp = np.copy(self.tbf) 874 tmp = np.copy(self.tbf)
871 tmp_max = tmp.max() 875 tmp_max = tmp.max()
872 tmp[img_x_bounds[0],:] = tmp_max 876 tmp[img_x_bounds[0],:] = tmp_max
873 tmp[img_x_bounds[1]-1,:] = tmp_max 877 tmp[img_x_bounds[1]-1,:] = tmp_max
874 title = 'bright field' 878 title = 'bright field'
875 msnc.quickImshow(tmp, title=title) 879 quickImshow(tmp, title=title)
876 del tmp 880 del tmp
877 msnc.quickPlot((range(x_sum.size), x_sum), 881 quickPlot((range(x_sum.size), x_sum),
878 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'), 882 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'),
879 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum_min, x_sum_max], 'r-'), 883 ([img_x_bounds[1]-1, img_x_bounds[1]-1], [x_sum_min, x_sum_max], 'r-'),
880 title='sum over theta and y') 884 title='sum over theta and y')
881 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+ 885 print(f'lower bound = {img_x_bounds[0]} (inclusive)\n'+
882 f'upper bound = {img_x_bounds[1]} (exclusive)]') 886 f'upper bound = {img_x_bounds[1]} (exclusive)]')
883 use_bounds = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) 887 use_bounds = input_yesno('Accept these bounds (y/n)?', 'y')
884 if use_bounds == 'no': 888 if not use_bounds:
885 use_fit = 'no' 889 use_fit = False
886 fit = msnc.fitStep(y=x_sum, model='rectangle', form='atan') 890 fit = Fit.fit_data(np.array(range(len(x_sum))), x_sum, models='rectangle',
887 x_low = fit.get('center1', None) 891 form='atan', guess=True)
888 x_upp = fit.get('center2', None) 892 parameters = fit.best_values
889 sig_low = fit.get('sigma1', None) 893 x_low = parameters.get('center1', None)
890 sig_upp = fit.get('sigma2', None) 894 x_upp = parameters.get('center2', None)
895 sig_low = parameters.get('sigma1', None)
896 sig_upp = parameters.get('sigma2', None)
891 if (x_low is not None and x_upp is not None and sig_low is not None and 897 if (x_low is not None and x_upp is not None and sig_low is not None and
892 sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and 898 sig_upp is not None and 0 <= x_low < x_upp <= x_sum.size and
893 (sig_low+sig_upp)/(x_upp-x_low) < 0.1): 899 (sig_low+sig_upp)/(x_upp-x_low) < 0.1):
894 if num_tomo_stacks == 1 or num_x_min is None: 900 if num_tomo_stacks == 1 or num_x_min is None:
895 x_low = int(x_low-(x_upp-x_low)/10) 901 x_low = int(x_low-(x_upp-x_low)/10)
904 tmp = np.copy(self.tbf) 910 tmp = np.copy(self.tbf)
905 tmp_max = tmp.max() 911 tmp_max = tmp.max()
906 tmp[x_low,:] = tmp_max 912 tmp[x_low,:] = tmp_max
907 tmp[x_upp-1,:] = tmp_max 913 tmp[x_upp-1,:] = tmp_max
908 title = 'bright field' 914 title = 'bright field'
909 msnc.quickImshow(tmp, title=title) 915 quickImshow(tmp, title=title)
910 del tmp 916 del tmp
911 msnc.quickPlot((range(x_sum.size), x_sum), 917 quickPlot((range(x_sum.size), x_sum),
912 ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'), 918 ([x_low, x_low], [x_sum_min, x_sum_max], 'r-'),
913 ([x_upp, x_upp], [x_sum_min, x_sum_max], 'r-'), 919 ([x_upp, x_upp], [x_sum_min, x_sum_max], 'r-'),
914 title='sum over theta and y') 920 title='sum over theta and y')
915 print(f'lower bound = {x_low} (inclusive)') 921 print(f'lower bound = {x_low} (inclusive)')
916 print(f'upper bound = {x_upp} (exclusive)]') 922 print(f'upper bound = {x_upp} (exclusive)]')
917 use_fit = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) 923 use_fit = input_yesno('Accept these bounds (y/n)?', 'y')
918 if use_fit == 'no': 924 if use_fit:
919 img_x_bounds = msnc.selectArrayBounds(x_sum, img_x_bounds[0], img_x_bounds[1], 925 img_x_bounds = [x_low, x_upp]
920 num_x_min, 'sum over theta and y')
921 else: 926 else:
922 img_x_bounds = [x_low, x_upp] 927 accept = False
928 while not accept:
929 mask, img_x_bounds = draw_mask_1d(x_sum, title='select x data range',
930 legend='sum over theta and y')
931 print(f'img_x_bounds = {img_x_bounds}')
932 while (len(img_x_bounds) != 1 or (len(x_sum) >= num_x_min and
933 img_x_bounds[0][1]-img_x_bounds[0][0]+1 < num_x_min)):
934 exit('Should not be here')
935 print('Please select exactly one continuous range')
936 mask, img_x_bounds = draw_mask_1d(x_sum, title='select x data range',
937 legend='sum over theta and y')
938 img_x_bounds = list(img_x_bounds[0])
939 quickPlot(x_sum, vlines=img_x_bounds, title='sum over theta and y')
940 print(f'img_x_bounds = {img_x_bounds} (lower bound inclusive, upper bound '+
941 'exclusive)')
942 accept = input_yesno('Accept these bounds (y/n)?', 'y')
943 if not accept:
944 img_x_bounds = None
923 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min: 945 if num_x_min is not None and img_x_bounds[1]-img_x_bounds[0]+1 < num_x_min:
924 logging.warning('Image bounds and pixel size prevent seamless stacking') 946 logging.warning('Image bounds and pixel size prevent seamless stacking')
925 #msnc.quickPlot(range(img_x_bounds[0], img_x_bounds[1]), 947 #quickPlot(range(img_x_bounds[0], img_x_bounds[1]),
926 # x_sum[img_x_bounds[0]:img_x_bounds[1]], 948 # x_sum[img_x_bounds[0]:img_x_bounds[1]],
927 # title='sum over theta and y', path=self.output_folder, 949 # title='sum over theta and y', path=self.output_folder,
928 # save_fig=self.save_plots, save_only=True) 950 # save_fig=self.save_plots, save_only=True)
929 msnc.quickPlot((range(x_sum.size), x_sum), 951 quickPlot((range(x_sum.size), x_sum),
930 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'), 952 ([img_x_bounds[0], img_x_bounds[0]], [x_sum_min, x_sum_max], 'r-'),
931 ([img_x_bounds[1], img_x_bounds[1]], [x_sum_min, x_sum_max], 'r-'), 953 ([img_x_bounds[1], img_x_bounds[1]], [x_sum_min, x_sum_max], 'r-'),
932 title='sum over theta and y', path=self.output_folder, 954 title='sum over theta and y', path=self.output_folder,
933 save_fig=self.save_plots, save_only=True) 955 save_fig=self.save_plots, save_only=True)
934 del x_sum 956 del x_sum
935 for i,stack in enumerate(stacks): 957 for i,stack in enumerate(stacks):
936 tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'], 958 tomo_stack = loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
937 stack['img_offset'], 1) 959 stack['img_offset'], 1)
938 tomo_stack = tomo_stack[0,:,:] 960 tomo_stack = tomo_stack[0,:,:]
939 if num_x_min is not None: 961 if num_x_min is not None:
940 tomo_stack_max = tomo_stack.max() 962 tomo_stack_max = tomo_stack.max()
941 tomo_stack[img_x_bounds[0],:] = tomo_stack_max 963 tomo_stack[img_x_bounds[0],:] = tomo_stack_max
942 tomo_stack[img_x_bounds[1]-1,:] = tomo_stack_max 964 tomo_stack[img_x_bounds[1]-1,:] = tomo_stack_max
943 title = f'tomography image at theta={self.config["theta_range"]["start"]}' 965 title = f'tomography image at theta={self.config["theta_range"]["start"]}'
944 if self.galaxy_flag: 966 if self.galaxy_flag:
945 msnc.quickImshow(tomo_stack, title=title, path='setup_pngs', 967 quickImshow(tomo_stack, title=title, path='setup_pngs',
946 name=f'tomo_{stack["index"]}.png', save_fig=True, save_only=True, 968 name=f'tomo_{stack["index"]}.png', save_fig=True, save_only=True,
947 show_grid=True) 969 show_grid=True)
948 else: 970 else:
949 msnc.quickImshow(tomo_stack, title=title, path=self.output_folder, 971 quickImshow(tomo_stack, title=title, path=self.output_folder,
950 name=f'tomo_{stack["index"]}.png', save_fig=self.save_plots, 972 name=f'tomo_{stack["index"]}.png', save_fig=self.save_plots,
951 save_only=True, show_grid=True) 973 save_only=True, show_grid=True)
952 del tomo_stack 974 del tomo_stack
953 logging.debug(f'img_x_bounds: {img_x_bounds}') 975 logging.debug(f'img_x_bounds: {img_x_bounds}')
954 976
955 if self.save_plots_only: 977 if self.save_plots_only:
956 msnc.clearFig('bright field') 978 clearImshow('bright field')
957 msnc.clearFig('sum over theta and y') 979 clearPlot('sum over theta and y')
958 if title: 980 if title:
959 msnc.clearFig(title) 981 clearPlot(title)
960 982
961 # Update config and save to file 983 # Update config and save to file
962 if preprocess is None: 984 if preprocess is None:
963 self.cf.config['preprocess'] = {'img_x_bounds' : img_x_bounds} 985 self.cf.config['preprocess'] = {'img_x_bounds' : img_x_bounds}
964 else: 986 else:
970 """ 992 """
971 preprocess = self.config.get('preprocess') 993 preprocess = self.config.get('preprocess')
972 zoom_perc = 100 994 zoom_perc = 100
973 if not self.galaxy_flag: 995 if not self.galaxy_flag:
974 if preprocess is None or 'zoom_perc' not in preprocess: 996 if preprocess is None or 'zoom_perc' not in preprocess:
975 if pyip.inputYesNo( 997 if input_yesno('\nDo you want to zoom in to reduce memory requirement (y/n)?', 'n'):
976 '\nDo you want to zoom in to reduce memory requirement (y/[n])? ', 998 zoom_perc = input_int(' Enter zoom percentage', 1, 100)
977 blank=True) == 'yes':
978 zoom_perc = pyip.inputInt(' Enter zoom percentage [1, 100]: ',
979 min=1, max=100)
980 else: 999 else:
981 zoom_perc = preprocess['zoom_perc'] 1000 zoom_perc = preprocess['zoom_perc']
982 if msnc.is_num(zoom_perc, 1., 100.): 1001 if is_num(zoom_perc, 1., 100.):
983 zoom_perc = int(zoom_perc) 1002 zoom_perc = int(zoom_perc)
984 else: 1003 else:
985 msnc.illegal_value('zoom_perc', zoom_perc, 'config file') 1004 illegal_value(zoom_perc, 'preprocess:zoom_perc', 'config file')
986 zoom_perc = 100 1005 zoom_perc = 100
987 num_theta_skip = 0 1006 num_theta_skip = 0
988 if not self.galaxy_flag: 1007 if not self.galaxy_flag:
989 if preprocess is None or 'num_theta_skip' not in preprocess: 1008 if preprocess is None or 'num_theta_skip' not in preprocess:
990 if pyip.inputYesNo( 1009 if input_yesno('Do you want to skip thetas to reduce memory requirement (y/n)?',
991 'Do you want to skip thetas to reduce memory requirement (y/[n])? ', 1010 'n'):
992 blank=True) == 'yes': 1011 num_theta_skip = input_int(' Enter the number skip theta interval', 0,
993 num_theta_skip = pyip.inputInt(' Enter the number skip theta interval'+ 1012 self.num_thetas-1)
994 f' [0, {self.num_thetas-1}]: ', min=0, max=self.num_thetas-1)
995 else: 1013 else:
996 num_theta_skip = preprocess['num_theta_skip'] 1014 num_theta_skip = preprocess['num_theta_skip']
997 if not msnc.is_int(num_theta_skip, 0): 1015 if not is_int(num_theta_skip, 0):
998 msnc.illegal_value('num_theta_skip', num_theta_skip, 'config file') 1016 illegal_value(num_theta_skip, 'preprocess:num_theta_skip', 'config file')
999 num_theta_skip = 0 1017 num_theta_skip = 0
1000 logging.debug(f'zoom_perc = {zoom_perc}') 1018 logging.debug(f'zoom_perc = {zoom_perc}')
1001 logging.debug(f'num_theta_skip = {num_theta_skip}') 1019 logging.debug(f'num_theta_skip = {num_theta_skip}')
1002 1020
1003 # Update config and save to file 1021 # Update config and save to file
1021 title = f'{base_name} fullres' 1039 title = f'{base_name} fullres'
1022 else: 1040 else:
1023 title = f'{base_name} {zoom_perc}p' 1041 title = f'{base_name} {zoom_perc}p'
1024 title += f'_{index}' 1042 title += f'_{index}'
1025 tomo_file = re.sub(r"\s+", '_', f'{self.output_folder}/{title}.npy') 1043 tomo_file = re.sub(r"\s+", '_', f'{self.output_folder}/{title}.npy')
1026 load_flag = 'no' 1044 load_flag = False
1027 available = False 1045 available = False
1028 if os.path.isfile(tomo_file): 1046 if os.path.isfile(tomo_file):
1029 available = True 1047 available = True
1030 if required: 1048 if required:
1031 load_flag = 'yes' 1049 load_flag = True
1032 else: 1050 else:
1033 load_flag = pyip.inputYesNo(f'\nDo you want to load {tomo_file} (y/n)? ') 1051 load_flag = input_yesno(f'\nDo you want to load {tomo_file} (y/n)?')
1034 stack = np.array([]) 1052 stack = np.array([])
1035 if load_flag == 'yes': 1053 if load_flag:
1036 t0 = time() 1054 t0 = time()
1037 logging.info(f'Loading {tomo_file} ...') 1055 logging.info(f'Loading {tomo_file} ...')
1038 try: 1056 try:
1039 stack = np.load(tomo_file) 1057 stack = np.load(tomo_file)
1040 except IOError or ValueError: 1058 except IOError or ValueError:
1041 stack = np.array([]) 1059 stack = np.array([])
1042 logging.error(f'Error loading {tomo_file}') 1060 logging.error(f'Error loading {tomo_file}')
1043 logging.info(f'... done in {time()-t0:.2f} seconds!') 1061 logging.info(f'... done in {time()-t0:.2f} seconds!')
1044 if stack.size: 1062 if stack.size:
1045 msnc.quickImshow(stack[:,0,:], title=title, path=self.output_folder, 1063 quickImshow(stack[:,0,:], title=title, path=self.output_folder,
1046 save_fig=self.save_plots, save_only=self.save_plots_only) 1064 save_fig=self.save_plots, save_only=self.save_plots_only)
1047 return stack, available 1065 return stack, available
1048 1066
1049 def _saveTomo(self, base_name, stack, index=None): 1067 def _saveTomo(self, base_name, stack, index=None):
1050 """Save a tomography stack. 1068 """Save a tomography stack.
1104 continue 1122 continue
1105 1123
1106 # Load a stack of tomography images 1124 # Load a stack of tomography images
1107 index = stack['index'] 1125 index = stack['index']
1108 t0 = time() 1126 t0 = time()
1109 tomo_stack = msnc.loadImageStack(tomo_stack_files[i], self.config['data_filetype'], 1127 tomo_stack = loadImageStack(tomo_stack_files[i], self.config['data_filetype'],
1110 stack['img_offset'], self.config['theta_range']['num'], num_theta_skip, 1128 stack['img_offset'], self.config['theta_range']['num'], num_theta_skip,
1111 img_x_bounds, img_y_bounds) 1129 img_x_bounds, img_y_bounds)
1112 tomo_stack = tomo_stack.astype('float64') 1130 tomo_stack = tomo_stack.astype('float64')
1113 logging.debug(f'loading stack {index} took {time()-t0:.2f} seconds!') 1131 logging.debug(f'loading stack {index} took {time()-t0:.2f} seconds!')
1114 1132
1143 # Downsize tomography stack to smaller size 1161 # Downsize tomography stack to smaller size
1144 tomo_stack = tomo_stack.astype('float32') 1162 tomo_stack = tomo_stack.astype('float32')
1145 if not self.galaxy_flag: 1163 if not self.galaxy_flag:
1146 title = f'red stack fullres {index}' 1164 title = f'red stack fullres {index}'
1147 if not self.test_mode: 1165 if not self.test_mode:
1148 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder, 1166 quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder,
1149 save_fig=self.save_plots, save_only=self.save_plots_only) 1167 save_fig=self.save_plots, save_only=self.save_plots_only)
1150 if zoom_perc != 100: 1168 if zoom_perc != 100:
1151 t0 = time() 1169 t0 = time()
1152 logging.info(f'Zooming in ...') 1170 logging.info(f'Zooming in ...')
1153 tomo_zoom_list = [] 1171 tomo_zoom_list = []
1158 logging.info(f'... done in {time()-t0:.2f} seconds!') 1176 logging.info(f'... done in {time()-t0:.2f} seconds!')
1159 del tomo_zoom_list 1177 del tomo_zoom_list
1160 if not self.galaxy_flag: 1178 if not self.galaxy_flag:
1161 title = f'red stack {zoom_perc}p {index}' 1179 title = f'red stack {zoom_perc}p {index}'
1162 if not self.test_mode: 1180 if not self.test_mode:
1163 msnc.quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder, 1181 quickImshow(tomo_stack[0,:,:], title=title, path=self.output_folder,
1164 save_fig=self.save_plots, save_only=self.save_plots_only) 1182 save_fig=self.save_plots, save_only=self.save_plots_only)
1165 1183
1166 # Convert tomography stack from theta,row,column to row,theta,column 1184 # Convert tomography stack from theta,row,column to row,theta,column
1167 tomo_stack = np.swapaxes(tomo_stack, 0, 1) 1185 tomo_stack = np.swapaxes(tomo_stack, 0, 1)
1168 1186
1212 sinogram = tomo_plane_T[two_offset+dist_from_edge:-dist_from_edge,:] 1230 sinogram = tomo_plane_T[two_offset+dist_from_edge:-dist_from_edge,:]
1213 else: 1231 else:
1214 logging.debug(f'sinogram range = [{dist_from_edge}, {two_offset-dist_from_edge}]') 1232 logging.debug(f'sinogram range = [{dist_from_edge}, {two_offset-dist_from_edge}]')
1215 sinogram = tomo_plane_T[dist_from_edge:two_offset-dist_from_edge,:] 1233 sinogram = tomo_plane_T[dist_from_edge:two_offset-dist_from_edge,:]
1216 if plot_sinogram and not self.test_mode: 1234 if plot_sinogram and not self.test_mode:
1217 msnc.quickImshow(sinogram.T, f'sinogram center offset{center_offset:.2f}', 1235 quickImshow(sinogram.T, f'sinogram center offset{center_offset:.2f}',
1218 path=self.output_folder, save_fig=self.save_plots, 1236 path=self.output_folder, save_fig=self.save_plots,
1219 save_only=self.save_plots_only, aspect='auto') 1237 save_only=self.save_plots_only, aspect='auto')
1220 1238
1221 # Inverting sinogram 1239 # Inverting sinogram
1222 t0 = time() 1240 t0 = time()
1242 # RV parameters for the denoise, gaussian, and ring removal will be different for different feature sizes 1260 # RV parameters for the denoise, gaussian, and ring removal will be different for different feature sizes
1243 edges = denoise_tv_chambolle(recon_plane, weight = weight) 1261 edges = denoise_tv_chambolle(recon_plane, weight = weight)
1244 vmax = np.max(edges[0,:,:]) 1262 vmax = np.max(edges[0,:,:])
1245 vmin = -vmax 1263 vmin = -vmax
1246 if self.galaxy_flag: 1264 if self.galaxy_flag:
1247 msnc.quickImshow(edges[0,:,:], title, path=path, save_fig=True, save_only=True, 1265 quickImshow(edges[0,:,:], title, path=path, save_fig=True, save_only=True,
1248 cmap='gray', vmin=vmin, vmax=vmax) 1266 cmap='gray', vmin=vmin, vmax=vmax)
1249 else: 1267 else:
1250 if path is None: 1268 if path is None:
1251 path=self.output_folder 1269 path=self.output_folder
1252 msnc.quickImshow(edges[0,:,:], f'{title} coolwarm', path=path, 1270 quickImshow(edges[0,:,:], f'{title} coolwarm', path=path,
1253 save_fig=self.save_plots, save_only=self.save_plots_only, cmap='coolwarm') 1271 save_fig=self.save_plots, save_only=self.save_plots_only, cmap='coolwarm')
1254 msnc.quickImshow(edges[0,:,:], f'{title} gray', path=path, 1272 quickImshow(edges[0,:,:], f'{title} gray', path=path,
1255 save_fig=self.save_plots, save_only=self.save_plots_only, cmap='gray', 1273 save_fig=self.save_plots, save_only=self.save_plots_only, cmap='gray',
1256 vmin=vmin, vmax=vmax) 1274 vmin=vmin, vmax=vmax)
1257 del edges 1275 del edges
1258 1276
1259 def _findCenterOnePlane(self, sinogram, row, thetas_deg, eff_pixel_size, cross_sectional_dim, 1277 def _findCenterOnePlane(self, sinogram, row, thetas_deg, eff_pixel_size, cross_sectional_dim,
1301 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, 1319 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg,
1302 eff_pixel_size, cross_sectional_dim, False, num_core) 1320 eff_pixel_size, cross_sectional_dim, False, num_core)
1303 title = f'edges row{row} center offset{center_offset_vo:.2f} Vo' 1321 title = f'edges row{row} center offset{center_offset_vo:.2f} Vo'
1304 self._plotEdgesOnePlane(recon_plane, title) 1322 self._plotEdgesOnePlane(recon_plane, title)
1305 if not self.galaxy_flag: 1323 if not self.galaxy_flag:
1306 if (pyip.inputYesNo('Try finding center using phase correlation '+ 1324 if input_yesno('Try finding center using phase correlation (y/n)?', 'n'):
1307 '(y/[n])? ', blank=True) == 'yes'):
1308 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=0.1, 1325 tomo_center = tomopy.find_center_pc(sinogram, sinogram, tol=0.1,
1309 rotc_guess=tomo_center) 1326 rotc_guess=tomo_center)
1310 error = 1. 1327 error = 1.
1311 while error > tol: 1328 while error > tol:
1312 prev = tomo_center 1329 prev = tomo_center
1317 print(f'Center at row {row} using phase correlation = {center_offset:.2f}') 1334 print(f'Center at row {row} using phase correlation = {center_offset:.2f}')
1318 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg, 1335 recon_plane = self._reconstructOnePlane(sinogram_T, tomo_center, thetas_deg,
1319 eff_pixel_size, cross_sectional_dim, False, num_core) 1336 eff_pixel_size, cross_sectional_dim, False, num_core)
1320 title = f'edges row{row} center_offset{center_offset:.2f} PC' 1337 title = f'edges row{row} center_offset{center_offset:.2f} PC'
1321 self._plotEdgesOnePlane(recon_plane, title) 1338 self._plotEdgesOnePlane(recon_plane, title)
1322 if (pyip.inputYesNo('Accept a center location ([y]) or continue '+ 1339 if input_yesno('Accept a center location (y) or continue search (n)?', 'y'):
1323 'search (n)? ', blank=True) != 'no'): 1340 center_offset = input_num(' Enter chosen center offset', -center, center,
1324 center_offset = pyip.inputNum( 1341 center_offset_vo)
1325 f' Enter chosen center offset [{-int(center)}, {int(center)}] '+
1326 f'([{center_offset_vo:.2f}])): ', blank=True)
1327 if center_offset == '':
1328 center_offset = center_offset_vo
1329 del sinogram_T 1342 del sinogram_T
1330 del recon_plane 1343 del recon_plane
1331 return float(center_offset) 1344 return float(center_offset)
1332 1345
1333 # perform center finding search 1346 # perform center finding search
1336 set_center = center_offset_vo 1349 set_center = center_offset_vo
1337 if galaxy_param['center_type_selector'] == 'user': 1350 if galaxy_param['center_type_selector'] == 'user':
1338 set_center = galaxy_param['set_center'] 1351 set_center = galaxy_param['set_center']
1339 set_range = galaxy_param['set_range'] 1352 set_range = galaxy_param['set_range']
1340 center_offset_step = galaxy_param['set_step'] 1353 center_offset_step = galaxy_param['set_step']
1341 if (not msnc.is_num(set_range, 0) or not msnc.is_num(center_offset_step) or 1354 if (not is_num(set_range, 0) or not is_num(center_offset_step) or
1342 center_offset_step <= 0): 1355 center_offset_step <= 0):
1343 logging.warning('Illegal center finding search parameter, skip search') 1356 logging.warning('Illegal center finding search parameter, skip search')
1344 del sinogram_T 1357 del sinogram_T
1345 return float(center_offset_vo) 1358 return float(center_offset_vo)
1346 set_range = center_offset_step*max(1, int(set_range/center_offset_step)) 1359 set_range = center_offset_step*max(1, int(set_range/center_offset_step))
1347 center_offset_low = set_center-set_range 1360 center_offset_low = set_center-set_range
1348 center_offset_upp = set_center+set_range 1361 center_offset_upp = set_center+set_range
1349 else: 1362 else:
1350 center_offset_low = pyip.inputInt('\nEnter lower bound for center offset '+ 1363 center_offset_low = input_int('\nEnter lower bound for center offset',
1351 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center)) 1364 -center, center)
1352 center_offset_upp = pyip.inputInt('Enter upper bound for center offset '+ 1365 center_offset_upp = input_int('Enter upper bound for center offset',
1353 f'[{center_offset_low}, {int(center)}]: ', 1366 center_offset_low, center)
1354 min=center_offset_low, max=int(center))
1355 if center_offset_upp == center_offset_low: 1367 if center_offset_upp == center_offset_low:
1356 center_offset_step = 1 1368 center_offset_step = 1
1357 else: 1369 else:
1358 center_offset_step = pyip.inputInt('Enter step size for center offset search '+ 1370 center_offset_step = input_int('Enter step size for center offset search',
1359 f'[1, {center_offset_upp-center_offset_low}]: ', 1371 1, center_offset_upp-center_offset_low)
1360 min=1, max=center_offset_upp-center_offset_low)
1361 num_center_offset = 1+int((center_offset_upp-center_offset_low)/center_offset_step) 1372 num_center_offset = 1+int((center_offset_upp-center_offset_low)/center_offset_step)
1362 center_offsets = np.linspace(center_offset_low, center_offset_upp, num_center_offset) 1373 center_offsets = np.linspace(center_offset_low, center_offset_upp, num_center_offset)
1363 for center_offset in center_offsets: 1374 for center_offset in center_offsets:
1364 if center_offset == center_offset_vo: 1375 if center_offset == center_offset_vo:
1365 continue 1376 continue
1371 title = f'edges row{row} center_offset{center_offset:.2f}' 1382 title = f'edges row{row} center_offset{center_offset:.2f}'
1372 if self.galaxy_flag: 1383 if self.galaxy_flag:
1373 self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs') 1384 self._plotEdgesOnePlane(recon_plane, title, path='find_center_pngs')
1374 else: 1385 else:
1375 self._plotEdgesOnePlane(recon_plane, title) 1386 self._plotEdgesOnePlane(recon_plane, title)
1376 if self.galaxy_flag or pyip.inputInt('\nContinue (0) or end the search (1): ', 1387 if self.galaxy_flag or input_int('\nContinue (0) or end the search (1)', 0, 1):
1377 min=0, max=1):
1378 break 1388 break
1379 1389
1380 del sinogram_T 1390 del sinogram_T
1381 del recon_plane 1391 del recon_plane
1382 if self.galaxy_flag: 1392 if self.galaxy_flag:
1383 center_offset = center_offset_vo 1393 center_offset = center_offset_vo
1384 else: 1394 else:
1385 center_offset = pyip.inputNum(f' Enter chosen center offset '+ 1395 center_offset = input_num(' Enter chosen center offset', -center, center)
1386 f'[{-int(center)}, {int(center)}]: ', min=-int(center), max=int(center))
1387 return float(center_offset) 1396 return float(center_offset)
1388 1397
1389 def _reconstructOneTomoStack(self, tomo_stack, thetas, row_bounds=None, 1398 def _reconstructOneTomoStack(self, tomo_stack, thetas, row_bounds=None,
1390 center_offsets=[], sigma=0.1, rwidth=30, num_core=1, algorithm='gridrec', 1399 center_offsets=[], sigma=0.1, rwidth=30, num_core=1, algorithm='gridrec',
1391 run_secondary_sirt=False, secondary_iter=100): 1400 run_secondary_sirt=False, secondary_iter=100):
1400 # https://tomopy.readthedocs.io/en/latest/api/tomopy.misc.corr.html 1409 # https://tomopy.readthedocs.io/en/latest/api/tomopy.misc.corr.html
1401 # RV: Add an option to do (extra) secondary iterations later or to do some sort of convergence test? 1410 # RV: Add an option to do (extra) secondary iterations later or to do some sort of convergence test?
1402 if row_bounds is None: 1411 if row_bounds is None:
1403 row_bounds = [0, tomo_stack.shape[0]] 1412 row_bounds = [0, tomo_stack.shape[0]]
1404 else: 1413 else:
1405 if not msnc.is_index_range(row_bounds, 0, tomo_stack.shape[0]): 1414 if not is_index_range(row_bounds, 0, tomo_stack.shape[0]):
1406 raise ValueError('Illegal row bounds in reconstructOneTomoStack') 1415 raise ValueError('Illegal row bounds in reconstructOneTomoStack')
1407 if thetas.size != tomo_stack.shape[1]: 1416 if thetas.size != tomo_stack.shape[1]:
1408 raise ValueError('theta dimension mismatch in reconstructOneTomoStack') 1417 raise ValueError('theta dimension mismatch in reconstructOneTomoStack')
1409 if not len(center_offsets): 1418 if not len(center_offsets):
1410 centers = np.zeros((row_bounds[1]-row_bounds[0])) 1419 centers = np.zeros((row_bounds[1]-row_bounds[0]))
1465 """ 1474 """
1466 self.is_valid = True 1475 self.is_valid = True
1467 1476
1468 # Find dark field images 1477 # Find dark field images
1469 dark_field = self.config['dark_field'] 1478 dark_field = self.config['dark_field']
1470 img_start, num_imgs, dark_files = msnc.findImageFiles( 1479 img_start, num_imgs, dark_files = findImageFiles(
1471 dark_field['data_path'], self.config['data_filetype'], 'dark field') 1480 dark_field['data_path'], self.config['data_filetype'], 'dark field')
1472 if img_start < 0 or num_imgs < 1: 1481 if img_start < 0 or num_imgs < 1:
1473 logging.error('Unable to find suitable dark field images') 1482 logging.error('Unable to find suitable dark field images')
1474 if dark_field['data_path']: 1483 if dark_field['data_path']:
1475 self.is_valid = False 1484 self.is_valid = False
1490 if dark_field['data_path']: 1499 if dark_field['data_path']:
1491 self.is_valid = False 1500 self.is_valid = False
1492 logging.info(f'Number of dark field images = {dark_field["num"]}') 1501 logging.info(f'Number of dark field images = {dark_field["num"]}')
1493 logging.info(f'Dark field image start index = {dark_field["img_start"]}') 1502 logging.info(f'Dark field image start index = {dark_field["img_start"]}')
1494 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif': 1503 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
1495 dark_files = msnc.combine_tiffs_in_h5(dark_files, num_imgs, 1504 dark_files = combine_tiffs_in_h5(dark_files, num_imgs,
1496 f'{self.config["work_folder"]}/dark_field.h5') 1505 f'{self.config["work_folder"]}/dark_field.h5')
1497 dark_field['data_path'] = dark_files[0] 1506 dark_field['data_path'] = dark_files[0]
1498 1507
1499 # Find bright field images 1508 # Find bright field images
1500 bright_field = self.config['bright_field'] 1509 bright_field = self.config['bright_field']
1501 img_start, num_imgs, bright_files = msnc.findImageFiles( 1510 img_start, num_imgs, bright_files = findImageFiles(
1502 bright_field['data_path'], self.config['data_filetype'], 'bright field') 1511 bright_field['data_path'], self.config['data_filetype'], 'bright field')
1503 if img_start < 0 or num_imgs < 1: 1512 if img_start < 0 or num_imgs < 1:
1504 logging.error('Unable to find suitable bright field images') 1513 logging.error('Unable to find suitable bright field images')
1505 self.is_valid = False 1514 self.is_valid = False
1506 img_start_old = bright_field.get('img_start') 1515 img_start_old = bright_field.get('img_start')
1518 logging.warning('Inconsistent image start index for bright field images') 1527 logging.warning('Inconsistent image start index for bright field images')
1519 self.is_valid = False 1528 self.is_valid = False
1520 logging.info(f'Number of bright field images = {bright_field["num"]}') 1529 logging.info(f'Number of bright field images = {bright_field["num"]}')
1521 logging.info(f'Bright field image start index = {bright_field["img_start"]}') 1530 logging.info(f'Bright field image start index = {bright_field["img_start"]}')
1522 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif': 1531 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
1523 bright_files = msnc.combine_tiffs_in_h5(bright_files, num_imgs, 1532 bright_files = combine_tiffs_in_h5(bright_files, num_imgs,
1524 f'{self.config["work_folder"]}/bright_field.h5') 1533 f'{self.config["work_folder"]}/bright_field.h5')
1525 bright_field['data_path'] = bright_files[0] 1534 bright_field['data_path'] = bright_files[0]
1526 1535
1527 # Find tomography images 1536 # Find tomography images
1528 tomo_stack_files = [] 1537 tomo_stack_files = []
1529 for stack in self.config['stack_info']['stacks']: 1538 for stack in self.config['stack_info']['stacks']:
1530 index = stack['index'] 1539 index = stack['index']
1531 img_start, num_imgs, tomo_files = msnc.findImageFiles( 1540 img_start, num_imgs, tomo_files = findImageFiles(
1532 stack['data_path'], self.config['data_filetype'], f'tomography set {index}') 1541 stack['data_path'], self.config['data_filetype'], f'tomography set {index}')
1533 if img_start < 0 or num_imgs < 1: 1542 if img_start < 0 or num_imgs < 1:
1534 logging.error('Unable to find suitable tomography images') 1543 logging.error('Unable to find suitable tomography images')
1535 self.is_valid = False 1544 self.is_valid = False
1536 img_start_old = stack.get('img_start') 1545 img_start_old = stack.get('img_start')
1548 logging.warning('Inconsistent image start index for tomography images') 1557 logging.warning('Inconsistent image start index for tomography images')
1549 self.is_valid = False 1558 self.is_valid = False
1550 logging.info(f'Number of tomography images for set {index} = {stack["num"]}') 1559 logging.info(f'Number of tomography images for set {index} = {stack["num"]}')
1551 logging.info(f'Tomography set {index} image start index = {stack["img_start"]}') 1560 logging.info(f'Tomography set {index} image start index = {stack["img_start"]}')
1552 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif': 1561 if num_imgs and tiff_to_h5_flag and self.config['data_filetype'] == 'tif':
1553 tomo_files = msnc.combine_tiffs_in_h5(tomo_files, num_imgs, 1562 tomo_files = combine_tiffs_in_h5(tomo_files, num_imgs,
1554 f'{self.config["work_folder"]}/tomo_field_{index}.h5') 1563 f'{self.config["work_folder"]}/tomo_field_{index}.h5')
1555 stack['data_path'] = tomo_files[0] 1564 stack['data_path'] = tomo_files[0]
1556 tomo_stack_files.append(tomo_files) 1565 tomo_stack_files.append(tomo_files)
1557 del tomo_files 1566 del tomo_files
1558 1567
1707 center_stack_index = None 1716 center_stack_index = None
1708 if find_center and 'center_stack_index' in find_center: 1717 if find_center and 'center_stack_index' in find_center:
1709 center_stack_index = find_center['center_stack_index'] 1718 center_stack_index = find_center['center_stack_index']
1710 if (not isinstance(center_stack_index, int) or 1719 if (not isinstance(center_stack_index, int) or
1711 center_stack_index not in available_stacks): 1720 center_stack_index not in available_stacks):
1712 msnc.illegal_value('center_stack_index', center_stack_index, 'config file') 1721 illegal_value(center_stack_index, 'find_center:center_stack_index', 'config file')
1713 else: 1722 else:
1714 if self.test_mode: 1723 if self.test_mode:
1715 assert(find_center.get('completed', False) == False) 1724 assert(find_center.get('completed', False) == False)
1716 else: 1725 else:
1717 print('\nFound calibration center offset info for stack '+ 1726 print('\nFound calibration center offset info for stack '+
1718 f'{center_stack_index}') 1727 f'{center_stack_index}')
1719 if (pyip.inputYesNo('Do you want to use this again ([y]/n)? ', 1728 if (input_yesno('Do you want to use this again (y/n)?', 'y')
1720 blank=True) != 'no' and find_center.get('completed', False) == True): 1729 and find_center.get('completed', False) == True):
1721 return 1730 return
1722 1731
1723 # Load the required preprocessed stack 1732 # Load the required preprocessed stack
1724 # preprocessed stack order: row,theta,column 1733 # preprocessed stack order: row,theta,column
1725 num_tomo_stacks = self.config['stack_info']['num'] 1734 num_tomo_stacks = self.config['stack_info']['num']
1747 raise OSError('Unable to load the required preprocessed tomography stack') 1756 raise OSError('Unable to load the required preprocessed tomography stack')
1748 assert(stacks[tomo_stack_index].get('preprocessed', False) == True) 1757 assert(stacks[tomo_stack_index].get('preprocessed', False) == True)
1749 else: 1758 else:
1750 while True: 1759 while True:
1751 if not center_stack_index: 1760 if not center_stack_index:
1752 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get ' 1761 center_stack_index = input_int('\nEnter tomography stack index to get '
1753 f'rotation axis centers {available_stacks}: ') 1762 f'rotation axis centers ({available_stacks})', inset=available_stacks)
1754 while center_stack_index not in available_stacks: 1763 while center_stack_index not in available_stacks:
1755 center_stack_index = pyip.inputInt('\nEnter tomography stack index to get ' 1764 center_stack_index = input_int('\nEnter tomography stack index to get '
1756 f'rotation axis centers {available_stacks}: ') 1765 f'rotation axis centers ({available_stacks})', inset=available_stacks)
1757 tomo_stack_index = available_stacks.index(center_stack_index) 1766 tomo_stack_index = available_stacks.index(center_stack_index)
1758 if not self.tomo_stacks[tomo_stack_index].size: 1767 if not self.tomo_stacks[tomo_stack_index].size:
1759 self.tomo_stacks[tomo_stack_index], available = self._loadTomo( 1768 self.tomo_stacks[tomo_stack_index], available = self._loadTomo(
1760 'red stack', center_stack_index, required=True) 1769 'red stack', center_stack_index, required=True)
1761 center_stack = self.tomo_stacks[tomo_stack_index] 1770 center_stack = self.tomo_stacks[tomo_stack_index]
1783 row_bounds[1] = center_stack.shape[0] 1792 row_bounds[1] = center_stack.shape[0]
1784 if center_rows[0] == -1: 1793 if center_rows[0] == -1:
1785 center_rows[0] = 0 1794 center_rows[0] = 0
1786 if center_rows[1] == -1: 1795 if center_rows[1] == -1:
1787 center_rows[1] = center_stack.shape[0]-1 1796 center_rows[1] = center_stack.shape[0]-1
1788 if not msnc.is_index_range(row_bounds, 0, center_stack.shape[0]): 1797 if not is_index_range(row_bounds, 0, center_stack.shape[0]):
1789 msnc.illegal_value('row_bounds', row_bounds) 1798 illegal_value(row_bounds, 'row_bounds', 'Tomo:findCenters')
1790 return 1799 return
1791 1800
1792 # Set thetas (in degrees) 1801 # Set thetas (in degrees)
1793 theta_range = self.config['theta_range'] 1802 theta_range = self.config['theta_range']
1794 theta_start = theta_range['start'] 1803 theta_start = theta_range['start']
1804 if pixel_size is None: 1813 if pixel_size is None:
1805 raise ValueError('Detector pixel size unavailable') 1814 raise ValueError('Detector pixel size unavailable')
1806 eff_pixel_size = 100.*pixel_size/zoom_perc 1815 eff_pixel_size = 100.*pixel_size/zoom_perc
1807 logging.debug(f'eff_pixel_size = {eff_pixel_size}') 1816 logging.debug(f'eff_pixel_size = {eff_pixel_size}')
1808 if num_tomo_stacks == 1: 1817 if num_tomo_stacks == 1:
1809 accept = 'yes' 1818 accept = True
1810 if not self.test_mode and not self.galaxy_flag: 1819 if not self.test_mode and not self.galaxy_flag:
1811 accept = 'no' 1820 accept = False
1812 print('\nSelect bounds for image reconstruction') 1821 print('\nSelect bounds for image reconstruction')
1813 if msnc.is_index_range(row_bounds, 0, center_stack.shape[0]): 1822 if is_index_range(row_bounds, 0, center_stack.shape[0]):
1814 a_tmp = np.copy(center_stack[:,0,:]) 1823 a_tmp = np.copy(center_stack[:,0,:])
1815 a_tmp_max = a_tmp.max() 1824 a_tmp_max = a_tmp.max()
1816 a_tmp[row_bounds[0],:] = a_tmp_max 1825 a_tmp[row_bounds[0],:] = a_tmp_max
1817 a_tmp[row_bounds[1]-1,:] = a_tmp_max 1826 a_tmp[row_bounds[1]-1,:] = a_tmp_max
1818 print(f'lower bound = {row_bounds[0]} (inclusive)') 1827 print(f'lower bound = {row_bounds[0]} (inclusive)')
1819 print(f'upper bound = {row_bounds[1]} (exclusive)') 1828 print(f'upper bound = {row_bounds[1]} (exclusive)')
1820 msnc.quickImshow(a_tmp, title=f'center stack theta={theta_start}', 1829 quickImshow(a_tmp, title=f'center stack theta={theta_start}',
1821 aspect='auto') 1830 aspect='auto')
1822 del a_tmp 1831 del a_tmp
1823 accept = pyip.inputYesNo('Accept these bounds ([y]/n)?: ', blank=True) 1832 accept = input_yesno('Accept these bounds (y/n)?', 'y')
1824 if accept == 'no': 1833 if accept:
1825 (n1, n2) = msnc.selectImageBounds(center_stack[:,0,:], 0,
1826 title=f'center stack theta={theta_start}')
1827 else:
1828 n1 = row_bounds[0] 1834 n1 = row_bounds[0]
1829 n2 = row_bounds[1] 1835 n2 = row_bounds[1]
1836 else:
1837 n1, n2 = selectImageBounds(center_stack[:,0,:], 0,
1838 title=f'center stack theta={theta_start}')
1830 else: 1839 else:
1831 tomo_ref_heights = [stack['ref_height'] for stack in stacks] 1840 tomo_ref_heights = [stack['ref_height'] for stack in stacks]
1832 n1 = int((1.+(tomo_ref_heights[0]+center_stack.shape[0]*eff_pixel_size- 1841 n1 = int((1.+(tomo_ref_heights[0]+center_stack.shape[0]*eff_pixel_size-
1833 tomo_ref_heights[1])/eff_pixel_size)/2) 1842 tomo_ref_heights[1])/eff_pixel_size)/2)
1834 n2 = center_stack.shape[0]-n1 1843 n2 = center_stack.shape[0]-n1
1836 if not self.test_mode and not self.galaxy_flag: 1845 if not self.test_mode and not self.galaxy_flag:
1837 tmp = center_stack[:,0,:] 1846 tmp = center_stack[:,0,:]
1838 tmp_max = tmp.max() 1847 tmp_max = tmp.max()
1839 tmp[n1,:] = tmp_max 1848 tmp[n1,:] = tmp_max
1840 tmp[n2-1,:] = tmp_max 1849 tmp[n2-1,:] = tmp_max
1841 if msnc.is_index_range(center_rows, 0, tmp.shape[0]): 1850 if is_index_range(center_rows, 0, tmp.shape[0]):
1842 tmp[center_rows[0],:] = tmp_max 1851 tmp[center_rows[0],:] = tmp_max
1843 tmp[center_rows[1]-1,:] = tmp_max 1852 tmp[center_rows[1]-1,:] = tmp_max
1844 extent = [0, tmp.shape[1], tmp.shape[0], 0] 1853 extent = [0, tmp.shape[1], tmp.shape[0], 0]
1845 msnc.quickImshow(tmp, title=f'center stack theta={theta_start}', 1854 quickImshow(tmp, title=f'center stack theta={theta_start}',
1846 path=self.output_folder, extent=extent, save_fig=self.save_plots, 1855 path=self.output_folder, extent=extent, save_fig=self.save_plots,
1847 save_only=self.save_plots_only, aspect='auto') 1856 save_only=self.save_plots_only, aspect='auto')
1848 del tmp 1857 del tmp
1849 #extent = [0, center_stack.shape[2], n2, n1] 1858 #extent = [0, center_stack.shape[2], n2, n1]
1850 #msnc.quickImshow(center_stack[n1:n2,0,:], title=f'center stack theta={theta_start}', 1859 #quickImshow(center_stack[n1:n2,0,:], title=f'center stack theta={theta_start}',
1851 # path=self.output_folder, extent=extent, save_fig=self.save_plots, 1860 # path=self.output_folder, extent=extent, save_fig=self.save_plots,
1852 # save_only=self.save_plots_only, show_grid=True, aspect='auto') 1861 # save_only=self.save_plots_only, show_grid=True, aspect='auto')
1853 1862
1854 # Get cross sectional diameter in mm 1863 # Get cross sectional diameter in mm
1855 cross_sectional_dim = center_stack.shape[2]*eff_pixel_size 1864 cross_sectional_dim = center_stack.shape[2]*eff_pixel_size
1857 1866
1858 # Determine center offset at sample row boundaries 1867 # Determine center offset at sample row boundaries
1859 logging.info('Determine center offset at sample row boundaries') 1868 logging.info('Determine center offset at sample row boundaries')
1860 1869
1861 # Lower row center 1870 # Lower row center
1862 use_row = 'no' 1871 use_row = False
1863 use_center = 'no' 1872 use_center = False
1864 row = center_rows[0] 1873 row = center_rows[0]
1865 if self.test_mode or self.galaxy_flag: 1874 if self.test_mode or self.galaxy_flag:
1866 assert(msnc.is_int(row, n1, n2-2)) 1875 assert(is_int(row, n1, n2-2))
1867 if msnc.is_int(row, n1, n2-2): 1876 if is_int(row, n1, n2-2):
1868 if self.test_mode or self.galaxy_flag: 1877 if self.test_mode or self.galaxy_flag:
1869 use_row = 'yes' 1878 use_row = True
1870 else: 1879 else:
1871 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') 1880 quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto')
1872 use_row = pyip.inputYesNo('\nCurrent row index for lower center = ' 1881 use_row = input_yesno('\nCurrent row index for lower center = '
1873 f'{row}, use this value ([y]/n)? ', blank=True) 1882 f'{row}, use this value (y/n)?', 'y')
1874 if self.save_plots_only: 1883 if self.save_plots_only:
1875 msnc.clearFig(f'theta={theta_start}') 1884 clearImshow(f'theta={theta_start}')
1876 if use_row != 'no': 1885 if use_row:
1877 center_offset = find_center.get('lower_center_offset') 1886 center_offset = find_center.get('lower_center_offset')
1878 if msnc.is_num(center_offset): 1887 if is_num(center_offset):
1879 use_center = pyip.inputYesNo('Current lower center offset = '+ 1888 use_center = input_yesno('Current lower center offset = '+
1880 f'{center_offset}, use this value ([y]/n)? ', blank=True) 1889 f'{center_offset}, use this value (y/n)?', 'y')
1881 if use_center == 'no': 1890 if not use_center:
1882 if use_row == 'no': 1891 if not use_row:
1883 if not self.test_mode: 1892 if not self.test_mode:
1884 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', 1893 quickImshow(center_stack[:,0,:], title=f'theta={theta_start}',
1885 aspect='auto') 1894 aspect='auto')
1886 row = pyip.inputInt('\nEnter row index to find lower center '+ 1895 row = input_int('\nEnter row index to find lower center', n1, n2-2, n1)
1887 f'[[{n1}], {n2-2}]: ', min=n1, max=n2-2, blank=True)
1888 if row == '': 1896 if row == '':
1889 row = n1 1897 row = n1
1890 if self.save_plots_only: 1898 if self.save_plots_only:
1891 msnc.clearFig(f'theta={theta_start}') 1899 clearImshow(f'theta={theta_start}')
1892 # center_stack order: row,theta,column 1900 # center_stack order: row,theta,column
1893 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg, 1901 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg,
1894 eff_pixel_size, cross_sectional_dim, num_core=num_core, 1902 eff_pixel_size, cross_sectional_dim, num_core=num_core,
1895 galaxy_param=galaxy_param) 1903 galaxy_param=galaxy_param)
1896 logging.info(f'lower_center_offset = {center_offset:.2f} {type(center_offset)}') 1904 logging.info(f'lower_center_offset = {center_offset:.2f} {type(center_offset)}')
1901 find_center['lower_center_offset'] = center_offset 1909 find_center['lower_center_offset'] = center_offset
1902 self.cf.saveFile(self.config_out) 1910 self.cf.saveFile(self.config_out)
1903 lower_row = row 1911 lower_row = row
1904 1912
1905 # Upper row center 1913 # Upper row center
1906 use_row = 'no' 1914 use_row = False
1907 use_center = 'no' 1915 use_center = False
1908 row = center_rows[1] 1916 row = center_rows[1]
1909 if self.test_mode or self.galaxy_flag: 1917 if self.test_mode or self.galaxy_flag:
1910 assert(msnc.is_int(row, lower_row+1, n2-1)) 1918 assert(is_int(row, lower_row+1, n2-1))
1911 if msnc.is_int(row, lower_row+1, n2-1): 1919 if is_int(row, lower_row+1, n2-1):
1912 if self.test_mode or self.galaxy_flag: 1920 if self.test_mode or self.galaxy_flag:
1913 use_row = 'yes' 1921 use_row = True
1914 else: 1922 else:
1915 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto') 1923 quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', aspect='auto')
1916 use_row = pyip.inputYesNo('\nCurrent row index for upper center = ' 1924 use_row = input_yesno('\nCurrent row index for upper center = '
1917 f'{row}, use this value ([y]/n)? ', blank=True) 1925 f'{row}, use this value (y/n)?', 'y')
1918 if self.save_plots_only: 1926 if self.save_plots_only:
1919 msnc.clearFig(f'theta={theta_start}') 1927 clearImshow(f'theta={theta_start}')
1920 if use_row != 'no': 1928 if use_row:
1921 center_offset = find_center.get('upper_center_offset') 1929 center_offset = find_center.get('upper_center_offset')
1922 if msnc.is_num(center_offset): 1930 if is_num(center_offset):
1923 use_center = pyip.inputYesNo('Current upper center offset = '+ 1931 use_center = input_yesrnNo('Current upper center offset = '+
1924 f'{center_offset}, use this value ([y]/n)? ', blank=True) 1932 f'{center_offset}, use this value (y/n)?', 'y')
1925 if use_center == 'no': 1933 if not use_center:
1926 if use_row == 'no': 1934 if not use_row:
1927 if not self.test_mode: 1935 if not self.test_mode:
1928 msnc.quickImshow(center_stack[:,0,:], title=f'theta={theta_start}', 1936 quickImshow(center_stack[:,0,:], title=f'theta={theta_start}',
1929 aspect='auto') 1937 aspect='auto')
1930 row = pyip.inputInt('\nEnter row index to find upper center '+ 1938 row = input_int('\nEnter row index to find upper center', lower_row+1, n2-1, n2-1)
1931 f'[{lower_row+1}, [{n2-1}]]: ', min=lower_row+1, max=n2-1, blank=True)
1932 if row == '': 1939 if row == '':
1933 row = n2-1 1940 row = n2-1
1934 if self.save_plots_only: 1941 if self.save_plots_only:
1935 msnc.clearFig(f'theta={theta_start}') 1942 clearImshow(f'theta={theta_start}')
1936 # center_stack order: row,theta,column 1943 # center_stack order: row,theta,column
1937 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg, 1944 center_offset = self._findCenterOnePlane(center_stack[row,:,:], row, thetas_deg,
1938 eff_pixel_size, cross_sectional_dim, num_core=num_core, 1945 eff_pixel_size, cross_sectional_dim, num_core=num_core,
1939 galaxy_param=galaxy_param) 1946 galaxy_param=galaxy_param)
1940 logging.info(f'upper_center_offset = {center_offset:.2f}') 1947 logging.info(f'upper_center_offset = {center_offset:.2f}')
1990 plane1 = stack1[upper_row,:] 1997 plane1 = stack1[upper_row,:]
1991 plane2 = stack2[lower_row,:] 1998 plane2 = stack2[lower_row,:]
1992 for i in range(-2, 3): 1999 for i in range(-2, 3):
1993 shift_i = shift+2*i 2000 shift_i = shift+2*i
1994 plane1_shifted = spi.shift(plane2, [0, shift_i]) 2001 plane1_shifted = spi.shift(plane2, [0, shift_i])
1995 msnc.quickPlot((plane1[0,:],), (plane1_shifted[0,:],), 2002 quickPlot((plane1[0,:],), (plane1_shifted[0,:],),
1996 title=f'stacks {stack1} {stack2} shifted {2*i} theta={self.start_theta}', 2003 title=f'stacks {stack1} {stack2} shifted {2*i} theta={self.start_theta}',
1997 path=self.output_folder, save_fig=self.save_plots, 2004 path=self.output_folder, save_fig=self.save_plots,
1998 save_only=self.save_plots_only) 2005 save_only=self.save_plots_only)
1999 if center_stack_index < self.num_tomo_stacks-1: 2006 if center_stack_index < self.num_tomo_stacks-1:
2000 stack1 = self.tomo_stacks[center_stack_index] 2007 stack1 = self.tomo_stacks[center_stack_index]
2009 plane1 = stack1[upper_row,:] 2016 plane1 = stack1[upper_row,:]
2010 plane2 = stack2[lower_row,:] 2017 plane2 = stack2[lower_row,:]
2011 for i in range(-2, 3): 2018 for i in range(-2, 3):
2012 shift_i = -shift+2*i 2019 shift_i = -shift+2*i
2013 plane1_shifted = spi.shift(plane2, [0, shift_i]) 2020 plane1_shifted = spi.shift(plane2, [0, shift_i])
2014 msnc.quickPlot((plane1[0,:],), (plane1_shifted[0,:],), 2021 quickPlot((plane1[0,:],), (plane1_shifted[0,:],),
2015 title=f'stacks {stack1} {stack2} shifted {2*i} theta={start_theta}', 2022 title=f'stacks {stack1} {stack2} shifted {2*i} theta={start_theta}',
2016 path=self.output_folder, save_fig=self.save_plots, 2023 path=self.output_folder, save_fig=self.save_plots,
2017 save_only=self.save_plots_only) 2024 save_only=self.save_plots_only)
2018 del plane1, plane2, plane1_shifted 2025 del plane1, plane2, plane1_shifted
2019 2026
2020 # Update config file 2027 # Update config file
2021 self.config = msnc.update('config.txt', 'check_centers', True, 'find_centers') 2028 self.config = update('config.txt', 'check_centers', True, 'find_centers')
2022 2029
2023 def reconstructTomoStacks(self, galaxy_param=None, num_core=None): 2030 def reconstructTomoStacks(self, galaxy_param=None, num_core=None):
2024 """Reconstruct tomography stacks. 2031 """Reconstruct tomography stacks.
2025 """ 2032 """
2026 if num_core is None: 2033 if num_core is None:
2039 assert(isinstance(galaxy_param, dict)) 2046 assert(isinstance(galaxy_param, dict))
2040 # Get rotation axis centers 2047 # Get rotation axis centers
2041 center_offsets = galaxy_param['center_offsets'] 2048 center_offsets = galaxy_param['center_offsets']
2042 assert(isinstance(center_offsets, list) and len(center_offsets) == 2) 2049 assert(isinstance(center_offsets, list) and len(center_offsets) == 2)
2043 lower_center_offset = center_offsets[0] 2050 lower_center_offset = center_offsets[0]
2044 assert(msnc.is_num(lower_center_offset)) 2051 assert(is_num(lower_center_offset))
2045 upper_center_offset = center_offsets[1] 2052 upper_center_offset = center_offsets[1]
2046 assert(msnc.is_num(upper_center_offset)) 2053 assert(is_num(upper_center_offset))
2047 else: 2054 else:
2048 if galaxy_param: 2055 if galaxy_param:
2049 logging.warning('Ignoring galaxy_param in reconstructTomoStacks (only for Galaxy)') 2056 logging.warning('Ignoring galaxy_param in reconstructTomoStacks (only for Galaxy)')
2050 galaxy_param = None 2057 galaxy_param = None
2051 lower_center_offset = None 2058 lower_center_offset = None
2126 logging.debug(f'... _reconstructOneTomoStack took {time()-t0:.2f} seconds!') 2133 logging.debug(f'... _reconstructOneTomoStack took {time()-t0:.2f} seconds!')
2127 logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!') 2134 logging.info(f'Reconstruction of stack {index} took {time()-t0:.2f} seconds!')
2128 if self.galaxy_flag: 2135 if self.galaxy_flag:
2129 x_slice = int(self.tomo_stacks[i].shape[0]/2) 2136 x_slice = int(self.tomo_stacks[i].shape[0]/2)
2130 title = f'{basetitle} {index} xslice{x_slice}' 2137 title = f'{basetitle} {index} xslice{x_slice}'
2131 msnc.quickImshow(self.tomo_recon_stacks[i][x_slice,:,:], title=title, 2138 quickImshow(self.tomo_recon_stacks[i][x_slice,:,:], title=title,
2132 path='center_slice_pngs', save_fig=True, save_only=True) 2139 path='center_slice_pngs', save_fig=True, save_only=True)
2133 y_slice = int(self.tomo_stacks[i].shape[0]/2) 2140 y_slice = int(self.tomo_stacks[i].shape[0]/2)
2134 title = f'{basetitle} {index} yslice{y_slice}' 2141 title = f'{basetitle} {index} yslice{y_slice}'
2135 msnc.quickImshow(self.tomo_recon_stacks[i][:,y_slice,:], title=title, 2142 quickImshow(self.tomo_recon_stacks[i][:,y_slice,:], title=title,
2136 path='center_slice_pngs', save_fig=True, save_only=True) 2143 path='center_slice_pngs', save_fig=True, save_only=True)
2137 z_slice = int(self.tomo_stacks[i].shape[0]/2) 2144 z_slice = int(self.tomo_stacks[i].shape[0]/2)
2138 title = f'{basetitle} {index} zslice{z_slice}' 2145 title = f'{basetitle} {index} zslice{z_slice}'
2139 msnc.quickImshow(self.tomo_recon_stacks[i][:,:,z_slice], title=title, 2146 quickImshow(self.tomo_recon_stacks[i][:,:,z_slice], title=title,
2140 path='center_slice_pngs', save_fig=True, save_only=True) 2147 path='center_slice_pngs', save_fig=True, save_only=True)
2141 elif not self.test_mode: 2148 elif not self.test_mode:
2142 x_slice = int(self.tomo_stacks[i].shape[0]/2) 2149 x_slice = int(self.tomo_stacks[i].shape[0]/2)
2143 title = f'{basetitle} {index} xslice{x_slice}' 2150 title = f'{basetitle} {index} xslice{x_slice}'
2144 msnc.quickImshow(self.tomo_recon_stacks[i][x_slice,:,:], title=title, 2151 quickImshow(self.tomo_recon_stacks[i][x_slice,:,:], title=title,
2145 path=self.output_folder, save_fig=self.save_plots, 2152 path=self.output_folder, save_fig=self.save_plots,
2146 save_only=self.save_plots_only) 2153 save_only=self.save_plots_only)
2147 msnc.quickPlot(self.tomo_recon_stacks[i] 2154 quickPlot(self.tomo_recon_stacks[i]
2148 [x_slice,int(self.tomo_recon_stacks[i].shape[2]/2),:], 2155 [x_slice,int(self.tomo_recon_stacks[i].shape[2]/2),:],
2149 title=f'{title} cut{int(self.tomo_recon_stacks[i].shape[2]/2)}', 2156 title=f'{title} cut{int(self.tomo_recon_stacks[i].shape[2]/2)}',
2150 path=self.output_folder, save_fig=self.save_plots, 2157 path=self.output_folder, save_fig=self.save_plots,
2151 save_only=self.save_plots_only) 2158 save_only=self.save_plots_only)
2152 self._saveTomo('recon stack', self.tomo_recon_stacks[i], index) 2159 self._saveTomo('recon stack', self.tomo_recon_stacks[i], index)
2171 2178
2172 # Create cross section profile in yz-plane 2179 # Create cross section profile in yz-plane
2173 tomosum = 0 2180 tomosum = 0
2174 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,2)) for tomo_recon_stack in 2181 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,2)) for tomo_recon_stack in
2175 self.tomo_recon_stacks] 2182 self.tomo_recon_stacks]
2176 msnc.quickPlot(tomosum, title='recon stack sum yz', path='center_slice_pngs', 2183 quickPlot(tomosum, title='recon stack sum yz', path='center_slice_pngs',
2177 save_fig=True, save_only=True) 2184 save_fig=True, save_only=True)
2178 2185
2179 # Create cross section profile in xz-plane 2186 # Create cross section profile in xz-plane
2180 tomosum = 0 2187 tomosum = 0
2181 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,1)) for tomo_recon_stack in 2188 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,1)) for tomo_recon_stack in
2182 self.tomo_recon_stacks] 2189 self.tomo_recon_stacks]
2183 msnc.quickPlot(tomosum, title='recon stack sum xz', path='center_slice_pngs', 2190 quickPlot(tomosum, title='recon stack sum xz', path='center_slice_pngs',
2184 save_fig=True, save_only=True) 2191 save_fig=True, save_only=True)
2185 2192
2186 # Create cross section profile in xy-plane 2193 # Create cross section profile in xy-plane
2187 num_tomo_stacks = len(stacks) 2194 num_tomo_stacks = len(stacks)
2188 row_bounds = self.config['find_center']['row_bounds'] 2195 row_bounds = self.config['find_center']['row_bounds']
2189 if not msnc.is_index_range(row_bounds, 0, self.tomo_recon_stacks[0].shape[0]): 2196 if not is_index_range(row_bounds, 0, self.tomo_recon_stacks[0].shape[0]):
2190 msnc.illegal_value('row_bounds', row_bounds, 'config file') 2197 illegal_value(row_bounds, 'find_center:row_bounds', 'config file')
2191 return 2198 return
2192 if num_tomo_stacks == 1: 2199 if num_tomo_stacks == 1:
2193 low_bound = row_bounds[0] 2200 low_bound = row_bounds[0]
2194 else: 2201 else:
2195 low_bound = 0 2202 low_bound = 0
2200 axis=(1,2)) for i in range(1, num_tomo_stacks-1)]) 2207 axis=(1,2)) for i in range(1, num_tomo_stacks-1)])
2201 if num_tomo_stacks > 1: 2208 if num_tomo_stacks > 1:
2202 tomosum = np.concatenate([tomosum, 2209 tomosum = np.concatenate([tomosum,
2203 np.sum(self.tomo_recon_stacks[num_tomo_stacks-1][row_bounds[0]:,:,:], 2210 np.sum(self.tomo_recon_stacks[num_tomo_stacks-1][row_bounds[0]:,:,:],
2204 axis=(1,2))]) 2211 axis=(1,2))])
2205 msnc.quickPlot(tomosum, title='recon stack sum xy', path='center_slice_pngs', 2212 quickPlot(tomosum, title='recon stack sum xy', path='center_slice_pngs',
2206 save_fig=True, save_only=True) 2213 save_fig=True, save_only=True)
2207 2214
2208 def combineTomoStacks(self, galaxy_param=None): 2215 def combineTomoStacks(self, galaxy_param=None):
2209 """Combine the reconstructed tomography stacks. 2216 """Combine the reconstructed tomography stacks.
2210 """ 2217 """
2248 logging.error('Incompatible reconstructed tomography stack dimensions') 2255 logging.error('Incompatible reconstructed tomography stack dimensions')
2249 return 2256 return
2250 2257
2251 # Get center stack boundaries 2258 # Get center stack boundaries
2252 row_bounds = self.config['find_center']['row_bounds'] 2259 row_bounds = self.config['find_center']['row_bounds']
2253 if not msnc.is_index_range(row_bounds, 0, self.tomo_recon_stacks[0].shape[0]): 2260 if not is_index_range(row_bounds, 0, self.tomo_recon_stacks[0].shape[0]):
2254 msnc.illegal_value('row_bounds', row_bounds, 'config file') 2261 illegal_value(row_bounds, 'find_center:row_bounds', 'config file')
2255 return 2262 return
2256 2263
2257 # Selecting x bounds (in yz-plane) 2264 # Selecting x bounds (in yz-plane)
2258 tomosum = 0 2265 tomosum = 0
2259 #RV FIX := 2266 #RV FIX :=
2263 if self.galaxy_flag: 2270 if self.galaxy_flag:
2264 if x_bounds[0] == -1: 2271 if x_bounds[0] == -1:
2265 x_bounds[0] = 0 2272 x_bounds[0] = 0
2266 if x_bounds[1] == -1: 2273 if x_bounds[1] == -1:
2267 x_bounds[1] = tomosum.size 2274 x_bounds[1] = tomosum.size
2268 if not msnc.is_index_range(x_bounds, 0, tomosum.size): 2275 if not is_index_range(x_bounds, 0, tomosum.size):
2269 msnc.illegal_value('x_bounds', x_bounds, 'galaxy input') 2276 illegal_value(x_bounds, 'x_bounds', 'galaxy input')
2270 tomosum_min = tomosum.min() 2277 tomosum_min = tomosum.min()
2271 tomosum_max = tomosum.max() 2278 tomosum_max = tomosum.max()
2272 msnc.quickPlot((range(tomosum.size), tomosum), 2279 quickPlot((range(tomosum.size), tomosum),
2273 ([x_bounds[0], x_bounds[0]], [tomosum_min, tomosum_max], 'r-'), 2280 ([x_bounds[0], x_bounds[0]], [tomosum_min, tomosum_max], 'r-'),
2274 ([x_bounds[1]-1, x_bounds[1]-1], [tomosum_min, tomosum_max], 'r-'), 2281 ([x_bounds[1]-1, x_bounds[1]-1], [tomosum_min, tomosum_max], 'r-'),
2275 title=f'recon stack sum yz', path='combine_pngs', save_fig=True, save_only=True) 2282 title=f'recon stack sum yz', path='combine_pngs', save_fig=True, save_only=True)
2276 else: 2283 else:
2277 if combine_stacks and 'x_bounds' in combine_stacks: 2284 if combine_stacks and 'x_bounds' in combine_stacks:
2278 x_bounds = combine_stacks['x_bounds'] 2285 x_bounds = combine_stacks['x_bounds']
2279 if not msnc.is_index_range(x_bounds, 0, tomosum.size): 2286 if not is_index_range(x_bounds, 0, tomosum.size):
2280 msnc.illegal_value('x_bounds', x_bounds, 'config file') 2287 illegal_value(x_bounds, 'combine_stacks:x_bounds', 'config file')
2281 elif not self.test_mode: 2288 elif not self.test_mode:
2282 msnc.quickPlot(tomosum, title='recon stack sum yz') 2289 accept = False
2283 if pyip.inputYesNo('\nCurrent image x-bounds: '+ 2290 while not accept:
2284 f'[{x_bounds[0]}, {x_bounds[1]}], use these values ([y]/n)? ', 2291 mask, x_bounds = draw_mask_1d(tomosum, current_index_ranges=x_bounds,
2285 blank=True) == 'no': 2292 title='select x data range', legend='recon stack sum yz')
2286 if pyip.inputYesNo( 2293 while len(x_bounds) != 1:
2287 'Do you want to change the image x-bounds ([y]/n)? ', 2294 print('Please select exactly one continuous range')
2288 blank=True) == 'no': 2295 mask, x_bounds = draw_mask_1d(tomosum, title='select x data range',
2289 x_bounds = [0, tomosum.size] 2296 legend='recon stack sum yz')
2290 else: 2297 x_bounds = list(x_bounds[0])
2291 x_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum yz') 2298 quickPlot(tomosum, vlines=x_bounds, title='recon stack sum yz')
2292 else: 2299 print(f'x_bounds = {x_bounds} (lower bound inclusive, upper bound '+
2293 msnc.quickPlot(tomosum, title='recon stack sum yz') 2300 'exclusive)')
2294 if pyip.inputYesNo('\nDo you want to change the image x-bounds (y/n)? ') == 'no': 2301 accept = input_yesno('Accept these bounds (y/n)?', 'y')
2302 if not accept:
2303 x_bounds = None
2304 else:
2305 if not input_yesno('\nDo you want to change the image x-bounds (y/n)?', 'n'):
2295 x_bounds = [0, tomosum.size] 2306 x_bounds = [0, tomosum.size]
2296 else: 2307 else:
2297 x_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum yz') 2308 accept = False
2309 while not accept:
2310 mask, x_bounds = draw_mask_1d(tomosum, title='select x data range',
2311 legend='recon stack sum yz')
2312 while len(x_bounds) != 1:
2313 print('Please select exactly one continuous range')
2314 mask, x_bounds = draw_mask_1d(tomosum, title='select x data range',
2315 legend='recon stack sum yz')
2316 x_bounds = list(x_bounds[0])
2317 quickPlot(tomosum, vlines=x_bounds, title='recon stack sum yz')
2318 print(f'x_bounds = {x_bounds} (lower bound inclusive, upper bound '+
2319 'exclusive)')
2320 accept = input_yesno('Accept these bounds (y/n)?', 'y')
2298 if False and self.test_mode: 2321 if False and self.test_mode:
2299 np.savetxt(f'{self.output_folder}/recon_stack_sum_yz.txt', 2322 np.savetxt(f'{self.output_folder}/recon_stack_sum_yz.txt',
2300 tomosum[x_bounds[0]:x_bounds[1]], fmt='%.6e') 2323 tomosum[x_bounds[0]:x_bounds[1]], fmt='%.6e')
2301 if self.save_plots_only: 2324 if self.save_plots_only:
2302 msnc.clearFig('recon stack sum yz') 2325 clearPlot('recon stack sum yz')
2303 2326
2304 # Selecting y bounds (in xz-plane) 2327 # Selecting y bounds (in xz-plane)
2305 tomosum = 0 2328 tomosum = 0
2306 #RV FIX := 2329 #RV FIX :=
2307 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,1)) for tomo_recon_stack in 2330 [tomosum := tomosum+np.sum(tomo_recon_stack, axis=(0,1)) for tomo_recon_stack in
2309 if self.galaxy_flag: 2332 if self.galaxy_flag:
2310 if y_bounds[0] == -1: 2333 if y_bounds[0] == -1:
2311 y_bounds[0] = 0 2334 y_bounds[0] = 0
2312 if y_bounds[1] == -1: 2335 if y_bounds[1] == -1:
2313 y_bounds[1] = tomosum.size 2336 y_bounds[1] = tomosum.size
2314 if not msnc.is_index_range(y_bounds, 0, tomosum.size): 2337 if not is_index_range(y_bounds, 0, tomosum.size):
2315 msnc.illegal_value('y_bounds', y_bounds, 'galaxy input') 2338 illegal_value(y_bounds, 'y_bounds', 'galaxy input')
2316 tomosum_min = tomosum.min() 2339 tomosum_min = tomosum.min()
2317 tomosum_max = tomosum.max() 2340 tomosum_max = tomosum.max()
2318 msnc.quickPlot((range(tomosum.size), tomosum), 2341 quickPlot((range(tomosum.size), tomosum),
2319 ([y_bounds[0], y_bounds[0]], [tomosum_min, tomosum_max], 'r-'), 2342 ([y_bounds[0], y_bounds[0]], [tomosum_min, tomosum_max], 'r-'),
2320 ([y_bounds[1]-1, y_bounds[1]-1], [tomosum_min, tomosum_max], 'r-'), 2343 ([y_bounds[1]-1, y_bounds[1]-1], [tomosum_min, tomosum_max], 'r-'),
2321 title=f'recon stack sum xz', path='combine_pngs', save_fig=True, save_only=True) 2344 title=f'recon stack sum xz', path='combine_pngs', save_fig=True, save_only=True)
2322 else: 2345 else:
2323 if combine_stacks and 'y_bounds' in combine_stacks: 2346 if combine_stacks and 'y_bounds' in combine_stacks:
2324 y_bounds = combine_stacks['y_bounds'] 2347 y_bounds = combine_stacks['y_bounds']
2325 if not msnc.is_index_range(y_bounds, 0, tomosum.size): 2348 if not is_index_range(y_bounds, 0, tomosum.size):
2326 msnc.illegal_value('y_bounds', y_bounds, 'config file') 2349 illegal_value(y_bounds, 'combine_stacks:y_bounds', 'config file')
2327 elif not self.test_mode: 2350 elif not self.test_mode:
2328 msnc.quickPlot(tomosum, title='recon stack sum xz') 2351 accept = False
2329 if pyip.inputYesNo('\nCurrent image y-bounds: '+ 2352 while not accept:
2330 f'[{y_bounds[0]}, {y_bounds[1]}], use these values ([y]/n)? ', 2353 mask, y_bounds = draw_mask_1d(tomosum, current_index_ranges=y_bounds,
2331 blank=True) == 'no': 2354 title='select y data range', legend='recon stack sum xz')
2332 if pyip.inputYesNo( 2355 while len(y_bounds) != 1:
2333 'Do you want to change the image y-bounds ([y]/n)? ', 2356 print('Please select exactly one continuous range')
2334 blank=True) == 'no': 2357 mask, y_bounds = draw_mask_1d(tomosum, title='select y data range',
2335 y_bounds = [0, tomosum.size] 2358 legend='recon stack sum xz')
2336 else: 2359 y_bounds = list(y_bounds[0])
2337 y_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum yz') 2360 quickPlot(tomosum, vlines=y_bounds, title='recon stack sum xz')
2338 else: 2361 print(f'y_bounds = {y_bounds} (lower bound inclusive, upper bound '+
2339 msnc.quickPlot(tomosum, title='recon stack sum xz') 2362 'exclusive)')
2340 if pyip.inputYesNo('\nDo you want to change the image y-bounds (y/n)? ') == 'no': 2363 accept = input_yesno('Accept these bounds (y/n)?', 'y')
2364 if not accept:
2365 y_bounds = None
2366 else:
2367 if not input_yesno('\nDo you want to change the image y-bounds (y/n)?', 'n'):
2341 y_bounds = [0, tomosum.size] 2368 y_bounds = [0, tomosum.size]
2342 else: 2369 else:
2343 y_bounds = msnc.selectArrayBounds(tomosum, title='recon stack sum xz') 2370 accept = False
2371 while not accept:
2372 mask, y_bounds = draw_mask_1d(tomosum, title='select y data range',
2373 legend='recon stack sum xz')
2374 while len(y_bounds) != 1:
2375 print('Please select exactly one continuous range')
2376 mask, y_bounds = draw_mask_1d(tomosum, title='select y data range',
2377 legend='recon stack sum xz')
2378 y_bounds = list(y_bounds[0])
2379 quickPlot(tomosum, vlines=y_bounds, title='recon stack sum xz')
2380 print(f'y_bounds = {y_bounds} (lower bound inclusive, upper bound '+
2381 'exclusive)')
2382 accept = input_yesno('Accept these bounds (y/n)?', 'y')
2344 if False and self.test_mode: 2383 if False and self.test_mode:
2345 np.savetxt(f'{self.output_folder}/recon_stack_sum_xz.txt', 2384 np.savetxt(f'{self.output_folder}/recon_stack_sum_xz.txt',
2346 tomosum[y_bounds[0]:y_bounds[1]], fmt='%.6e') 2385 tomosum[y_bounds[0]:y_bounds[1]], fmt='%.6e')
2347 if self.save_plots_only: 2386 if self.save_plots_only:
2348 msnc.clearFig('recon stack sum xz') 2387 clearPlot('recon stack sum xz')
2349 2388
2350 # Combine reconstructed tomography stacks 2389 # Combine reconstructed tomography stacks
2351 logging.info(f'Combining reconstructed stacks ...') 2390 logging.info(f'Combining reconstructed stacks ...')
2352 t0 = time() 2391 t0 = time()
2353 num_tomo_stacks = len(stacks) 2392 num_tomo_stacks = len(stacks)
2376 filename = 'recon combined sum xy' 2415 filename = 'recon combined sum xy'
2377 if zoom_perc is None or zoom_perc == 100: 2416 if zoom_perc is None or zoom_perc == 100:
2378 filename += ' fullres.dat' 2417 filename += ' fullres.dat'
2379 else: 2418 else:
2380 filename += f' {zoom_perc}p.dat' 2419 filename += f' {zoom_perc}p.dat'
2381 msnc.quickPlot(tomosum, title='recon combined sum xy', 2420 quickPlot(tomosum, title='recon combined sum xy',
2382 path=self.output_folder, save_fig=self.save_plots, 2421 path=self.output_folder, save_fig=self.save_plots,
2383 save_only=self.save_plots_only) 2422 save_only=self.save_plots_only)
2384 if False: 2423 if False:
2385 np.savetxt(f'{self.output_folder}/recon_combined_sum_xy.txt', 2424 np.savetxt(f'{self.output_folder}/recon_combined_sum_xy.txt',
2386 tomosum, fmt='%.6e') 2425 tomosum, fmt='%.6e')
2402 if self.galaxy_flag: 2441 if self.galaxy_flag:
2403 if z_bounds[0] == -1: 2442 if z_bounds[0] == -1:
2404 z_bounds[0] = 0 2443 z_bounds[0] = 0
2405 if z_bounds[1] == -1: 2444 if z_bounds[1] == -1:
2406 z_bounds[1] = tomosum.size 2445 z_bounds[1] = tomosum.size
2407 if not msnc.is_index_range(z_bounds, 0, tomosum.size): 2446 if not is_index_range(z_bounds, 0, tomosum.size):
2408 msnc.illegal_value('z_bounds', z_bounds, 'galaxy input') 2447 illegal_value(z_bounds, 'z_bounds', 'galaxy input')
2409 tomosum_min = tomosum.min() 2448 tomosum_min = tomosum.min()
2410 tomosum_max = tomosum.max() 2449 tomosum_max = tomosum.max()
2411 msnc.quickPlot((range(tomosum.size), tomosum), 2450 quickPlot((range(tomosum.size), tomosum),
2412 ([z_bounds[0], z_bounds[0]], [tomosum_min, tomosum_max], 'r-'), 2451 ([z_bounds[0], z_bounds[0]], [tomosum_min, tomosum_max], 'r-'),
2413 ([z_bounds[1]-1, z_bounds[1]-1], [tomosum_min, tomosum_max], 'r-'), 2452 ([z_bounds[1]-1, z_bounds[1]-1], [tomosum_min, tomosum_max], 'r-'),
2414 title=f'recon stack sum xy', path='combine_pngs', save_fig=True, save_only=True) 2453 title=f'recon stack sum xy', path='combine_pngs', save_fig=True, save_only=True)
2415 else: 2454 else:
2416 msnc.quickPlot(tomosum, title='recon combined sum xy') 2455 if not input_yesno('\nDo you want to change the image z-bounds (y/n)?', 'n'):
2417 if pyip.inputYesNo(
2418 '\nDo you want to change the image z-bounds (y/[n])? ',
2419 blank=True) != 'yes':
2420 z_bounds = [0, tomosum.size] 2456 z_bounds = [0, tomosum.size]
2421 else: 2457 else:
2422 z_bounds = msnc.selectArrayBounds(tomosum, title='recon combined sum xy') 2458 accept = False
2459 while not accept:
2460 mask, z_bounds = draw_mask_1d(tomosum, title='select z data range',
2461 legend='recon stack sum xy')
2462 while len(z_bounds) != 1:
2463 print('Please select exactly one continuous range')
2464 mask, z_bounds = draw_mask_1d(tomosum, title='select z data range',
2465 legend='recon stack sum xy')
2466 z_bounds = list(z_bounds[0])
2467 quickPlot(tomosum, vlines=z_bounds, title='recon stack sum xy')
2468 print(f'z_bounds = {z_bounds} (lower bound inclusive, upper bound exclusive)')
2469 accept = input_yesno('Accept these bounds (y/n)?', 'y')
2423 if self.save_plots_only: 2470 if self.save_plots_only:
2424 msnc.clearFig('recon combined sum xy') 2471 clearPlot('recon combined sum xy')
2425 if z_bounds[0] != 0 or z_bounds[1] != tomosum.size: 2472 if z_bounds[0] != 0 or z_bounds[1] != tomosum.size:
2426 tomo_recon_combined = tomo_recon_combined[z_bounds[0]:z_bounds[1],:,:] 2473 tomo_recon_combined = tomo_recon_combined[z_bounds[0]:z_bounds[1],:,:]
2427 2474
2428 # Plot center slices 2475 # Plot center slices
2429 if self.galaxy_flag: 2476 if self.galaxy_flag:
2432 save_only = True 2479 save_only = True
2433 else: 2480 else:
2434 path = self.output_folder 2481 path = self.output_folder
2435 save_fig = self.save_plots 2482 save_fig = self.save_plots
2436 save_only = self.save_plots_only 2483 save_only = self.save_plots_only
2437 msnc.quickImshow(tomo_recon_combined[int(tomo_recon_combined.shape[0]/2),:,:], 2484 quickImshow(tomo_recon_combined[int(tomo_recon_combined.shape[0]/2),:,:],
2438 title=f'recon combined xslice{int(tomo_recon_combined.shape[0]/2)}', 2485 title=f'recon combined xslice{int(tomo_recon_combined.shape[0]/2)}',
2439 path=path, save_fig=save_fig, save_only=save_only) 2486 path=path, save_fig=save_fig, save_only=save_only)
2440 msnc.quickImshow(tomo_recon_combined[:,int(tomo_recon_combined.shape[1]/2),:], 2487 quickImshow(tomo_recon_combined[:,int(tomo_recon_combined.shape[1]/2),:],
2441 title=f'recon combined yslice{int(tomo_recon_combined.shape[1]/2)}', 2488 title=f'recon combined yslice{int(tomo_recon_combined.shape[1]/2)}',
2442 path=path, save_fig=save_fig, save_only=save_only) 2489 path=path, save_fig=save_fig, save_only=save_only)
2443 msnc.quickImshow(tomo_recon_combined[:,:,int(tomo_recon_combined.shape[2]/2)], 2490 quickImshow(tomo_recon_combined[:,:,int(tomo_recon_combined.shape[2]/2)],
2444 title=f'recon combined zslice{int(tomo_recon_combined.shape[2]/2)}', 2491 title=f'recon combined zslice{int(tomo_recon_combined.shape[2]/2)}',
2445 path=path, save_fig=save_fig, save_only=save_only) 2492 path=path, save_fig=save_fig, save_only=save_only)
2446 2493
2447 # Save combined reconstructed tomography stack to file 2494 # Save combined reconstructed tomography stack to file
2448 if self.galaxy_flag: 2495 if self.galaxy_flag: