Ejemplo n.º 1
0
def colorwheel_RGB(rad):
    """Makes a 4-quadrant RGB color-wheel as a np array to be inserted into the color-image
    
    Args:
        rad (int): (`optional`) Radius of color-wheel in pixels. (default None -> height/16)
            Set rad = 0 to remove color-wheel. 

    Returns:
        ``ndarray``: Numpy array of shape (2*rad, 2*rad). 
    """

    # make black -> white gradients
    dim = rad * 2
    grad_x = np.array([np.arange(dim) - rad for _ in range(dim)]) / rad
    grad_y = grad_x.T

    # make the binary mask
    tr = dist(dim, dim, shift=True) * dim
    circ = np.where(tr > rad, 0, 1)

    # magnitude of RGB values (equiv to value in HSV)
    bmag = np.sqrt((grad_x**2 + grad_y**2))

    # remove 0 to divide, make other grad distributions
    bmag = np.where(bmag != 0, bmag, 1)
    cang = grad_x / bmag
    sang = np.sqrt(1 - cang * cang)

    # define the quadrants
    q1 = ((grad_x >= 0) * (grad_y <= 0)).astype(int)
    q2 = ((grad_x < 0) * (grad_y < 0)).astype(int)
    q3 = ((grad_x <= 0) * (grad_y >= 0)).astype(int)
    q4 = ((grad_x > 0) * (grad_y > 0)).astype(int)

    # Apply to colors
    green = q1 * bmag * np.abs(sang)
    green = green + q2 * bmag
    green = green + q3 * bmag * np.abs(cang)

    red = q1 * bmag
    red = red + q2 * bmag * np.abs(sang)
    red = red + q4 * bmag * np.abs(cang)

    blue = (q3 + q4) * bmag * np.abs(sang)

    # apply masks
    green = green * circ
    red = red * circ
    blue = blue * circ

    # stack into one image and fix center from divide by 0 error
    cwheel = np.dstack((red, green, blue))
    cwheel[rad, rad] = [0, 0, 0]
    cwheel = np.array(cwheel / np.max(cwheel))
    return cwheel
