from NSFopen.read import read
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
from scipy.optimize import curve_fit
from glob import glob
filename = 'spectroscopy.nid'
filename_large = 'spectroscopy_large.nid'
# If USE_LARGE_MAP is True, then it will use the larger force-curve map, downloading if not already locally available
USE_LARGE_MAP = False
if USE_LARGE_MAP and filename_large not in glob('*nid'):
import urllib.request
urllib.request.urlretrieve("https://www.dropbox.com/s/knzuh588g0uvjfi/spectroscopy_large.nid?dl=1", filename_large)
filename = filename_large
elif USE_LARGE_MAP:
filename = filename_large
# Read Date
afm = read(filename, verbose=False)
data = afm.data
param = afm.param
# probe
k = param.Tip.Prop0['Value'] # spring constant
sens = param.Tip.Sensitivity['Value'] # deflection sensitivity (if needed)
radius = 10e-9 # tip radius
nu = 0.35 # Poisson ratio (between 0.35 and 0.5)
spec_backward = data['Spec']['Backward'] # for DMT model we will use backward modulation
d = spec_backward['Deflection'] # force channel
z_signal = list(spec_backward.index)
z_chans = ['Z-Axis Sensor', 'Z-AxisSensor', 'Z-Axis'] # depending on system, Z-Axis channel may be labeled differently
available_chan = [i for i in z_signal if i in z_chans][0]
z = data['Spec']['Backward'][available_chan] # Z-axis data
sz = int(param.Spec.maps[4]) # Map size
extent = param.Spec.maps[:4] # X and Y physical dimensions of map
## Functions
# corrects for any baseline tilt
def baseline_correct(x, y, degree=1, start=0, end=0.5):
i0 = int(np.floor(len(x) * start))
i1 = int(np.floor(len(x) * end))
xx = x[i0:i1]
yy = y[i0:i1]
p = np.polyfit(xx, yy, degree)
y -= np.polyval(p, x)
return y
# fits DMT model to data
def DMT(x, *param):
elast, depth, wadh = param
scale = 4./3. * elast * np.sqrt(radius) / (1 - nu**2)
x = x - depth
y = scale * np.sign(x) * np.abs(x)**(3./2.) - wadh
return y
def DMTfit(*args, maxForce=np.Inf):
import time
t = time.time()
# args are: [separation, load (force)]
if not isinstance(args[0], list):
args = [[ar] for ar in args]
E = []
for x, y in zip(*args):
# x = x - y / spring_constant # separation
lenY = int(np.floor(len(y) * 0.8))
a = min(y[lenY:]) # adhesion
i0 = np.where(y == a)[0][0] # contact point estimator index
i1 = np.where(y > maxForce)[0] # max force index
if i1.size == 0:
i1 = -1
else:
i1 = i1[0]
try:
coeff, var_matrix = curve_fit(DMT, x[i0:i1], y[i0:i1], p0=[1e9, x[i0], -a])
except RuntimeError:
E.append(np.nan)
else:
E.append(max(coeff[0], 0))
print('Elapsed Time: %3.2f sec\n' % (time.time() - t))
return np.array(E), (coeff, i0, i1)
# correct for tilt in baseline
dd = [baseline_correct(z_, d_, start=0.5, end=0.8) for z_, d_ in zip(z, d)]
# separation
zz = [zz - dd/k for dd, zz in zip(dd, z)]
# elastic modulus
Eb, _ = DMTfit(zz, dd)
Eb /= 1e9
# adhesion
adh = np.array([np.min(d) for d in dd]) * -1e9
# converts 1-D array to 2-D map and puts rows in correct order
def toMap(arr, sz):
arr_ = np.reshape(arr, (sz, sz))
arr_[::2] = np.fliplr(arr_[::2])
return np.fliplr(arr_)
Ebim = toMap(Eb, sz)
Adh = toMap(adh, sz)
Elapsed Time: 0.24 sec
fig, ax = plt.subplots(2, 2, figsize=(10, 8), gridspec_kw={'height_ratios': [2, 1]}, dpi=300)
fig.tight_layout(w_pad=8.0, h_pad=1.0)
im = ax.copy()
cmap = "afmhot" # colormap
xlbl = 'X [$\mu$m]'
ylbl = 'Y [$\mu$m]'
zlbl = ["Young's Modulus [GPa]", "Adhesion [nN]"]
im[0, 0] = ax[0, 0].imshow(Ebim)
im[0, 1] = ax[0, 1].imshow(Adh)
im[1, 0] = ax[1, 0].hist(Eb)
im[1, 1] = ax[1, 1].hist(adh)
# sets labels and colorbar, etc.
for i in range(2):
ax_divider = make_axes_locatable(ax[0, i])
cax = ax_divider.append_axes("right", size="5%", pad="2%")
cb = plt.colorbar(im[0, i], cax=cax)
cb.set_label(zlbl[i])
ax[0, i].set(xlabel=xlbl, ylabel=ylbl)
ax[1, i].set(xlabel=zlbl[i], ylabel='Counts')
plt.show()