Gatan | AMETEKSkip to Main Content
No options found

LiveView linear profile

Python Script

Compute ROI-based profiles over time from the LiveView image in DigitalMicrograph.

Preview

#Code to compute ROI-based profiles over time from the live-view image in GMS. 
#Run the code with the live-view image front-most in GMS. 
#To stop calculation, close the live view image. 
#Lines of code between #XXXXXXXX... lines are specific to computing the ROI-based profile
	#All other lines of code are general, and can be re-used to produce other kinds of profiles from a live-view image or its FFT

#Requires Scipy. 
#To install packages like scipy, see instructions in GMS Help:Python:Installation and Configuration:Additional Packages

#Code written by Ben Miller. Last Updated 2020-06

import numpy as np
import time
import sys
if (DM.IsScriptOnMainThread() == False):
	print( ' MatplotLib and scipy scripts require to be run on the main thread.',
		'\n Uncheck the "Execute on Background Thread"',
		'checkbox at the bottom of the Script Window' )
	exit()
import scipy
from scipy import ndimage
from scipy import signal
from scipy import fftpack
from scipy.ndimage.interpolation import geometric_transform
from numpy import linalg as LA

integration_width = 100 #width of profile (perpendicular to profile direction) in pixels
initial_result_image_width = 100 #how many profiles can be displayed in the intial result window (window is automatically expanded as needed)
time_interval_mult=1 #multiple for the median time interval between frames (set higher to ensure time between each frame is exactly identical)
N_init=9 #number of intial frames to use to estimate median time interval

