Python Scripting

Note

Pygwy only works with Python 2.7. Any Python 3 version that might exist in future will have to be created from scratch and will not be backward compatible.

Python scripting allows you to automate data SPM processing tasks, both within Gwyddion and by using its functionality in standalone Python scripts run outside Gwyddion. The scripts can range from simple macro-like executions of several functions in sequence to complex programs combining data from multiple files and utilising third-party libraries or programs.

The Gwyddion module providing this functionality is called pygwy and the term pygwy is by extension used also for Python scripting with Gwyddion in general, even if not directly related to the pygwy module itself.

The term Python scripting implies Gwyddion scripts are written in Python. Python is a powerful and widely used high-level, general-purpose, interpreted programming language with concise, easy-to-understand syntax, wide set of libraries and support for a large number of operating system and environments. It is also well documented with tutorials available. And all the available Python features and facilities can be used in Gwyddion Python scripts. The only new thing is that you also gain access to Gwyddion data structures, functions and program state.

Extending and embedding, modules and modules

Since you can combine Gwyddion and Python in several different ways and both programs also use the term module extensively, and for different things, a clarification is in order.

Gwyddion provides a Python extension module called gwy. So you can run Python, load the gwy module the same way as any other module using

import gwy

and start utilising its functions and classes in the Python shell or a script. This is usually called a standalone pygwy script. Of course, you are now running Python, not Gwyddion. So you cannot use functionality related to interaction with Gwyddion (the program). Even if a Gwyddion instance is running simultaneously it is completely independent on the script and cannot be directly controlled by it.

On the other hand, the pygwy module (a Gwyddion module) allows combining things the other way round and running the Python interpreter inside Gwyddion, i.e. embedding Python in Gwyddion. When you execute Data ProcessPygwy Console a Python scripting console appears from which you can run Python commands. In this case the Python code can interact with the current Gwyddion instance in various ways. A script run in this manner is usually simply called pygwy script (without any epithet).

The Command entry in the lower part of the console window allows executing simple individual commands. They are immediately executed when you press Enter and their output is printed in the log area above.

Longer scripts can be typed, pasted or loaded into the main area in the upper part. The button Execute (Ctrl-E) then runs the script. The output is again displayed in the log area, together with any error messages. The other control buttons enable saving (Ctrl-S) and loading (Ctrl-O) the scripts. Button Clear Log clears the log area.

The pygwy console showing a simple script (printing the range of values of the current image) and its output.

There are some other subtle differences from standalone scripts. The gwy module is always automatically imported in the console. In addition, it will be available there even if you find the Python extension module on disk and delete it because here it is created on the fly from within Gwyddion.

Finally, you can write Gwyddion extension modules in Python. Such modules will appear in the Data Process menu, similar to other data processing functions. You can also write modules that implement loading and saving of data in new formats. This is slightly more involved than the previous two options and will be explained below. This kind of module is usually called Python data processing module (with Gwyddion context implied), or Gwyddion Python data processing module if the context is unclear.

In addition to the main gwy module, there is also a small module gwyutils that offers some extra auxiliary functions. A part of them provide workaround for functionality that used not to be covered well by gwy itself and are mostly obsolete. Some are, however, still useful. The gwyutils module is not imported automatically; you always have to import it explicitly with

import gwyutils

Depending on how Gwyddion was installed, it may be necessary to add the path to gwyutils.py to sys.path beforehand:

import sys
sys.path.append('/the/path/to/the/gwyutils/module')

Data fields – how images are represented

The probably most important Gwyddion data structure you will encounter is DataField that represents an image.

It is essentially an array of N×M floating point data values that also carries additional information, such as physical dimensions or units. The data values are always in base SI units (e.g. metres or Volts, nor nanometres or millivolts). In most cases you will work with DataFields that came from a file. However, creating a new zero filled 128 by 128 data field (with physical dimensions one by one) is not difficult:

data_field = gwy.DataField(128, 128, 1.0, 1.0)

Pixel dimensions are called resolutions in Gwyddion and can be obtained using functions get_xres() and get_yres():

# Pixel width
xres = data_field.get_xres()

# Pixel height
yres = data_field.get_yres()

Or you can create a data field as a copy of another

another_field = data_field.duplicate()

or one that is similar to another data field but zero filled

