Micro-Manager Programming Guide
- 1 Micro-Manager Programming Guide
- 1.1 Introduction
- 1.2 Setting up the run-time environment
- 1.3 Getting started
- 1.4 Using device properties
- 1.5 Working with multiple devices
- 1.6 Using serial ports
- 1.7 Using State devices
- 1.8 Using configurations
- 1.9 Synchronization
- 1.10 Shutter control
- 1.11 Configuring the system
- 1.12 Configuration file
Micro-Manager Programming Guide
Created on November 16, 2005 by Nenad Amodaj
Updated for release 1.0.37(beta) on July 28, 2006
Before diving into Programming within Micro-Manager, it is useful to review the general architecture.
The MMCore API enables the program designer to build user interface or automation protocol in a device-independent way and with minimal effort. MMCore provides implementations for the most of the common tasks that automated microscope is expected to perform in the laboratory or screening setting.
We are using the term "program" in a relatively loose sense, since MMCore implementation is in essence language agnostic. For example, a "program" can be C++ source code implementing complicated user interface or a simple and small Beanshell (Java-like) script to acquire a sequence of images. The MMCore API is designed with scripting transparency in mind. For purely practical reasons all examples in this guide are shown in Java programming language while API reference is specified in C++, but we assume that translation of both to any other programming environment is straightforward.
Setting up the run-time environment
Micro-Manager is in principle both platform independent and language neutral. But, in practice MMCore component is intended to run only on Windows, Mac OS X and Linux, and to be used from C++, Java, Matlab and Python programs. Setting up the environment is somewhat different for each language/OS combination.
See also: Search Paths
MMCore Java API is contained in the MMCoreJ.jar. Any Java program using MMCore API must have MMCoreJ.jar in its class path. When CMMCore Java object is first created in the calling program it will automatically attempt to load native library MMCoreJ_wrap. This library must be visible to the Java run-time. Default locations and exact names of libraries are platform dependent.
On Windows, native library file is MMCoreJ_wrap.dll and it must reside either in the system path or in the current working directory of the program in order to be detected by the Java run-time.
In C++ MMCore can be used as a static library which needs to be linked to the calling program. Interface is specified in header files MMCore.h and Configuration.h.
MMCore can be used in Matlab through its Java interface. After setting up the Java environment as described above, MMCoreJ.jar must be added to Matlab Java class path and the directory for the MMCore dynamic libraries (including MMCoreJ_wrap) must be added to the system path.
The current version of the MMCore does not support programming environments other than Java, Matlab and C++. Additional support for dynamic languages such as Python will be added in the future, if there is enough interest .
You can use any editor or Java IDE of your preference to try examples from this guide. Java run-time should be version 1.5 or higher. Instructions to work with several Java IDEs are available:
- Netbeans, which seems to be the recommended IDE (see previous instructions)
- Eclipse.(see previous instructions)
Creating the CMMCore object
First thing to do is to create the CMMCore object. For example, we can create the object and display the software version:
String info = core.getVersionInfo();
When executed, this little program should display something like:
MMCore version 1.0.16 (debug)
However, at this point MMCore canâ€™t do much more than that, because it does not know about any devices yet. In the next section we are going to try to use a camera by loading the appropriate device adapter.
In order to control a hardware device MMCore needs to load the corresponding device adapter. We use the term "adapter" rather than "driver" to make a distinction from the low-level software supplied by the device manufacturer. For example, digital cameras come with manufacturer's drivers which need to be installed independently from the Micro-Manager. adapters are relatively simple software components translating specific device driver API to common Micro-Manager plug-in interface. For simpler devices controlled by serial ports, there are no special manufacturer's drivers to install. In that case adapter is at the same time a device driver as well.
Device adapters are packaged as dynamic libraries and CMMCore loads them only when specifically requested by the calling program. Let's see how we can configure MMCore to control a camera.
The command to load device in MMCore has the following signature:
We can use it like this:
The command above has three parameters: label, library and name. Device label is the name we want to assign to a specific device. It is completely arbitrary and entirely up to us. We chose to call our camera simply "Camera". "DemoCamera" is the dynamic library name where the adapter resides. "DCam" is the name of the device adapter we want to load.
After loading the adapter the device is still inactive. Before starting to control the device we must perform initialization:
The following program puts all of the above together: loads the camera adapter, initializes it and additionally snaps an image with exposure set to 50ms.
core.loadDevice("Camera", "DemoCamera", "DCam");
byte image = (byte) core.getImage();
long width = core.getImageWidth();
long height = core.getImageHeight();
For clarity the above example omits some details necessary to really compile and run this example. Complete Java code is here: Tutorial1.java
If an error occurs during execution of the function call, CMMCore object will throw a java exception, which you can handle in any standard way. For example:
Using device properties
Each device loaded into the MMCore will expose a number of properties which we can read or change. A property is simply a named tag, or a field consisting of a name and value.
// set some properties
To get or set the property you have to supply two parameters: device label and property name. "getProperty" method will return the value, while in the "setProperty" method you have to supply the value as an additional, third parameter. All property values are always treated as strings regardless of the actual data type they represent.
How do you find out which properties are supported by a particular device? This code prints all properties of the "Camera" and their current values:
Note the use of the StrVector class as a simple vector containing of strings (String class) containing property names. This class is defined within MMCoreJ.jar.}}
How do you find out which values are valid for a particular property? This code prints all allowed values for the "PixelType" property:
If the getAllowedProperties() call returns empty vector, it means that the range of possible values is so broad that it is not practical to enumerate them, or that the device adapter does not have this information. You could interpret that as if any value is allowed. However, it is not guaranteed that the device will be able to accept any particular value in a given context. On the other hand, if the returned vector is not empty you can expect that setting any of the allowed values will succeed.
Some properties are read-only. For example, you can discover if the camera property "Description" is read-only by using:
Complete Java code is here: Tutorial2.java.
Relationship between properties and device specific calls
By examining the two examples [content/code_examples/Tutorial1.java Tutorial1.java] and [content/code_examples/Tutorial2.java Tutorial2.java] you can get an insight on two different ways to control devices: device specific API and generalized property mechanism.
Device specific API consists of commands which imply device of certain type. This API reflects capabilities expected from and any automated microscope, regardless of specific devices used to build it. For example:
core.setPosition("Z", 120.0); // works only for stages
On the other hand, the property mechanism is very general and does not assume anything about the device. In this way you can use a very flexible conceptual model in which the entire system is just a collection of various devices and each device has a number of property tags which you read or change. The property mechanism makes possible to build robust user interfaces and programs which automatically re-configure based on specific run-time configuration of the system.
// have exactly the same effect as
Also, the property mechanism allows us to control many details which are very specific to the device type. For example, some cameras will have an "Offset" property available and some will not.
Working with multiple devices
Let us consider a somewhat more complicated system with four devices: camera, shutter, and two filter wheels.
// load devices
Complete Java code is here: Tutorial3.java.
Using serial ports
Many devices use serial ports for communication with the host computer. For MMCore serial port is also a device with number of properties to manipulate.
Once the property is loaded and initialized, you can send and receive terminated command strings like this:
The last parameter in both send and receive commands is a terminating sequence, in this case carriage return. The receive command getSerialPortAnswer() will wait until it detects the terminating sequence or times out.
Most of the devices use similar protocol and therefore in principle you can control any device supporting serial port communication even if you do not have device adapter. Just send and receive commands through the serial port. Of course, this way you won't be able to use many of the more advanced features of the Micro-Manager system (such as device synchronization, metadata) and your code will not portable.
Using State devices
State device is any device with a relatively small number of discrete states. The most common examples of state device are filter switchers (wheels) and objective turrets.
// set emission filter to position 2
// verify position
Almost invariably state devices serve as placeholders for interchangeable equipment: objectives or filters. To make code more readable, and more device independent, instead of just using position numbers as shown above it is much better to refer to positions with meaningful names such as "Nikon S Fluor 10X" or "Chroma-D360". State devices support position labeling feature and any position can be assigned with an arbitrary name:
// set position using label
// verify position
The state device used in tutorial examples is really a software simulator (from the DemoCamera adapter library) and it does not use any hardware connections. But most state devices are controlled through serial ports, so in order to control them you'll need to link state device to appropriate serial port device:
// setup serial port
// setup filter wheels
The code above looks fairly straightforward, but there are a couple of important points to consider here. We first loaded serial port device "P1" on COM1 and then we just supplied the filter wheel devices "WA" and "WB" with the port label. Devices will use port labels through MMCore internal mechanisms to transmit and receive data from ports, and will not be able to lock them or take control of them.
You will notice that both devices "WA" and "WB" appear to be connected to the same port "P1". That's because they physically belong to the same controller box which uses single port to control multiple devices. This is a relatively common situation, but having each filter on a different port is also fine.
In all examples from previous sections we started manipulating properties only after we initialized the system either by initializeDevice() or initializeAllDevices(). In fact, most of the device properties are even not available before the device has been initialized. But, in the example above we set port related properties before initializing the system. We had to do it that way because these properties must be specified correctly in advance in order for initialization to succeed. Which properties, if any, are required or accessible before initialization depends on the specific device adapter. Camera adapters, for example, usually do need or expose any pre-initialization properties.
The order in which devices are loaded will be the same order in which they are going to be initialized in the initializeAllDevices() command. Therefore, port devices should be loaded before other devices using them.
In practical situations we often need to execute groups of multiple commands over an over again. For example, to set the correct light path for imaging DAPI fluorescence channel you need to set three filters in proper positions:
Or equivalently, by exploiting property mechanism:
To simplify programming Micro-Manager provides configuration feature in which you can define groups of commands and execute them as a single command. To define "DAPI" configuration (group of commands) you need to write:
// use configuration command many times
Each time you execute setConfiguration("DAPI"), the three filters will be set to the defined positions. There is no limit in the number of devices or number of different properties you include in one configuration. You can set objectives, filters, stage positions, camera parameters in a single configuration command. Typically predefined configurations are automatically defined once at the initialization (when the program starts up) phase and used thereafter in the acquisition scripts or interactively.
To discover which configurations are currently defined in your system and what exact settings they consist of, you can use:
Note the two new classes defined in the MMCoreJ: Configuration and PropertySetting. PropertySetting is a triplet of strings: DeviceLabel, PropertyName and PropertyValue. Configuration is just a collection of PropertySetting objects.
To discover which configuration the system is currently in:
This command can return an empty string if the system current state does not match any of the defined configurations. getConfiguration command will return the matching configuration name (if any) even if the individual commands were used instead setConfiguration.
Each device loaded in MMCore reports its status by using "Busy" flag. A device declares that it is busy if it is still executing the previous command. To check the status of a single device or the entire system:
// check if any of the devices in the system are busy
Very often you check the device status because you shouldn't proceed with some action until the devices stopped moving or executing previous commands. To relieve the programmer of the boring task of writing polling loops, we provided special commands to implement waiting for devices:
// move to new XY position
// Set all the positions. For some of the devices it will take a while
// Just go ahead and snap an image. The system will automatically wait
If the shutter is associated with the camera it usually needs to be opened before an image is taken and closed as soon as acquisition is done. If the "auto shutter" feature is turned on the system will automatically perform these operations in the snapImage command.
// take image with manual shutter
Configuring the system
As you have seen in the earlier sections at startup MMCore object doesn't know about any devices, there are no labels defined, no options set and no synchronization objects assigned. Before normal use of the software the entire system needs to be configured according to the current hardware setup. Here is an example configuration program to be executed at system startup:
Complete example: Tutorial4.java
Instead of executing the initialization script or a program to load devices, create configurations and perform other setup tasks each time the system starts, you can create equivalent configuration file which can be executed with a single command:
In this case MMConfig.cfg is a text file in containing a list of simple commands to build the desired system state: devices, labels, equipment, properties, and configurations. The format of this file and other topics related to configuring the system are covered in Configuration Guide.
The current system configuration can be saved by the complementary saveConfiguration() command. For example you can build-up the system by using script commands described in this Guide and when you verify that everything is working, you can save the configuration in the file so that it can be recalled in the future with the single command.