Thank you for choosing Diffraction Limited for your imaging needs! The following guide is to help you develop applications for controlling your Aluma brand cameras. We hope you find it straightforward and informative.
Download the installers for the DLAPI SDK here:
As of this moment the SDK consists of: the API library binaries, header files, and this documentation.
Asynchronous Operation
DLAPI works on an asynchronous query and response messaging protocol that is exposed to the user via a the dl::IPromise interface class. Any API endpoint which communicates directly will return a pointer to an dl::IPromise instance which can be used to monitor the status of that message. These are divided into four types of interactions:
- Parameter Queries: operations which attempt to retrieve a parameter from the camera, such as dl::ICamera::queryStatus(), or dl::ISensor::queryInfo().
- Parameter Getters: When a query has completed successfully, the user may access the parameter they've queried via an associated get call (e.g. dl::ICamera::getStatus() or dl::ISensor::getInfo() respectively).
- Parameter Setters: operations which attempt to update a parameter on the camera, such as dl::ICamera::setNetworkSettings() or dl::ISensor::setSetting().
- Actions: operations which execute a firmware action such as dl::ICamera::pulseGuide() or dl::ISensor::startExposure().
dl::IPromise objects report a status that notify the user when the operation they've called has completed, or thrown an error. It also stores human-readable error messages, retrieved with dl::IPromise::getLastError(), for easy troubleshooting at the call-site.
As dl::IPromise objects are created by DLAPI, their memory is owned and managed by DLAPI; It is therefore the responsibility of the user to handle dl::IPromise objects, and release them when they're done with them. This can be done by calling dl::IPromise::release() when you are done monitoring the status of a given interaction (this does not require the interaction be complete).
The example below demonstrates the workflow required to instantiate, monitor, and release a promise, as well as how to obtain any error messages from the promise (if it didn't complete successfully).
Device Interfaces
All basic components of a camera have been divided into class interfaces, such as:
- ICamera which controls camera-level settings, status information, and provides accessors to all connected peripherals.
- ISensor which controls imaging, and basic sensor-level settings.
- ITEC which controls and reports the status of the thermo-electric cooler of the camera (if available).
- IAO which controls and reports the status of any adaptive optics units connected to the camera.
- IFW which controls and reports the status of any filter wheel units connected to the camera.
Data Structures
A number of convenience structures have been created to minimize the number of calls required for obtaining basic operating parameters for cameras, sensors, and other peripherals, as well as some convenience structures for various actions (like starting an exposure). You should familiarize yourself in particular with the dl::IPromise and IImage interfaces, as well as the dl::TSubframe and dl::TExposureOptions structs.
DLAPI has a number of convenience typedefs defined for interface classes, such as dl::IImagePtr or dl::ICameraPtr for readability.
First Steps
DLAPI is accessed entirely from a single header file: dlapi.h, and all its functionality is contained within the dl namespace. Once you've added it to your project's include path, you can access your Aluma brand cameras via the dl::IGateway interface. dl::IGateway objects are instantiated and managed completely within DLAPI via the dl::getGateway() and dl::deleteGateway() functions.
The internal implementation of dl::IGateway is a singleton, and thus you can make multiple calls to dl::getGateway() safely, so long as they are accompanied by their partnered dl::deleteGateway() call—but you should get in the habit of using dependency injection, and only calling the get/delete methods once.
Once you've got an dl::IGateway instance, you can query for USB or Network cameras, and control them normally. The sample Program below searches for a connected USB camera, and obtains the camera's serial string if found. Remember that convenience typedefs exist for all pointer-to-interface classes (e.g. dl::IGateway has an accompanying dl::IGatewayPtr).
Querying Camera Status
The next step is to query a parameter from the camera, which requires some knowledge about how DLAPI handles Input/Output (I/O). Because the DLAPI internals use asynchronous I/O to communicate with the camera, a promise-based approach to camera control (via the dl::IPromise interface) has been implemented—as such, accessing camera parameters requires a little bit of overhead. Any camera or peripheral endpoint which returns a pointer to an dl::IPromise object executes asynchronously on a separate thread. You can use the dl::IPromise returned object to monitor the status of the command either synchronously (blocking), or asynchronously (non-blocking).
Take, for example, the example above: if instead of retrieving the camera's serial number, we wanted to retrieve the camera status, and wait until the call returned. You would need to do a few things:
It is strongly recommended that you write some custom function to handle promises for you, monitoring their status and releasing them when they're complete. Such a function might look something like this:
Controlling Your Camera's Peripherals
Many of the settings you'll need to access during the normal operation of your camera are accessible via one of the camera's peripheral interfaces. Controlling cooling uses the dl::ITEC interface, whereas controlling the imaging sensor is done via an dl::ISensor interface. These are accessible via the dl::ICamera::getTEC() and dl::ICamera::getSensor() commands respectively.
Let's attempt to change the fan speed for the main sensor's fan. This can be achieved by updating the value of the dl::ISensor::Settings::FanSpeed setting:
Your First Image
Acquiring your first image follows much of the same setup as before, and requires only a little extra overhead to get the job done. First, we need to know the dimensions of the sensor we're imaging with:
The dl::ISensor::Info structure contains the dl::ISensor::Info::pixelsX and dl::ISensor::Info::pixelsY parameters, which represent the full height and width of the sensor in pixels. Next, we need to construct a subframe for the image. In this case, let's take a full-frame image, binned 1x1.
With the subframe armed, we can start the exposure. For this example, let's set up a 1 second light frame exposure.
Now it's our responsibility to wait until the camera's sensor reports ready for download. We can do that using the getCameraStatus() function we wrote above, and a bit of business logic:
Now we can start the download proper using a call to dl::ISensor::startDownload(), but because this can take some time (several seconds depending on the sensor and its digitization rate), it's wise to write a promise handling function that is non-blocking, so let's do that here:
Once that function returns true, we can retrieve the image buffer from the camera. It's good practice to make a local copy of the buffer as soon as you retrieve it as follows:
Now let's tie this all together from start to finish:
And that's the basics of camera control. See the ICamera, ISensor, ITEC, IFW, and IAO classes for more details on how to control your camera and its peripherals.
Cooler Control Best Practices
Cameras with coolers can be controlled via the ITEC interface, and there are some considerations we advise application developers in following when using the ITEC interface.
Initialization
After retrieving a pointer to an ICamera object from the Gateway, you need to check whether the camera supports the use of a cooler. This is achieved via the ICamera::Capability
API.
Assuming the camera you've connected to supports a cooler, we advise retrieving the min/max cooler setpoints from the sensor info:
Once you know the minimum and maximum cooler setpoints, and whether the camera supports the use of a cooler, you can control the cooler via the ITEC interface after you've called ICamera::initialize()
.
Some special considerations are necessary: The majority of sensors recommend not exceeding temperature changes greater than 10 degrees Celcius per minute (especially for extended periods of time). This is especially true for cameras with high cooling power (i.e. greater than 50 C changes). We advise slowly ramping up/down the temperature in 5 degree increments every 30 seconds until you reach the desired setpoint. Cameras with high cooling power perform this function automatically, but it never hurts to be cautious.
During normal operation, the cooler status is reported as part of the ICamera::Status
object. You can retrieve that value using a normal camera status query. Values of note include: ICamera::Status::coolerPower
which measures the duty cycle of the cooler's power consumption in percent, ICamera::Status::sensorTemperature
which measures the temperature of the sensor in degrees Celcius (if ICamera::Capability::eSupportsCoolerTemp
is true
), and ICamera::Status::heatSinkTemperature
which measures the camera's heatsink temperature in degrees Celcius (if ICamera::Capability::eSupportsHeatSinkTemp
is true
).
When shutting down the cooler, you make the call to ITEC::setState()
with the first parameter set to false
. You can set the cooler setpoint to whatever temperature you like (ambient, 0, etc) as it is ignored by the camera. Remember to ramp up the temperature to prevent sensor damage.