- Nenad Amodaj, nenad@amodaj.com
- Nico Sturman, nico@cmp.ucsf.edu
- Karl Hoover, karl.hoover@gmail.com
Introduction
One of the most powerful features of Micro-Manager is that anyone with average command of C++ programming language can write device adapters (or as some prefer to say “drivers”) for the new hardware, or even improve on existing ones. We chose “device adapter” for micro-manager device modules so that we can use the term “driver” for manufacturers’ native device software components. Micro-manager device adapter performs normalization of various software command sets specific of each manufacturer to a uniform Micro-Manager interface protocol. This document will introduce tools and workflow for creating device adapters from scratch in the form of a hands-on step by step tutorial.
Let’s assume that we are writing a device adapter, for a device called “MyDevice.” The workflow for development of the new adapter should look like this:
- Check out the latest source code from the Micro-Manger repository
- If required, install device manufacturer’s drivers, SDK and any other software/hardware required for the particular device we are working with.
- Create new directories and project files for our new device adapter. In this step we can use some of the provided skeleton or demo adapters as an example or a starting point.
- Write code for controlling the new device and build a run-time module. The final result is a dynamically linked library file that conforms to Micro-Manager’s API and contains one or more newly developed device adapters.
- Download and install the latest version of the Micro-Manager application, drop-in the new dynamic library file (to its installation directory) and do final testing with the full application
- Test and debug our new adapter.
Terminology
- Module – a run-time software component located in a single file, usually an executable or dynamic library
- Device Adapter – a C++ class that converts a device manufacturer’s command set (or API) to Micro-Manager’s device interface API
- Driver – one or more software components provided by the device manufacturer to enable other software such as Micro-Manager to control the device (some devices do not require drivers)
- Library – dynamic library (e.g. dll, dylib, so, etc.) conforming to Micro-Manager’s module API and naming convention that contains one or more Device Adapters
Directory layout
Before doing anything else we should check out the latest version of Micro-Manager from the repository (see Micro-Manager Source Code) to an appropriate base directory on our computer. The checkout process is outlined in the next few steps. Building and debugging Micro-Manager source code has more specific advice on building Micro-Manager for each platform.
First we check out Micro-Manager source code from:
https://github.com/micro-manager/micro-manager
(and, as a Git submodule, https://github.com/micro-manager/mmCoreAndDevices)
into directory /projects/micro-manager
on our computer. Then we
check out required 3rd-party libraries from:
https://valelab4.ucsf.edu/svn/3rdpartypublic/
into directory /projects/3rdpartypublic
on our computer.
At this point we can install any proprietary drivers or SDKs that may be required by the manufacturer of our hardware. Devices that we intend to control via serial port (RS-232) do not require any drivers. It is probably a good idea to follow whatever is the default location for manufacturer’s driver or SDK and run their software demos afterwards to validate the installation. If we do have to link our code against manufacturer’s proprietary libraries we should copy the required files and directories (usually header files and dynamic or static libraries) to the following location:
/projects/3rdparty/MyDevice
The top-level directory can be called something other than “projects”, but within our top-level directory we must have three parallel sub-directories with fixed names:
/micro-manager
(development source code)/3rdpartypublic
(required open source third-party libraries)/3rdparty
(optional proprietary libraries that may be required for our hardware)
Development directories and example
The place designated for development of our new adapter is:
/projects/micro-manager/mmCoreAndDevices/TestDeviceAdapters
In this directory we will find a very simple “skeleton” camera adapter project “MMCamera” that we can use to get started, to understand the basic principles of Micro-manager API and also use files as template to get started on our own project. All our development projects should be located in “TestDeviceAdapters” directory in the same way as for MMCamera. In our case we will create a new directory for our new Device Adapter:
/projects/micro-manager/mmCoreAndDevices/TestDeviceAdapters/MyDevice
Project settings (Windows)
Micro-manager Device Adapter is a C++ class residing in a Library (dynamically linked). One Library usually contains more than one DeviceAdapter. Micro-manager application does not link against any of the Device Adapter libraries at build time. Libraries are discovered by scanning the application default directory for dynamic libraries following the specific naming convention:
mmgr_dal_<device name>.dll
In other words it will try to load at run-time only libraries with
prefix mmgr_dal
. In our case our new adapter (once we are finished
with it) should build as mmgr_dal_mydevice.dll
.
See Visual Studio project settings for device adapters for how to set up a project in Visual Studio to build a device adapter.
Device Adapter design
Plugin interface
Plug-in interface along with utility classes is contained in single directory:
/projects/micromanager1.4/MMDevice
Device adapters incorporate code from this directory via a static library called MMDevice-SharedRuntime.lib. See Visual Studio project settings for device adapters for details on how this works.
A device adapter is a C++ class for controlling one specific device, e.g. stage, camera, shutter, etc. Each device adapter must implement one of the abstract interfaces defined in the MMDevice.h. Device Library (dynamic library) can contain one or more device adapters. The Library module must also implement plug-in API defined in the ModuleInterface.h in order to be loaded by Micro-manager applications. In theory, these two files plus MMDeviceConstants.h (for error codes and other constants) are all we need to create device adapters: if you implement the required API with the correct behavior, the Micro-Manager application will be able to make use of it.
MMDevice.h, ModuleInterface.h and MMDeviceConstants.h essentially define the plug-in interface and are also used by the Micro-manager application. We should never modify these files – any discrepancy between your version and the one used to build the main application will likely result in the system crash or severe malfunction
Utility classes and files
To make building device adapters easier, we provide a couple of additional files to use as building blocks. First, DeviceBase.h contains base class partial API implementations for all device classes. The idea is to derive our particular device adapter class from the appropriate class defined in the DeviceBase.h. This header file contains method implementations as well, so there is no corresponding cpp file.
Since DeviceBase.h and other utility files are not used by the main application, you can modify them if you wish. However, the best strategy is to avoid changing any of the supplied utility files since they implement some of the essential device behavior that the main application is relying on.
Property.h and Property.cpp provide the implementation of the Micro-Manager “Property” mechanism, but again, only on the device side – they are not used in the rest of the application. Property class is used by the DeviceBase.h classes and most of the time you won’t have to deal with it directly.
DeviceUtils.h and DeviceUtils.cpp contain a couple of relatively simple helper functions we may or may not want to use.
ImgBuffer.h and ImgBuffer.cpp provide a simple implementation of the generic image buffer, which you may or may not find useful for building camera adapters. We can disregard this class if we have a better one or if our device does not need to deal with images.
Anatomy of a Device Adapter
This section outlines guidelines for designing a well-behaved Device Adapter. The MMCamera project included in the TestDeviceAdapters directory is a very simple example that can be used to follow the outline.
Constructor and destructor
Besides ensuring that all member variables get initialized in the constructor and all resources released in destructor, there are a couple of important guidelines to follow with respect to Micro-manager run time environment.
The first rule is that we should not communicate with the hardware either in the constructor nor destructor. Connection to the hardware should be established in the Initialize() method and terminated in Shutdown(). The constructor and destructor should be designed in such way that creating and destroying an instance of the Device Adapter class (without initializing) carries low overhead and that create/destroy sequence can be repeated multiple times without any effect on the hardware.
The second rule is to use the constructor to create and set any device properties that are independent of hardware, such as description strings, adapter name, etc. The concept of properties is explained in detail in the Properties section below.
The third rule is to use the constructor to create properties that are required for proper initialization of the hardware. The best example for this is the serial port property. In order to communicate with the hardware, the device adapter needs to know the serial port (COM port) name that device is attached to. When implementing the Initialize() method that establishes the initial communication with the hardware, we already must know the serial port name. Therefore, this property and its value assignment must be set before the Initialize() method is called. Such properties that are required to have their values determined before initialization are called “pre-initialization” properties.
Initialize() and Shutdown() methods
The Initialize() and Shudown() methods are critical to ensure that Device Adapter behaves as expected within Micro-manager environment. Rules for implementing these two methods are as follows:
- The Device Adapter instance does not communicate with hardware before Initialize() is called for the first time.
- Initialize() establishes the connection with the hardware and makes device ready to accept commands
- Once the device is successfully initialized, repeated calls to Initialize() (if they occur) should have no effect
- Shutdown() disconnects Device Adapter from the hardware and practically reverses the effect of Initialize()
- Once the device is successfully shut down, repeated calls to Shutdown() should not have any effect
- After Shutdown() is called device should never attempt to communicate with the hardware, except after Inititalize() is called again
- Calling Shutdown() without a previous Initialize() should have no effect.
Device Types, Names and Labels
Device Type is defined by one of the abstract classes contained in MMDevice.h. Each abstract device class implements an API corresponding to various physical hardware devices, e.g. filter wheels, stages, shutters, cameras, etc. We should pick a device type class that is a best match for our hardware device. Sometimes we have to use multiple device types for the same hardware unit, e.g. motorized microscope. In that case we would provide multiple Device Adapter classes that are at run-time connected to the same controller (or Hub) device.
Device Name within Micro-manager should be understood as something akin to a class name in C++. It does not have to be the same as the actual class name, but for all practical purposes it serves as such. It has to be unique within one Library. Micro-manager run-time environment creates instances of Device Adapters by providing the Device Name to the Library which acts as a class factory. The Library looks up the name, instantiates the corresponding C++ class and returns the pointer to the Device Adapter instance.
Device labels are run-time names assigned by the application to each instance, regardless of the actual Device Adapter class. Labels are used as handles for accessing any Device Adapter instance. Typically users assign labels to instances of their devices and the only rule is that they must be unique for each device. So if we equate Device Name as an equivalent of a “class”, a Label would be equivalent to “pointer”.
Properties
The Property mechanism allows the developer to customize settings and behavior of each new Device Adapter. While MM::Device API represents minimal functionality for each device type, is specified in advance and must be implemented by each Device Adapter class, properties are not prescribed and we can have as many or as few as we deem appropriate for controlling our hardware.
Each Device Adapter class can have zero or more properties. Each property is a simple name-value pair. At run time the application assigns values to properties in one of the three ways: in the configuration process, interactively through the GUI, or by running
Instance management
Micro-manager API in principle allows for multiple instances of the same Device Adapter, i.e. there may be multiple objects carrying the same Device Name. However, depending on the particular device and implementation, multiple instances of the same Device Adapter may or may not make sense. It is the responsibility of the library to either manage instances and any global data that may be shared between them.
Often in case of implementing a Library with multiple devices that are practically identical, we are faced with a design decision regarding instance management. How are we going to distinguish instances within our library since each class instance actually corresponds to a very specific physical hardware device?
Multiple independent devices
The simplest case is if we just have multiple identical devices all independently attached to the host computer. For example we have one or more filter changers each attached to a separate COM port. We create a single class “MyFilterChanger” derived from the StateDeviceBase class and provide a property called “Port” which should be set at the actual port device is attached to. At run-time Micro-manager application can create any number of instances of the MyFilterChanger class and everything work fine as long each one is assigned a different COM port.
Multiple devices sharing the same controller
More complicated example would be a controller box with two filter changers, say A and B. The controller is attached to a single COM port and each command in the command set has a variable that can be either “A” or “B” depending on which filter changer we want to move. The most straightforward way would be to implement “MyController” class with “Port” property and have that adapter communicate with the hardware box. MyController is obviously not a filter changer but rather a generic device used to communicate with a host computer. For
Now we can add two filter changer devices with distinct device names “MyFilterA” and “MyFilterB”. The library will now contain three Device Adapters: MyController, MyFilterA and MyFilterB. Since the last two are virtually identical except for using a different variable (“A” or “B”) when talking to the controller, internally we can use the same class to implement both of them. Deciding whether instance of that class will act as device A or device B can be easily accomplished by passing an appropriate parameter in the constructor.
Micro-manager supports the concept of the “parent” device (Hub) which exactly fits the description of the MyController. Designing and implementing multi-device libraries with Hub controller is described at lenngth in section Complex device management: writing Hub adapters.
Application Layer Services (Core Callbacks)
Device Adapter classes are typically derived from appropriate base classes which provide default behavior and various utility methods. But besides base-class inherited methods, Device Adapters also use functionality provided by the parent application. We refer to these application layer services as “callbacks”. The API for callback methods is defined in MMDevice.h in the abstract class MM::Core. At-run time each Device Adapter can obtain a pointer to the instance of the MM::Core interface and use any of the methods provided by it.
Threads
A minimal, cross-platform set of thread management utilities is provided in MMThreads.h to make Device Adapter implementation easier. Any other thread management library can be used instead. For devices that can in principle run on different operating systems we recommend avoiding native, platform specific thread functions.
Complex device management: writing Hub adapters
This is an advanced topic and is generally not relevant for implementation of single device libraries.
Introduction
Concept of a Hub or “parent” device was introduced to micro-manager to represent complex motorized microscopes. Typically a motorized microscope contains multiple devices: stages, filter changers, shutters, etc. All of these devices are controlled through a microscope stand which serves as a master (parent) device. Such device is designated in micro-manager as “Hub”. A Hub on the typically does not perform any concrete task other than managing connection with the host and dispatching commands to its “child” devices. However, in certain cases hub devices can be very complex. They can monitor notifications that come from the microscope stand and synchronize status of its child devices. They also perform automatic detection of which hardware components are installed and build configurations accordingly.
Hub concept is by no means restricted to motorized microscopes. Any hardware component that has a single controller connected to multiple devices fits the description of a Hub. For example a simple filter wheel controller is a Hub because it is connected to multiple filter wheels. Filter wheel devices depend on the hub to be able to communicate with the host. At the very least, in its minimal functionality, a Hub represents and encapsulates a communication port (USB or RS232) that is shared between multiple devices.
Implementation guidelines
A well-behaved hub device should perform the following functions:
- Auto-detection of all installed “child” devices by implementing MM::Hub::DetectInstalledDevices(), MM::Hub::GetInstalledDevice() and MM::Hub::GetNumberOfInstalledDevices()
- If the device uses a serial port, communication with the host computer by managing the port and providing the “Port” property
Since the Hub now represents the master controller, devices should not access any hardware resources (such as ports) directly, but rather via the associated Hub (parent). In the same vein, using global references to any other shared resources should be avoided and hopefully completely eliminated. All shared resources should be managed and encapsulated by the Hub device and the access to these should be granted only by calling appropriate Hub methods.
If we build device adapter libraries following the above guidelines, our devices will automatically support configurations with multiple hubs and provide more robust operation within micro-manager environment.
Obtaining the parent Hub instance
Since now all access to shared resources is handled by the Hub, we have to describe how is any specific device (e.g. a filter wheel or a stage) supposed to find its parent Hub, i.e. the controller that it is connected to. For example, in order to send a command through serial port (or some other way) a device must first obtain an instance of its parent Hub and then call appropriate method.
Micro-manager provides appropriate mechanisms that automatically establish and identify parent-child relationships between devices. Getting a parent Hub instance is very simple:
MM::Hub* hub = GetParentHub();
if (!hub)
return ERR_NO_HUB;
GetParentHub() method is implemented in the base class to all devices MM::DeviceBase. If this call fails (null pointer returned) it means that the parent hub device is not configured. This error means that the Micro-manager configuration file lacks information needed to establish parent device relationship.
When successful this call returns only a Hub base class instance and its API can’t do any useful work. So, we have to dynamically cast to the hub that belongs to our library and the API that has methods that we need.
MyHub* myHub = dynamic_cast<MyHub*>(hub);
if (!myHub)
return ERR_NO_HUB;
Micro-manager configuration process ensures that cast always succeeds, i.e. we can only get the Hub instance of the class belonging to our library (MyHub). Failure of dynamic cast points to some unusual and catastrophic bug in the build procedure or misunderstanding in the library design.
Once we obtain a handle to our specific Hub object we can call custom methods to send or receive commands. For example:
int ret = myHub->SendCommandToHardware(this, myCmd);
if (ret != DEVICE_OK)
return ret;
Actual methods in the Hub interface exposed to its “child” devices are completely at the discretion of the library designer. Typically they are related to exchange of commands between the hardware controller and the device.
Practical considerations
It is important to keep in mind that a device obtains a pointer to its parent Hub dynamically, by request to the Micro-manager system (GetParentHub() method). However, the parent Hub instance becomes available only during and after initialization. So, we should never attempt to obtain Hub pointer in the constructor (before Intialize() has been called).
The first time we can access parent Hub services is in the Initialize() method. The obvious question is if it makes more sense to cache the pointer to the Hub obtained in the Initialize() for later use, or to call GetParentHub() each time we need to send commands to the hardware. Either way is fine, with later being more robust and transparent, but somewhat less efficient. The overhead of calling GetParentHub() is low, so for most cases it does not make that much difference in terms of speed.
Parent-child relationship between Hub and its devices persists in the configuration file through directive “Parent”. For example:
Parent,MyDevice,MyHub
establishes that MyDevice belongs to MyHub, i.e. that MyHub is parent to MyDevice. The only requirement is that MyHub is listed in the configuration file before MyDevice, which will ensure that parent device is initialized before its dependent child devices. Appropriate “Parent” directives and order of initialization is automatically ensured if we use Hardware Configuration Wizard to create configuration files.
Example Hub library: Arduino
Arduino library is a good example of the Hub implementation and the relationship with its child devices. It would be a good idea to have a look at the source code before attempting to write a new Hub class from scratch: Arduino device adapter library.
Especially important are implementations of the following methods from the MM::Hub device API:
MM::DeviceDetectionStatus DetectDevice(void);
int DetectInstalledDevices();
These two methods are critical for the configuration process. Hardware Configuration Wizard uses the first one automatically detect the port to which the Hub is connected and the second one to list all installed hardware devices.
Device Adapter Examples
Before writing a new device adapter, you may find it worthwhile to examine the source code of some of the numerous open source device adapters already written, available in our repository.
DemoCamera
The DemoCamera project is a (slightly complex) example on how to create a working device adapter. You can use it as a starting point for building your own device adapter. DemoCamera DLL contains a simple monochrome / color camera simulator and a number of demo devices: objective turret, filter changer, stage, image processor etc. The camera adapter is able to simulate parameter read-backs. There is also an option to simulate timeout errors. Most of these devices don’t really perform any actions, they just respond to commands and their main purpose is to enable testing of the main application without connecting to the hardware.
DemoCamera.cpp includes relatively extensive comments to help you understand how the API methods should work.
Testing and debugging device adapters
C++ environment
The best way to test a device adapter is to load it in the Micro-Manager (MMStudio) application, using the Hardware Configuration Wizard. You can attach a C++ debugger (such as Visual Studio or lldb) to a Micro-Manager process after launching.
The CoreLog logging can contain helpful information (including whatever
your device adapter logs using the LogMessage()
function). Make sure
to enable Debug Logging (in Tools > Options).
To debug DLL loading issues (such as when your device adapter is labeled “(unavailable)” in the Hardware Configuration Wizard), examine the CoreLog section from when you opened the Hardware Configuration Wizard. It should log the error causing the failure. On Windows, you may want to test if any dependencies are missing by using one of the Dependency Walker alternatives.
Scripting environment
To test devices from Micro-Manager scripting environment you can use the “Script Panel” (Tools menu). The scripting environment has a built-in reference to the MMCore API as “mmc”. Here is an example of a simple test script:
mmc.unloadAllDevices();
mmc.loadDevice("Camera", "DemoCamera", "DCam");
mmc.initializeDevice("Camera");
mmc.setCameraDevice("Camera");
mmc.setExposure(50);
mmc.snapImage();
if (mmc.getBytesPerPixel() == 1) {
// 8-bit grayscale pixels
byte[] img = (byte[])mmc.getImage();
print("Image snapped, " + img.length + " pixels total, 8 bits each.");
print("Pixel [0,0] value = " + img[0]);
} else if (mmc.getBytesPerPixel() == 2){
// 16-bit grayscale pixels
short[] img = (short[])mmc.getImage();
print("Image snapped, " + img.length + " pixels total, 16 bits each.");
print("Pixel [0,0] value = " + img[0]);
} else {
print("Dont' know how to handle images with " + mmc.getBytesPerPixel() +
" byte pixels.");
}
If you plan to actively use scripting you may want to look at the Micro-Manager_Programming_Guide as well as at the Scripting Guide.
Changes from the previous releases
API version 38 (1.4.0 release)
New Core API entries
- MM::DeviceDetectionStatus detectDevice(char* deviceName) – for automatic device detection
- void unloadDevice(const char* label) throw (CMMError) – unload a device adapter
Property API changes
- int applyProperties(std::vector
& props, std::string& ) - void updateCoreProperties() throw (CMMError)
- void setProperty(const char* label, const char* propName, …) new entries added which accept bool, long, float, and double.
Property sequencing API
- bool isPropertySequenceable(const char* label, const char* propName) const throw (CMMError);
- void startPropertySequence(const char* label, const char* propName) const throw (CMMError);
- void stopPropertySequence(const char* label, const char* propName) const throw (CMMError);
- long getPropertySequenceMaxLength(const char* label, const char* propName) const throw (CMMError);
- void loadPropertySequence(const char* label, const char* propName, std::vector<std::string> eventSequence) const throw (CMMError);
- bool debugLogEnabled(void) – access the current log state
- std::string saveLogArchive(void) & std::string saveLogArchiveWithPreamble(char* preamble, int length) - create a gzip of the current system log, optionally prepend some client text
- static void addSearchPath(const char *path) – add path to search for device adapters
- unsigned getNumberOfCameraChannels() const – number of simulaneous channels acquired by the camera.
Extensions to metadata API
- void* getLastImageMD(Metadata& md) const throw (CMMError);
- void* popNextImageMD(Metadata& md) throw (CMMError);
Acquisition context API
- void acqBefore() throw (CMMError);
- void acqAfter() throw (CMMError);
- void acqBeforeFrame() throw (CMMError);
- void acqAfterFrame() throw (CMMError);
- void acqBeforeStack() throw (CMMError);
- void acqAfterStack() throw (CMMError);
- template
T\* getSpecificDevice(const char\* deviceLabel) const throw (CMMError) – retrieve device instance by name.
API version 33 (1.3.43 release)
- New Device categories
- ProgrammableIO()
- SLM() ( Projector, Spatial Light Modulator, or Anti-Camera)
- CommandDispatch()
- MMDevice.h changes terminology
- Channels (in the context of color cameras) become Components, hence:
- GetNumberOfChannels -> GetNumberOfComponents.
- GetChannelName → GetComponentName
- New method signature for devices
- PrepareSequenceAcqusition()
- Autofocus devices add methods
- GetOffset(double &offset)
- SetOffset(double offset)
- Core (in MMDevice.h) new method signatures
- OnCoordinateUpdate(const Device* caller)
- GetChannelConfigs(std::vector<std::string>& groups)
- CMMCore (in MMCore.h) new signatures
- void setAutoFocusOffset(double offset) throw (CMMError);
- double getAutoFocusOffset() throw (CMMError);
- std::string getSLMDevice();
- std::string getChannelGroup();
- void setSLMDevice(const char* slmLabel) throw (CMMError);
- void setChannelGroup(const char* channelGroup) throw (CMMError);
- CMMCore support for pixel size calibration and configuration group management.
API version 31 (1.3.17 release)
- BaseSequenceThread. Implemented basic sequence acquisition functionality in svc() method like calculating number of frames, capturing with the specified interval and etc.
- CCameraBase
- Implemented virtual function IsCapturing returning true if the acquisition thread is run. If you do not use the BaseSequence thread to implement Sequence acquisition, then you will need to provide your own implementation of this function.
- Added virtual ThreadRun() method. This method is called from inside the acquisition thread and implements receiving image data from the camera.
- Added virtual OnThreadExiting() method called from inside the acquisition thread and implements actions necessary to finalize the sequence capturing.
- CMMCore
- Added isSequenceRunning() method which should be used instead of “Busy()” method of the camera to determine if the camera adapter is executing a sequence of image capturing. Note: normally, camera adapters should not return Busy status while the sequence acquisition thread is running.
- Circular buffer now supports 32-bit images.
API version 26 to 30 (1.3.15 release)
1. XYStageDevices. Default implementations for the functions SetRelativePositionUm(double, double), SetPositionUm(double, double), and GetPositionUm(double) are now provided in DeviceBase.h. You are encouraged not to override these functions but rather implement the Get and SetPostionSteps. This allows Micro-Manager to provide a general mechanism to let the use mirror the axis and change axis orientation so that a generalized coordinate system can be used. Although these implementations are not yet provided for single axis stages we anticipate this to happen soon and encourage you to use the ‘PositionSteps’ functions as the main entry point in communicating with the actual device
2. CameraDevice – Added Properties ‘TransposeMirrorX’, ‘TransposeMirrorY’, ‘TransposeXY’, ‘TranposeCorrection’, that can be used to inform Micro-Manager of the camera orientation. See https://valelab.ucsf.edu/~nico/MMwiki/index.php/Overview for information on how coordinates and directionality are handled in Micro-Manager
3. CameraDevice – Changes in Sequence mode: There is now a default implementation for SequenceAcquisition. You should override this with a better implementation if you can. Two more overloaded forms of StartSequenceAcquistion have been added with default implementations that should b e implemented where possible (one form does not request a number of images, another adds the parameter bool stopOnOverflow).
4. SignalIODevice – Added the functions SetGateOpen and GetGateOpen that are used to configure IO devices as shutters.
API version 24 to 26 (1.2.15 release)
1. CameraDevice - Made the functions ‘int GetBinning()’ and ‘int SetBinning(int binSize)’ mandatory. GetBinning is used in the Core to adjust pixelSize based on binning. The function ‘GetNominalPixelSize’ was removed.
2. StageDevice - Added the function ‘int SetRelativePositionUm(double d)’. A default implementation is provided, however, you are encouraged to override this function (which will be madated some time in the future).
3. XYStageDevice - Added function ‘int SetRelativePositionUm (double dx, double dy)’. A default implementation is provided, however, you are encouraged to override this function (which will be madated some time in the future).
4. MagnifierDevice - This is a new device that should be used by devices that effect the magnification in a predictable way. A single method needs to be implemented for such a device: ‘double GetMagnification()’
API version 20 to 24 (1.2 release)
New methods for the base MMDevice class
- HasProperty() – for finding out if device supports a property before using it. This will avoid generating and handling exceptions if the desired property does not exist. Default implementation is in the DeviceBase.h, automatic and requires no overriding.
- GetPropertyType() – returns property type. Default implementation is in the DeviceBase.h, automatic and requires no overriding.
- UsesDelay() – Default implementation returns “false”, i.e. declaring that the device does not use “Delay” setting. If the particular adapter does use delay settings, it must override this method by returning “true”. Very few devices use this feature.
- HasPropertyLimits(), GetPropertyLowerLimit(), GetPropertyUpperLimit() – This set of methods allows the device to specify value limits for “continuous” settings. The default implementation of these methods is automatic, but the limits (if any) must be specified in the code when particular property is declared using SetPropertyLimits(). For an example see DemoStreamingCamera.cpp.
New device types
- ImageStreamer, ImageProcessor and SignalIO device types were added. Instances of these devices are still under development and the API is likely to change.
We do not recommend writing adapters for these device types just as yet.
API version 14 to 20 (1.1 release)
New device type Processor virtual device is added to the MMDevice API to enable real-time processing and closed loop control. Continuous (burst) acquisition mode Camera API was extended to optionally support continuous acquisition mode. This mode allows cameras to run at full speed, under their own timing. Additional extensions were also made in the CoreCallback API to provide access to the circular buffer service. MMTime utility class A new utility class MMTime was added to simplify cross-platform high-resolution timer functionality, for time-out loops, time stamps, etc.
API version 12 to 14
This section is very important if you already have custom adapters for the previous API revision. Your existing adapters won’t work with the newer Micro-Manager releases if you don’t make changes listed below. DLL naming convention All adapter DLL file names now have prefix “mmgr_dal_”, e.g. mmgr_dal_hamamatsu.dll. However, there are no changes required for the configuration files. The prefix is internally appended by the MMCore when looking for the device adapter and it does not appear in any external naming convention.
Example project files in this package are all updated with the new convention. The linker will add prefix to the output file name. You need to modify your existing projects to conform to the new file naming convention.
Additional methods required in all adapter modules All adapter modules (DLL libraries) now support additional methods (see ModuleInterface.h):
extern "C" {
MODULE_API MM::Device* CreateDevice(const char* name);
MODULE_API void DeleteDevice(MM::Device* pDevice);
MODULE_API long GetModuleVersion();
MODULE_API long GetDeviceInterfaceVersion();
MODULE_API unsigned GetNumberOfDevices();
MODULE_API bool GetDeviceName(unsigned deviceIndex, char* name, unsigned bufferLength);
MODULE_API bool GetDeviceDescription(unsigned deviceIndex, char* name, unsigned bufferLength);
MODULE_API void InitializeModuleData();
}
The only thing you need to add to your existing modules is the implementation of the method “InitializeModuleData()” (see example projects). This method will be called each time MMCore loads the adapter library. In this method we create a list of adapters available in the DLL library. For example, DemoCamera library (DemoCamera.cpp) has the following InitializeModuleData() method implementation:
MODULE_API void InitializeModuleData()
{
AddAvailableDeviceName(g_CameraDeviceName, "Demo camera");
AddAvailableDeviceName(g_WheelDeviceName, "Demo filter wheel");
AddAvailableDeviceName(g_ObjectiveDeviceName, "Demo objective turret");
AddAvailableDeviceName(g_StageDeviceName, "Demo stage");
AddAvailableDeviceName(g_LightPathDeviceName, "Demo light path");
}
The first parameter in “AddAvailableDeviceName”, is the adapter name and the second one is a short description.