similar_field = data_field.new_alike()

The DataField object has lots of methods, from simple, such as obtaining the physical dimensions or simple statistical quantities

# Physical dimensions
xreal, yreal = data_field.get_xreal(), data_field.get_yreal()

# Value range
zmin, zmax = data_field.get_min_max()

# Mean value
mean = data_field.get_avg()

to complex ones performing complicated operations with many tunable parameters, such as marking of scars (strokes)

data_field.mark_scars(result_field, high, low, min_len, max_width, False)

You can find them all in the reference documentation. Some common operations may involve combining a few functions together. For instance the equivalent of Fix Zero can be done by combining get_min() and add():

data_field.add(-data_field.get_min())

If you do more complex data processing, just using the existing methods may not be sufficient and you will need to read or modify the individual pixel values directly. There are several possible methods. The simplest is direct indexing; to read the value in the top-left corner you can just use

value = data_field[0]

and similarly

data_field[0] = 1.5

sets the top-left corner value to 1.5. Note that the image data are flattened, i.e. present in one big array, ordered row by row, and even though DataField represents an image only one index is used (as opposed to separate row and column indices). So, the last value on the third row of the 128 by 128 data field could be changed to −1.0 by

data_field[2*128 + 127] = -1.0

Another possibility is the methods get_data() and set_data() that extract the entire data to a Python list and fill the entire DataField data from a Python list (or other sequence), respectively. Note these method always copy the data. If you change the extracted list it does not change the data field's data and vice versa. For instance if you want to get your hands dirty Fix Zero can be replicated as follows:

data = data_field.get_data()
m = min(data)
data = [z-m for z in data]
data_field.set_data(data)

Finally, if you use NumPy you might want to directly operate on the data field's using NumPy functions. The helper module gwyutils provides functions that enable this. Calling

array = gwyutils.data_field_data_as_array(data_field)

creates a NumPy array that references the data field's data. This has to be used with care because some DataField operations change the data representation (for instance resizing) and then the NumPy array breaks. Generally, it is not recommended to mix haphazardly operations with the array and DataField methods.

When you are done modifying a data field corresponding to an image in Gwyddion you should call

data_field.data_changed()

This emits a signal of the DataField object notifying any data windows and thumbnails displaying the image that they should update themselves. Generally, this should be only done once for each changed data field at the end of the script (or when you are finished with this particular image). If you forget this the display may get out of sync with the real image content.

Containers – how files are represented

An SPM file is represented by Container which is a dictionary-like object holding everything that can be present in a GWY file. Every file is upon import transformed to a Container with the same structure. So there is no difference between GWY files and other file types once they are loaded into the program.

Items in a Container are identified by string keys (or equivalent integer keys, though in Python this is used more seldom), for instance "/0/data" identifies image number 0 and "/5/select/rectangle" identifies rectangular selection on image number 5. The locations of the various pieces of data are described in the GWY file format specification.

You can access items in a Container by simple indexing:

# Set image 0 to our data field
container['/0/data'] = data_field

# Get rectangular selection for image 5
selection = container['/5/select/rectangle']

However, Container differs from a Python dictionary in several aspects. It can hold only certain data types: booleans, integers, floating point values, strings and serialisable Gwyddion objects (which is essentially any Gwyddion data object you can come across). It has methods such as set_object_by_name() or get_boolean_by_name() that emphasise the specific type of data being stored or extracted and you can use them instead of indexing. Furthermore, even with these limitations if you start storing random things with random names there you will not obtain something Gwyddion understands as an SPM file and most likely confuse it to no end, possibly even crash. So always stick to item names given in the specification and the corresponding item types.

There are functions that help with construction of keys identifying various items in the file, called gwy_app_get_something_key_for_id() where something stands for the item type. So the key for image 3 can be constructed using

key = gwy.gwy_app_get_data_key_for_id(3)

Note that these functions construct the integer keys (that work equally well but are opaque). You can use functions gwy_name_from_key() and gwy_key_from_name() to convert between integer and string keys. Calling

gwy.gwy_name_from_key(gwy.gwy_app_get_data_key_for_id(3))

will produce "/3/data" as expected.

