Migration de DataLab v0.20 vers v1.0#
Avertissement
Note de compatibilité critique :
DataLab v1.0 introduit des changements majeurs qui ne sont pas rétrocompatibles avec v0.20.
Les plugins développés pour v0.20 ne fonctionneront pas et doivent être mis à jour
Les changements d’API nécessitent des modifications de code pour toutes les intégrations personnalisées
Veuillez lire ce guide attentivement avant de passer à la v1.0.
DataLab v1.0 introduit des changements architecturaux importants par rapport à la v0.20. Le changement le plus important est l”externalisation des fonctionnalités de traitement du signal et de l’image dans une bibliothèque séparée appelée Sigima.
Ce changement architectural améliore la modularité, la testabilité et permet la réutilisation des fonctions de traitement dans d’autres projets. Cependant, cela nécessite des mises à jour des plugins existants pour s’adapter à la nouvelle API.
Ce guide décrit les étapes pour migrer les plugins de DataLab v0.20 vers v1.0.
Qu’est-ce qui a changé dans la v1.0 ?#
Changements architecturaux majeurs#
Le changement le plus significatif dans DataLab v1.0 est la création de la bibliothèque Sigima, qui contient désormais :
Objets signal et image (
SignalObj,ImageObj, classes ROI)Paramètres de traitement (toutes les classes
*Param)Fonctions de traitement (modules
sigima.proc.signaletsigima.proc.image)Algorithmes pour le traitement du signal et de l’image, ou conversion de types de données, transformation de coordonnées, … (module
sigima.tools)Fonctions d’entrée/sortie (lecture et écriture de signaux/images)
Utilitaires de données de test (génération de signaux et d’images de test)
Objets résultat (
TableResultetGeometryResult, remplaçantResultPropertiesetResultShape)
Ce qui reste dans DataLab :
Composants d’interface graphique (panneaux, widgets, intégration de tracés)
Système de plugins (module
datalab.plugins)Logique applicative (fenêtre principale, configuration, gestion de projet)
Adaptateurs de métadonnées de résultats (
TableAdapter,GeometryAdapterpour stocker les résultats dans les métadonnées des objets)
Renommage du package#
Le package DataLab a été renommé de cdl à datalab par souci de clarté :
# v0.20
import cdl.obj
import cdl.param
from cdl.plugins import PluginBase
# v1.0
import sigima.objects
import sigima.params
from datalab.plugins import PluginBase
Convention de nommage des fichiers de plugin#
Les noms de fichiers de plugin doivent suivre la nouvelle convention de nommage :
Important
Les fichiers de plugin doivent être renommés de cdl_*.py en datalab_*.py.
Exemples :
cdl_myplugin.py→datalab_myplugin.pycdl_custom_filters.py→datalab_custom_filters.pycdl_example.py→datalab_example.py
Cette convention de nommage est requise pour que DataLab découvre et charge automatiquement les plugins au démarrage. Les plugins qui ne suivent pas ce schéma de nommage ne seront pas chargés.
Note
Le mécanisme de découverte des plugins recherche les fichiers Python correspondant au motif datalab_*.py dans le répertoire des plugins. Assurez-vous de mettre à jour les noms de fichiers de vos plugins en conséquence.
Nouveaux objets résultat#
DataLab v1.0 introduit deux nouveaux types de résultats immuables :
TableResult : Remplace
ResultPropertiespour les résultats scalaires tabulaires (par exemple, statistiques, mesures)GeometryResult : Remplace
ResultShapepour les résultats géométriques (par exemple, caractéristiques détectées, contours)
Ces nouveaux types de résultats sont orientés calcul et exempts de logique spécifique à l’application (par exemple, Qt, métadonnées). Tous les comportements liés aux métadonnées ont été migrés vers la couche applicative de DataLab en utilisant des classes d’adaptation (TableAdapter, GeometryAdapter).
Accès à l’UUID de l’objet#
La manière d’accéder à l’UUID d’un objet a changé :
# v0.20 - Direct attribute access
obj_uuid = obj.uuid
# v1.0 - Use helper function from datalab.objectmodel
from datalab.objectmodel import get_uuid
obj_uuid = get_uuid(obj)
# Alternative in v1.0 - Access metadata directly
obj_uuid = obj.get_metadata_option("uuid")
Ce changement s’applique aux objets SignalObj et ImageObj. L’UUID est désormais stocké dans les métadonnées de l’objet plutôt que comme un attribut direct.
Note
La fonction get_uuid() est définie dans datalab.objectmodel, et non comme une méthode de l’objet lui-même. Importez-la avant utilisation : from datalab.objectmodel import get_uuid.
Interface processeur unifiée#
Les méthodes du processeur ont été unifiées et renommées :
La plupart des méthodes spécifiques
compute_*utilisent désormais la méthode génériquerun_feature()Conventions de nommage mises à jour :
compute_11→compute_1_to_1,compute_10→compute_1_to_0, etc.
Mise à jour des imports#
Changements des chemins d’import#
Le tableau suivant donne l’équivalence entre les imports DataLab v0.20 et v1.0.
Le tableau ci-dessous présente la correspondance entre l’API DataLab v0.20 et v1.0. Pour la plupart des entrées, seule l’instruction d’import doit être mise à jour.
DataLab v0.20 |
DataLab v1.0 |
|---|---|
Renommages de modules/packages |
|
|
|
|
|
|
|
|
|
|
|
|
|
Création et manipulation d’objets |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Classes de paramètres |
|
|
|
|
|
|
|
|
|
|
|
Enveloppes de fonctions de calcul |
|
|
|
|
|
|
|
|
|
Méthodes du processeur (p est une instance de processeur de signal ou d’image) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Objets résultat |
|
|
|
|
|
|
|
|
|
|
|
Fonctions de données de test |
|
|
|
|
|
|
|
|
|
|
|
Nouveaux paramètres d’image |
|
|
|
|
|
|
|
Fonctions d’entrée/sortie |
|
|
|
|
|
|
|
|
|
Énumérations |
|
|
|
|
|
Fonctionnalités spécifiques à DataLab |
|
|
|
|
|
|
|
Migration du code des plugins#
Exemple 1 : Plugin de traitement simple#
Voici comment un plugin de filtre personnalisé simple doit être mis à jour :
DataLab v0.20 :
import numpy as np
import scipy.ndimage as spi
import cdl.computation.image as cpi
import cdl.obj
import cdl.param
import cdl.plugins
def weighted_average_denoise(data: np.ndarray) -> np.ndarray:
"""Apply a custom denoising filter to an image."""
def filter_func(values: np.ndarray) -> float:
central_pixel = values[len(values) // 2]
differences = np.abs(values - central_pixel)
weights = np.exp(-differences / np.mean(differences))
return np.average(values, weights=weights)
return spi.generic_filter(data, filter_func, size=5)
class CustomFilters(cdl.plugins.PluginBase):
"""DataLab Custom Filters Plugin"""
PLUGIN_INFO = cdl.plugins.PluginInfo(
name="My custom filters",
version="1.0.0",
description="This is an example plugin",
)
def create_actions(self) -> None:
"""Create actions"""
acth = self.imagepanel.acthandler
proc = self.imagepanel.processor
with acth.new_menu(self.PLUGIN_INFO.name):
for name, func in (("Weighted average denoise", weighted_average_denoise),):
# Wrap function to handle ImageObj objects
wrapped_func = cpi.Wrap11Func(func)
acth.new_action(
name, triggered=lambda: proc.compute_11(wrapped_func, title=name)
)
DataLab v1.0 :
import numpy as np
import scipy.ndimage as spi
import sigima.proc.image as sipi
import datalab.plugins
def weighted_average_denoise(data: np.ndarray) -> np.ndarray:
"""Apply a custom denoising filter to an image."""
def filter_func(values: np.ndarray) -> float:
central_pixel = values[len(values) // 2]
differences = np.abs(values - central_pixel)
weights = np.exp(-differences / np.mean(differences))
return np.average(values, weights=weights)
return spi.generic_filter(data, filter_func, size=5)
class CustomFilters(datalab.plugins.PluginBase):
"""DataLab Custom Filters Plugin"""
PLUGIN_INFO = datalab.plugins.PluginInfo(
name="My custom filters",
version="1.0.0",
description="This is an example plugin",
)
def create_actions(self) -> None:
"""Create actions"""
acth = self.imagepanel.acthandler
proc = self.imagepanel.processor
with acth.new_menu(self.PLUGIN_INFO.name):
for name, func in (("Weighted average denoise", weighted_average_denoise),):
# Wrap function to handle ImageObj objects
wrapped_func = sipi.Wrap1to1Func(func)
acth.new_action(
name,
triggered=lambda: proc.compute_1_to_1(wrapped_func, title=name),
)
Changements clés :
cdl.computation.image→sigima.proc.imagecdl.plugins→datalab.pluginsWrap11Func→Wrap1to1Funccompute_11→compute_1_to_1
Exemple 2 : Plugin utilisant des fonctionnalités intégrées#
DataLab v0.20 :
import cdl.obj
import cdl.param
import cdl.plugins
class ExtractBlobs(cdl.plugins.PluginBase):
"""DataLab Example Plugin"""
PLUGIN_INFO = cdl.plugins.PluginInfo(
name="Extract blobs (example)",
version="1.0.0",
description="This is an example plugin",
)
def preprocess(self) -> None:
"""Preprocess image"""
panel = self.imagepanel
param = cdl.param.BinningParam.create(sx=2, sy=2)
panel.processor.compute_binning(param)
panel.processor.compute_moving_median(cdl.param.MovingMedianParam.create(n=5))
def detect_blobs(self) -> None:
"""Detect circular blobs"""
panel = self.imagepanel
param = cdl.param.BlobOpenCVParam()
param.filter_by_color = False
param.min_area = 600.0
param.max_area = 6000.0
param.filter_by_circularity = True
param.min_circularity = 0.8
param.max_circularity = 1.0
panel.processor.compute_blob_opencv(param)
def create_actions(self) -> None:
"""Create actions"""
acth = self.imagepanel.acthandler
with acth.new_menu(self.PLUGIN_INFO.name):
acth.new_action("Preprocess image", triggered=self.preprocess)
acth.new_action("Detect circular blobs", triggered=self.detect_blobs)
DataLab v1.0 :
import sigima.objects
import sigima.params
import datalab.plugins
class ExtractBlobs(datalab.plugins.PluginBase):
"""DataLab Example Plugin"""
PLUGIN_INFO = datalab.plugins.PluginInfo(
name="Extract blobs (example)",
version="1.0.0",
description="This is an example plugin",
)
def preprocess(self) -> None:
"""Preprocess image"""
panel = self.imagepanel
param = sigima.params.BinningParam.create(sx=2, sy=2)
panel.processor.run_feature("binning", param)
panel.processor.run_feature(
"moving_median", sigima.params.MovingMedianParam.create(n=5)
)
def detect_blobs(self) -> None:
"""Detect circular blobs"""
panel = self.imagepanel
param = sigima.params.BlobOpenCVParam()
param.filter_by_color = False
param.min_area = 600.0
param.max_area = 6000.0
param.filter_by_circularity = True
param.min_circularity = 0.8
param.max_circularity = 1.0
panel.processor.run_feature("blob_opencv", param)
def create_actions(self) -> None:
"""Create actions"""
acth = self.imagepanel.acthandler
with acth.new_menu(self.PLUGIN_INFO.name):
acth.new_action("Preprocess image", triggered=self.preprocess)
acth.new_action("Detect circular blobs", triggered=self.detect_blobs)
Changements clés :
cdl.param→sigima.paramscdl.plugins→datalab.pluginscompute_binning(param)→run_feature("binning", param)compute_moving_median(param)→run_feature("moving_median", param)compute_blob_opencv(param)→run_feature("blob_opencv", param)
Exemple 3 : Plugin créant des objets et gérant les résultats#
DataLab v0.20 :
import numpy as np
import cdl.obj as dlo
import cdl.tests.data as test_data
from cdl.computation import image as cpima
from cdl.config import _
from cdl.plugins import PluginBase, PluginInfo
def add_noise_to_image(src: dlo.ImageObj, p: dlo.NormalRandomParam) -> dlo.ImageObj:
"""Add gaussian noise to image"""
dst = cpima.dst_11(src, "add_gaussian_noise", f"mu={p.mu},sigma={p.sigma}")
test_data.add_gaussian_noise_to_image(dst, p)
return dst
class PluginTestData(PluginBase):
"""DataLab Test Data Plugin"""
PLUGIN_INFO = PluginInfo(
name=_("Test data"),
version="1.0.0",
description=_("Testing DataLab functionalities"),
)
def add_noise_to_image(self) -> None:
"""Add noise to image"""
self.imagepanel.processor.compute_11(
add_noise_to_image,
paramclass=dlo.NormalRandomParam,
title=_("Add noise"),
)
def create_noisygauss_image(self) -> None:
"""Create 2D noisy gauss image"""
newparam = self.edit_new_image_parameters(
hide_image_height=True, hide_image_type=True
)
if newparam is not None:
obj = test_data.create_noisygauss_image(newparam, add_annotations=False)
self.proxy.add_object(obj)
def create_actions(self) -> None:
"""Create actions"""
iah = self.imagepanel.acthandler
with iah.new_menu(_("Test data")):
iah.new_action(_("Add noise to image"), triggered=self.add_noise_to_image)
iah.new_action(
_("Create 2D noisy gauss image"),
triggered=self.create_noisygauss_image,
select_condition="always",
)
DataLab v1.0 :
import numpy as np
import sigima.objects
import sigima.tests.data as test_data
from sigima.proc import image as sipi
from datalab.config import _
from datalab.plugins import PluginBase, PluginInfo
def add_noise_to_image(
src: sigima.objects.ImageObj, p: sigima.objects.NormalDistribution2DParam
) -> sigima.objects.ImageObj:
"""Add gaussian noise to image"""
dst = sipi.dst_1to1(src, "add_gaussian_noise", f"mu={p.mu},sigma={p.sigma}")
test_data.add_gaussian_noise_to_image(dst, p)
return dst
class PluginTestData(PluginBase):
"""DataLab Test Data Plugin"""
PLUGIN_INFO = PluginInfo(
name=_("Test data"),
version="1.0.0",
description=_("Testing DataLab functionalities"),
)
def add_noise_to_image(self) -> None:
"""Add noise to image"""
self.imagepanel.processor.compute_1_to_1(
add_noise_to_image,
paramclass=sigima.objects.NormalDistribution2DParam,
title=_("Add noise"),
)
def create_noisy_gaussian_image(self) -> None:
"""Create 2D noisy gauss image"""
newparam = self.edit_new_image_parameters(
hide_height=True, hide_type=True
)
if newparam is not None:
obj = test_data.create_noisy_gaussian_image(newparam, add_annotations=False)
self.proxy.add_object(obj)
def create_actions(self) -> None:
"""Create actions"""
iah = self.imagepanel.acthandler
with iah.new_menu(_("Test data")):
iah.new_action(_("Add noise to image"), triggered=self.add_noise_to_image)
iah.new_action(
_("Create 2D noisy gaussian image"),
triggered=self.create_noisy_gaussian_image,
select_condition="always",
)
Changements clés :
cdl.obj→sigima.objectscdl.tests.data→sigima.tests.datacdl.computation.image→sigima.proc.imagedst_11→dst_1to1compute_11→compute_1_to_1dlo.NormalRandomParam→sigima.objects.NormalDistribution2DParamcreate_noisygauss_image→create_noisy_gaussian_imagehide_image_height→hide_height,hide_image_type→hide_type
Travailler avec les objets résultat#
Modifications des objets résultat#
L’architecture des objets résultat a été complètement repensée dans la v1.0 :
Objets résultat v0.20 :
ResultProperties: Pour les résultats tabulaires (statistiques, etc.)ResultShape: Pour les résultats géométriques (caractéristiques détectées, etc.)Ces objets contenaient du code spécifique à Qt et de la logique de gestion des métadonnées
Les méthodes comme
add_to(obj)etfrom_metadata_entry()faisaient partie de la classe résultat
Objets résultat v1.0 :
TableResult: Résultats tabulaires immuables, orientés calculGeometryResult: Résultats géométriques immuables, orientés calculAucune dépendance Qt ou métadonnées
La gestion des métadonnées a été déplacée vers les classes d’adaptation DataLab
Utilisation de TableResult et GeometryResult#
Dans la v1.0, lorsque votre plugin reçoit des résultats d’opérations compute_1_to_0, vous travaillerez avec des objets TableResult ou GeometryResult.
Exemple 1 : Calcul des paramètres dynamiques du signal (TableResult)
Cet exemple montre comment calculer les paramètres dynamiques (ENOB, SINAD, THD, etc.) et travailler avec le TableResult résultant :
import sigima.params
import sigima.proc.signal
from datalab.adapters_metadata import TableAdapter
# Get the current signal from the panel
panel = self.signalpanel
obj = panel.objview.get_current_object()
# Compute dynamic parameters
param = sigima.params.DynamicParam.create(full_scale=1.0)
table = sigima.proc.signal.dynamic_parameters(obj, param)
# Access specific values from the table
enob = table["enob"][0] # Effective Number of Bits
sinad = table["sinad"][0] # Signal-to-Noise and Distortion Ratio
thd = table["thd"][0] # Total Harmonic Distortion
# Convert to dictionary for easy access
result_dict = table.as_dict()
print(f"ENOB: {result_dict['enob']}")
print(f"SINAD: {result_dict['sinad']} dB")
# Store result in object metadata using adapter
adapter = TableAdapter(table)
adapter.add_to(obj)
# Convert to pandas DataFrame for further processing
df = table.to_dataframe()
print(df)
Exemple 2 : Détection de pics dans une image (GeometryResult)
Cet exemple montre comment détecter des pics dans une image et travailler avec le GeometryResult résultant :
import sigima.params
import sigima.proc.image
from datalab.adapters_metadata import GeometryAdapter
# Get the current image from the panel
panel = self.imagepanel
obj = panel.objview.get_current_object()
# Configure peak detection parameters
param = sigima.params.Peak2DDetectionParam.create(
size=50, # Neighborhood size
threshold=0.5, # Relative threshold
create_rois=True # Create ROI for each peak
)
# Compute peak detection
geometry = sigima.proc.image.peak_detection(obj, param)
# Access geometry data
coords = geometry.coords # numpy array of shape (n_peaks, 2)
n_peaks = len(coords)
print(f"Detected {n_peaks} peaks")
# Iterate over detected peaks
for i, (x, y) in enumerate(coords):
print(f"Peak {i+1}: x={x:.2f}, y={y:.2f}")
# Check geometry kind
from sigima.objects import KindShape
assert geometry.kind == KindShape.POINT
# Store geometry in object metadata using adapter
adapter = GeometryAdapter(geometry)
adapter.add_to(obj)
Exemple 3 : Calcul de la bande passante (GeometryResult avec segments)
Cet exemple montre comment calculer la bande passante d’un signal, qui renvoie une géométrie de segment :
import sigima.proc.signal
from datalab.adapters_metadata import GeometryAdapter
# Get the current signal from the panel
panel = self.signalpanel
obj = panel.objview.get_current_object()
# Compute -3dB bandwidth
geometry = sigima.proc.signal.bandwidth_3db(obj)
# Access segment coordinates [x0, y0, x1, y1]
x0, y0, x1, y1 = geometry.coords[0]
print(f"Bandwidth segment: ({x0}, {y0}) to ({x1}, {y1})")
# Calculate bandwidth length
bandwidth = geometry.segments_lengths()[0]
print(f"Bandwidth@-3dB: {bandwidth:.2f}")
# Store in metadata
adapter = GeometryAdapter(geometry)
adapter.add_to(obj)
Récupération des résultats depuis les métadonnées#
Pour récupérer les résultats stockés dans les métadonnées de l’objet :
from datalab.adapters_metadata import TableAdapter, GeometryAdapter
obj = ... # Some signal or image object
# Iterate over all table results
for adapter in TableAdapter.iterate_from_obj(obj):
result = adapter.result # TableResult instance
title = adapter.title
df = result.to_dataframe()
# Iterate over all geometry results
for adapter in GeometryAdapter.iterate_from_obj(obj):
result = adapter.result # GeometryResult instance
title = adapter.title
coords = result.coords
Modifications des méthodes du processeur#
L’interface du processeur a été unifiée dans la v1.0. La plupart des méthodes spécifiques compute_* utilisent désormais la méthode générique run_feature().
Utilisation de run_feature()#
La méthode run_feature() est une interface unifiée pour exécuter des fonctionnalités de traitement :
# v1.0 - Generic interface
proc = panel.processor
# Simple features (no parameters)
proc.run_feature("normalize")
proc.run_feature("fft")
# Features with parameters
param = sigima.params.GaussianParam.create(sigma=2.0)
proc.run_feature("gaussian_filter", param)
# Features with direct function reference
import sigima.proc.image as sipi
proc.run_feature(sipi.normalize)
proc.run_feature(sipi.gaussian_filter, param)
Renommage des méthodes#
Plusieurs méthodes du processeur ont été renommées par souci de cohérence :
v0.20 |
v1.0 |
|---|---|
|
|
|
|
|
|
|
|
|
|
Fonctionnalités intégrées utilisant run_feature#
La plupart des fonctionnalités de traitement intégrées qui avaient auparavant des méthodes dédiées compute_* utilisent désormais run_feature() :
# v0.20
proc.compute_binning(param)
proc.compute_moving_median(param)
proc.compute_blob_opencv(param)
proc.compute_gaussian_filter(param)
# v1.0
proc.run_feature("binning", param)
proc.run_feature("moving_median", param)
proc.run_feature("blob_opencv", param)
proc.run_feature("gaussian_filter", param)
Certaines fonctionnalités complexes ont toujours des méthodes dédiées :
# These still use dedicated methods in v1.0
proc.compute_roi_extraction(roi)
proc.compute_multigaussianfit()
proc.compute_peak_detection(param)
Tester votre plugin#
Après la migration, testez minutieusement votre plugin :
Vérification des imports : Vérifiez que tous les imports fonctionnent correctement
Création de paramètres : Vérifiez que les classes de paramètres sont correctement instanciées
Création d’objets : Testez les fonctions de création d’objets signal/image
Opérations de traitement : Vérifiez que toutes les fonctionnalités de traitement fonctionnent comme prévu
Gestion des résultats : Vérifiez que les résultats sont correctement générés et stockés
Intégration de l’interface graphique : Testez que les menus et actions apparaissent correctement
Problèmes courants et solutions#
Erreurs d’import#
Problème : ModuleNotFoundError: No module named 'cdl'
Solution : Mettez à jour tous les imports de cdl.* vers soit datalab.* soit sigima.* selon le module.
Erreurs d’attribut sur le processeur#
Problème : AttributeError: 'ImageProcessor' object has no attribute 'compute_binning'
Solution : Remplacez les appels de méthode compute_* par des appels run_feature() :
# Wrong (v0.20 style)
proc.compute_binning(param)
# Correct (v1.0 style)
proc.run_feature("binning", param)
Incompatibilité d’objet résultat#
Problème : AttributeError: 'TableResult' object has no attribute 'add_to'
Solution : Utilisez des classes d’adaptation pour les opérations de métadonnées :
# Wrong (v0.20 style)
result.add_to(obj)
# Correct (v1.0 style)
from datalab.adapters_metadata import TableAdapter
TableAdapter(result).add_to(obj)
Paramètres manquants#
Problème : ImportError: cannot import name 'SomeParam' from 'datalab'
Solution : Importez les paramètres depuis sigima.params au lieu de cdl.param :
# Wrong
from cdl.param import GaussianParam
# Correct
from sigima.params import GaussianParam
Ressources supplémentaires#
Obtenir de l’aide#
Si vous rencontrez des problèmes pendant la migration :
Consultez le suivi des problèmes GitHub
Consultez les exemples de plugins intégrés dans le répertoire
datalab/plugins