Mercurial > repos > rv43 > tomo
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}') | 
