Capturing an image
Example module: helloindicapture.
giveMeAnImage(): display with no code at all
HelloIndiCapture::HelloIndiCapture(...)
: IndiModule(...)
{
giveMeADevice("camera", "Camera", INDI::BaseDevice::CCD_INTERFACE);
giveMeAnImage();
}giveMeAnImage() builds, on its own, the image (read-only, ElementImg element) and imgparms (stretch mode, jpeg quality) properties — nothing to declare in JSON for this. What the module still has to provide is the capture itself and sending back the image once received.
Requesting the capture
void HelloIndiCapture::onExternalEvent(OST::ExtEvent event)
{
if (event.ev != OST::ExtEvType::SV || event.prpkey != "actions" || event.eltkey != "capture") return;
requestCapture(getString("devices", "camera"),
getFloat("shot", "exposure"),
getFloat("shot", "gain"),
getFloat("shot", "offset"));
getEltBool("actions", "capture")->setValue(false, true);
}requestCapture(device, exposure, gain, offset) sends the request to the device; shot and actions are two ordinary properties declared in JSON (see The smallest module), nothing INDI-specific in their declaration. requestCapture() already enables BLOB reception on this device itself (setBLOBMode(B_ALSO, ...)) — no need to call it yourself beforehand.
Receiving the BLOB
void HelloIndiCapture::updateProperty(INDI::Property property)
{
IndiModule::updateProperty(property);
if (QString(property.getDeviceName()) == getString("devices", "camera") && property.getType() == INDI_BLOB)
{
newBLOB(property);
}
}
void HelloIndiCapture::newBLOB(INDI::PropertyBlob pblob)
{
loadImageFromBlob(pblob, 64, [this]() { refreshPreview(); });
}Capturing an image is the only case, in this series of examples, that overrides updateProperty() (protected) rather than onUpdateProperty() (public) — see the overview. The IndiModule::updateProperty(property) chain call on the first line isn’t optional: it’s what keeps the devices.* list of values up to date on a connection. Forgetting it breaks this refresh silently, with no visible error.
A module driving a camera and a focuser doesn’t have to choose between the two mechanisms: IndiModule::updateProperty(property) itself calls onUpdateProperty(property) on exit, for every property, not just those of the device that motivated the override. Overriding updateProperty() to intercept the camera’s BLOBs (as above) and overriding onUpdateProperty() to track the focuser’s position (as in helloindiwrite) therefore coexist without conflict in the same module, as long as the chaining above is done correctly.
On recent versions of libindi, a BLOB arrives like any other property via updateProperty(), typed INDI_BLOB — there’s no longer a dedicated callback to override. newBLOB() above is an ordinary private method of the module, not an override; the manual dispatch (property.getType() == INDI_BLOB) is currently the only way to trigger it, exactly as dummy.cpp does in the core code. Simplifying this so a module no longer needs to override updateProperty() itself is discussed in issue ost/ostserver#271, not yet settled.
Republishing the image
void HelloIndiCapture::refreshPreview()
{
if (!_image) return;
QImage rawImage = _image->getRawQImage();
atomicSaveJpeg(rawImage, getWebroot() + "/" + getModuleName() + ".jpeg");
OST::ImgData data = _image->ImgStats();
data.mUrlJpeg = getModuleName() + ".jpeg";
getEltImg("image", "image")->setValue(data, true);
}loadImageFromBlob() fills _image (the module’s single resident frame) and already applies the current stretch; refreshPreview() only has to save a jpeg to the webroot and push its URL into the image element. refreshPreview() is also called back automatically by the framework whenever the user changes imgparms.stretch — a module that needs this only has to write this method once.
ImgStats() only fills the fields of OST::ImgData that can be computed directly from raw pixels: min/max/mean/stddev/median per channel, histogram, dimensions. The HFRavg, HFRavgDev and starsCount fields do exist in the struct, but ImgStats() leaves them at zero.
Any more advanced image analysis (star detection, HFR, plate solving…) is a business-specific topic, specific to each module, that falls outside this framework’s scope — covering it isn’t this documentation’s job. For inspiration from a real implementation, see for example the core Focus module (src/modules/focus/focus.cpp), which handles this through its own instance of the Solver class (libs/solver.cpp).