NCEM's 4D-STEM Basic Jupyter Notebook

  • Quickly process and investigate 4D-STEM data from the TitanX

To start:

  • Change the dirName and fName
  • Select Cell -- Run All
  • Scroll to bottom and investigate your data

In [ ]:
dirName = r'C:\Users\Peter\Data\Te NP 4D-STEM'
fName = r'07_45x8 ss=5nm_spot11_CL=100 0p1s_alpha=4p63mrad_bin=4_300kV.dm4'

In [ ]:
%matplotlib notebook

import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
from PIL import Image
from pathlib import Path
from ncempy.io import dm

import ipywidgets as widgets
from ipywidgets import interact, interactive

Import the data and reshape to 4D

  • Change dirName to the directory where your data lives
  • Change the fName to the full file name

In [ ]:
#Load the data using ncempy
fPath = Path(dirName) / Path(fName)
with dm.fileDM(fPath.as_posix()) as dm1:
    dm1.parseHeader()
    im1 = dm1.getDataset(0)

    scanI = int(dm1.allTags['.ImageList.2.ImageTags.Series.nimagesx'])
    scanJ = int(dm1.allTags['.ImageList.2.ImageTags.Series.nimagesy'])
    numkI = im1['data'].shape[2]
    numkJ = im1['data'].shape[1]

    data = im1['data'].reshape([scanJ,scanI,numkJ,numkI])
    
    print('Data shape is {}'.format(data.shape))

Find the location of the zero beam and generate BF

  • Assumes the first diffraction pattern will have the least structure.
  • Use center of intensity to find pattern center

In [ ]:
fg1,ax1 = plt.subplots(3,1,num=1,figsize=(6,10))
ax1[0].imshow(data[0,0,:,:])

#Find center of intensity
cm0 = [int(ii) for ii in ndimage.measurements.center_of_mass(data[0,0,:,:])]

#Plot the first diffraction pattern and found center
ax1[0].plot(cm0[1],cm0[0],'rx')
ax1[0].legend(['Center of central beam'])
ax1[0].set(title='First diffraction pattern\nCenter = {}'.format(cm0))

#Generate a bright field image
box0 = 25
BF0 = np.sum(np.sum(data[:,:,cm0[0]-box0:cm0[0]+box0,cm0[1]-box0:cm0[1]+box0],axis=3),axis=2)
ax1[1].imshow(BF0)
ax1[1].set(title='Bright field image')

ax1[2].imshow(np.sum(np.sum(data,axis=3),axis=2))
ax1[2].set(title='Sum of all diffraction intensity')

fg1.tight_layout()

Investigate the data

Scroll back and forth in the two axes with update of current position on Bright Field image


In [ ]:
im1 = data[:,:,::1,::1]
fg1,(ax1,ax2) = plt.subplots(1,2,num=2,figsize=(8,8))
p1 = ax1.plot(4,4,'or')
p1 = p1[0]
ax1.imshow(BF0)
im2 = ax2.imshow(np.log(im1[4,4,:,:]+50))

#Updates the plots
def axUpdate(i,j):
    p1.set_xdata(i)
    p1.set_ydata(j)
    im2.set_array(np.log(im1[j,i,:,:]+50))

ax1.set(title='Bright Field Image',xlabel='i',label='j')
ax2.set(title='Diffraction pattern (log(I))')

#Connect the function and the sliders
w = interactive(axUpdate, i=(0,BF0.shape[1]-1), j=(0,BF0.shape[0]-1))

wB = widgets.Button(
    description='Save current DP',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip=''
)

def saveCurrentDP(a):
    curI = w.children[0].get_interact_value()
    curJ = w.children[1].get_interact_value()
    im = Image.fromarray(data[curJ,curI,:,:])
    outName = fPath.as_posix() + '_DP{}i_{}j.tif'.format(curI,curJ)
    im.save(outName)

wB.on_click(saveCurrentDP)

display(w)
display(wB)

Find the maximum intensity for every pixel in the diffraction pattern

  • Useful to see features close to the noise floor

In [ ]:
DPmax = np.max(im1.reshape((im1.shape[0]*im1.shape[1],im1.shape[2],im1.shape[3])),axis=0)

#Plot the image
fg2,ax2 = plt.subplots(1,1)
ax2.imshow(np.log(DPmax))
ax2.set(title='Maximum intensity for each detector pixel (log)');