Source code for tomolog_cli.tomolog_32id

# #########################################################################
# Copyright (c) 2022, UChicago Argonne, LLC. All rights reserved.         #
#                                                                         #
# Copyright 2022. UChicago Argonne, LLC. This software was produced       #
# under U.S. Government contract DE-AC02-06CH11357 for Argonne National   #
# Laboratory (ANL), which is operated by UChicago Argonne, LLC for the    #
# U.S. Department of Energy. The U.S. Government has rights to use,       #
# reproduce, and distribute this software.  NEITHER THE GOVERNMENT NOR    #
# UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR        #
# ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.  If software is     #
# modified to produce derivative works, such modified software should     #
# be clearly marked, so as not to confuse it with the version available   #
# from ANL.                                                               #
#                                                                         #
# Additionally, redistribution and use in source and binary forms, with   #
# or without modification, are permitted provided that the following      #
# conditions are met:                                                     #
#                                                                         #
#     * Redistributions of source code must retain the above copyright    #
#       notice, this list of conditions and the following disclaimer.     #
#                                                                         #
#     * Redistributions in binary form must reproduce the above copyright #
#       notice, this list of conditions and the following disclaimer in   #
#       the documentation and/or other materials provided with the        #
#       distribution.                                                     #
#                                                                         #
#     * Neither the name of UChicago Argonne, LLC, Argonne National       #
#       Laboratory, ANL, the U.S. Government, nor the names of its        #
#       contributors may be used to endorse or promote products derived   #
#       from this software without specific prior written permission.     #
#                                                                         #
# THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS     #
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT       #
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS       #
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago     #
# Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,        #
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,    #
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;        #
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER        #
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT      #
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN       #
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE         #
# POSSIBILITY OF SUCH DAMAGE.                                             #
# #########################################################################

import os
import uuid
import pathlib
import meta
import h5py
import numpy as np
import matplotlib
matplotlib.use('Agg')  # use non-GUI backend before importing pyplot
import matplotlib.pyplot as plt
from matplotlib_scalebar.scalebar import ScaleBar
from mpl_toolkits.axes_grid1 import make_axes_locatable

from tomolog_cli import utils
from tomolog_cli import log
from tomolog_cli import TomoLog

__author__ = "Viktor Nikitin,  Francesco De Carlo"
__copyright__ = "Copyright (c) 2022, UChicago Argonne, LLC."
__docformat__ = 'restructuredtext en'
__all__ = ['TomoLog32ID', ]

FILE_NAME_PROJ1 = 'projection_google1.jpg'


