Files
tono/docs/library.md

5.9 KiB

Using tono as a Python library

tono's processing nodes can be used as a standalone Python library for scripting, batch processing, and integration into other tools — no web server required.

Installation

pip install -e .

This installs the core library with all signal processing dependencies (numpy, scipy, scikit-image, etc.) but not the web server (aiohttp). To install the full app with the server, use pip install -e ".[server]".

Quick start

import tono

# Load an SPM data file
fields = tono.load("scan.gwy")
height = fields[0]

# Apply a processing node
leveled = tono.apply("PlaneLevelField", height)
filtered = tono.apply("GaussianFilter", leveled, sigma=2.0)

# Access the raw numpy array
print(filtered.data.shape)   # (256, 256)
print(filtered.data.mean())  # height in metres

API reference

Loading data

tono.load(path) -> list[DataField]

Load an SPM data file. Returns one DataField per channel.

fields = tono.load("scan.gwy")       # Gwyddion
fields = tono.load("image.sxm")      # Nanonis
fields = tono.load("data.ibw")       # Igor Binary Wave
fields = tono.load("scan.hdf5")      # HDF5
fields = tono.load("photo.png")      # Standard images

tono.channel_names(path) -> list[str]

Get channel names without loading the full data.

names = tono.channel_names("scan.gwy")
# ['Height', 'Phase', 'Amplitude']

tono.supported_formats() -> frozenset[str]

List all supported file extensions.

Processing

tono.apply(node_name, *args, **kwargs)

Run a processing node. Positional arguments are mapped to required inputs in declaration order. Returns a single output if the node has one output, or a tuple if it has multiple.

# Positional: first required input is `field`
result = tono.apply("GaussianFilter", my_field, sigma=3.0)

# All keyword arguments
result = tono.apply("GaussianFilter", field=my_field, sigma=3.0)

# Nodes with multiple outputs return a tuple
field_out, mask_out = tono.apply("ThresholdMask", my_field, method="otsu")

tono.get_node(name) -> node_instance

Get a node instance for direct use. This gives full control over the node's process() method.

gauss = tono.get_node("GaussianFilter")
(result,) = gauss.process(field=my_field, sigma=3.0)

tono.nodes() -> list[str]

List all available node class names.

for name in tono.nodes():
    print(name)

Creating data

tono.field(data, xreal=1e-6, yreal=1e-6, ...) -> DataField

Create a DataField from a numpy array.

import numpy as np

# From a numpy array
f = tono.field(np.random.randn(256, 256))

# With physical dimensions (10 um x 10 um scan)
f = tono.field(data, xreal=10e-6, yreal=10e-6, si_unit_z="V")

Data types

DataField

The core 2D data container, analogous to Gwyddion's GwyDataField.

Attribute Type Description
data np.ndarray 2D float64 array (yres x xres)
xres, yres int Pixel dimensions
xreal, yreal float Physical dimensions in metres
dx, dy float Pixel size in metres (property)
si_unit_xy str Lateral unit (e.g. "m")
si_unit_z str Value unit (e.g. "m", "V", "A")
domain str "spatial" or "frequency"

Key methods:

  • field.copy() — deep copy
  • field.replace(data=..., si_unit_z=...) — copy with selected fields replaced

Other types

  • LineData — 1D data with optional x-axis and units
  • RecordTable — list of {quantity, value, unit} dicts (scalar measurements)
  • DataTable — list of row dicts (tabular data like grain statistics)

Batch processing example

import tono
from pathlib import Path

input_dir = Path("scans/")
results = {}

for path in input_dir.glob("*.gwy"):
    fields = tono.load(path)
    height = fields[0]

    # Standard processing pipeline
    leveled = tono.apply("PlaneLevelField", height)
    filtered = tono.apply("GaussianFilter", leveled, sigma=1.5)

    # Extract statistics
    stats_node = tono.get_node("Statistics")
    (table,) = stats_node.process(field=filtered)
    results[path.name] = table

    print(f"{path.name}: processed {height.xres}x{height.yres} "
          f"({height.xreal*1e6:.1f} x {height.yreal*1e6:.1f} um)")

Integration with matplotlib

import tono
import matplotlib.pyplot as plt
import numpy as np

fields = tono.load("scan.gwy")
field = fields[0]

# Convert physical coordinates for axis labels
extent = [0, field.xreal * 1e6, 0, field.yreal * 1e6]  # in micrometres

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

axes[0].imshow(field.data * 1e9, extent=extent, cmap="afmhot")
axes[0].set_title("Raw")
axes[0].set_xlabel("x (um)")
axes[0].set_ylabel("y (um)")

leveled = tono.apply("PlaneLevelField", field)
axes[1].imshow(leveled.data * 1e9, extent=extent, cmap="afmhot")
axes[1].set_title("Leveled")

filtered = tono.apply("GaussianFilter", leveled, sigma=2.0)
axes[2].imshow(filtered.data * 1e9, extent=extent, cmap="afmhot")
axes[2].set_title("Filtered")

for ax in axes:
    ax.set_xlabel("x (um)")

plt.tight_layout()
plt.savefig("pipeline.png", dpi=150)

Available nodes

Use tono.nodes() to list all nodes. Major categories include:

Category Example nodes
Level & Correct PlaneLevelField, PolyLevelField, FacetLevelField, LineCorrection, DriftCorrection
Filter GaussianFilter, MedianFilter, KuwaharaFilter, WaveletDenoise, EdgeDetect
Spectral FFT2D, FFT2DInverse, FFTFilter, PSDF, ACF2D, CrossCorrelate
Measure Statistics, Histogram, CrossSection, Curvature, FractalDimension
Detect FeatureDetection, HoughTransform, TemplateMatch, PixelClassification
Mask ThresholdMask, GrainMark, DrawMask, MaskMorphology
Grains GrainAnalysis, WatershedSegmentation, GrainDistributions
Geometry CropResizeField, RotateField, Resample, AffineCorrection
Tip TipModel, TipDeconvolution, BlindTipEstimate