Skip to content

Commit a240072

Browse files
authored
Merge pull request #34 from New-Horizons-SPM/STS-Grid-Dev
New STS Grid with drift correction
2 parents e7dbd7d + bd6e6e1 commit a240072

12 files changed

Lines changed: 609 additions & 15 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Full documentation available [here](https://new-horizons-spm.github.io/scanbot/)
1616
* STM
1717
- Bias dependent imaging with drift correction
1818
- Automated sample surveying (NxN grid)
19+
* STS
20+
- STS Grids with drift correction
1921
* nc-AFM
2022
- z-dependent nc-AFM
2123
- nc-AFM registration

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "scanbot"
3-
version = "4.2.0"
3+
version = "4.3.0"
44
description = "Collection of automated STM and nc-AFM commands compatible with Nanonis V5 SPM Controller"
55
authors = ["Julian Ceddia <jdceddia@gmail.com>"]
66
readme = "README.md"

‎scanbot/App/src/DataAcquisition.js‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { Footer, Card, GoBack } from './Components';
22
import "./styles/Scanbot.css";
33

44
const CardRow = () => (
5-
<div className="card-row">
6-
<Card className="survey-card" title="Survey" content="Survey your sample" navigateTo="/survey" />
7-
<Card className="bias-dep-card" title="Bias Dependent Imaging" content="Start a series of bias dependent images" navigateTo="/bias-dep" />
8-
<Card className="stm-control-card" title="Automation" content="Tools to assist in STM operation" navigateTo="/stm-control" />
5+
<div className="card-row-4">
6+
<Card className="survey-card card-4" title="Survey" content="Survey your sample" navigateTo="/survey" />
7+
<Card className="bias-dep-card card-4" title="Bias Dependent Imaging" content="Start a series of bias dependent images" navigateTo="/bias-dep" />
8+
<Card className="sts-grid-card card-4" title="STS Grid" content="Acquire a drift-corrected STS grid" navigateTo="/sts-grid" />
9+
<Card className="stm-control-card card-4" title="Automation" content="Tools to assist in STM operation" navigateTo="/stm-control" />
910
</div>
1011
);
1112

‎scanbot/App/src/STSGrid.js‎

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import React, { useEffect, useState, useRef } from 'react';
2+
import { Sidebar, usePersistedState, GoBack } from './Components';
3+
import { positiveInt, number, positiveNumber } from './Validations';
4+
import emptyFrameIcon from './img/frame.png';
5+
import './styles/Survey.css'
6+
7+
function STSGrid() {
8+
const initialAllFormData = {
9+
0: {Vset: '-0.5', Iset: '50', Vmov: '0', Imov: '0' },
10+
1: {NDC: '5', VDC: '-1', IDC: '10', pxdc: '128', tdc: '0', tbdc: '1' },
11+
2: {path: '', s: 'scanbot-sts-grid' }
12+
}
13+
const [allFormData, setAllFormData] = usePersistedState('sts-grid-allFormData', initialAllFormData)
14+
const [running, setRunning] = useState(false)
15+
const [lastImage, setLastImage] = useState({ src: emptyFrameIcon, alt: "blank image", width: 300, height: 300} );
16+
const [gridGif, setGridGif] = useState([{ src: emptyFrameIcon, alt: "blank image", width: 300, height: 300}] );
17+
const [lastTimestamp, setLastTimestamp] = useState(0);
18+
const timerIdRef = useRef(null);
19+
20+
const handleInputChange = (formIndex, name, value, index) => {
21+
var goahead = true
22+
23+
// Validations
24+
const positiveNumbers = ['Imov','Iset','IDC','tdc','tbdc']
25+
const positiveInts = ['NDC','pxdc']
26+
const numbers = ['Vset','Vmov', 'VDC']
27+
if(positiveInts.includes(name)) {
28+
goahead = positiveInt(value)
29+
}
30+
if(numbers.includes(name)) {
31+
goahead = number(value)
32+
}
33+
if(positiveNumbers.includes(name)) {
34+
goahead = positiveNumber(value)
35+
}
36+
37+
if(goahead) {
38+
setAllFormData({
39+
...allFormData,
40+
[formIndex]: {
41+
...allFormData[formIndex],
42+
[name]: value
43+
}
44+
});
45+
}
46+
};
47+
48+
const handleFormSubmit = () => {
49+
if(running){
50+
setRunning(false)
51+
stop()
52+
}else{
53+
startSTSGrid()
54+
}
55+
};
56+
57+
const stop = () => {
58+
fetch("/stop")
59+
}
60+
61+
const startSTSGrid = () => {
62+
const userArgs = unpackArgs()
63+
64+
fetch('/run_stsGrid', {
65+
method: 'POST',
66+
headers: {
67+
'Content-Type': 'application/json',
68+
},
69+
body: JSON.stringify({userArgs: userArgs}),
70+
})
71+
.then(response => response.json())
72+
.then(data => {
73+
if(data['status'] === "success"){
74+
var image = lastImage
75+
image['src'] = emptyFrameIcon
76+
if(lastImage){
77+
URL.revokeObjectURL(lastImage);
78+
URL.revokeObjectURL(gridGif[0]);
79+
}
80+
81+
setLastImage(image)
82+
setGridGif([image])
83+
84+
setRunning(true)
85+
console.log('Success:', data);
86+
}else{
87+
console.log('Fail', data)
88+
}
89+
90+
})
91+
.catch((error) => {
92+
console.error('Error:', error);
93+
});
94+
}
95+
96+
const unpackArgs = () => {
97+
var unpackedArgs = []
98+
const formIdx = Object.keys(allFormData)
99+
for (let idx = 0; idx < formIdx.length; idx++) {
100+
const form = allFormData[formIdx[idx]]
101+
const keys = Object.keys(form)
102+
for (let index = 0; index < keys.length; index++) {
103+
const key = '-' + keys[index]
104+
var param = key + '=' + form[keys[index]]
105+
unpackedArgs = [...unpackedArgs, param]
106+
}
107+
}
108+
console.log("unpacked")
109+
console.log(unpackedArgs)
110+
return unpackedArgs
111+
}
112+
113+
const formData = [
114+
{
115+
title: "Grid Settings",
116+
inputs: [
117+
{ label: "STS Setpoint Current (pA)", type: "text", id: "Iset", name: "Iset", value: allFormData[0]['Iset'], description: "(float) Setpoint current applied before acquiring each STS (pA). 0 = leave setting in Nanonis"},
118+
{ label: "STS Setpoint Voltage (V)", type: "text", id: "Vset", name: "Vset", value: allFormData[0]['Vset'], description: "(float) Bias at which the setpoint is taken (V). 0 = leave setting in Nanonis"},
119+
{ label: "Tip Move Setpoint Current (pA)", type: "text", id: "Imov", name: "Imov", value: allFormData[0]['Imov'], description: "(float) Change the setpoint current to this value when the tip moves between grid points (pA). 0 = do not change"},
120+
{ label: "Tip Move Setpoint Voltage (V)", type: "text", id: "Vmov", name: "Vmov", value: allFormData[0]['Vmov'], description: "(float) Change the bias to this value when the tip moves between grid points (V). 0 = do not change"},
121+
]
122+
},
123+
{
124+
title: "Drift Correction Settings",
125+
inputs: [
126+
{ label: "Drift correction after N points", type: "text", id: "NDC", name: "NDC", value: allFormData[1]['NDC'], description: "(int) Perform drift correction after this many points in the grid have been acquired" },
127+
{ label: "Setpoint currect (pA)", type: "text", id: "IDC", name: "IDC", value: allFormData[1]['IDC'], description: "(float) Change the setpoint current to this value when acquiring images for drift correction (pA). 0 = do not change"},
128+
{ label: "Image bias (V)", type: "text", id: "VDC", name: "VDC", value: allFormData[1]['VDC'], description: "(float) Change the bias to this value when acquiring images for drift correction (V). 0 = do not change"},
129+
{ label: "Number of pixels", type: "text", id: "pxdc", name: "pxdc", value: allFormData[1]['pxdc'], description: "(int) Number of pixels in the drift correction images. 0 = nanonis current setting" },
130+
{ label: "Fwd speed (s/line)", type: "text", id: "tdc", name: "tdc", value: allFormData[1]['tdc'], description: "(float) Drift correction image speed - Time per line (forward direction) (s). 0 = nanonis current setting" },
131+
{ label: "Bwd speed multiplier", type: "text", id: "tbdc", name: "tbdc", value: allFormData[1]['tbdc'], description: "(float) Drift correction image speed - Backward direction speed multiplier. E.g. 1=same speed, 2=twice as fast, 0.5=half speed" },
132+
]
133+
},
134+
{
135+
title: "Drift correction",
136+
inputs: [
137+
{ label: "Save path", type: "text", id: "path", name: "path", value: allFormData[2]['path'], description: "(str) Full path of the desired save location of grid data. Leave blank to save in the current Nanonis Path"},
138+
{ label: "Save suffix", type: "text", id: "s", name: "s", value: allFormData[2]['s'], description: "(str) Filename suffix for the saved grid data"},
139+
]
140+
},
141+
{
142+
title: "Actions",
143+
inputs: [
144+
{ label: `${!running ? 'Start' : 'Stop'} Scanning`, type: "submit", id: "submit", name: "submit", description: "Start/stop the Scanbot STS Grid acquisition", click: handleFormSubmit}
145+
]
146+
}
147+
];
148+
149+
async function fetchGif(){
150+
if(running){
151+
const gifResponse = await fetch('/get_gif')
152+
if(gifResponse.ok){
153+
const gifBlob = await gifResponse.blob()
154+
const gifUrl = URL.createObjectURL(gifBlob)
155+
URL.revokeObjectURL(gridGif);
156+
var gif = gridGif.slice()
157+
URL.revokeObjectURL(gif);
158+
gif['src'] = gifUrl
159+
setGridGif([gif])
160+
}
161+
}
162+
}
163+
164+
useEffect(() => {
165+
const pollingCallback = async () => {
166+
const timestamp = lastTimestamp
167+
const response = await fetch('/image_updates', {
168+
method: 'POST',
169+
headers: {
170+
'Content-Type': 'application/json',
171+
},
172+
body: JSON.stringify({timestamp: timestamp}),
173+
})
174+
175+
if(response.ok){
176+
const blob = await response.blob()
177+
const url = URL.createObjectURL(blob);
178+
179+
var image = lastImage
180+
image['src'] = url
181+
setLastImage(image)
182+
setLastTimestamp(Date.now())
183+
184+
fetchGif();
185+
}
186+
187+
const actionResponse = await fetch('/get_state')
188+
const actionData = await actionResponse.json()
189+
const action = actionData['action']
190+
const isRunning = actionData['running']
191+
192+
if(!isRunning || !(action === "stsgrid")) {
193+
setRunning(false)
194+
}
195+
};
196+
197+
const startPolling = () => {
198+
timerIdRef.current = setInterval(pollingCallback, 3000);
199+
};
200+
201+
const stopPolling = () => {
202+
clearInterval(timerIdRef.current);
203+
};
204+
205+
if (running) {
206+
startPolling();
207+
}
208+
209+
return () => {
210+
stopPolling();
211+
};
212+
213+
// eslint-disable-next-line react-hooks/exhaustive-deps
214+
}, [running]);
215+
216+
useEffect(() => {
217+
const handleBeforeUnload = (event) => {
218+
if (running) {
219+
// Standard for most browsers
220+
event.preventDefault();
221+
// Required for some browsers (e.g., Chrome)
222+
event.returnValue = '';
223+
}
224+
};
225+
226+
window.addEventListener('beforeunload', handleBeforeUnload);
227+
228+
return () => {
229+
window.removeEventListener('beforeunload', handleBeforeUnload);
230+
};
231+
}, [running]);
232+
233+
const cleanUp = () => {
234+
if(running) {
235+
const userResponse = window.confirm("There is a scan in progress. If you proceed, the scan will be stopped.");
236+
if(userResponse){
237+
stop()
238+
return true
239+
}
240+
return false
241+
}
242+
return true
243+
}
244+
245+
return (
246+
<div className="survey-container">
247+
<div className='sidebar-container'>
248+
<Sidebar
249+
formData={formData}
250+
onInputChange={handleInputChange}
251+
/>
252+
</div>
253+
<div className='bias-dep-main-content'>
254+
<img className="bias-dep-image last-image" src={lastImage['src']} alt={lastImage['alt'] }/>
255+
<img className="bias-dep-image gif" src={gridGif[0]['src']} alt={gridGif['alt'] }/>
256+
</div>
257+
258+
<GoBack
259+
cleanUp={cleanUp}
260+
navigateTo={'/data-acquisition'}
261+
/>
262+
</div>
263+
);
264+
}
265+
266+
export default STSGrid

‎scanbot/App/src/img/sts-grid.gif‎

8 MB
Loading

‎scanbot/App/src/img/sts-grid.png‎

62 KB
Loading

‎scanbot/App/src/index.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Configuration from './Configuration';
66
import DataAcquisition from './DataAcquisition';
77
import Survey from './Survey';
88
import BiasDep from './BiasDep';
9+
import STSGrid from './STSGrid';
910
import STMControl from './STMControl';
1011
import './styles/index.css';
1112

@@ -18,6 +19,7 @@ root.render(
1819
<Route path="/data-acquisition" element={<DataAcquisition />} />
1920
<Route path="/survey" element={<Survey />} />
2021
<Route path="/bias-dep" element={<BiasDep />} />
22+
<Route path="/sts-grid" element={<STSGrid />} />
2123
<Route path="/stm-control" element={<STMControl />} />
2224
</Routes>
2325
</BrowserRouter>

‎scanbot/App/src/styles/Components.css‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
background-size: 100%;
1818
}
1919

20+
.card-4 {
21+
width: 15%;
22+
}
23+
2024
.card::before {
2125
content: '';
2226
position: absolute;

‎scanbot/App/src/styles/Scanbot.css‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@
4141
width: 80%;
4242
}
4343

44+
.card-row-4 {
45+
display: flex;
46+
justify-content: space-around;
47+
margin: 20px;
48+
margin-left: 5%;
49+
width: 90%;
50+
}
51+
4452
.configuration-card {
4553
background-image: url('../img/config.png');
4654
background-color: rgb(220,228,230);
@@ -64,11 +72,21 @@
6472
background-size: 60%;
6573
}
6674

75+
.sts-grid-card {
76+
background-image: url('../img/sts-grid.png');
77+
background-size: 60%;
78+
}
79+
6780
.bias-dep-card:hover {
6881
background-image: url('../img/bias-dep.gif');
6982
background-size: 60%;
7083
}
7184

85+
.sts-grid-card:hover {
86+
background-image: url('../img/sts-grid.gif');
87+
background-size: 60%;
88+
}
89+
7290
.stm-control-card {
7391
background-image: url('../img/automation.png');
7492
background-size: 70%;

0 commit comments

Comments
 (0)