comparison workflow/run_tomo.py @ 71:1cf15b61cd83 draft

planemo upload for repository https://github.com/rolfverberg/galaxytools commit 366e516aef0735af2998c6ff3af037181c8d5213
author rv43
date Mon, 20 Mar 2023 13:56:57 +0000
parents fba792d5f83b
children d5e1d4ea2b7e
comparison
equal deleted inserted replaced
70:97c4e2cbbad9 71:1cf15b61cd83
30 import tomopy 30 import tomopy
31 except: 31 except:
32 pass 32 pass
33 from yaml import safe_load, safe_dump 33 from yaml import safe_load, safe_dump
34 34
35 from msnctools.fit import Fit 35 try:
36 from msnctools.general import illegal_value, is_int, is_int_pair, is_num, is_index_range, \ 36 from msnctools.fit import Fit
37 input_int, input_num, input_yesno, input_menu, draw_mask_1d, select_image_bounds, \ 37 except:
38 select_one_image_bound, clear_imshow, quick_imshow, clear_plot, quick_plot 38 from fit import Fit
39 39 try:
40 from workflow.models import import_scanparser, FlatField, TomoField, TomoWorkflow 40 from msnctools.general import illegal_value, is_int, is_int_pair, is_num, is_index_range, \
41 from workflow.__version__ import __version__ 41 input_int, input_num, input_yesno, input_menu, draw_mask_1d, select_image_bounds, \
42 select_one_image_bound, clear_imshow, quick_imshow, clear_plot, quick_plot
43 except:
44 from general import illegal_value, is_int, is_int_pair, is_num, is_index_range, \
45 input_int, input_num, input_yesno, input_menu, draw_mask_1d, select_image_bounds, \
46 select_one_image_bound, clear_imshow, quick_imshow, clear_plot, quick_plot
47
48 try:
49 from workflow.models import import_scanparser, FlatField, TomoField, TomoWorkflow
50 from workflow.__version__ import __version__
51 except:
52 pass
42 53
43 num_core_tomopy_limit = 24 54 num_core_tomopy_limit = 24
44 55
45 def nxcopy(nxobject:NXobject, exclude_nxpaths:list[str]=[], nxpath_prefix:str='') -> NXobject: 56 def nxcopy(nxobject:NXobject, exclude_nxpaths:list[str]=[], nxpath_prefix:str='') -> NXobject:
46 '''Function that returns a copy of a nexus object, optionally exluding certain child items. 57 '''Function that returns a copy of a nexus object, optionally exluding certain child items.
264 nxentry.data.makelink(nxentry.reduced_data.rotation_angle, name='rotation_angle') 275 nxentry.data.makelink(nxentry.reduced_data.rotation_angle, name='rotation_angle')
265 nxentry.data.attrs['signal'] = 'reduced_data' 276 nxentry.data.attrs['signal'] = 'reduced_data'
266 277
267 return(nxroot) 278 return(nxroot)
268 279
269 def find_centers(self, nxroot, center_rows=None): 280 def find_centers(self, nxroot, center_rows=None, center_stack_index=None):
270 """Find the calibrated center axis info 281 """Find the calibrated center axis info
271 """ 282 """
272 logger.info('Find the calibrated center axis info') 283 logger.info('Find the calibrated center axis info')
273 284
274 if not isinstance(nxroot, NXroot): 285 if not isinstance(nxroot, NXroot):
275 raise ValueError(f'Invalid parameter nxroot ({nxroot})') 286 raise ValueError(f'Invalid parameter nxroot ({nxroot})')
276 nxentry = nxroot[nxroot.attrs['default']] 287 nxentry = nxroot[nxroot.attrs['default']]
277 if not isinstance(nxentry, NXentry): 288 if not isinstance(nxentry, NXentry):
278 raise ValueError(f'Invalid nxentry ({nxentry})') 289 raise ValueError(f'Invalid nxentry ({nxentry})')
279 if self.galaxy_flag: 290 if self.galaxy_flag:
280 if center_rows is None: 291 if center_rows is not None:
281 raise ValueError(f'Missing parameter center_rows ({center_rows})') 292 center_rows = tuple(center_rows)
282 if not is_int_pair(center_rows): 293 if not is_int_pair(center_rows):
283 raise ValueError(f'Invalid parameter center_rows ({center_rows})') 294 raise ValueError(f'Invalid parameter center_rows ({center_rows})')
284 elif center_rows is not None: 295 elif center_rows is not None:
285 logging.warning(f'Ignoring parameter center_rows ({center_rows})') 296 logger.warning(f'Ignoring parameter center_rows ({center_rows})')
286 center_rows = None 297 center_rows = None
298 if self.galaxy_flag:
299 if center_stack_index is not None and not is_int(center_stack_index, ge=0):
300 raise ValueError(f'Invalid parameter center_stack_index ({center_stack_index})')
301 elif center_stack_index is not None:
302 logger.warning(f'Ignoring parameter center_stack_index ({center_stack_index})')
303 center_stack_index = None
287 304
288 # Create plot galaxy path directory and path if needed 305 # Create plot galaxy path directory and path if needed
289 if self.galaxy_flag: 306 if self.galaxy_flag:
290 if not os_path.exists('tomo_find_centers_plots'): 307 if not os_path.exists('tomo_find_centers_plots'):
291 mkdir('tomo_find_centers_plots') 308 mkdir('tomo_find_centers_plots')
296 # Check if reduced data is available 313 # Check if reduced data is available
297 if ('reduced_data' not in nxentry or 'reduced_data' not in nxentry.data): 314 if ('reduced_data' not in nxentry or 'reduced_data' not in nxentry.data):
298 raise KeyError(f'Unable to find valid reduced data in {nxentry}.') 315 raise KeyError(f'Unable to find valid reduced data in {nxentry}.')
299 316
300 # Select the image stack to calibrate the center axis 317 # Select the image stack to calibrate the center axis
301 # reduced data axes order: stack,row,theta,column 318 # reduced data axes order: stack,theta,row,column
302 # Note: Nexus cannot follow a link if the data it points to is too big, 319 # Note: Nexus cannot follow a link if the data it points to is too big,
303 # so get the data from the actual place, not from nxentry.data 320 # so get the data from the actual place, not from nxentry.data
304 num_tomo_stacks = nxentry.reduced_data.data.tomo_fields.shape[0] 321 tomo_fields_shape = nxentry.reduced_data.data.tomo_fields.shape
322 if len(tomo_fields_shape) != 4 or any(True for dim in tomo_fields_shape if not dim):
323 raise KeyError('Unable to load the required reduced tomography stack')
324 num_tomo_stacks = tomo_fields_shape[0]
305 if num_tomo_stacks == 1: 325 if num_tomo_stacks == 1:
306 center_stack_index = 0 326 center_stack_index = 0
307 center_stack = np.asarray(nxentry.reduced_data.data.tomo_fields[0])
308 if not center_stack.size:
309 raise KeyError('Unable to load the required reduced tomography stack')
310 default = 'n' 327 default = 'n'
311 else: 328 else:
312 if self.test_mode: 329 if self.test_mode:
313 center_stack_index = self.test_config['center_stack_index']-1 # make offset 0 330 center_stack_index = self.test_config['center_stack_index']-1 # make offset 0
331 elif self.galaxy_flag:
332 if center_stack_index is None:
333 center_stack_index = int(num_tomo_stacks/2)
334 if center_stack_index >= num_tomo_stacks:
335 raise ValueError(f'Invalid parameter center_stack_index ({center_stack_index})')
314 else: 336 else:
315 center_stack_index = input_int('\nEnter tomography stack index to calibrate the ' 337 center_stack_index = input_int('\nEnter tomography stack index to calibrate the '
316 'center axis', ge=0, le=num_tomo_stacks-1, default=int(num_tomo_stacks/2)) 338 'center axis', ge=1, le=num_tomo_stacks, default=int(1+num_tomo_stacks/2))
317 center_stack = \ 339 center_stack_index -= 1
318 np.asarray(nxentry.reduced_data.data.tomo_fields[center_stack_index])
319 if not center_stack.size:
320 raise KeyError('Unable to load the required reduced tomography stack')
321 default = 'y' 340 default = 'y'
322 341
323 # Get thetas (in degrees) 342 # Get thetas (in degrees)
324 thetas = np.asarray(nxentry.reduced_data.rotation_angle) 343 thetas = np.asarray(nxentry.reduced_data.rotation_angle)
325 344
329 nxentry.reduced_data.attrs['zoom_perc']) 348 nxentry.reduced_data.attrs['zoom_perc'])
330 else: 349 else:
331 eff_pixel_size = nxentry.instrument.detector.x_pixel_size 350 eff_pixel_size = nxentry.instrument.detector.x_pixel_size
332 351
333 # Get cross sectional diameter 352 # Get cross sectional diameter
334 cross_sectional_dim = center_stack.shape[2]*eff_pixel_size 353 cross_sectional_dim = tomo_fields_shape[3]*eff_pixel_size
335 logger.debug(f'cross_sectional_dim = {cross_sectional_dim}') 354 logger.debug(f'cross_sectional_dim = {cross_sectional_dim}')
336 355
337 # Determine center offset at sample row boundaries 356 # Determine center offset at sample row boundaries
338 logger.info('Determine center offset at sample row boundaries') 357 logger.info('Determine center offset at sample row boundaries')
339 358
340 # Lower row center 359 # Lower row center
341 # center_stack order: row,theta,column
342 if self.test_mode: 360 if self.test_mode:
343 lower_row = self.test_config['lower_row'] 361 lower_row = self.test_config['lower_row']
344 elif self.galaxy_flag: 362 elif self.galaxy_flag:
345 lower_row = min(center_rows) 363 if center_rows is None:
346 if not 0 <= lower_row < center_stack.shape[0]-1: 364 lower_row = 0
347 raise ValueError(f'Invalid parameter center_rows ({center_rows})') 365 else:
348 else: 366 lower_row = min(center_rows)
349 lower_row = select_one_image_bound(center_stack[:,0,:], 0, bound=0, 367 if not 0 <= lower_row < tomo_fields_shape[2]-1:
368 raise ValueError(f'Invalid parameter center_rows ({center_rows})')
369 else:
370 lower_row = select_one_image_bound(
371 nxentry.reduced_data.data.tomo_fields[center_stack_index,0,:,:], 0, bound=0,
350 title=f'theta={round(thetas[0], 2)+0}', 372 title=f'theta={round(thetas[0], 2)+0}',
351 bound_name='row index to find lower center', default=default) 373 bound_name='row index to find lower center', default=default, raise_error=True)
352 lower_center_offset = self._find_center_one_plane(center_stack[lower_row,:,:], lower_row, 374 logger.debug('Finding center...')
353 thetas, eff_pixel_size, cross_sectional_dim, path=path, num_core=self.num_core) 375 t0 = time()
376 lower_center_offset = self._find_center_one_plane(
377 #np.asarray(nxentry.reduced_data.data.tomo_fields[center_stack_index,:,lower_row,:]),
378 nxentry.reduced_data.data.tomo_fields[center_stack_index,:,lower_row,:],
379 lower_row, thetas, eff_pixel_size, cross_sectional_dim, path=path,
380 num_core=self.num_core)
381 logger.debug(f'... done in {time()-t0:.2f} seconds')
354 logger.debug(f'lower_row = {lower_row:.2f}') 382 logger.debug(f'lower_row = {lower_row:.2f}')
355 logger.debug(f'lower_center_offset = {lower_center_offset:.2f}') 383 logger.debug(f'lower_center_offset = {lower_center_offset:.2f}')
356 384
357 # Upper row center 385 # Upper row center
358 if self.test_mode: 386 if self.test_mode:
359 upper_row = self.test_config['upper_row'] 387 upper_row = self.test_config['upper_row']
360 elif self.galaxy_flag: 388 elif self.galaxy_flag:
361 upper_row = max(center_rows) 389 if center_rows is None:
362 if not lower_row < upper_row < center_stack.shape[0]: 390 upper_row = tomo_fields_shape[2]-1
363 raise ValueError(f'Invalid parameter center_rows ({center_rows})') 391 else:
364 else: 392 upper_row = max(center_rows)
365 upper_row = select_one_image_bound(center_stack[:,0,:], 0, 393 if not lower_row < upper_row < tomo_fields_shape[2]:
366 bound=center_stack.shape[0]-1, title=f'theta={round(thetas[0], 2)+0}', 394 raise ValueError(f'Invalid parameter center_rows ({center_rows})')
367 bound_name='row index to find upper center', default=default) 395 else:
368 upper_center_offset = self._find_center_one_plane(center_stack[upper_row,:,:], upper_row, 396 upper_row = select_one_image_bound(
369 thetas, eff_pixel_size, cross_sectional_dim, path=path, num_core=self.num_core) 397 nxentry.reduced_data.data.tomo_fields[center_stack_index,0,:,:], 0,
398 bound=tomo_fields_shape[2]-1, title=f'theta={round(thetas[0], 2)+0}',
399 bound_name='row index to find upper center', default=default, raise_error=True)
400 logger.debug('Finding center...')
401 t0 = time()
402 upper_center_offset = self._find_center_one_plane(
403 #np.asarray(nxentry.reduced_data.data.tomo_fields[center_stack_index,:,upper_row,:]),
404 nxentry.reduced_data.data.tomo_fields[center_stack_index,:,upper_row,:],
405 upper_row, thetas, eff_pixel_size, cross_sectional_dim, path=path,
406 num_core=self.num_core)
407 logger.debug(f'... done in {time()-t0:.2f} seconds')
370 logger.debug(f'upper_row = {upper_row:.2f}') 408 logger.debug(f'upper_row = {upper_row:.2f}')
371 logger.debug(f'upper_center_offset = {upper_center_offset:.2f}') 409 logger.debug(f'upper_center_offset = {upper_center_offset:.2f}')
372 del center_stack
373 410
374 center_config = {'lower_row': lower_row, 'lower_center_offset': lower_center_offset, 411 center_config = {'lower_row': lower_row, 'lower_center_offset': lower_center_offset,
375 'upper_row': upper_row, 'upper_center_offset': upper_center_offset} 412 'upper_row': upper_row, 'upper_center_offset': upper_center_offset}
376 if num_tomo_stacks > 1: 413 if num_tomo_stacks > 1:
377 center_config['center_stack_index'] = center_stack_index+1 # save as offset 1 414 center_config['center_stack_index'] = center_stack_index+1 # save as offset 1
407 # Check if reduced data is available 444 # Check if reduced data is available
408 if ('reduced_data' not in nxentry or 'reduced_data' not in nxentry.data): 445 if ('reduced_data' not in nxentry or 'reduced_data' not in nxentry.data):
409 raise KeyError(f'Unable to find valid reduced data in {nxentry}.') 446 raise KeyError(f'Unable to find valid reduced data in {nxentry}.')
410 447
411 # Create an NXprocess to store image reconstruction (meta)data 448 # Create an NXprocess to store image reconstruction (meta)data
412 # if 'reconstructed_data' in nxentry:
413 # logger.warning(f'Existing reconstructed data in {nxentry} will be overwritten.')
414 # del nxentry['reconstructed_data']
415 # if 'data' in nxentry and 'reconstructed_data' in nxentry.data:
416 # del nxentry.data['reconstructed_data']
417 nxprocess = NXprocess() 449 nxprocess = NXprocess()
418 450
419 # Get rotation axis rows and centers 451 # Get rotation axis rows and centers
420 lower_row = center_info.get('lower_row') 452 lower_row = center_info.get('lower_row')
421 lower_center_offset = center_info.get('lower_center_offset') 453 lower_center_offset = center_info.get('lower_center_offset')
428 460
429 # Get thetas (in degrees) 461 # Get thetas (in degrees)
430 thetas = np.asarray(nxentry.reduced_data.rotation_angle) 462 thetas = np.asarray(nxentry.reduced_data.rotation_angle)
431 463
432 # Reconstruct tomography data 464 # Reconstruct tomography data
433 # reduced data axes order: stack,row,theta,column 465 # reduced data axes order: stack,theta,row,column
434 # reconstructed data order in each stack: row/z,x,y 466 # reconstructed data order in each stack: row/z,x,y
435 # Note: Nexus cannot follow a link if the data it points to is too big, 467 # Note: Nexus cannot follow a link if the data it points to is too big,
436 # so get the data from the actual place, not from nxentry.data 468 # so get the data from the actual place, not from nxentry.data
437 if 'zoom_perc' in nxentry.reduced_data: 469 if 'zoom_perc' in nxentry.reduced_data:
438 res_title = f'{nxentry.reduced_data.attrs["zoom_perc"]}p' 470 res_title = f'{nxentry.reduced_data.attrs["zoom_perc"]}p'
440 res_title = 'fullres' 472 res_title = 'fullres'
441 load_error = False 473 load_error = False
442 num_tomo_stacks = nxentry.reduced_data.data.tomo_fields.shape[0] 474 num_tomo_stacks = nxentry.reduced_data.data.tomo_fields.shape[0]
443 tomo_recon_stacks = num_tomo_stacks*[np.array([])] 475 tomo_recon_stacks = num_tomo_stacks*[np.array([])]
444 for i in range(num_tomo_stacks): 476 for i in range(num_tomo_stacks):
477 # Convert reduced data stack from theta,row,column to row,theta,column
478 logger.debug(f'Reading reduced data stack {i+1}...')
479 t0 = time()
445 tomo_stack = np.asarray(nxentry.reduced_data.data.tomo_fields[i]) 480 tomo_stack = np.asarray(nxentry.reduced_data.data.tomo_fields[i])
446 if not tomo_stack.size: 481 logger.debug(f'... done in {time()-t0:.2f} seconds')
447 raise KeyError(f'Unable to load tomography stack {i} for reconstruction') 482 if len(tomo_stack.shape) != 3 or any(True for dim in tomo_stack.shape if not dim):
483 raise ValueError(f'Unable to load tomography stack {i+1} for reconstruction')
484 tomo_stack = np.swapaxes(tomo_stack, 0, 1)
485 assert(len(thetas) == tomo_stack.shape[1])
448 assert(0 <= lower_row < upper_row < tomo_stack.shape[0]) 486 assert(0 <= lower_row < upper_row < tomo_stack.shape[0])
449 center_offsets = [lower_center_offset-lower_row*center_slope, 487 center_offsets = [lower_center_offset-lower_row*center_slope,
450 upper_center_offset+(tomo_stack.shape[0]-1-upper_row)*center_slope] 488 upper_center_offset+(tomo_stack.shape[0]-1-upper_row)*center_slope]
451 t0 = time() 489 t0 = time()
452 logger.debug(f'Running _reconstruct_one_tomo_stack on {self.num_core} cores ...') 490 logger.debug(f'Running _reconstruct_one_tomo_stack on {self.num_core} cores ...')
453 tomo_recon_stack = self._reconstruct_one_tomo_stack(tomo_stack, thetas, 491 tomo_recon_stack = self._reconstruct_one_tomo_stack(tomo_stack, thetas,
454 center_offsets=center_offsets, num_core=self.num_core, algorithm='gridrec') 492 center_offsets=center_offsets, num_core=self.num_core, algorithm='gridrec')
455 logger.debug(f'... done in {time()-t0:.2f} seconds') 493 logger.debug(f'... done in {time()-t0:.2f} seconds')
456 logger.info(f'Reconstruction of stack {i} took {time()-t0:.2f} seconds') 494 logger.info(f'Reconstruction of stack {i+1} took {time()-t0:.2f} seconds')
457 495
458 # Combine stacks 496 # Combine stacks
459 tomo_recon_stacks[i] = tomo_recon_stack 497 tomo_recon_stacks[i] = tomo_recon_stack
460 498
461 # Resize the reconstructed tomography data 499 # Resize the reconstructed tomography data
495 533
496 # Plot a few reconstructed image slices 534 # Plot a few reconstructed image slices
497 if num_tomo_stacks == 1: 535 if num_tomo_stacks == 1:
498 basetitle = 'recon' 536 basetitle = 'recon'
499 else: 537 else:
500 basetitle = f'recon stack {i}' 538 basetitle = f'recon stack {i+1}'
501 for i, stack in enumerate(tomo_recon_stacks): 539 for i, stack in enumerate(tomo_recon_stacks):
502 title = f'{basetitle} {res_title} xslice{x_slice}' 540 title = f'{basetitle} {res_title} xslice{x_slice}'
503 quick_imshow(stack[z_range[0]:z_range[1],x_slice,y_range[0]:y_range[1]], 541 quick_imshow(stack[z_range[0]:z_range[1],x_slice,y_range[0]:y_range[1]],
504 title=title, path=path, save_fig=self.save_figs, save_only=self.save_only, 542 title=title, path=path, save_fig=self.save_figs, save_only=self.save_only,
505 block=self.block) 543 block=self.block)
548 nxentry_copy.data.makelink(nxprocess.data.reconstructed_data, name='reconstructed_data') 586 nxentry_copy.data.makelink(nxprocess.data.reconstructed_data, name='reconstructed_data')
549 nxentry_copy.data.attrs['signal'] = 'reconstructed_data' 587 nxentry_copy.data.attrs['signal'] = 'reconstructed_data'
550 588
551 return(nxroot_copy) 589 return(nxroot_copy)
552 590
553 def combine_data(self, nxroot): 591 def combine_data(self, nxroot, x_bounds=None, y_bounds=None):
554 """Combine the reconstructed tomography stacks. 592 """Combine the reconstructed tomography stacks.
555 """ 593 """
556 logger.info('Combine the reconstructed tomography stacks') 594 logger.info('Combine the reconstructed tomography stacks')
557 595
558 if not isinstance(nxroot, NXroot): 596 if not isinstance(nxroot, NXroot):
572 # Check if reconstructed image data is available 610 # Check if reconstructed image data is available
573 if ('reconstructed_data' not in nxentry or 'reconstructed_data' not in nxentry.data): 611 if ('reconstructed_data' not in nxentry or 'reconstructed_data' not in nxentry.data):
574 raise KeyError(f'Unable to find valid reconstructed image data in {nxentry}.') 612 raise KeyError(f'Unable to find valid reconstructed image data in {nxentry}.')
575 613
576 # Create an NXprocess to store combined image reconstruction (meta)data 614 # Create an NXprocess to store combined image reconstruction (meta)data
577 # if 'combined_data' in nxentry:
578 # logger.warning(f'Existing combined data in {nxentry} will be overwritten.')
579 # del nxentry['combined_data']
580 # if 'data' in nxentry 'combined_data' in nxentry.data:
581 # del nxentry.data['combined_data']
582 nxprocess = NXprocess() 615 nxprocess = NXprocess()
583 616
584 # Get the reconstructed data 617 # Get the reconstructed data
585 # reconstructed data order: stack,row(z),x,y 618 # reconstructed data order: stack,row(z),x,y
586 # Note: Nexus cannot follow a link if the data it points to is too big, 619 # Note: Nexus cannot follow a link if the data it points to is too big,
587 # so get the data from the actual place, not from nxentry.data 620 # so get the data from the actual place, not from nxentry.data
588 tomo_recon_stacks = np.asarray(nxentry.reconstructed_data.data.reconstructed_data) 621 num_tomo_stacks = nxentry.reconstructed_data.data.reconstructed_data.shape[0]
589 num_tomo_stacks = tomo_recon_stacks.shape[0]
590 if num_tomo_stacks == 1: 622 if num_tomo_stacks == 1:
591 return(nxroot) 623 logger.info('Only one stack available: leaving combine_data')
624 return(None)
625
626 # Combine the reconstructed stacks
627 # (load one stack at a time to reduce risk of hitting Nexus data access limit)
592 t0 = time() 628 t0 = time()
593 logger.debug(f'Combining the reconstructed stacks ...') 629 logger.debug(f'Combining the reconstructed stacks ...')
594 tomo_recon_combined = tomo_recon_stacks[0,:,:,:] 630 tomo_recon_combined = np.asarray(nxentry.reconstructed_data.data.reconstructed_data[0])
595 if num_tomo_stacks > 2: 631 if num_tomo_stacks > 2:
596 tomo_recon_combined = np.concatenate([tomo_recon_combined]+ 632 tomo_recon_combined = np.concatenate([tomo_recon_combined]+
597 [tomo_recon_stacks[i,:,:,:] for i in range(1, num_tomo_stacks-1)]) 633 [nxentry.reconstructed_data.data.reconstructed_data[i]
634 for i in range(1, num_tomo_stacks-1)])
598 if num_tomo_stacks > 1: 635 if num_tomo_stacks > 1:
599 tomo_recon_combined = np.concatenate([tomo_recon_combined]+ 636 tomo_recon_combined = np.concatenate([tomo_recon_combined]+
600 [tomo_recon_stacks[num_tomo_stacks-1,:,:,:]]) 637 [nxentry.reconstructed_data.data.reconstructed_data[num_tomo_stacks-1]])
601 logger.debug(f'... done in {time()-t0:.2f} seconds') 638 logger.debug(f'... done in {time()-t0:.2f} seconds')
602 logger.info(f'Combining the reconstructed stacks took {time()-t0:.2f} seconds') 639 logger.info(f'Combining the reconstructed stacks took {time()-t0:.2f} seconds')
603 640
604 # Resize the combined tomography data set 641 # Resize the combined tomography data stacks
605 # combined data order: row/z,x,y 642 # combined data order: row/z,x,y
606 if self.test_mode: 643 if self.test_mode:
607 x_bounds = None 644 x_bounds = None
608 y_bounds = None 645 y_bounds = None
609 z_bounds = self.test_config.get('z_bounds') 646 z_bounds = self.test_config.get('z_bounds')
610 elif self.galaxy_flag: 647 elif self.galaxy_flag:
611 exit('TODO')
612 if x_bounds is not None and not is_int_pair(x_bounds, ge=0, 648 if x_bounds is not None and not is_int_pair(x_bounds, ge=0,
613 lt=tomo_recon_stacks[0].shape[1]): 649 lt=tomo_recon_stacks[0].shape[1]):
614 raise ValueError(f'Invalid parameter x_bounds ({x_bounds})') 650 raise ValueError(f'Invalid parameter x_bounds ({x_bounds})')
615 if y_bounds is not None and not is_int_pair(y_bounds, ge=0, 651 if y_bounds is not None and not is_int_pair(y_bounds, ge=0,
616 lt=tomo_recon_stacks[0].shape[1]): 652 lt=tomo_recon_stacks[0].shape[1]):
697 dark_field_scans = nxentry.spec_scans.dark_field 733 dark_field_scans = nxentry.spec_scans.dark_field
698 dark_field = FlatField.construct_from_nxcollection(dark_field_scans) 734 dark_field = FlatField.construct_from_nxcollection(dark_field_scans)
699 prefix = str(nxentry.instrument.detector.local_name) 735 prefix = str(nxentry.instrument.detector.local_name)
700 tdf_stack = dark_field.get_detector_data(prefix) 736 tdf_stack = dark_field.get_detector_data(prefix)
701 if isinstance(tdf_stack, list): 737 if isinstance(tdf_stack, list):
702 exit('TODO') 738 assert(len(tdf_stack) == 1) # TODO
739 tdf_stack = tdf_stack[0]
703 740
704 # Take median 741 # Take median
705 if tdf_stack.ndim == 2: 742 if tdf_stack.ndim == 2:
706 tdf = tdf_stack 743 tdf = tdf_stack
707 elif tdf_stack.ndim == 3: 744 elif tdf_stack.ndim == 3:
755 bright_field_scans = nxentry.spec_scans.bright_field 792 bright_field_scans = nxentry.spec_scans.bright_field
756 bright_field = FlatField.construct_from_nxcollection(bright_field_scans) 793 bright_field = FlatField.construct_from_nxcollection(bright_field_scans)
757 prefix = str(nxentry.instrument.detector.local_name) 794 prefix = str(nxentry.instrument.detector.local_name)
758 tbf_stack = bright_field.get_detector_data(prefix) 795 tbf_stack = bright_field.get_detector_data(prefix)
759 if isinstance(tbf_stack, list): 796 if isinstance(tbf_stack, list):
760 exit('TODO') 797 assert(len(tbf_stack) == 1) # TODO
798 tbf_stack = tbf_stack[0]
761 799
762 # Take median if more than one image 800 # Take median if more than one image
763 """Median or mean: It may be best to try the median because of some image 801 """Median or mean: It may be best to try the median because of some image
764 artifacts that arise due to crinkles in the upstream kapton tape windows 802 artifacts that arise due to crinkles in the upstream kapton tape windows
765 causing some phase contrast images to appear on the detector. 803 causing some phase contrast images to appear on the detector.
816 if image_key and 'data' in nxentry.instrument.detector: 854 if image_key and 'data' in nxentry.instrument.detector:
817 field_indices = [index for index, key in enumerate(image_key) if key == 0] 855 field_indices = [index for index, key in enumerate(image_key) if key == 0]
818 first_image = np.asarray(nxentry.instrument.detector.data[field_indices[0],:,:]) 856 first_image = np.asarray(nxentry.instrument.detector.data[field_indices[0],:,:])
819 theta = float(nxentry.sample.rotation_angle[field_indices[0]]) 857 theta = float(nxentry.sample.rotation_angle[field_indices[0]])
820 z_translation_all = nxentry.sample.z_translation[field_indices] 858 z_translation_all = nxentry.sample.z_translation[field_indices]
821 z_translation_levels = sorted(list(set(z_translation_all))) 859 vertical_shifts = sorted(list(set(z_translation_all)))
822 num_tomo_stacks = len(z_translation_levels) 860 num_tomo_stacks = len(vertical_shifts)
823 else: 861 else:
824 tomo_field_scans = nxentry.spec_scans.tomo_fields 862 tomo_field_scans = nxentry.spec_scans.tomo_fields
825 tomo_fields = TomoField.construct_from_nxcollection(tomo_field_scans) 863 tomo_fields = TomoField.construct_from_nxcollection(tomo_field_scans)
826 vertical_shifts = tomo_fields.get_vertical_shifts() 864 vertical_shifts = tomo_fields.get_vertical_shifts()
827 if not isinstance(vertical_shifts, list): 865 if not isinstance(vertical_shifts, list):
1110 tomo_stack = tomo_stack.astype('float32') 1148 tomo_stack = tomo_stack.astype('float32')
1111 if not self.test_mode: 1149 if not self.test_mode:
1112 if len(tomo_stacks) == 1: 1150 if len(tomo_stacks) == 1:
1113 title = f'red fullres theta {round(thetas[0], 2)+0}' 1151 title = f'red fullres theta {round(thetas[0], 2)+0}'
1114 else: 1152 else:
1115 title = f'red stack {i} fullres theta {round(thetas[0], 2)+0}' 1153 title = f'red stack {i+1} fullres theta {round(thetas[0], 2)+0}'
1116 quick_imshow(tomo_stack[0,:,:], title=title, path=path, save_fig=self.save_figs, 1154 quick_imshow(tomo_stack[0,:,:], title=title, path=path, save_fig=self.save_figs,
1117 save_only=self.save_only, block=self.block) 1155 save_only=self.save_only, block=self.block)
1118 # if not self.block: 1156 # if not self.block:
1119 # clear_imshow(title) 1157 # clear_imshow(title)
1120 if False and zoom_perc != 100: 1158 if False and zoom_perc != 100:
1133 quick_imshow(tomo_stack[0,:,:], title=title, path=path, save_fig=self.save_figs, 1171 quick_imshow(tomo_stack[0,:,:], title=title, path=path, save_fig=self.save_figs,
1134 save_only=self.save_only, block=self.block) 1172 save_only=self.save_only, block=self.block)
1135 # if not self.block: 1173 # if not self.block:
1136 # clear_imshow(title) 1174 # clear_imshow(title)
1137 1175
1138 # Convert tomography stack from theta,row,column to row,theta,column
1139 t0 = time()
1140 tomo_stack = np.swapaxes(tomo_stack, 0, 1)
1141 logger.debug(f'Converting coordinate order took {time()-t0:.2f} seconds')
1142
1143 # Save test data to file 1176 # Save test data to file
1144 if self.test_mode: 1177 if self.test_mode:
1145 row_index = int(tomo_stack.shape[0]/2) 1178 # row_index = int(tomo_stack.shape[0]/2)
1146 np.savetxt(f'{self.output_folder}/red_stack_{i+1}.txt', tomo_stack[row_index,:,:], 1179 # np.savetxt(f'{self.output_folder}/red_stack_{i+1}.txt', tomo_stack[row_index,:,:],
1180 # fmt='%.6e')
1181 row_index = int(tomo_stack.shape[1]/2)
1182 np.savetxt(f'{self.output_folder}/red_stack_{i+1}.txt', tomo_stack[:,row_index,:],
1147 fmt='%.6e') 1183 fmt='%.6e')
1148 1184
1149 # Combine resized stacks 1185 # Combine resized stacks
1150 reduced_tomo_stacks.append(tomo_stack) 1186 reduced_tomo_stacks.append(tomo_stack)
1151 1187
1166 """Find center for a single tomography plane. 1202 """Find center for a single tomography plane.
1167 """ 1203 """
1168 # Try automatic center finding routines for initial value 1204 # Try automatic center finding routines for initial value
1169 # sinogram index order: theta,column 1205 # sinogram index order: theta,column
1170 # need column,theta for iradon, so take transpose 1206 # need column,theta for iradon, so take transpose
1207 sinogram = np.asarray(sinogram)
1171 sinogram_T = sinogram.T 1208 sinogram_T = sinogram.T
1172 center = sinogram.shape[1]/2 1209 center = sinogram.shape[1]/2
1173 1210
1174 # Try using Nghia Vo’s method 1211 # Try using Nghia Vo’s method
1175 t0 = time() 1212 t0 = time()
1497 # 'exclusive)') 1534 # 'exclusive)')
1498 # accept = input_yesno('Accept these bounds (y/n)?', 'y') 1535 # accept = input_yesno('Accept these bounds (y/n)?', 'y')
1499 accept = True 1536 accept = True
1500 logger.debug(f'y_bounds = {y_bounds}') 1537 logger.debug(f'y_bounds = {y_bounds}')
1501 1538
1502 # Selecting z bounds (in xy-plane) (only valid for a single image set) 1539 # Selecting z bounds (in xy-plane) (only valid for a single image stack)
1503 if num_tomo_stacks != 1: 1540 if num_tomo_stacks != 1:
1504 z_bounds = None 1541 z_bounds = None
1505 else: 1542 else:
1506 tomosum = 0 1543 tomosum = 0
1507 [tomosum := tomosum+np.sum(tomo_recon_stacks[i], axis=(1,2)) 1544 [tomosum := tomosum+np.sum(tomo_recon_stacks[i], axis=(1,2))
1546 logger.info(f'output_folder = {output_folder}') 1583 logger.info(f'output_folder = {output_folder}')
1547 logger.info(f'save_figs = {save_figs}') 1584 logger.info(f'save_figs = {save_figs}')
1548 logger.info(f'test_mode = {test_mode}') 1585 logger.info(f'test_mode = {test_mode}')
1549 1586
1550 # Check for correction modes 1587 # Check for correction modes
1588 legal_modes = ['reduce_data', 'find_center', 'reconstruct_data', 'combine_data', 'all']
1551 if modes is None: 1589 if modes is None:
1552 modes = ['all'] 1590 modes = ['all']
1553 logger.debug(f'modes {type(modes)} = {modes}') 1591 if not all(True if mode in legal_modes else False for mode in modes):
1592 raise ValueError(f'Invalid parameter modes ({modes})')
1554 1593
1555 # Instantiate Tomo object 1594 # Instantiate Tomo object
1556 tomo = Tomo(num_core=num_core, output_folder=output_folder, save_figs=save_figs, 1595 tomo = Tomo(num_core=num_core, output_folder=output_folder, save_figs=save_figs,
1557 test_mode=test_mode) 1596 test_mode=test_mode)
1558 1597
1579 # Combine reconstructed tomography stacks 1618 # Combine reconstructed tomography stacks
1580 if 'combine_data' in modes or 'all' in modes: 1619 if 'combine_data' in modes or 'all' in modes:
1581 data = tomo.combine_data(data) 1620 data = tomo.combine_data(data)
1582 1621
1583 # Write output file 1622 # Write output file
1584 if not test_mode: 1623 if data is not None and not test_mode:
1585 if center_data is None: 1624 if center_data is None:
1586 data = tomo.write(data, output_file) 1625 data = tomo.write(data, output_file)
1587 else: 1626 else:
1588 data = tomo.write(center_data, output_file) 1627 data = tomo.write(center_data, output_file)
1589 1628
1629 logger.info(f'Completed modes: {modes}')