class CListen(DM.Py_ScriptObject):

	#Initialization Function
	def __init__(self, img):
		self.name = "ImageO"
		self.i = 0
		self.sleeptime = 0
		self.N_init=N_init
		self.profiletime=0
		self.time_outliers=[]
		self.duration=[]
		self.j = True #this is only for IS player testing
		#get the original image and assign it to self.imgref
		self.imgref = img
		#get the shape and calibration of the original image
		(input_sizex, input_sizey) = img.GetNumArray().shape
		origin, self.scale_orig, self.unit_stringo = self.imgref.GetDimensionCalibration(0, 0)
		#XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
		#Get a line ROI from the image
		self.line_ROI=self.get_line_ROI_coords(img)
		# Process the first image
		processedimagedata = self.processimage(img.GetNumArray(), self.line_ROI,integration_width)
		#Set the size and calibration of the result image
		(r_img_size,) = processedimagedata.shape
		(self.scale,self.unit_string) = (self.scale_orig, self.unit_stringo)
		#XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
		#create empty set for result images
		self.result_images = {}
		#create 1st result image and set calibration
		self.result_images[self.name] = DM.CreateImage(np.zeros((r_img_size,initial_result_image_width)))
		self.result_images[self.name].SetDimensionCalibration(1,0,self.scale,self.unit_string,0)
		self.result_images[self.name].ShowImage()		
		#get numpy array from result image
		self.result_array = self.result_images[self.name].GetNumArray()
		DM.Py_ScriptObject.__init__(self)
		self.end=time.perf_counter()
		self.stop = 0
	
	#Function to end Image Listener
	def __del__(self):
		DM.Py_ScriptObject.__del__(self)
		
	#XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX	
	#Function to Get Line ROI Coordinates from an image
	def get_line_ROI_coords(self,img):
		roi_disp = img.GetImageDisplay(0)
		num_rois = roi_disp.CountROIs()
		#If an ROI is selected, get this one, otherwise get 1st ROI
		for r in range(num_rois):
			if roi_disp.IsROISelected(roi_disp.GetROI(r)):
				roi = roi_disp.GetROI(r)
				break
			else:
				roi = roi_disp.GetROI(0)
		#If line ROI is found, get coordinates, otherwise prompt user and stop script
		try:
			#left, top, right, bottom = roi.GetLine()
			line_ROI=roi.GetLine()
		except:
			DM.OkDialog( 'Error: No ROI Found. \n\nBefore Running this Script, Select an Image with an ROI' )
			print('\nError: No ROI Found. Script Stopped. \nBefore Running this Script, Select an Image with an ROI')
			sys.exit()
		return line_ROI
		
	# Function to Process the Image Data in Each Image and Produce a Vector (integration width =1)
	#(This function is no longer used)
	def processimage_old(self, numpy_data, lr):
		#Produce a profile line from an image and 4 coordinates
		x_y_length=[lr[2]-lr[0],lr[3]-lr[1]]
		num=np.trunc(LA.norm(x_y_length)).astype('int')
		#Produce line coordinates
		x, y = np.linspace(lr[0], lr[2], num), np.linspace(lr[1], lr[3], num)
		coords=np.vstack((y,x))
		# Extract the values along the line, using cubic interpolation
		processed_output=scipy.ndimage.map_coordinates(numpy_data, coords)
		return(processed_output)
		
	# Function to Process the Image Data in Each Image and Produce a Vector	
	def processimage(self, numpy_data, lr, integration_width):
		#Get Coordinates function
		def get_coordinates(start, end, n):
			length_f=LA.norm([end[0] - start[0],end[1] - start[1]])
			num_samples = int(np.trunc(length_f))
			a = np.linspace(0, num_samples - 1, num_samples)  # along line
			t = np.linspace(-(n-1)*0.5, (n-1)*0.5, n)  # orthogonal to line
			x, y = np.meshgrid(a, t)
			dy = (end[0] - start[0]) / num_samples
			dx = (end[1] - start[1]) / num_samples
			yy = start[0] + dy * x + dx * y
			xx = start[1] + dx * x - dy * y
			return yy, xx
		yy, xx = get_coordinates(lr[:2], lr[2:], int(np.trunc(integration_width)))
		interpdata = scipy.ndimage.map_coordinates(numpy_data, (xx, yy))
		if len(interpdata.shape) > 1:
			return np.sum(interpdata, 0)/interpdata.shape[0]
		else:
			return interpdata
	#XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX	

	
	#This function is run each time the image changes
	def HandleDataChangedEvent(self, flags, image):
		if not self.stop:
			if self.j: #this is only for IS player testing
				#start timing
				start=self.end
				self.j = not self.j #this is only for IS player testing
				(result_sizey, result_sizex) = self.result_array.shape
				#if the result image is nearly full, make it 2x larger
				if self.i > result_sizex-2:
					#create a new numpy array 2x larger
					self.result_array_temp = np.append(self.result_array, np.zeros_like(self.result_array), axis=1)
					#close the old results image in DM
					DM.DeleteImage(self.result_images[self.name])
					print("ImageDeleted")
					#create a new results image and calibrate it
					self.name="Image{0}".format(self.i)
					self.result_images[self.name] = DM.CreateImage(np.copy((self.result_array_temp)))
					self.result_images[self.name].SetDimensionCalibration(1,0,self.scale,self.unit_string,0)
					print("ImageCreated")
					#display new result image in DM
					self.result_images[self.name].ShowImage()	
					#get numpy array from new result image
					self.result_array = (self.result_images[self.name].GetNumArray())
				#XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
				#compute ROI profile from the image, and place this profile into results image
				self.result_array[:,self.i] = self.processimage(self.imgref.GetNumArray(), self.line_ROI,integration_width)
				#XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
				self.result_images[self.name].UpdateImage()
				#end timing and output time to process this frame
				self.end=time.perf_counter()
				self.duration.append(self.end-start-self.sleeptime)
				print("Processed Image "+str(self.i)+"  Processzing Time= "+str(self.duration[-1]))
				if self.i==self.N_init: self.profiletime = time_interval_mult*np.median(np.asarray(self.duration))
				self.sleeptime = max(0,self.profiletime-self.duration[-1])
				if self.sleeptime == 0: self.time_outliers.append((self.i,self.profiletime-self.duration[-1]))
				DM.Sleep(self.sleeptime)
				self.i = self.i+1
			else: 
				self.j = not self.j #this is only for IS player testing
	#Function to end script if source image window is closed
	def HandleWindowClosedEvent(self, event_flags, window):
		print("Window Closed")
		self.stop = 1
		self.__del__()
		print("Script Ended")
		DM.Sleep(2)
		print(self.time_outliers[self.N_init:])
		#del self.imgref
				

#Main Code Starts Here
#Get front image in GMS		
img1 = DM.GetFrontImage()
#Get the image window, so we can check if it gets closed
imageDoc = DM.GetFrontImageDocument()
imDocWin = imageDoc.GetWindow()
#initiate the image listener
listener = CListen(img1)
#check if the source window closes
listener.WindowHandleWindowClosedEvent(imDocWin, 'pythonplugin')
#check if the source image changes
listener.ImageHandleDataChangedEvent(img1, 'pythonplugin')