The list of everything inside a Container can be obtained using methods keys() that produces a list of integer keys, or keys_by_name() that produces a list of string identifiers. This can be occasionally useful if you need to scan the container. In most cases, however, functions listing individual data kinds are more convenient.

Other data types

A Gwyddion file does not contain just images. It can also contain masks, selections, volume data, graphs, XYZ data, ... So here we give a brief overview of other objects you may reasonably encounter.

DataField is also used to represent masks and presentations. They must always have the same dimensions as the main data field. In mask data fields the value 1 (or larger) stands for masked and 0 (or smaller) for unmasked. Values between 0 and 1 should be avoided. In presentations the value range does not matter. They are stored under keys of the form "/0/mask" or "/0/show".

Container also represents metadata. Metadata Containers have different rules: all the metadata values must be strings. Otherwise both keys and values are free-form.

Brick represents volume data. It is very similar to DataField except it has three dimensions except of two.

DataLine represents conversely regularly spaced one-dimensional data. You will not actually encounter it in GWY files (curve plots are represented differently) but it is used in various data processing functions.

Spectra represents set of spectra curves, i.e. single point spectroscopy data.

SIUnit represents physical unit. It is an auxiliary object that you will not encounter standalone but all kinds of data objects carry SIUnits specifying the physical units of dimensions or values.

GraphModel represents a graph containing a set of curves.

GraphCurveModel represents a single curve in a graph.

Selection represents a selection on an image or graph. It is a sequence of coordinates. Selection is in fact an abstract base class, each type of selection (point, line, rectangle, ...) is a separate subclass.

Finding and enumerating data

Knowing everything about Containers, DataFields and SIUnits, you would probably also like to know how to get the data to process.

In the context of scripts or modules that should work with the currently active data, as normal Gwyddion data processing modules do, the question is how to obtain the currently active data. The answer is function gwy_app_data_browser_get_current(). It takes a AppWhat argument specifying what kind of current item you are interested in and returns the appropriate item. It can be used to obtain the data objects themselves

# Get the active image (DataField)
data_field = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD)

# Get the active file (Container)
container = gwy.gwy_app_data_browser_get_current(gwy.APP_CONTAINER)

or their number in the file

# Get the active image number
i = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD_ID)

# Get the active graph number
i = gwy.gwy_app_data_browser_get_current(gwy.APP_GRAPH_MODEL_ID)

or their integer keys

# Get the active image key
key = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD_KEY)

# Get the active mask data field key
key = gwy.gwy_app_data_browser_get_current(gwy.APP_MASK_FIELD_KEY)

and also some less common things such as the active page in the data browser. When there is no active item of the requested type the function returns None.

On the other hand, frequently you need to find all data of certain kind in the file. The list of all image (channel) numbers can be obtained with

ids = gwy.gwy_app_data_browser_get_data_ids(container)

When the function returns the list [0, 1, 4] it means the container contains images with numbers 0, 1 and 4. In other words, there will be images under keys "/0/data", "/1/data" and "/4/data". Similar functions exist for enumeration of graphs, spectra, volume or XYZ data. There is also the function gwy_app_data_browser_get_containers() that produces the list of all files (i.e. Containers representing files) currently open.

Finally, it is often useful to locate data objects by title. A simple search function gwy_app_data_browser_find_data_by_title() is provided. It matches the titles using wildcards similar to Unix shell:

# Find images with title exactly "Height"
ids = gwy.gwy_app_data_browser_find_data_by_title(container, 'Height')

# Find images with title starting with "Current"
ids = gwy.gwy_app_data_browser_find_data_by_title(container, 'Current*')

# Find images with title containing "03"
ids = gwy.gwy_app_data_browser_find_data_by_title(container, '*03*')

When you add new image or other data type to a file use a data browser function:

i = gwy.gwy_app_data_browser_add_data_field(container, data_field, True)

The last parameter specifies whether to show the data in a data window or not. The function returns the number assigned to the new data field. You can then use it to construct identifiers of related data. Similar functions exist for other data types.

Running module functions

Utilisation of DataField (and other) methods, perhaps combined with a bit of direct data access, is the most straightforward and often preferred approach to data processing in a pygwy script. Nevertheless, sometimes you also want to run a Gwyddion function exactly as if it was selected from the menu. Either because the function is not directly available as a DataField method nor easily replicated, or you prefer writing a macro-like script that executes various existing Gwyddion functions.