[docs] class TomoLog32ID(TomoLog): ''' Class to publish experiment meta data, tomography projection and reconstruction on a google slide document. ''' def __init__(self, args): super().__init__(args) self.energy_key = '/measurement/instrument/monochromator/energy' self.sample_in_x_key = '/process/acquisition/flat_fields/sample/in_x' self.phase_ring_setup_y_key = '/measurement/instrument/phase_ring/setup/y' self.binning_rec = -1 self.nct_resolution = -1 self.mct_resolution = -1 self.double_fov = False self.file_name_proj1 = FILE_NAME_PROJ1
[docs] def publish_descr(self, presentation_id, page_id): descr = super().publish_descr(presentation_id, page_id) # add here beamline dependent bullets descr += self.read_meta_item( "Scan energy: {self.meta[self.energy_key][0]} {self.meta[self.energy_key][1]}") descr = descr[:-1] self.google_slide.create_textbox_with_bullets( presentation_id, page_id, descr, 240, 120, 0, 18, 8, 0)
[docs] def run_log(self): # read meta, calculate resolutions mp = meta.read_meta.Hdf5MetadataReader(self.args.file_name) self.meta = mp.readMetadata() mp.close() if (self.meta[self.sample_in_x_key][0] != 0): self.double_fov = True log.warning('Sample in x is off center: %s. Handling the data set as a double FOV' % self.meta[self.sample_in_x_key][0]) self.nct_resolution = float(self.meta[self.resolution_key][0])/1000 self.mct_resolution = float(self.meta[self.pixel_size_key][0])# / float( #self.meta[self.magnification_key][0].replace("x", "")) presentation_id, page_id = self.init_slide() self.publish_descr(presentation_id, page_id) proj = self.read_raw() recon = self.read_recon() self.publish_proj(presentation_id, page_id, proj) self.publish_recon(presentation_id, page_id, recon)
[docs] def read_raw(self): proj = [] with h5py.File(self.args.file_name) as fid: if self.double_fov == True: log.warning('Data read: Handling the data set as a double FOV') image_0 = np.flip(fid['exchange/data'][0][:], axis=1) image_1 = fid['exchange/data'][-1][:] data = np.hstack((image_0, image_1)) else: data = fid['exchange/data'][0][:] proj.append(data) log.info('Reading CT projection') try: proj.append(fid['exchange/data2'][:]) log.info('Reading microCT projection') except: pass return proj
[docs] def read_recon(self): width = int(self.meta[self.width_key][0]) # temp height = int(self.meta[self.height_key][0]) recon = [] try: if self.args.save_format == 'h5': fname = os.path.dirname(self.args.file_name)+'_rec/'+os.path.basename(self.args.file_name)[:-3]+'_rec.h5' with h5py.File(fname,'r') as fid: data = fid['exchange/recon'] h,w = data.shape[:2] if self.args.idz == -1: self.args.idz = int(h//2) if self.args.idy == -1: self.args.idy = int(w//2) if self.args.idx == -1: self.args.idx = int(w//2) if self.double_fov == True: binning_rec = np.log2(width//(w//2)) else: binning_rec = np.log2(width//(w)) x = data[:,:,self.args.idx] y = data[:,self.args.idy] z = data[self.args.idz] else: basename = os.path.basename(self.args.file_name)[:-3] dirname = os.path.dirname(self.args.file_name) # set the correct prefix to find the reconstructions rec_prefix = 'recon' top = os.path.join(dirname+'_rec', basename+'_rec') tiff_file_list = sorted( list(filter(lambda x: x.endswith(('.tif', '.tiff')), os.listdir(top)))) z_start = int(tiff_file_list[0].split('.')[0].split('_')[1]) z_end = int(tiff_file_list[-1].split('.')[0].split('_')[1]) + 1 height = z_end-z_start fname_tmp = os.path.join(top, tiff_file_list[0]) # take size tmp = utils.read_tiff(fname_tmp).copy() if self.double_fov == True: width = width * 2 binning_rec = 1 else: binning_rec = width//tmp.shape[0] w = width//binning_rec h = height #tmp binning_rec = 1 w = tmp.shape[-1] if self.args.idz == -1: self.args.idz = int(h//2) if self.args.idy == -1: self.args.idy = int(w//2) if self.args.idx == -1: self.args.idx = int(w//2) if self.double_fov == True: binning_rec = np.log2(width//(w//2)) else: binning_rec = np.log2(width//w) z = utils.read_tiff( f'{dirname}_rec/{basename}_rec/{rec_prefix}_{self.args.idz:05}.tiff').copy() # read x,y slices by lines y = np.zeros((h, w), dtype='float32') x = np.zeros((h, w), dtype='float32') for j in range(z_start, z_end): zz = utils.read_tiff( f'{dirname}_rec/{basename}_rec/{rec_prefix}_{j:05}.tiff') y[j-z_start, :] = zz[self.args.idy] x[j-z_start, :] = zz[:, self.args.idx] # check if inversion is needed for the phase-contrast imaging at 32id phase_ring_y = float(self.meta[self.phase_ring_setup_y_key][0]) coeff_rec = 1 if abs(phase_ring_y) < 1e-2: coeff_rec = -1 recon = [coeff_rec*x, coeff_rec*y, coeff_rec*z] self.binning_rec = binning_rec log.info('Adding reconstruction') except ZeroDivisionError: log.error( 'Reconstructions for %s are larger than raw data image width. This is the case in a 0-360. Please use: --double-fov' % top) log.warning('Skipping reconstruction') except: log.warning('Skipping reconstruction') return recon
[docs] def read_rec_line(self): try: if self.args.save_format == 'h5': fname = os.path.dirname(self.args.file_name)+'_rec/'+os.path.basename(self.args.file_name)[:-3]+'_rec.h5' with h5py.File(fname,'r') as fid: line = fid.attrs['rec_line'].decode("utf-8") else: basename = os.path.basename(self.args.file_name)[:-3] dirname = os.path.dirname(self.args.file_name) with open(f'{dirname}_rec/{basename}_rec/rec_line.txt', 'r') as fid: line = fid.readlines()[0] except: log.warning('Skipping the command line for reconstruction') line = ' ' return line
[docs] def publish_proj(self, presentation_id, page_id, proj): # 32-id datasets may include both nanoCT and microCT data as proj[0] and proj[1] respectively log.info('Transmission X-Ray Microscope Instrument') log.info('Plotting nanoCT projection') self.plot_projection(proj[0], self.file_name_proj0) proj_url = self.google_drive.upload_or_update_file(self.file_name_proj0, 'image/jpeg', self.args.parent_folder_id) self.google_slide.create_image( presentation_id, page_id, proj_url, 170, 170, 0, 145) self.google_slide.create_textbox_with_text( presentation_id, page_id, 'Nano-CT projection', 90, 20, 10, 155, 8, 0) try: log.info('Plotting microCT projection') self.plot_projection(proj[1], self.file_name_proj1,scalebar='micro') proj_url = self.google_drive.upload_or_update_file(self.file_name_proj1, 'image/jpeg', self.args.parent_folder_id) self.google_slide.create_image( presentation_id, page_id, proj_url, 170, 170, 0, 270) self.google_slide.create_textbox_with_text( presentation_id, page_id, 'Micro-CT projection', 90, 20, 10, 280, 8, 0) except: log.warning('No microCT data available')
[docs] def publish_recon(self, presentation_id, page_id, recon): if len(recon) == 3: # publish reconstructions self.plot_recon(recon, self.file_name_recon) recon_url = self.dbx.upload(self.file_name_recon) rec_line = self.read_rec_line() self.google_slide.create_image( presentation_id, page_id, recon_url, 370, 370, 130, 25) self.google_slide.create_textbox_with_text( presentation_id, page_id, 'Reconstruction', 90, 20, 270, 0, 10, 0) self.google_slide.create_textbox_with_text( presentation_id, page_id, rec_line, 1000, 20, 185, 391, 6, 0)
[docs] def plot_projection(self, proj, fname,scalebar='nano'): # auto-adjust colorbar values according to a histogram mmin, mmax = utils.find_min_max(proj) proj[proj > mmax] = mmax proj[proj < mmin] = mmin # plot fig = plt.figure(constrained_layout=True, figsize=(6, 4)) ax = fig.add_subplot() im = ax.imshow(proj, cmap='gray') # Create scale bar if scalebar=='nano': scalebar = ScaleBar(self.nct_resolution, "um", length_fraction=0.25) else: scalebar = ScaleBar(self.mct_resolution, "um", length_fraction=0.25) ax.add_artist(scalebar) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.1) plt.colorbar(im, cax=cax) # plt.show() # save plt.savefig(fname, bbox_inches='tight', pad_inches=0, dpi=300) plt.cla() plt.close(fig)
[docs] def plot_recon(self, recon, fname): fig = plt.figure(constrained_layout=True, figsize=(6, 12)) grid = fig.add_gridspec(3, 1, height_ratios=[1, 1, 1]) slices = ['x', 'y', 'z'] # autoadjust colorbar values according to a histogram if self.args.min == self.args.max: self.args.min, self.args.max = utils.find_min_max( np.concatenate(recon)) sl = [self.args.idx, self.args.idy, self.args.idz] for k in range(3): recon[k][0, 0] = self.args.max recon[k][0, 1] = self.args.min recon[k][recon[k] > self.args.max] = self.args.max recon[k][recon[k] < self.args.min] = self.args.min ax = fig.add_subplot(grid[k]) im = ax.imshow(recon[k], cmap='gray') # Create scale bar scalebar = ScaleBar(self.nct_resolution * 2**self.binning_rec, "um", length_fraction=0.25) ax.add_artist(scalebar) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.1) plt.colorbar(im, cax=cax) ax.set_ylabel(f'slice {slices[k]}={sl[k]}', fontsize=14) # save plt.savefig(fname, bbox_inches='tight', pad_inches=0, dpi=300) plt.cla() plt.close(fig)