Capturer une image

Module d’exemple : helloindicapture.

giveMeAnImage() : l’affichage sans aucun code

HelloIndiCapture::HelloIndiCapture(...)
    : IndiModule(...)
{
    giveMeADevice("camera", "Camera", INDI::BaseDevice::CCD_INTERFACE);
    giveMeAnImage();
}

giveMeAnImage() construit à elle seule les propriétés image (lecture seule, élément ElementImg) et imgparms (mode de stretch, qualité jpeg) — rien à déclarer en JSON pour ça. Ce que le module doit encore fournir, c’est la capture elle-même et le renvoi de l’image une fois reçue.

Demander la 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, exposition, gain, offset) envoie la demande au device ; shot et actions sont deux propriétés classiques déclarées en JSON (voir Le plus petit module), rien de spécifique à INDI dans leur déclaration. requestCapture() active déjà lui-même la réception des BLOBs sur ce device (setBLOBMode(B_ALSO, ...)) — inutile de l’appeler soi-même avant.

Recevoir le 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(); });
}

Capturer une image est le seul cas, dans cette série d’exemples, qui surcharge updateProperty() (protégé) plutôt que onUpdateProperty() (public) — voir la vue d’ensemble. Le chaînage IndiModule::updateProperty(property) en première ligne n’est pas optionnel : c’est lui qui maintient la liste de valeurs de devices.* à jour sur une connexion. L’oublier casse ce rafraîchissement silencieusement, sans erreur visible.

Combiner la caméra (updateProperty) et un autre device (onUpdateProperty)

Un module qui pilote une caméra et un focuser n’a pas à choisir entre les deux mécanismes : IndiModule::updateProperty(property) appelle lui-même onUpdateProperty(property) en sortie, pour toute propriété, pas seulement celles du device qui a motivé la surcharge. Surcharger updateProperty() pour intercepter les BLOBs de la caméra (comme ci-dessus) et surcharger onUpdateProperty() pour suivre la position du focuser (comme helloindiwrite) coexistent donc sans conflit dans le même module, tant que le chaînage ci-dessus est bien fait.

newBLOB() n’est plus une méthode virtuelle d’INDI::BaseClient

Sur les versions récentes de libindi, un BLOB arrive comme n’importe quelle autre propriété via updateProperty(), typée INDI_BLOB — il n’y a plus de callback dédié à surcharger. newBLOB() ci-dessus est une méthode privée ordinaire du module, pas une surcharge ; le dispatch manuel (property.getType() == INDI_BLOB) est le seul moyen actuel de la déclencher, exactement comme le fait dummy.cpp dans le code du core. Simplifier ça pour qu’un module n’ait plus besoin de surcharger updateProperty() lui-même est évoqué dans l’issue ost/ostserver#271, pas encore tranché.

Republier l’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() remplit _image (la trame résidente du module, une seule à la fois) et applique déjà le stretch courant ; refreshPreview() n’a plus qu’à sauver un jpeg dans le webroot et pousser son URL dans l’élément image. refreshPreview() est aussi rappelée automatiquement par le framework chaque fois que l’utilisateur change imgparms.stretch — un module qui en a besoin n’écrit cette méthode qu’une seule fois.

ImgStats() ne donne que des statistiques de pixels, pas le HFR ni le comptage d’étoiles

ImgStats() remplit uniquement les champs de OST::ImgData qui se calculent directement à partir des pixels bruts : min/max/mean/stddev/median par canal, histogram, dimensions. Les champs HFRavg, HFRavgDev et starsCount existent bien dans la structure, mais ImgStats() les laisse à zéro.

Toute analyse d’image plus poussée (détection d’étoiles, HFR, plate solving…) est un sujet métier, propre à chaque module, qui n’entre pas dans le périmètre de ce framework — ce n’est pas le rôle de cette documentation de le couvrir. Pour s’inspirer d’une implémentation réelle, voir par exemple le module Focus du core (src/modules/focus/focus.cpp), qui gère ça via sa propre instance de la classe Solver (libs/solver.cpp).