A data processing function (corresponding to a menu or toolbox item) is run using gwy_process_func_run():

gwy.gwy_process_func_run('level', container, gwy.RUN_IMMEDIATE)

The first argument 'level' is the function name, the second is the container (which should generally be always the current container to prevent strange things from happening) and the last argument is the mode in which to run the function. Data processing functions have two possible modes RUN_INTERACTIVE, which means if the function has any dialogue it should show it, and RUN_IMMEDIATE, which means the function should not display any dialogues and immediately perform the operation. Note that not all functions can be run in both modes. Some have no user interface, while others can only be run interactively.

How the function knows which data to process? It processes the current data. For macro-like scripts or data processing functions written in Python this is exactly what you want. You can also select (make current) another data in the script, as described in the following section.

Where will the result end up? Some functions modify existing data, some create new images (and other types of output). Some even allow choosing between these two options. Whatever the function does when run from the GUI it also does when run from a pygwy script. If new images are created you need to find them. This can be done as follows

ids_before = set(gwy.gwy_app_data_browser_get_data_ids(container))
gwy.gwy_process_func_run('some_func', container, gwy.RUN_IMMEDIATE)
ids_after = set(gwy.gwy_app_data_browser_get_data_ids(container))
new_ids = ids_after - ids_before

Usually the set new_ids will have single member, the numeric identifier of the newly created image, but some modules may create multiple outputs.

Utilisation of module functions can be mixed with direct operations with the DataFields. If you do this it is important to know that module functions may modify, add, remove and also replace data items in the container. The following code snippet that runs a function and then fetches a data field

gwy.gwy_process_func_run('weird_func', container, gwy.RUN_IMMEDIATE)
data_field = container['/5/data']

is not equivalent to

data_field = container['/5/data']
gwy.gwy_process_func_run('weird_func', container, gwy.RUN_IMMEDIATE)

because the function 'weird_func' could have replaced (or removed) the data field at '/5/data'. So while data_field will remain a valid DataField, it may no longer represent the image number five in the file.

The function name is the name of high-level functions provided by data processing modules. Each corresponds to some action available in the Data Process menu. If you extend Gwyddion with a module, the functions it defines will be available the same way as functions of built-in modules.

The list of functions can be obtained in several ways (apart from studying the source code):

  • Using the on-line module browser. If you mouse over a module name the browser will show you the list of all provided functions. Those with type listed as proc are data processing functions. If you know the menu path the function can be looked up quickly by searching the expanded on-line module browser. Note that the on-line browser lists modules (and functions) available in the last stable release.
  • Looking the function up within Gwyddion in InfoModule Browser. It provides the same information as the on-line browser, except that it lists modules and functions actually present in your Gwyddion installation.
  • Using the log. When you use a data processing function it will appear in the log, including the names of its parameters, called settings. This is perhaps the most convenient method since you can interactively see the functions appearing in the log as you execute them.

Some functions have no adjustable parameters, for instance "fix_zero". But many have some, and some have many. The last used values of all parameters are stored in settings. When a data processing function is executed it reads its parameters from the settings and, depending on the mode, either immediately performs the operation or shows a dialogue where you can adjust the values. Hence if you simply run a function from a pygwy script it will use the parameters stored in settings. This is useful if you want to use the function interactively first, find a good set of parameters and then run it many times with exactly the same settings using a script.

On the other hand, sometimes you want the script to run the function with predefined settings. In this case you can obtain the settings with

settings = gwy.gwy_app_settings_get()

It is again a Container and there are again some rules what is stored where. Module function settings are generally stored under "/module/modulename". Module settings are not formally documented so there are essentially two ways of finding out what settings a module has: observing the log which lists all the parameters and inspecting at the settings file. For instance the plane levelling module can be run with masking mode set to exclude as follows (note that you can achieve the same result directly using DataField methods):

settings['/module/level/mode'] = int(gwy.MASKING_EXCLUDE)
gwy.gwy_process_func_run('level', container, gwy.RUN_IMMEDIATE)

Managing files

One of the main purposes of scripting is automation of operations that need to be carried out many times. This often includes the processing of multiple or all data in one file and processing several data files in sequence.

