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.
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
→ 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 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 clears the log area.
(
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 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.
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
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')
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.
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_
where
“something” stands for the item type. So the key for image
3 can be constructed using
something
_key_for_id()
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.
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.
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.
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
will have single
member, the numeric identifier of the newly created image, but some
modules may create multiple outputs.
new_ids
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
will remain a valid
DataField, it may no longer represent the image number five
in the file.
data_field
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):
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/
.
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
modulename
"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)
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)
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
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.
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()
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.