Nanoforce
Package to import and analyse AFM force curves produced using Nanoscope 5 & 6 and Nanosurf .nid files
Python AFM Analysis Tutorial
This tutorial demonstrates the basics of the AFM python tools for data analysis. Currently the scripts are available as an offline file or can be installed from PyPI using:
pip install nanoforce
Following the steps below will show you how to carry out simple analysis and plotting of AFM data produced with Nanoscope and explain how to carryout your own more specific analysis. For each snippet of code - try running it to see what it outputs. You can download the tutorial as a Jupyter notebook from:
https://github.com/crj341/nanoforce/blob/master/AFM_tutorial.ipynb
Prerequisites
To use the tools you will need a few python packages (numpy, plotly, scipy and easygui). To install these type the following commands into a terminal window (unless you have installed the nanoforce package from PyPI (i.e. using pip), in which case they should have installed automatically):
pip install numpy
pip install plotly
pip install scipy
pip install easygui
Getting started
Start off by creating a folder containing your script (and the afm.py file if you have not installed the package from PyPI). For beginers it may be best to use a jupyter notebook here, as has been used for this tutorial. Open your script file and type (note this is case sensetive):
from nanoforce import AFM
To carry out basic analysis you can use the inbuilt 'run' command.
(Check the window hasn't opened behind python if this is running slowly.)
expt = AFM()
expt.run()
This will open a window, prompting you to select the AFM data you wish to analyse. Navigate to the folder and select the file with the '.000' extension. The tools will automatically align the data with thebaseline and set the surface height to zero. A plot will open with the processed force curves, use the slider to move through each experiment. The code will also aclculate the adhesion and modules. The mean and standard deviation will appera in the output.
It is likley you will wish to carry out more detailled analysis than provided by this function. The following sections will guide you through all the individual functions available.
Naming variables
If you have carried out multiple experiments and would like to compare the results, you should assign an instance of the AFM class for each. Name a variable for each experiment and call the class:
expt_1 = AFM()
expt_2 = AFM()
expt_3 = AFM()
Importing data - Nanoscope files (.000)
To import a nanoscope dataset use the 'input_files' command for each experiment:
Note - the GUI for selecting a file will only run on a local version of python (not in the Colab tutorial). This will prompt an error in Colab - use manual file_name entry below.
expt_1.input_files()
expt_2.input_files()
expt_3.input_files()
This will open a window prompting you to select the '.000' file for each experiment. The code will then automatcally import all files with the same name.
If you would prefer to manually set the file name (useful if you are running the script multiple time), you can name the 'file_name' variable and set the 'gui_on' flag in 'import_files' to 'False':
expt_1.file_name = '/insert/your/file/path/here.000'
expt_1.input_files(gui_on = False)
Importing data - Nanoscope files (.000)
To import a Nanosurf .nid dataset first set the file name:
expt_1.file_name = r'C:\file\path\goes\here.nid
Then call the following function to import all force curve data from the file@
epxt_1.nanosurf_import
Naming experiments
It may be useful to name each experiment, for plotting etc. To do so, use the following command:
expt_1.set_run_name('Sample 1')
Finding parameters - Nanoscope files only
The next step is to import the relevant parameters from the file (such as deflection sensetivity and spring constant). To do so, due the 'nanoscope_params' function as follows:
expt_1.nanoscope_params()
expt_2.nanoscope_params()
expt_3.nanoscope_params()
By default this will search for parameters in the Nanoscope 6 format. To use a Nanoscope 5 file, change the 'nanoscope_version' input as follows (if a value other than 5 or 6 is given, or the script detects a file in the wrong format, python will exit):
expt_1.nanoscope_params(nanoscope_version=5)
If you would like to manually set the deflaction sensetivity, use the following function:
expt_1.set_def_sens(100)
If you would like to manually set the spring constant, use the following function:
expt_2.set_spr_const(0.32)
Reading force curve data - Nanoscope files only
Now we have the parameters, we need the actual data. To import this, use the 'nanoscope_read' command:
expt_1.nanoscope_read()
expt_2.nanoscope_read()
expt_3.nanoscope_read()
Plot raw data
To view the raw force curve data before any processing use the command below. The number in the brackets specifies which curve to plot. Remember, when using python, the numbers start at 0 (i.e. for 100 force curves the numbers are 0 to 99).
expt_1.plot_raw(1)
Baseline
To adjust the baseline for each curve use the 'baseline' function. This works by taking the mean value over a specified area and setting this to zero. There are default values included to get you started:
expt_1.baseline()
expt_2.baseline()
expt_3.baseline()
If you need to change the region used for baselining (for example, if there is noise on part of the approach) use the 'start_pos' and 'end_pos' inputs. Note these should be a fraction of the distance from the contact section of the curve towards the baseline. The default values are 'start_pos = 0.45' and 'end_pos = 0.8'. Change them using as follows:
expt_1.baseline(start_pos = 0.5, end_pos = 0.7)
You can also choose to exculed curves with a noisy baseline. The code calculates teh standard deviation over the specified region. If this is above a specified value the curve will be deleted. You can do this spearatley for the approach and retract curves by setting 'max_approach_noise' and 'max_retract_noise'. The default value for both is 1000 (i.e. all curves retained) and this can be changed as follows:
expt_1.baseline(max_approach_noise = 2, max_retract_noise = 4)
To change the baseline region and maximum noise together:
expt_1.baseline(
start_pos = 0.5,
end_pos = 0.7,
max_approach_noise = 2,
max_retract_noise = 4
)
Contact point
To set surface height to zero, use the 'contact' function. Note this currently interpolates between the first point below zero and the previous point, so may have trouble for noisy data:
expt_1.contact()
expt_2.contact()
expt_3.contact()
By default, the length position used will be the piezo position, rather than the actual tip position. If a cantilever is likley to experience large deflection when in contact with the surface, the tip position can be calculated using the measured force and deflection sensitivity by calling the 'adjust_tip_position' function.
expt_1.adjust_tip_position()
Plot adjusted curves
To view individual curves once they have been aligned, use the 'plot_adjusted' command, typing the number of the curve to plot in the brackets:
expt_1.plot_adjusted(10)
You can also plot all of the curves and use a slider to cycle through them, using 'plot_curves':
expt_3.plot_curves()
Delete bad curves
If a curve is noisy and needs to be removed, use the 'delete_curve' function. Put the numbers for all the curves to delete in the brackets:
expt_1.delete_curve(8,59,87)
expt_2.delete_curve(17)
Calcluate adhesion
To calculate the adhesion for each curve, use 'calc_adhesion'. The mean and standard deviation for the adhesion calculated for each experiment will be printed in the output.
expt_1.calc_adhesion()
expt_2.calc_adhesion()
expt_3.calc_adhesion()
It is possible to plot a histogram of the calculated adhesion values by setting 'plot_hist' to 'True':
expt_2.calc_adhesion(plot_hist = True)
Calculate modulus
To caclculate the elastic modulus, use 'calc_modulus':
expt_1.calc_modulus()
expt_2.calc_modulus()
expt_3.calc_modulus()
The method used currently follows the Hertz model for a spherical indenter:
Where:
is Applied Force (nN)
is Reduced Modulus (MPa)
is Sphere Radius (nm)
is Total Deformation (i.e. indentation depth) (nm)
is Indenter elastic modulus (MPa)
is Sample elastic modulus (MPa)
is Indenter Poisson Ratio
is Sample Poisson Ratio
The method assumes a hard indenter is used, i.e.
If a soft indenter, such as a colloidal probe, is used the 'soft_indenter' input should be set to 'True' and the Poisson Ratio and Elastic Modulus (MPa) of the indenter set using the 'indenter_poisson_ratio' and 'indenter_modulus' inputs respectivley.
expt_1.calc_modulus(poisson_ratio = 0.5, indenter_radius = 10 * 1e-9, soft_indenter = True, indenter_poisson_ratio = 0.5, indenter_modulus = 300)
The default method uses as surface poisson ratio of 0.5 and indenter radius of 10 nm (input required in m, i.e. 10E-09). To set these manually use the 'poisson_ratio' and 'indenter_radius' inputs:
expt_1.calc_modulus(poisson_ratio = 0.4, indenter_radius = 20e-9)
As with adhesion, it is possible to plot a histogram of the results:
expt_2.calc_modulus(plot_hist = True)
It is also possible to use the Sneddon model:
expt_1.calc_modulus_sneddon(poisson_ratio = 0.5, opening_angle = 35)
Or the four sided pyramid model:
expt_1.calc_modulus_pyramid(poisson_ratio = 0.5, face_angle = 35)
Plot adhesion vs modulus
To view a scatter plot of adhesion vs modulus use the following command:
expt_1.plot_adhesion_modulus()
Saving and reloading results
To save the calculated results, including force curves and parameters, in a format which can be re imported by the 'nanoforce' package later on use 'save_data'. This will open a window prompting you to select a folder and file name for saving. Leave the file extension blank.
expt_1.save_data()
You can also specify the filename and folder in the brackets:
expt_1.save_data('filename')
expt_2.save_data(r'\path\to\file')
To load saved data, create a new variable with the AFM class and call the 'load_data' function:
from afm import AFM
loaded_data = AFM()
loaded_data.load_data()
It is also possible to save individual variables for use in other softwares. This can be done using the numpy 'savetxt' function, as follows for adhesion (a full list of available variables can be found in the 'Custom plotting and analysis' section below):
import numpy as np
np.savetxt('file_name_here.csv', expt_1.adheision)
Built in comparison plots
There are a number of functions built in for plotting comparisons of the adhesion and modulus data for separate experiments. These have been left as basic functions. For more specific plots, folow the custom plotting section below.
To use the comparison plots, call the functions listed below directly from the AFM class with input variables for each experiment as an input.
Histogram comparison of adhesion
AFM.overlay_adhesion_hist(expt_1,expt_2,expt_3)
Histogram comparison of modulus
AFM.overlay_modulus_hist(expt_1,expt_2,expt_3)
Box plot comparison of adhesion
AFM.overlay_adhesion_box(expt_1,expt_2,expt_3)
Box plot comparison of modulus
AFM.overlay_modulus_box(expt_1,expt_2,expt_3)
Bar chart (with error bars) comparison of adhesion
AFM.overlay_adhesion_bar(expt_1,expt_2,expt_3)
Bar chart (with error bars) comparison of modulus
AFM.overlay_modulus_bar(expt_1,expt_2,expt_3)
Force mapping
Force mapping can be carried out by aquiring a fine grid of force curves on a sample surface. The scan area and grid size will depend on the uniformity of the surface and size of features, balanced with the length of time required for the scan. The force curves can be analyses as described above ant the force mapping function used to plot the reuslts. By default this function assumes a square grid of force curves across a 10 micron scan area and plots an adhesion map for this data.
expt_1.force_map()
If the grid is not square, the x_cells and y_cells inputs can be used to specify the grid size in the x and y directions. The auto_cell input must be set to False to manualy set the grid size.
expt_1.force_map(x_cells = 10, y_cells = 10, auto_cell = False)
The scan size can be specified using the x_scale and y_scale inputs.
expt_1.force_map(x_scale = 10, y_scale = 10)
The data to plot can be set using the plot input. Available plots are adhesion, modulus, topography (based on tip contact height), indent (indentation depth), max_force and snap_in.
expt_1.force_map(plot = 'modulus')
Finally, if using .nid files a 3d topographical representation of the surface, coloured with the chosen variable can be plotted by setting topography_3d to True.
expt_1.force_map(topography_3d = True)
Editing and saving figures
All built in figures are produced using Plotly. To make changes to the formatting of these figures, the figure must be returned by the plotting function. This is achevied by setting the return_fig input to True.
fig_1 = force_map(return_fig = True)
This can then be edited using as described in the plotly documentation for each plot type. The returned figure can also be saved, again following the steps in the plotly documentation.
Custom plotting and analysis
The calculated variables are stored within each 'experiment' variable. These can be called when writing custom plotting or analysis scripts specific to your application. The variables available for each experiment are as follows:
expt_1.approach # All approach curve data (nN)
expt_1.retract # All retract curve data (nN)
expt_1.z_position # All tip position data (nm)
expt_1.approach[:,i] # Adhesion curve for experiment i
expt_1.retract[:,i] # Adhesion curve for experiment i
expt_1.z_position[:,i] # Adhesion curve for experiment i
expt_1.def_sens # Deflection sensetivity (nm/V)
expt_1.spr_const # Spring constant (N/m)
expt_1.adheison # Adhesion data (nN)
expt_1.modulus # Modulus data (MPa)
expt_1.run_name # Experiment name
Below is an example custom plotting script to overlay retract curves for multips experiments. This example uses plotly. There are a number of plotting tools in this package, and more detail (including example scripts) is available from https://plotly.com/python/
import plotly.graph_objects as go
from afm import AFM
expt_1 = AFM()
expt_2 = AFM()
expt_1.run()
expt_2.run()
fig = go.Figure()
curve_number = 12
fig.add_trace(go.Scatter(y = expt_1.retract[:,curve_number],
x = expt_1.z_position[:,curve_number],
mode='lines',
name = 'Sample 1'))
fig.add_trace(go.Scatter(y = expt_2.retract[:,curve_number],
x = expt_2.z_position[:,curve_number],
mode='lines',
name = 'Sample 2'))
fig.update_layout(
yaxis_title_text='Force (nN)',
xaxis_title_text='Tip separation (nm)')
fig.show()
Example analysis script
An example analysis script is available from Git Hub:
https://github.com/crj341/nanoforce/blob/master/afm_analysis.py
A script is also available here to apply a clustering algorithm to identify groups of corresponding adhesion and modulus values, while removing noise:
https://github.com/crj341/nanoforce/blob/master/afm_clustering.py
Release history:
0.0.1 to 0.0.2 - Development
0.0.3 - Initial release
0.0.4 - Added dependenices to setup (numpy, scipy, easygui, plotly)
0.0.5 to 0.0.7 - Updated README and added class/function descriptions
0.0.8 - Fixed issue extracting spring constants with no decimal point
0.0.9 - Fixed packaging error
0.0.10 - Added support for Nanoscope version 5
0.0.11 to 0.1.0 - Bug fixes
0.1.1 - Full release
0.1.2 to 0.1.5 - Updated 'delete_curves' numbering system, added warning for bad curves in 'contact'
0.1.6 to 0.1.7 - Added support for files not starting at 000
0.1.8 - Added mac support
0.1.9 - Added violin plots for adhesion and modulus
0.1.10 - Fixed units issue for modulus calculaiton
0.1.11 - Added option to calculate actual tip position in 'nanoscope read'. Added option to include indenter Poisson Ratio and Moudlus in calc_modulus.
0.1.12 to - 0.1.24 - Fixed bugs
0.2.0 - New features now stable. Updated README.
0.2.1 to 0.2.19 - Initial support for Nanosurf AFM .nid files.
0.2.20 to 0.2.21 - Added Sneddon model and Four sided pyramid for modulus calculation.
0.2.22 - Fixed bug determining points per force curve.
0.2.23 - Fixed bug where adhesion was calculated as nan.
0.2.24 - Updated Readme for Nanosurf .nid files.
0.3.0 - New features now stable. Updated README.
0.3.1 - Added adhesion map function.
0.3.2 - Added error message to baseline function.
0.3.3 - Snap in force, max indent force, contact height and indent depth calculated.
0.3.4 to 0.3.6 - Additional force map plotting options.
0.3.7 - All figures now returnable. Updated README.
0.3.8 - Bug fixed which prevented force_map being called more than once per dataset.
0.3.9 - Bug fixed where indentation depth calculated incorrectly when using 'adjust_tip_position'.
0.3.10 to 0.3.15 - Updated Sneddon model modulus function to use line fit of indentation.
0.3.16 to 0.3.21 - Update to Hertz model.
0.3.22 to 0.3.28 - Added function to determine snap in distance.
0.3.29 t0 0.3.32 - Added function to calculate work of adhesion.