Ejemplo n.º 2
0
def sim_images(mphi=None, ephi=None, pscope=None, isl_shape=None, del_px=1, 
    def_val=0, add_random=False, save_path=None, save_name=None,
    isl_thk=20, isl_xip0=50, mem_thk=50, mem_xip0=1000, v=1, Filter=True):
    """Simulate LTEM images for a given electron phase shift through a sample. 

    This function returns LTEM images simulated for in-focus and at +/- def_val 
    for comparison to experimental data and reconstruction. 

    It was primarily written for simulating images of magnetic island 
    structures, and as such the sample is defined in two parts: a uniform 
    support membrane across the region and islands of magnetic material defined
    by an array isl_shape. The magnetization is defined with 2D arrays 
    corresponding to the x- and y-components of the magnetization vector. 

    There are many required parameters here that must be set to account for the 
    support membrane. The default values apply to 20nm permalloy islands on a 
    50nm SiN membrane window. 

    There is a known bug where sharp edges in the ephi creates problems with the
    image simulations. As a workaround, this function applies a light gaussian 
    filter (sigma = 1 pixel) to the ephi and isl_shape arrays. This can be 
    controlled with the ``filter`` argument. 

    Args:
        mphi (2D array): Numpy array of size (M, N), magnetic phase shift
        ephi (2D array): Numpy array of size (M, N), Electrostatic phase shift
        pscope (``Microscope`` object): Contains all microscope parameters
            as well as methods for propagating electron wave functions. 
        isl_shape (2D/3D array): Array of size (z,y,x) or (y,x). If 2D the 
            thickness will be taken as the isl_shape values multiplied by 
            isl_thickness. If 3D, the isl_shape array will be summed along 
            the z-axis becoming and multiplied by isl_thickness.  
            (Default) None -> uniform flat material with thickness = isl_thk. 
        del_px (Float): Scale factor (nm/pixel). Default = 1. 
        def_val (Float): The defocus values at which to calculate the images.
        add_random (Float): Whether or not to add amorphous background to
            the simulation. True or 1 will add a default background, any 
            other value will be multiplied to scale the additional phase term. 
        save_path: String. Will save a stack [underfocus, infocus, overfocus] 
            as well as (mphi+ephi) as tiffs along with a params.text file. 
            (Default) None -> Does not save. 
        save_name (str): Name prepended to saved files. 
        v (int): Verbosity. Set v=0 to suppress print statements. 
        isl_thk (float): Island thickness (nm). Default 20 (nm). 
        isl_xip0 (float): Island extinction distance (nm). Default 50 (nm). 
        mem_thk (float): Support membrane thickness (nm). Default 50 (nm). 
        mem_xip0 (float): Support membrane extinction distance (nm). Default 
            1000 (nm). 
        Filter (Bool): Apply a light gaussian filter to ephi and isl_shape. 

    Returns: 
        tuple: (Tphi, im_un, im_in, im_ov)

        - Tphi (`2D array`) -- Numpy array of size (M,N). Total electron phase shift (ephi+mphi).
        - im_un (`2D array`) -- Numpy array of size (M,N). Simulated image at delta z = -def_val.
        - im_in (`2D array`) -- Numpy array of size (M,N). Simulated image at zero defocus.
        - im_ov (`2D array`) -- Numpy array of size (M,N). Simulated image at delta z = +def_val.

    """
    vprint = print if v>=1 else lambda *a, **k: None
    
    if Filter:
        ephi = gaussian_filter(ephi, sigma=1)

    Tphi = mphi + ephi
    vprint(f'Total fov is ({np.shape(Tphi)[1]*del_px:.3g},{np.shape(Tphi)[0]*del_px:.3g}) nm')
    dy, dx = Tphi.shape

    if add_random:
        if type(add_random) == bool:
            add_random = 1.0
        ran_phi = np.random.uniform(low = -np.pi/128*add_random,
                                    high = np.pi/128*add_random,
                                    size=[dy,dx])
        if np.max(ephi) > 1: # scale by ephi only if it's given and relevant
            ran_phi *= np.max(ephi)
        Tphi += ran_phi

    #amplitude
    if isl_shape is None:
        thk_map = np.ones(Tphi.shape)*isl_thk
    else:
        if type(isl_shape) != np.ndarray:
            isl_shape = np.array(isl_shape)
        if isl_shape.ndim == 3:
            thk_map = np.sum(isl_shape, axis=0)*isl_thk
        elif isl_shape.ndim == 2:
            thk_map = isl_shape*isl_thk
        else:
            vprint(textwrap.dedent(f"""
                Mask given must be 2D (y,x) or 3D (z,y,x) array. 
                It was given as a {isl_shape.ndim} dimension array."""))
            sys.exit(1)
        if Filter:
            thk_map = gaussian_filter(thk_map, sigma=1)

    Amp = np.exp((-np.ones([dy,dx]) * mem_thk / mem_xip0) - (thk_map / isl_xip0))
    ObjWave = Amp * (np.cos(Tphi) + 1j * np.sin(Tphi))

    # compute unflipped images
    qq = dist(dy, dx, shift=True)
    pscope.defocus = 0.0
    im_in = pscope.getImage(ObjWave,qq,del_px)
    pscope.defocus = -def_val
    im_un = pscope.getImage(ObjWave,qq,del_px)
    pscope.defocus = def_val
    im_ov = pscope.getImage(ObjWave,qq,del_px)
    
    if save_path is not None:
        vprint(f'saving to {save_path}')
        im_stack = np.array([im_un, im_in, im_ov])
        if not os.path.exists(save_path):
                os.makedirs(save_path)
        res = 1/del_px
        tifffile.imsave(os.path.join(save_path, f'{save_name}_align.tiff'), im_stack.astype('float32'), 
            imagej = True,
            resolution = (res, res),
            metadata={'unit': 'nm'})
        tifffile.imsave(os.path.join(save_path, f'{save_name}_phase.tiff'), Tphi.astype('float32'), 
            imagej = True,
            resolution = (res, res),
            metadata={'unit': 'nm'})

        with io.open(os.path.join(save_path, f'{save_name}_params.txt'), 'w') as text:
            text.write(f"def_val (nm) \t{def_val:g}\n")
            text.write(f"del_px (nm/pix) \t{del_px:g}\n") 
            text.write(f"scope En. (V) \t{pscope.E:g}\n")        
            text.write(f"im_size (pix) \t({dy:g},{dx:g})\n")
            text.write(f"sample_thk (nm) \t{isl_thk:g}\n") 
            text.write(f"sample_xip0 (nm) \t{isl_xip0:g}\n") 
            text.write(f"mem_thk (nm) \t{mem_thk:g}\n") 
            text.write(f"mem_xip0 (nm) \t{mem_xip0:g}\n") 
            text.write(f"add_random \t{add_random:g}\n") 

    return (Tphi, im_un, im_in, im_ov)
