The Nanosurf Interface#

\(_{Yongtao}\) \(_{Liu,}\) \(_{liuy3@ornl.gov,}\) \(_{youngtaoliu@gmail.com}\)

\(_{July}\) \(_{2026}\)

Overview#

PyNanosurf is the AEcroscopy interface for the Nanosurf DriveAFM, connecting to a running Nanosurf Studio session through the official Nanosurf Python API rather than the Igor Pro / COM route used by PyCypher (Chapter 2). It offers the same kind of control — scan parameters, imaging lifecycle, tip positioning, signal readout — but there are a few conventions worth knowing if you’re coming from the Cypher tutorial:

  • Units. All spatial parameters use SI units (meters, Hz, N/m, …). PyCypher instead works in LVDT voltage units.

  • Pixel origin. Nanosurf uses a top-left image origin — pixel (0, 0) is the top-left corner of the scan frame. PyCypher uses bottom-left.

  • Connection. PyNanosurf connects to an already-running Nanosurf Studio session; it does not launch the software.

  • Dependency. PyNanosurf imports Nanosurf’s own nanosurf Python package, which is not installed by pip install aecroscopywave — it ships with your Nanosurf Studio installation. Make sure it’s importable in your environment before using this interface.

Import#

from aecroscopywave.interfaces import Nanosurf

Initialize PyNanosurf#

Creates an instance of PyNanosurf, which connects to the running Nanosurf Studio session. Nanosurf Studio must already be open before calling this.

pdn = Nanosurf.PyNanosurf()

Set and Get Scan Parameters#

set_scan_parameters() is the Nanosurf analogue of PyCypher.PV() — it takes a dictionary of parameter names and values, but in physical units rather than Igor Pro command strings. Supported keys: scan_range_x, scan_range_y (fast/slow axis range, meters), line_rate (Hz), points_per_line, lines_per_frame, offset_x, offset_y (frame center, meters), and rotation (degrees).

Unknown keys are not applied — they come back in the ignored field of the returned status dict, so typos are easy to catch.

status = pdn.set_scan_parameters({
    "scan_range_x": 5e-6,
    "scan_range_y": 5e-6,
    "points_per_line": 256,
    "lines_per_frame": 256,
    "line_rate": 1.0,
})
status
pdn.get_scan_parameters()

Start / Stop Imaging#

start_imaging() and stop_imaging() are the Nanosurf analogues of PyCypher.set_Master_Panel(do_scan="Frame Up" / "Stop"). set_imaging() is a convenience wrapper that mirrors set_Master_Panel() directly: set parameters, then optionally issue a scan command in one call.

pdn.start_imaging()
# Poll until the frame finishes
import time
while pdn.is_scanning():
    time.sleep(0.5)
# Equivalent to the two cells above, in one call
pdn.set_imaging(
    parameters={"scan_range_x": 5e-6, "points_per_line": 256},
    do_scan="start",
)

Approach#

start_approach() brings the tip into contact with the sample surface — the DriveAFM equivalent of PyCypher.engage().

pdn.start_approach()

Move Tip#

move_tip() positions the tip at a pixel location within the current scan frame, the same idea as PyCypher.move_tip() from Chapter 2 — but remember the top-left pixel origin and physical (meter) units mentioned above. Internally it converts the pixel index to a normalized [-1, 1] coordinate (pixel_to_xy()), then to an absolute physical position (xy_to_physical()), and writes that as the new frame offset; pixel_to_physical() combines both steps if you just need the coordinates without moving the tip.

# Move the tip to the pixel at the center of a 256x256 frame
pdn.move_tip((128, 128), transit_time=0.5)

Read Signals and Cantilever Properties#

read_signals() returns the instantaneous deflection, lateral, detector-sum, and lock-in amplitude/phase signals. get_cantilever_properties() returns the active cantilever’s calibration values.

pdn.read_signals()
pdn.get_cantilever_properties()

Z Controller Setpoint#

pdn.get_z_setpoint()
pdn.set_z_setpoint(0.5)

Workspace and Automation Queue#

Beyond single-frame imaging, PyNanosurf exposes Nanosurf Studio’s workspace and automation queue directly — a built-in way to queue up multiple frames or spectroscopy grids and run them unattended, without the manual per-point looping shown for PyCypher in Chapter 6.

# Define a couple of regions to image, add them to the queue, then run it
frame_a = pdn.add_frame("Region A", x=5e-6, y=-3e-6, width=2e-6, height=2e-6)
frame_b = pdn.add_frame("Region B", x=-5e-6, y=3e-6, width=2e-6, height=2e-6)

pdn.add_to_queue(frame_a)
pdn.add_to_queue(frame_b)

pdn.run_automation_queue()
while pdn.is_automation_running():
    time.sleep(1)

Disconnect#

Releases the Nanosurf Studio session when you’re done.

pdn.disconnect()