You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Application professionnelle de traitement d'image par nœuds (node-based) pour le développement, la vérification et la comparaison en vision par ordinateur.
CV Studio est une application avancée de traitement d'image basée sur un éditeur visuel de nœuds (nodes). Elle permet de créer des pipelines de vision par ordinateur par glisser-déposer, en connectant visuellement des blocs fonctionnels.
Cas d'usage
Domaine
Description
Prototypage
Tester et comparer rapidement différents algorithmes CV
Éducation
Apprendre la vision par ordinateur de manière interactive
Développement
Construire et valider des pipelines avant la production
Recherche
Expérimenter avec des modèles ML et des techniques CV traditionnelles
Surveillance
Détection d'objets en temps réel, tracking, alertes
Audio
Classification audio, spectrogrammes, traitement du signal
Caractéristiques clés
🎨 Éditeur visuel — Interface drag-and-drop DearPyGUI avec zoom (10%–500%)
🔄 Traitement temps réel — Résultats instantanés lors de la construction du pipeline
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ InputNode │────▶│ ProcessNode / │────▶│ VisualNode / │
│ (source) │ │ DLNode / etc. │ │ ActionNode │
└─────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ TimestampedQueue (FIFO horodaté) │
│ Chaque nœud publie ses données avec un timestamp. │
│ Les nœuds consommateurs récupèrent en ordre FIFO. │
└─────────────────────────────────────────────────────────────┘
Types de données échangés
Type
Constante
Description
Image
TYPE_IMAGE
Frame OpenCV (numpy array BGR)
Audio
TYPE_AUDIO
Chunk audio (dict avec samples, sr, etc.)
JSON
TYPE_JSON
Résultats structurés (détections, etc.)
Float
TYPE_FLOAT
Valeur flottante
Int
TYPE_INT
Valeur entière
Boolean
TYPE_BOOLEAN
Vrai/Faux
Time
TYPE_TIME_MS
Timestamp en millisecondes
5. Système de nœuds
Classe de base : Node (node/basenode.py)
Tous les nœuds héritent de la classe Node qui fournit :
Propriétés principales
classNode:
node_label="BaseNode"# Nom affiché dans l'interfacenode_tag="BaseNode"# Identifiant interne unique# Types de donnéesTYPE_IMAGE="IMAGE"TYPE_AUDIO="AUDIO"TYPE_JSON="JSON"TYPE_FLOAT="FLOAT"TYPE_INT="INT"TYPE_BOOLEAN="BOOLEAN"TYPE_TIME_MS="TIME_MS"# Directions de portINPUT="INPUT"OUTPUT="OUTPUT"
La classe Node fournit 20+ méthodes draw_*_info() pour dessiner :
Boîtes englobantes (bounding boxes)
Labels de classification
Squelettes de pose
Masques de segmentation
Résultats de tracking
QR codes détectés
Classe abstraite : DpgNodeABC (node/node_abc.py)
classDpgNodeABC(ABC):
@abstractmethoddefadd_node(self, parent, node_id, pos, ...):
"""Crée le nœud dans l'interface DearPyGUI"""@abstractmethoddefupdate(self, node_id, connection_list, node_image_dict, node_result_dict):
"""Logique de traitement"""@abstractmethoddefclose(self, node_id):
"""Libération des ressources"""@abstractmethoddefget_setting_dict(self, node_id):
"""Export des paramètres"""@abstractmethoddefset_setting_dict(self, node_id, setting_dict):
"""Import des paramètres"""
Pattern Factory
Chaque fichier de nœud expose une classe FactoryNode :
classFactoryNode:
node_label="Mon Nœud"# Affiché dans le menunode_tag="MonNoeud"# Tag internenode_color= [R, G, B] # Couleur du nœuddefcreate(self, node_id, ...):
"""Instancie le nœud concret"""returnMonNoeudImpl(node_id, ...)
@dataclassclassTimestampedData:
data: Any# Payload (image, audio, json...)timestamp: float# Unix timestampnode_id: str# ID du nœud source
TimestampedQueue
classTimestampedQueue:
defput(self, data, timestamp=None) # Ajouter avec horodatagedefget_latest(self) # Obtenir le plus récent (sans retirer)defget_oldest(self) # Obtenir le plus ancien (sans retirer)defpop_oldest(self) # FIFO pop (retirer le plus ancien)defclear(self) # Vider la filedefsize(self) # Nombre d'élémentsdefis_empty(self) # File vide ?defget_all(self) # Tous les éléments
NodeDataQueueManager
classNodeDataQueueManager:
defget_queue(self, node_id_name, data_type) # Obtenir/créer une filedefput_data(self, node_id_name, data_type, value, timestamp=None)
defget_latest(self, node_id_name, data_type) # Dernière donnée
QueueBackedDict (Adaptateur rétrocompatible)
classQueueBackedDict:
# Interface dictionnaire standarddict[node_id] =value# Écriture avec auto-timestampvalue=dict[node_id] # Lecture du plus récentnode_idindict# Test d'existencedict.get(node_id, default) # Get avec défaut# Méthodes étenduesdict.set_with_timestamp(node_id, value, timestamp) # Timestamp explicitedict.get_timestamp(node_id) # Récupérer le timestamp
9. Architecture src/ (moderne)
Le répertoire src/ introduit une architecture professionnelle optionnelle, 100% rétrocompatible.
fromsrc.utils.gpu_utilsimportlog_gpu_info, get_onnx_providerslog_gpu_info() # Affiche les GPU disponiblesproviders=get_onnx_providers(use_gpu=True) # ['CUDAExecutionProvider', 'CPUExecutionProvider']
10. Création de nœuds personnalisés
Étape 1 : Créer le fichier
Créer un fichier dans la catégorie appropriée, par exemple node/ProcessNode/node_mon_filtre.py.
Étape 2 : Implémenter le nœud
#!/usr/bin/env python# -*- coding: utf-8 -*-importnumpyasnpimportdearpygui.dearpyguiasdpgfromnode.node_abcimportDpgNodeABCclassFactoryNode:
"""Classe Factory exposée à l'éditeur."""node_label="Mon Filtre"# Nom dans le menunode_tag="MonFiltre"# ID interne uniquenode_color= [100, 200, 150] # Couleur RGB du nœuddef__init__(self):
passdefcreate(self, node_id, parent, pos, opencv_setting_dict):
returnMonFiltreNode(node_id, parent, pos, opencv_setting_dict)
classMonFiltreNode(DpgNodeABC):
"""Implémentation du nœud."""_ver="0.0.1"node_label="Mon Filtre"node_tag="MonFiltre"def__init__(self, node_id, parent, pos, opencv_setting_dict):
self.node_id=node_idself.parent=parentself.pos=posself._opencv_setting_dict=opencv_setting_dict# Tags pour les widgets DearPyGUIself.tag_node_name=f"{node_id}:{self.node_tag}"self.tag_node_input=f"{self.tag_node_name}:IMAGE:Input0"self.tag_node_output=f"{self.tag_node_name}:IMAGE:Output0"self.tag_slider=f"{self.tag_node_name}:slider_value"defadd_node(self, parent, node_id, pos, opencv_setting_dict, **kwargs):
"""Construit l'interface du nœud dans DearPyGUI."""withdpg.node(
tag=self.tag_node_name,
parent=parent,
label=self.node_label,
pos=pos,
):
# Port d'entrée IMAGEwithdpg.node_attribute(
tag=self.tag_node_input,
attribute_type=dpg.mvNode_Attr_Input,
):
dpg.add_text("Image In")
# Paramètre (slider)withdpg.node_attribute(attribute_type=dpg.mvNode_Attr_Static):
dpg.add_slider_int(
tag=self.tag_slider,
label="Intensité",
default_value=50,
min_value=0,
max_value=100,
width=150,
)
# Port de sortie IMAGEwithdpg.node_attribute(
tag=self.tag_node_output,
attribute_type=dpg.mvNode_Attr_Output,
):
dpg.add_text("Image Out")
defupdate(self, node_id, connection_list, node_image_dict, node_result_dict):
"""Appelé à chaque frame. Traite l'image."""# Récupérer l'image d'entréeinput_image=Noneforconnection_infoinconnection_list:
connection_type=connection_info[0].split(":")[2]
ifconnection_type=="IMAGE":
src_tag=":".join(connection_info[0].split(":")[:2])
input_image=node_image_dict.get(src_tag, None)
breakifinput_imageisNone:
return {"image": None, "json": None}
# Lire la valeur du sliderintensity=dpg.get_value(self.tag_slider)
# Appliquer le traitementoutput_image=self._apply_filter(input_image, intensity)
return {"image": output_image, "json": None}
def_apply_filter(self, image, intensity):
"""Logique de traitement personnalisée."""# Exemple : ajustement de luminositéfactor=intensity/50.0result=np.clip(image*factor, 0, 255).astype(np.uint8)
returnresultdefclose(self, node_id):
"""Nettoyage."""passdefget_setting_dict(self, node_id):
"""Sérialise les paramètres."""return {
"ver": self._ver,
"pos": dpg.get_item_pos(self.tag_node_name),
"slider_value": dpg.get_value(self.tag_slider),
}
defset_setting_dict(self, node_id, setting_dict):
"""Restaure les paramètres."""if"slider_value"insetting_dict:
dpg.set_value(self.tag_slider, setting_dict["slider_value"])
Étape 3 : Le nœud apparaît automatiquement
L'éditeur charge dynamiquement tous les fichiers node_*.py dans chaque dossier de catégorie. Votre nœud apparaîtra dans le menu correspondant au prochain lancement.
Bonnes pratiques
Nommer le fichier node_<nom>.py
Utiliser des tags uniques basés sur node_id et node_tag
Toujours gérer le cas où l'entrée est None
Nettoyer les ressources dans close()
Sérialiser tous les paramètres utilisateur dans get_setting_dict()
11. Configuration
Fichier principal : node_editor/setting/setting.json
# API Keys (ne jamais commiter !)OPENAI_API_KEY=sk-...GOOGLE_API_KEY=...# MongoDBMONGODB_URI=mongodb://localhost:27017# MQTTMQTT_BROKER=localhostMQTT_PORT=1883
Personnalisation
# Copier et modifier la config
cp node_editor/setting/setting.json ma_config.json
# Lancer avec la config personnalisée
python main.py --setting ma_config.json
12. Tests
Lancement des tests
# Tous les tests
python -m pytest tests/ -v
# Tests spécifiques
python -m pytest tests/test_core/ -v
python -m pytest tests/test_utils/ -v
# Tests du système de files
python -m pytest tests/test_timestamped_queue.py tests/test_queue_adapter.py -v
# Avec couverture
python -m pytest tests/ --cov=src --cov=node --cov-report=html
# Tests marqués
python -m pytest tests/ -m "not slow"# Exclure les tests lents
python -m pytest tests/ -m "unit"# Seulement les tests unitaires