Ejemplo n.º 3
0
def SITIE(image=None,
          defval=None,
          scale=1,
          E=200e3,
          ptie=None,
          i=-1,
          flipstack=False,
          pscope=None,
          data_loc='',
          dataname='',
          sym=False,
          qc=None,
          save=False,
          v=1):
    """Uses a modified derivative to get the magnetic phase shift with TIE from a single image.

    This technique is only applicable to uniformly thin samples from which the 
    only source of contrast is magnetic Fresnel contrast. All other sources of 
    contrast including sample contamination, thickness variation, and diffraction 
    contrast will give false magnetic inductions. For more information please 
    refer to: Chess, J. J. et al. Ultramicroscopy 177, 78–83 (2018).

    This function has two ways of picking which image to use. First, if an image
    is given directly along with a defocus value, it will use that. You should 
    also be sure to specify the scale of the image and accelerating voltage of 
    the microscope (default 200kV). 

    You can also choose to pass it an image from a ``TIE_params`` object, in which
    case you need specify only whether to choose from the imstack or flipstack 
    and the index of the image to use. It's possible that in the future this 
    method of selecting an image will be removed or moved to a separate function. 
    
    Args: 
        image (2D array): Input image to reconstruct. 
        defval (float): Defocus value corresponding to ``image``. 
        scale (float): Scale (nm/pixel) corresponding to ``image``. 
        E (float): Accelerating voltage of microscope that produced ``image``. 
        ptie (``TIE_params`` object): Object containing the image. From 
            TIE_params.py
        i (int): Index of `the ptie.imstack` or `ptie.flipstack` to 
            reconstruct. This is not the defocus index like in TIE. Default 
            value is -1 which corresponds to the most overfocused image. 
        flipstack (bool): (`optional`) Whether to pull the image from ptie.imstack[i] or
            ptie.flipstack[i]. Default is False, calls image from imstack.
        pscope (``Microscope`` object): Should have same accelerating voltage
            as the microscope that took the images.
        dataname (str): The output filename to be used for saving the images. 
        sym (bool): (`optional`) Fourier edge effects are marginally improved by 
            symmetrizing the images before reconstructing. Default False.
        qc (float/str): (`optional`) The Tikhonov frequency to use as filter.
            Default None. If you use a Tikhonov filter the resulting 
            phase shift and induction is no longer quantitative. 
        save (bool/str): Whether you want to save the output.

            ===========  ============
            input value  saved images
            ===========  ============
            True         All images   
            'b'          bx, by, and color image
            'color'      Color image  
            False        None         
            ===========  ============

            Files will be saved as 
            ptie.data_loc/images/dataname_<defval>_<key>.tiff, where <key> is 
            the key for the returned dictionary that corresponds to the image. 
        v (int): (`optional`) Verbosity. 

            ===  ========
            v    print output
            ===  ========
            0    No output
            1    Default output
            2    Extended output for debugging. 
            ===  ======== 

    Returns: 
        dict: A dictionary of image arrays 
        
        =========  ==============
        key        value
        =========  ==============
        'byt'      y-component of integrated magnetic induction
        'bxt'      x-component of integrated magnetic induction
        'bbt'      Magnitude of integrated magnetic induction
        'phase_m'  Magnetic phase shift (radians)
        'color_b'  RGB image of magnetization
        =========  ==============
    """
    results = {
        'byt': None,
        'bxt': None,
        'bbt': None,
        'phase_m': None,
        'color_b': None
    }

    # turning off the print function if v=0
    vprint = print if v >= 1 else lambda *a, **k: None

    if image is not None and defval is not None:
        vprint(
            f"Running with given image, defval = {defval:g}nm, and scale = {scale:.3g}nm/pixel"
        )
        ptie = TIE_params(imstack=[image],
                          defvals=[defval],
                          data_loc=data_loc,
                          v=0)
        ptie.set_scale(scale)
        dim_y, dim_x = image.shape
        if pscope is None:
            pscope = Microscope(E=E)
    else:
        # selecting the right defocus value for the image
        if i >= ptie.num_files:
            print("i given outside range.")
            sys.exit(1)
        else:
            if ptie.num_files > 1:
                unders = list(reversed([-1 * i for i in ptie.defvals]))
                defvals = unders + [0] + ptie.defvals
                defval = defvals[i]
            else:
                defval = ptie.defvals[0]
            vprint(f'SITIE defocus: {defval:g} nm')

        right, left = ptie.crop['right'], ptie.crop['left']
        bottom, top = ptie.crop['bottom'], ptie.crop['top']
        dim_y = bottom - top
        dim_x = right - left
        vprint(f"Reconstructing with ptie image {i} and defval {defval}")

        if flipstack:
            print("Reconstructing with single flipped image.")
            image = ptie.flipstack[i].data[top:bottom, left:right]
        else:
            image = ptie.imstack[i].data[top:bottom, left:right]

    if sym:
        print("Reconstructing with symmetrized image.")
        dim_y *= 2
        dim_x *= 2

    # setup the inverse frequency distribution
    q = dist(dim_y, dim_x)
    q[0, 0] = 1
    if qc is not None and qc is not False:  # add Tikhonov filter
        print("Reconstructing with Tikhonov value: {:}".format(qc))
        qi = q**2 / (q**2 + qc**2)**2
    else:  # normal laplacian method
        print("Reconstructing with normal Laplacian method")
        qi = 1 / q**2
    qi[0, 0] = 0
    ptie.qi = qi  # saves the freq dist

    # constructing "infocus" image
    infocus = np.ones(np.shape(image)) * np.mean(image)
    # calculate "derivative" and normalize
    dIdZ = 2 * (image - infocus)
    dIdZ -= np.sum(dIdZ) / np.size(infocus)

    ### Now calling the phase reconstruct in the normal way
    print('Calling SITIE solver\n')
    resultsB = phase_reconstruct(ptie, infocus, dIdZ, pscope, defval, sym=sym)
    results['byt'] = resultsB['ind_y']
    results['bxt'] = resultsB['ind_x']
    results['bbt'] = np.sqrt(resultsB['ind_x']**2 + resultsB['ind_y']**2)
    results['phase_m'] = resultsB['phase']
    results['color_b'] = color_im(resultsB['ind_x'],
                                  resultsB['ind_y'],
                                  hsvwheel=True,
                                  background='black')
    if v >= 1:
        show_im(results['color_b'],
                "B field color, HSV colorhweel",
                cbar=False,
                scale=scale)

    # save the images
    if save:
        save_results(defval,
                     results,
                     ptie,
                     dataname,
                     sym,
                     qc,
                     save,
                     v,
                     directory="SITIE")
    print('Phase reconstruction completed.')
    return results