Concerning the processing of multiple images in one file, the data enumeration and search functions described above make easy to obtain the data you look for. A small complete sample script that plane-levels all images in a file can be written as follows:

container = gwy.gwy_app_data_browser_get_current(gwy.APP_CONTAINER)
for i in gwy.gwy_app_data_browser_get_data_ids(container):
    data_field = container[gwy.gwy_app_get_data_key_for_id(i)]
    coeffs = data_field.fit_plane()
    data_field.plane_level(*coeffs)
    data_field.data_changed()

For execution of data processing function with gwy_process_func_run(), the only missing piece is selecting the image in the data browser so that they will operate on the correct one. This is done by calling gwy_app_data_browser_select_data_field():

gwy.gwy_app_data_browser_select_data_field(container, i)

where i is the image number. Similar functions exist for other data types. When you are finished with the processing it is advisable to select again the image that was selected at the beginning. If you forget tools may behave odd (at least until you select different image by switching to its data window). A small complete running a data processing function on all images in the current file can be written:

container = gwy.gwy_app_data_browser_get_current(gwy.APP_CONTAINER)
orig_i = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD_ID)
for i in gwy.gwy_app_data_browser_get_data_ids(container):
    gwy.gwy_app_data_browser_select_data_field(container, i)
    gwy_process_func_run('align_rows', container, gwy.RUN_IMMEDIATE)
gwy.gwy_app_data_browser_select_data_field(container, orig_i)

It is also simple to iterate over all open files using gwy_app_data_browser_get_containers():

for container in gwy.gwy_app_data_browser_get_containers():
    for i in gwy.gwy_app_data_browser_get_data_ids(container):
        # Nothing new here

If you want to process multiple files on disk you need some means to find or identify them on the disk but that is beyond this guide (glob and os Python modules are your friends). To load an SPM file just use gwy_file_load():

container = gwy.gwy_file_load(filename, gwy.RUN_NONINTERACTIVE)

This function will identify the file type and call the correct file module to load it. The resulting Container is not automatically added to the data browser. In other words, Gwyddion does not know about it and you cannot immediately use data processing module functions on it. It is your own private Container. You can add it to the data browser using

gwy.gwy_app_data_browser_add(container)

Alternatively, you can use the application-level file loading function

container = gwy.gwy_app_file_load(filename)

that not only loads the file but also adds it to the data browser. When you process many files you should also ensure they do not pile up in the memory. Function gwy_app_data_browser_remove() removes a Container from the data browser. To really release the resource you should also ensure the data objects are not kept around in some other structures in your script.

Note that we used RUN_NONINTERACTIVE instead of RUN_IMMEDIATE in the gwy_file_load() example. File functions have two possible modes, RUN_INTERACTIVE, which has the same meaning as for data processing functions, and RUN_NONINTERACTIVE, which means the function must not display any dialogues. Most file functions do not have any user interface, notable exceptions being image export and import of raw data and other types of data that cannot be loaded automatically.

File saving is quite similar:

gwy.gwy_file_save(container, filename, gwy.RUN_NONINTERACTIVE)

The file format is determined automatically from the extension. The same function can be used for image export. There are two main methods of creating a file with false-colour rendered image. The simple function gwy_app_get_channel_thumbnail() creates a GdkPixbuf which can be then save to a file using its save() method. This is most useful for thumbnails and simple preview images. If you want all the features of the image export module you can use gwy_file_save() to an image format (PNG, PDF, SVG, ...)

gwy.gwy_file_save(container, "image.png", gwy.RUN_NONINTERACTIVE)

Of course, similar to data processing functions, the image export will render the image according to its current settings. You can change the settings (they reside under "/module/pixmap") to render the image in a predefined manner. Be warned that the image export has lots of settings.

We can put everything together in a simple standalone script that loads all files in the current directory matching a given pattern, finds the height channel in each, performs some levelling by calling module functions and extract a few basic statistical quantities:

import gwy, glob, sys

# Set up parameters for the 'align_rows' function.
settings = gwy.gwy_app_settings_get()
settings['/module/linematch/direction'] = int(gwy.ORIENTATION_HORIZONTAL)
settings['/module/linematch/do_extract'] = False
settings['/module/linematch/do_plot'] = False
settings['/module/linematch/method'] = 2

print 'Filename\tRa\tRms\tSkewness\tKurtosis'

# Go through files matching a given pattern:
for filename in glob.glob('MARENA-0??C-Maximum.0??'):
    container = gwy.gwy_file_load(filename, gwy.RUN_NONINTERACTIVE)
    gwy.gwy_app_data_browser_add(container)

    # Find channel(s) called 'Height', expecting to find one.
    ids = gwy.gwy_app_data_browser_find_data_by_title(container, 'Height')
    if len(ids) == 1:
        i = ids[0]
        # Select the channel and run some functions.
        gwy.gwy_app_data_browser_select_data_field(container, i)
        gwy.gwy_process_func_run('align_rows', container, gwy.RUN_IMMEDIATE)
        gwy.gwy_process_func_run('flatten_base', container, gwy.RUN_IMMEDIATE)
        # Extract simple statistics and print them.
        data_field = container[gwy.gwy_app_get_data_key_for_id(i)]
        avg, ra, rms, skew, kurtosis = data_field.get_stats()
        print '%s\t%g\t%g\t%g\t%g' % (filename, ra, rms, skew, kurtosis)
    else:
        sys.stderr.write('Expected one Height channel in %s but found %u.\n'
                         % (filename, len(ids)))

    gwy.gwy_app_data_browser_remove(container)

Writing modules in Python

Python modules are not very different from scripts. However, they must define certain functions and variables so that Gwyddion knows what kind of functionality the module provides, where to put it in the menus or how to execute it. Gwyddion recognises several kinds of modules: file, data processing (or, more precisely, image data processing), graph, volume data processing, XYZ data processing, layers and tools. The last two types are complex and cannot be currently written in Python. All the other types can.

All extension modules written in Python have to be placed in the user's directory in the pygwy subdirectory. This means in ~/.gwyddion/pygwy (Unix) or Documents and Settings\gwyddion\pygwy (MS Windows) for Gwyddion to find them.

The variables a module has to define are more or less common to all types. For historical reasons they have all prefix plugin_, even though module_ would be more appropriate:

plugin_type

The module type, as an uppercase string. It can be one of "FILE", "PROCESS", "GRAPH", "VOLUME" and "XYZ" that correspond to the possible types in an obvious manner.

plugin_desc

A brief description of the function, used for tooltips for data processing-type modules, and as a file type description for file modules.

plugin_menu

The menu path where the module should appear, as a string with slashes denoting submenus. The menu path is relative to the menu root for module functions of given kind. For instance data processing modules will always appear under Data Process and the variable should not include this part. File modules do not define menu path (if they do it is ignored).

plugin_icon

Optional default icon name as a string (for putting the function to the toolbox). You can choose from Gwyddion and GTK+ stock icons. The former have names with underscores like "gwy_level", the latter with dashes like "gtk-execute". See the API documentations for the lists of available icons. There is no default icon.

plugin_sens

Optional sensitivity flags as a combination of GwyMenuSensFlags values, such as MENU_FLAG_DATA or MENU_FLAG_DATA_MASK. The flags determine when the menu item (or toolbox button) should be available. If it is not given the default is to require the corresponding type of data to be present, for instance graph modules require a graph to exist. Usually the default works fine but it can be incorrect for instance for data synthesis modules (that do not require any data to already be loaded) or sometimes a module requires a mask, etc.

plugin_run

Optional run mode flags as a combination of the flags RUN_INTERACTIVE and RUN_IMMEDIATE. This is mostly useful to indicate the function requires user input and cannot be run non-interactively. The default is that the function can be run in both modes.

All kinds of data processing modules (image, volume and XYZ) need to define function run() that takes two arguments, the current Container and the mode (RUN_INTERACTIVE or RUN_IMMEDIATE). If you do not care about the mode you can also define the function just with one argument. A simple complete example of data processing module follows:

plugin_menu = "/Pygwy Examples/Invert"
plugin_desc = "Invert values"
plugin_type = "PROCESS"

def run(data, mode):
   key = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD_KEY)
   gwy.gwy_app_undo_qcheckpoint(data, [key])
   data_field = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD)
   data_field.invert(False, False, True)
   data_field.data_changed()

It performs simple value inversion using the invert() of DataField. The call to gwy_app_undo_qcheckpoint() implements proper undo support. If you want to support undo then call it with the list of integer keys of all data items the function will modify before you start modifying them.