Ejemplo n.º 4
0
def TIE(i=-1,
        ptie=None,
        pscope=None,
        dataname='',
        sym=False,
        qc=None,
        save=False,
        hsv=True,
        long_deriv=False,
        v=1):
    """Sets up the TIE reconstruction and calls phase_reconstruct. 

    This function calculates the necessary arrays, derivatives, etc. and then 
    passes them to phase_reconstruct which solve the TIE. 
    
    Args: 
        i (int): Index of ptie.defvals to use for reconstruction. Default value 
            is -1 which corresponds to the most defocused images for a central 
            difference method derivative. i is ignored if using a longitudinal
            derivative. 
        ptie (``TIE_params`` object): Object containing the images and other
            data parameters. From TIE_params.py
        pscope (``Microscope`` object): Should have same accelerating voltage
            as the microscope that took the images.
        dataname (str): The output filename to be used for saving the images. 
        sym (bool): (`optional`) Fourier edge effects are marginally improved by 
            symmetrizing the images before reconstructing. Default False.
        qc (float/str): (`optional`) The Tikhonov frequency to use as filter.
            Default None. If you use a Tikhonov filter the resulting 
            phase shift and induction is no longer quantitative. 
        save (bool/str): Whether you want to save the output.

            ===========  ============
            input value  saved images
            ===========  ============
            True         All images   
            'b'          bx, by, and color image
            'color'      Color image  
            False        None         
            ===========  ============

            Files will be saved as 
            ptie.data_loc/images/dataname_<defval>_<key>.tiff, where <key> is 
            the key for the results dictionary that corresponds to the image. 
        hsv (bool): Whether to use the hsv colorwheel (True) or the 4-fold colorwheel (False).
        long_deriv (bool): Whether to use the longitudinal derivative (True) or
            central difference method (False). Default False. 
        v (int): (`optional`) Verbosity. 

            ===  ========
            v    print output
            ===  ========
            0    No output
            1    Default output
            2    Extended output for debugging. 
            ===  ========

    Returns: 
        dict: A dictionary of image arrays
        
        =========  ==============
        key        value
        =========  ==============
        'byt'      y-component of integrated magnetic induction
        'bxt'      x-component of integrated magnetic induction
        'bbt'      Magnitude of integrated magnetic induction
        'phase_m'  Magnetic phase shift (radians)
        'phase_e'  Electrostatic phase shift (if using flip stack) (radians)
        'dIdZ_m'   Intensity derivative for calculating phase_m
        'dIdZ_e'   Intensity derivative for calculating phase_e (if using flip stack)
        'color_b'  RGB image of magnetization
        'inf_im'   In-focus image
        =========  ==============
    """
    results = {
        'byt': None,
        'bxt': None,
        'bbt': None,
        'phase_e': None,
        'phase_m': None,
        'dIdZ_m': None,
        'dIdZ_e': None,
        'color_b': None,
        'inf_im': None
    }

    # turning off the print function if v=0
    vprint = print if v >= 1 else lambda *a, **k: None
    if long_deriv:
        unders = list(reversed([-1 * ii for ii in ptie.defvals]))
        defval = unders + [0] + ptie.defvals
        if ptie.flip:
            vprint('Aligning with complete longitudinal derivatives:\n',
                   defval, '\nwith both flip/unflip tfs.')
        else:
            vprint('Aligning with complete longitudinal derivatives:\n',
                   defval, '\nwith only unflip tfs.')
    else:
        defval = ptie.defvals[i]
        if ptie.flip:
            vprint(
                f'Aligning for defocus value: {defval:g}, with both flip/unflip tfs.'
            )
        else:
            vprint(
                f'Aligning for defocus value: {defval:g}, with only unflip tfs.'
            )

    right, left = ptie.crop['right'], ptie.crop['left']
    bottom, top = ptie.crop['bottom'], ptie.crop['top']
    dim_y = bottom - top
    dim_x = right - left
    tifs = select_tifs(i, ptie, long_deriv)

    if sym:
        vprint("Reconstructing with symmetrized image.")
        dim_y *= 2
        dim_x *= 2

    # make the inverse laplacian, uses python implementation of IDL dist funct
    q = dist(dim_y, dim_x)
    q[0, 0] = 1
    if qc is not None and qc is not False:
        vprint("Reconstructing with Tikhonov value: {:}".format(qc))
        qi = q**2 / (q**2 + qc**2)**2
    else:  # normal Laplacian method
        vprint("Reconstructing with normal Laplacian method")
        qi = 1 / q**2
    qi[0, 0] = 0
    ptie.qi = qi  # saves the freq dist

    # If rotation and translation to be applied
    if ptie.rotation != 0 or ptie.x_transl != 0 or ptie.y_transl != 0:
        rotate, x_shift, y_shift = ptie.rotation, ptie.x_transl, ptie.y_transl
        for ii in range(len(tifs)):
            tifs[ii] = scipy.ndimage.rotate(tifs[ii],
                                            rotate,
                                            reshape=False,
                                            order=0)
            tifs[ii] = scipy.ndimage.shift(tifs[ii], (-y_shift, x_shift),
                                           order=0)
        mask = scipy.ndimage.rotate(ptie.mask, rotate, reshape=False, order=0)
        mask = scipy.ndimage.shift(mask, (-y_shift, x_shift), order=0)

    # crop images and apply mask
    if ptie.rotation == 0 and ptie.x_transl == 0 and ptie.y_transl == 0:
        mask = ptie.mask[top:bottom, left:right]
    else:
        mask = mask[top:bottom, left:right]

    # crop images and apply mask
    # mask = ptie.mask[top:bottom, left:right]
    for ii in range(len(tifs)):
        tifs[ii] = tifs[ii][top:bottom, left:right]
        tifs[ii] *= mask

    # Normalizing, scaling the images
    scaled_tifs = scale_stack(tifs)
    scaled_tifs += 1e-9

    # get the infocus image
    if long_deriv and ptie.flip:
        inf_unflip = scaled_tifs[len(tifs) // 4]
        inf_flip = scaled_tifs[3 * len(tifs) // 4]
        inf_im = (inf_unflip + inf_flip) / 2
    else:
        inf_im = scaled_tifs[len(tifs) // 2]

    # Inverting masked areas on infocus image because we divide by it
    inf_im += 1 - mask
    # Make sure there are no zeros left:
    inf_im = np.where(scaled_tifs[len(tifs) // 2] == 0, 0.001, inf_im)
    results['inf_im'] = inf_im

    if v >= 2:
        print("""\nScaled images (+- = unflip/flip, +- = over/underfocus)
        in order [ +- , -- , 0 , ++ , -+ ]""")
        for im in scaled_tifs:
            print("max: {:.3f}, min: {:.2f}, total intensity: {:.4f}".format(
                np.max(im), np.min(im), np.sum(im)))
        print()

    # Calculate derivatives
    if long_deriv:
        # have to renormalize each stack
        unflip_stack = tifs[:ptie.num_files]
        unflip_stack = scale_stack(unflip_stack) + 1e-9
        flip_stack = tifs[ptie.num_files:]
        flip_stack = scale_stack(flip_stack) + 1e-9
        vprint('Computing the longitudinal derivative.')
        unflip_deriv = polyfit_deriv(unflip_stack, defval, v)
        if ptie.flip:
            vprint('Computing the flip stack longitudinal derivative.')
            flip_deriv = polyfit_deriv(flip_stack, defval, v)
            dIdZ_m = (unflip_deriv - flip_deriv) / 2
            dIdZ_e = (unflip_deriv + flip_deriv) / 2
        else:
            dIdZ_m = unflip_deriv

    else:  # three point derivative, default.
        if ptie.flip:
            dIdZ_m = 1 / 2 * (scaled_tifs[3] - scaled_tifs[0] -
                              (scaled_tifs[4] - scaled_tifs[1]))
            dIdZ_e = 1 / 2 * (scaled_tifs[3] - scaled_tifs[0] +
                              (scaled_tifs[4] - scaled_tifs[1]))
        else:
            dIdZ_m = scaled_tifs[2] - scaled_tifs[0]

    # Set derivatives to have 0 total "energy"
    dIdZ_m *= mask
    totm = np.sum(dIdZ_m) / np.sum(mask)
    dIdZ_m -= totm
    dIdZ_m *= mask
    results['dIdZ_m'] = dIdZ_m

    if ptie.flip:
        dIdZ_e *= mask
        tote = np.sum(dIdZ_e) / np.sum(mask)
        dIdZ_e -= tote
        dIdZ_e *= mask
        results['dIdZ_e'] = dIdZ_e

    ### Now time to call phase_reconstruct, first for E if we have a flipped tfs
    vprint('Calling TIE solver\n')
    if ptie.flip:
        resultsE = phase_reconstruct(ptie,
                                     inf_im,
                                     dIdZ_e,
                                     pscope,
                                     defval,
                                     sym=sym,
                                     long_deriv=long_deriv)
        # We only care about the E phase.
        results['phase_e'] = resultsE['phase']

    ### Now run for B,
    resultsB = phase_reconstruct(ptie,
                                 inf_im,
                                 dIdZ_m,
                                 pscope,
                                 defval,
                                 sym=sym,
                                 long_deriv=long_deriv)
    results['byt'] = resultsB['ind_y']
    results['bxt'] = resultsB['ind_x']
    results['bbt'] = np.sqrt(resultsB['ind_x']**2 + resultsB['ind_y']**2)
    results['phase_m'] = resultsB['phase']
    results['color_b'] = color_im(resultsB['ind_x'],
                                  resultsB['ind_y'],
                                  hsvwheel=hsv,
                                  background='black')

    if v >= 1:
        show_im(results['color_b'],
                "B-field color HSV colorwheel",
                cbar=False,
                scale=ptie.scale)

    # save the images
    if save:
        save_results(defval,
                     results,
                     ptie,
                     dataname,
                     sym,
                     qc,
                     save,
                     v,
                     long_deriv=long_deriv)

    vprint('Phase reconstruction completed.')
    return results
Ejemplo n.º 5
0
def SITIE(ptie=None, pscope=None, dataname='', sym=False, qc=None, save=True, i=-1, flipstack=False, v=1):
    """Uses a modified derivative to get the magnetic phase shift with TIE from a single image.

    This technique is only appplicable to uniformly thin samples from which the 
    only source of contrast is magnetic Fresnel contrast. All other sources of 
    contrast including dirt on the sample, thickness variation, and diffraction 
    contrast will give false magnetic inductions. For more information please 
    refer to: Chess, J. J. et al. Ultramicroscopy 177, 78–83 (2018).
    
    Args: 
        ptie: TIE_params object. Object containing the image from TIE_params.py
        pscope : microscope object. Should have correct accelerating voltage as
            the microscope that took the images.
        dataname : String. The output filename to be used for saving the images. 
                Files will be saved ptie.data_loc/images/dataname_<defval>_<key>.tiff
        sym: Boolean. Fourier edge effects are marginally improved by 
            symmetrizing the images before reconstructing (image reconstructed 
            is 4x as large). Default False.
        qc: Float. The Tikhonov frequency to use as filter, or "percent" to use 
            15% of q, Default None. 
        save: Bool or string. Whether you want to save the output. Default False. 
            save = True    ->  saves all images. 
            save = 'b'     ->  save just bx, by, and color_b
            save = 'color' ->  saves just color_b
            save = False   ->  don't save.
            Saves the images to ptie.data_loc/images/
        long_deriv: Bool. Whether to use the longitudinal derivative (True) or 
            central difference method (False). Default False. 
        i: Int. index of __the ptie.imstack or ptie.flipstack__ to 
            reconstruct. This is not the defocus index like in TIE. Default 
            value is -1 which corresponds to the most overfocused image. 
        flipstack: Bool. Whether to pull the image from the ptie.dmrstack[i] or
            ptie.flipstack[i]. Default is False, calls image from imstack.
        v: Int. Verbosity. 
            0 : ##TODO no output
            1 : Default output
            2 : Extended output for debugging. 

    Returns: A dictionary of arrays. 
        results = {
            'byt' : y-component of integrated magnetic induction,
            'bxt' : x-copmonent of integrated magnetic induction,
            'bbt' : magnitude of integrated magnetic induction, 
            'phase_m' : magnetic phase shift (radians),
            'color_b' : RGB image of magnetization,
        }
    """
    results = {
        'byt' : None,
        'bxt' : None,
        'bbt' : None, 
        'phase_m' : None,
        'color_b' : None}

    # turning off the print function if v=0
    vprint = print if v>=1 else lambda *a, **k: None

    # selecting the right defocus value for the image
    if i >= ptie.num_files:
        print("i given outside range.")
        sys.exit(1)
    else:
        if ptie.num_files > 1: 
            unders = list(reversed([-1*i for i in ptie.defvals]))
            defvals = unders + [0] + ptie.defvals
            defval = defvals[i]
        else:
            defval = ptie.defvals[0]
        vprint(f'SITIE defocus: {defval} nm')

    right, left = ptie.crop['right']  , ptie.crop['left']
    bottom, top = ptie.crop['bottom'] , ptie.crop['top']
    dim_y = bottom - top 
    dim_x = right - left 

    if flipstack:
        print("Reconstructing with single flipped image.")
        image = ptie.flipstack[i].data[top:bottom, left:right]
    else:
        image = ptie.imstack[i].data[top:bottom, left:right]

    if sym:
        print("Reconstructing with symmetrized image.")
        dim_y *= 2
        dim_x *= 2

    # setup the inverse frequency distribution
    q = dist(dim_y,dim_x)
    q[0, 0] = 1
    if qc is not None and qc is not False: # add Tikhonov filter
        if qc == 'percent':
            print("Reconstructing with Tikhonov percentage: 15%")
            qc = 0.15 * q * ptie.scale**2
        else:
            qc = qc 
            print("Reconstructing with Tikhonov value: {:}".format(qc))
        qi = q**2 / (q**2 + qc**2)**2
    else: # normal laplacian method
        print("Reconstructing with normal Laplacian method")
        qi = 1 / q**2
    qi[0, 0] = 0
    ptie.qi = qi # saves the freq dist

    # constructing "infocus" image
    infocus = np.ones(np.shape(image))*np.mean(image)
    # calculate "derivative" and normalize
    dIdZ = 2 * (image - infocus) 
    dIdZ -= np.sum(dIdZ)/np.size(infocus)

    ### Now calling the phase reconstruct in the normal way
    print('Calling SITIE solver\n')
    resultsB = phase_reconstruct(ptie, infocus, 
                                dIdZ, pscope, defval, sym = sym)
    results['byt'] = resultsB['ind_y']
    results['bxt'] = resultsB['ind_x']
    results['bbt'] = np.sqrt(resultsB['ind_x']**2 + resultsB['ind_y']**2)
    results['phase_m'] = resultsB['phase']
    results['color_b'] = color_im(resultsB['ind_x'], resultsB['ind_y'],
        hsvwheel=True, background='black')
    if v >= 1:
        show_im(results['color_b'], "B field color, HSV colorhweel")
    
    # save the images
    if save: 
        save_results(defval, results, ptie, dataname, sym, qc, save, v, directory = "SITIE")
    print('Phase reconstruction completed.')
    return results
Ejemplo n.º 6
0
def TIE(i=-1, ptie=None, pscope=None, dataname='', sym=False, qc=None, save=False, long_deriv=False, v=1):
    """Sets up the TIE reconstruction and calls phase_reconstruct. 

    This function calculates the necessary arrays, derivatives, etc. and then 
    calls passes them to phase_reconstruct which solve the TIE. 
    
    Args: 
        i: Int. index of ptie.defvals to use for reconstruction. Default value is -1
            which corresponds to the most defocused images for a central 
            difference method derivative. i is ignored if using a longitudinal
            derivative. 
        ptie: TIE_params object. Object containing the image from TIE_params.py
        pscope : microscope object. Should have correct accelerating voltage as
            the microscope that took the images.
        dataname : String. The output filename to be used for saving the images. 
                Files will be saved ptie.data_loc/images/dataname_<defval>_<key>.tiff
        sym: Boolean. Fourier edge effects are marginally improved by 
            symmetrizing the images before reconstructing (image reconstructed 
            is 4x as large). Default False.
        qc: Float. The Tikhonov frequency to use as filter, or "percent" to use 
            15% of q, Default None. If you use a Tikhonov filter the resulting 
            magnetization is no longer quantitative!
        save: Bool or string. Whether you want to save the output. Default False. 
            save = True    ->  saves all images. 
            save = 'b'     ->  save just bx, by, and color_b
            save = 'color' ->  saves just color_b
            save = False   ->  don't save.
            Saves the images to ptie.data_loc/images/
        long_deriv: Bool. Whether to use the longitudinal derivative (True) or 
            central difference method (False). Default False. 
            __Currently has bugs__. Qualitatively looks alright but quantitatively
            is not accurate. 
        v: Int. Verbosity. 
            0 : no output
            1 : Default output
            2 : Extended output for debugging. 

    Returns: A dictionary of arrays. 
        results = {
            'byt' : y-component of integrated magnetic induction,
            'bxt' : x-copmonent of integrated magnetic induction,
            'bbt' : magnitude of integrated magnetic induction, 
            'phase_m' : magnetic phase shift (radians),
            'phase_e' : electrostatic phase shift (if using flip stack) (radians),
            'dIdZ_m' : intensity derivative for calculating phase_m,
            'dIdZ_e' : intensity derivative for calculating phase_e (if using flip stack), 
            'color_b' : RGB image of magnetization,
            'inf_im' : the in-focus image
        }
    """
    results = {
        'byt' : None,
        'bxt' : None,
        'bbt' : None, 
        'phase_e' : None,
        'phase_m' : None,
        'dIdZ_m' : None,
        'dIdZ_e' : None, 
        'color_b' : None,
        'inf_im' : None}

    # turning off the print function if v=0
    vprint = print if v>=1 else lambda *a, **k: None
    if long_deriv:
        unders = list(reversed([-1*ii for ii in ptie.defvals]))
        defval = unders + [0] + ptie.defvals
        if ptie.flip:
            vprint('Aligning with complete longitudinal derivates:\n', defval, '\nwith both flip/unflip tfs.')
        else:
            vprint('Aligning with complete longitudinal derivates:\n', defval, '\nwith only unflip tfs.')
    else:
        defval = ptie.defvals[i]
        if ptie.flip:
            vprint('Aligning for defocus value: ', defval, ' with both flip/unflip tfs.')
        else:
            vprint('Aligning for defocus value: ', defval, ' with only unflip tfs.')

    right, left = ptie.crop['right']  , ptie.crop['left']
    bottom, top = ptie.crop['bottom'] , ptie.crop['top']
    dim_y = bottom - top 
    dim_x = right - left 
    tifs = select_tifs(i, ptie, long_deriv)

    if sym:
        vprint("Reconstructing with symmetrized image.")
        dim_y *= 2
        dim_x *= 2

    # make the inverse laplacian, uses python implementation of IDL dist funct 
    q = dist(dim_y,dim_x)
    q[0, 0] = 1
    if qc is not None and qc is not False:
        if qc == 'percent':
            vprint("Reconstructing with Tikhonov percentage: 15%")
            qc = 0.15 * q * ptie.scale**2
        else:
            qc = qc 
            vprint("Reconstructing with Tikhonov value: {:}".format(qc))

        qi = q**2 / (q**2 + qc**2)**2
    else: # normal laplacian method
        vprint("Reconstructing with normal Laplacian method")
        qi = 1 / q**2
    qi[0, 0] = 0
    ptie.qi = qi # saves the freq dist

    # crop images and apply mask 
    mask = ptie.mask[top:bottom, left:right]
    for ii in range(len(tifs)):
        tifs[ii] = tifs[ii][top:bottom, left:right]
        tifs[ii] *= mask

    # Normalizing, scaling the images 
    scaled_tifs = scale_stack(tifs)
    # very small offset from 0, affects uniform magnetizations
    # but can be compensated for (if 0) by symmetrizing the image.
    scaled_tifs += 1e-9 

    # get the infocus image
    if long_deriv and ptie.flip:
        inf_unflip = scaled_tifs[len(tifs)//4]
        inf_flip = scaled_tifs[3*len(tifs)//4]
        inf_im = (inf_unflip+inf_flip)/2
    else: 
        inf_im = scaled_tifs[len(tifs)//2]

    # Inverting masked areas on infocus image because we divide by it
    inf_im += 1 - mask
    # Make sure there are no zeros left: 
    inf_im = np.where(scaled_tifs[len(tifs)//2] == 0, 0.001, inf_im)
    results['inf_im'] = inf_im    
    
    if v >= 2: 
        print("""\nScaled images (+- = unflip/flip, +- = over/underfocus)
        in order [ +- , -- , 0 , ++ , -+ ]""")
        for im in scaled_tifs:
            print("max: {:.3f}, min: {:.2f}, total intensity: {:.4f}".format(
                np.max(im), np.min(im), np.sum(im)))
        print()

    # Calculate derivatives
    if long_deriv: 
        unflip_stack = tifs[:ptie.num_files]
        flip_stack = tifs[ptie.num_files:]

        if long_deriv == 'multi': 
            vprint('Computing the longitudinal derivative with Multiprocessing.')
            unflip_deriv = polyfit_deriv_multiprocess(unflip_stack, defval)
        else:
            vprint('Computing the longitudinal derivative normally.')
            unflip_deriv = polyfit_deriv(unflip_stack, defval, v)

        if ptie.flip:
            if long_deriv == 'multi':
                vprint('Computing the flip stack longitudinal derivative with Multiprocessing.')
                flip_deriv = polyfit_deriv_multiprocess(flip_stack, defval)
            else:
                vprint('Computing the flip stack longitudinal derivative normally.')
                flip_deriv = polyfit_deriv(flip_stack, defval, v)

            dIdZ_m = (unflip_deriv - flip_deriv)/2 
            dIdZ_e = (unflip_deriv + flip_deriv)/2 
        else:
            dIdZ_m = unflip_deriv 

    else:
        if ptie.flip:
            dIdZ_m = 1/2 * (scaled_tifs[3] - scaled_tifs[0] - 
                          (scaled_tifs[4] - scaled_tifs[1]))
            dIdZ_e = 1/2 * (scaled_tifs[3] - scaled_tifs[0] + 
                          (scaled_tifs[4] - scaled_tifs[1]))
        else:
            dIdZ_m = scaled_tifs[2] - scaled_tifs[0]
    
    # Set derivatives to have 0 total "energy" 
    dIdZ_m *= mask 
    totm = np.sum(dIdZ_m)/np.sum(mask)
    dIdZ_m -= totm
    dIdZ_m *= mask 
    results['dIdZ_m'] = dIdZ_m

    if ptie.flip:
        dIdZ_e *= mask
        tote = np.sum(dIdZ_e)/np.sum(mask)
        dIdZ_e -= tote
        dIdZ_e *= mask 
        results['dIdZ_e'] = dIdZ_e

    ### Now time to call phase_reconstruct, first for E if we have a flipped tfs 
    vprint('Calling TIE solver\n')
    if ptie.flip:
        resultsE = phase_reconstruct(ptie, inf_im, dIdZ_e, pscope, 
                                defval, sym = sym, long_deriv = long_deriv)   
        # We only care about the E phase.  
        results['phase_e'] = resultsE['phase']

    ### Now run for B, 
    resultsB = phase_reconstruct(ptie, inf_im, dIdZ_m, pscope, 
                                defval, sym = sym, long_deriv = long_deriv)
    results['byt'] = resultsB['ind_y']
    results['bxt'] = resultsB['ind_x']
    results['bbt'] = np.sqrt(resultsB['ind_x']**2 + resultsB['ind_y']**2)
    results['phase_m'] = resultsB['phase']
    results['color_b'] = color_im(resultsB['ind_x'], resultsB['ind_y'],
                                    hsvwheel=True, background='black') 

    if v >= 1:
        show_im(results['color_b'], "B-field color HSV colorwheel")

    # save the images
    if save: 
        save_results(defval, results, ptie, dataname, sym, qc, save, v, long_deriv = long_deriv)

    vprint('Phase reconstruction completed.')
    return results