Graph modules differ only slightly, their run() function takes just a single argument, which is a Graph.

File modules have to provide several functions. If they implement file import they define load() and detect_by_content(). The loading function takes the file name (and optionally run mode) and returns either a Container representing the loaded file or None. The detection function's goal is to figure out if a file is of the format implemented by the module. It takes the file name, byte strings containing beginning and end of the file content (as they usually contain information useful for file type detection) and file size. It returns an integer score between 0 and 100 where 0 means definitely not this file type and 100 means for sure this file type. No Gwyddion file module returns score larger than 100. If you really need to override a built-in module you can do it by returning a score larger than 100, but generally it is not recommended.

If the module implements file saving it has conversely define functions save() and detect_by_name(). The saving function takes a Container, file name and optional run mode and should write the file. Often you do not want or cannot save everything the currently open file contains. In this case you can obtain the current image (or other data object) using gwy_app_data_browser_get_current() exactly as in a data processing module and save only this image. The detection function is much simpler here because as the file is yet to be written the function can only use the file name. A simple complete example of file module, implementing both loading and saving, follows. A real module should take care of error handling which is not included here for brevity and clarity:

plugin_type = "FILE"
plugin_desc = "High definition stable format store (.hdsf)"

def detect_by_name(filename):
    if filename.endswith(".hdsf"):
        return 100
    return 0

def detect_by_content(filename, head, tail, filesize):
    if head.startswith("HDSF:"):
        return 100
    return 0

def load(filename, mode=None):
    f = file(filename)
    header = f.readline()
    magic, xres, yres, xreal, yreal = header.split()
    xres, yres = int(xres), int(yres)
    xreal, yreal = float(xreal), float(yreal)
    container = gwy.Container()
    data_field = gwy.DataField(xres, yres, xreal, yreal)
    data_field.set_data([float(z) for z in f])
    container['/0/data'] = data_field
    return container

def save(container, filename, mode=None):
    data_field = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD)
    if not data_field:
        return False
    f = file(filename, 'w')
    f.write('HDSF: %d %d %g %g\n'
            % (data_field.get_xres(), data_field.get_yres(),
               data_field.get_xreal(), data_field.get_yreal()))
    f.write('\n'.join('%g' % z for z in data_field))
    f.write('\n')
    f.close()
    return True

Pygwy modules are reloaded if they have changed since the last time they were used. So you can modify them while Gwyddion is running and it will always use the latest version of the module. They are, however, not re-registered. Meaning the file-level code is executed when the module is reloaded, but the values of plugin_ variables only matter when the module is first loaded. Subsequent changes have no effect until you quit Gwyddion and run it again. So a file module cannot suddenly change to a graph module – which is probably a good thing.

Graphical user interface

If you want to add graphical user interface to the module you can use PyGTK together with Gwyddion's widgets for displaying the data. Note you need to import PyGTK explicitly with

import gtk

as, unlike gwy, it is not imported automatically. A module with an optional graphical user interface should generally define the two-argument form or run() and honour the mode by acting immediately or presenting the user interface. The typical structure for a module is:

import gtk

# Define module info...

def run(data, mode):
    if mode == gwy.RUN_INTERACTIVE:
        dialogue = gtk.Dialog(title='Title...', flags=gtk.DIALOG_MODAL,
                              buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                       gtk.STOCK_OK, gtk.RESPONSE_OK))

        # Construct and show the user interface...

        response = dialogue.run()
        dialogue.destroy()
        if response != gtk.RESPONSE_OK:
            return

    # Process the data...

Pygwy scripts do not have a mode, they are simply executed, so while they can also present a dialogue in a similar manner if they do the dialogue will be shown always. The creation and use of checkboxes, buttons, sliders, entries and various other user interface element is beyond the scope of this guide. Please refer to PyGTK's extensive documentation.

The module can use the settings the same way as other Gwyddion modules. At the beginning, read the parameters from settings, handling the case your parameters may not be present in the setting yet:

settings = gwy.gwy_app_settings_get()

tuning_parameter = 1.0
if '/module/mymodule/tuning_parameter' in settings:
    tuning_parameter = settings['/module/mymodule/tuning_parameter']

and at the end save parameter values to the settings so that you can recall them next time:

settings['/module/mymodule/tuning_parameter'] = tuning_parameter

If you want to display image data you can use Gwyddion DataView widget. It works somewhat indirectly. Instead of giving it the DataField to display you always give it a Container and then tell it where to find the things to display. Image display is done by creating a so-called data view layer and adding it to the data view. A simple module that just displays the current image could look like:

import gtk, gobject

plugin_menu = "/Pygwy Examples/Display Data"
plugin_desc = "Just display the current image"
plugin_type = "PROCESS"

def run(data, mode):
    dialogue = gtk.Dialog(title='Display Data', flags=gtk.DIALOG_MODAL,
                          buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,))

    data_field = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD)

    container = gwy.Container()
    container['/0/data'] = data_field

    data_view = gwy.DataView(container)
    data_view.set_zoom(400.0/data_field.get_xres())
    data_view.set_data_prefix('/0/data')

    layer = gwy.LayerBasic()
    layer.set_data_key('/0/data')
    data_view.set_base_layer(layer)

    dialogue.vbox.pack_start(data_view, False, 4)

    dialogue.show_all()
    dialogue.run()
    dialogue.destroy()

Note the use of set_zoom() that limits the data view size to 400 pixels.

To let the user select points or lines interactively on the preview you need to create another layer, called vector layer and add it to the data view. Each vector layer has corresponding type of Selection that contains the coordinates of selected geometrical shapes. A simple module allowing the user to select a point on the image and displaying its coordinates could be implemented as follows:

import gtk, gobject

plugin_menu = "/Pygwy Examples/Analyse"
plugin_desc = "Display the current image and selected coordinates"
plugin_type = "PROCESS"

def update_info_label(selection, i, label, vformat):
    if not selection:
        label.set_markup('')
        return

    coords = selection[0]
    label.set_markup('(%.*f, %.*f) %s'
                     % (vformat.precision, coords[0]/vformat.magnitude,
                        vformat.precision, coords[1]/vformat.magnitude,
                        vformat.units))

def run(data, mode):
    dialogue = gtk.Dialog(title='Analyse Data', flags=gtk.DIALOG_MODAL,
                          buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,))

    data_field = gwy.gwy_app_data_browser_get_current(gwy.APP_DATA_FIELD)

    container = gwy.Container()
    container['/0/data'] = data_field

    data_view = gwy.DataView(container)
    data_view.set_zoom(400.0/data_field.get_xres())
    data_view.set_data_prefix('/0/data')

    layer = gwy.LayerBasic()
    layer.set_data_key('/0/data')
    data_view.set_base_layer(layer)

    layer = gobject.new(gobject.type_from_name('GwyLayerPoint'))
    data_view.set_top_layer(layer)
    layer.set_selection_key('/0/select/point')
    selection = layer.ensure_selection()

    dialogue.vbox.pack_start(data_view, False, 4)

    info_label = gtk.Label()
    info_label.set_alignment(0.0, 0.5)
    dialogue.vbox.pack_start(info_label, False, 4)

    value_format = data_field.get_value_format_xy(gwy.SI_UNIT_FORMAT_VFMARKUP)
    selection.connect('changed', update_info_label, info_label, value_format)

    dialogue.show_all()
    response = dialogue.run()
    dialogue.destroy()

Remarks

The documentation of functions available in Python is relatively complete but the price is that only a small portion of it was written specifically for the Python functions. Most of the documentation is generated from the documentation of corresponding C functions. Hence the documentation occasionally contains residue of information relevant only in C.

This includes namely ownership rules and other rules for array-like function arguments. In Python everything is passed and returned by value. In particular strings, that are not even modifiable in Python, so any comments on string ownership and modification have to be disregarded.

Gwyddion Python functions that take array-like arguments accept any Python sequence (list, tuple, array, ...) with items of the expected type (usually floats or integers). And if they return array-like data they always return newly created lists you can freely modify without influencing anything.

Typically, Python functions also do not take separate sequence-size arguments that the documentation generated from C might talk about. If there is some restriction on the number of items, it of course continues to hold in Python. For instance, the set_data() method of Selection simply takes a flat sequence of coordinates. However, the sequence length must be still a multiple of object size, i.e. the number of coordinates constituting one selected object for given selection type.