Migrating from DataLab v0.20 to v1.0#
Warning
Critical compatibility notice:
DataLab v1.0 introduces major breaking changes that are not backward compatible with v0.20.
Plugins developed for v0.20 will not work and must be updated
API changes require code modifications for all custom integrations
Please read this guide carefully before upgrading to v1.0.
DataLab v1.0 introduces significant architectural changes compared to v0.20. The most important change is the externalization of signal and image processing features into a separate library called Sigima.
This architectural change improves modularity, testability, and enables the reuse of processing functions in other projects. However, it requires updates to existing plugins to adapt to the new API.
This guide describes the steps to migrate plugins from DataLab v0.20 to v1.0.
What changed in v1.0?#
Major architectural changes#
The most significant change in DataLab v1.0 is the creation of the Sigima library, which now contains:
Signal and image objects (
SignalObj,ImageObj, ROI classes)Processing parameters (all
*Paramclasses)Processing functions (
sigima.proc.signalandsigima.proc.imagemodules)Algorithms for signal and image processing, or data type conversion, coordinates transformation, … (
sigima.toolsmodule)I/O functions (reading and writing signals/images)
Test data utilities (test signal and image generation)
Result objects (
TableResultandGeometryResult, replacingResultPropertiesandResultShape)
What remains in DataLab:
GUI components (panels, widgets, plot integration)
Plugin system (
datalab.pluginsmodule)Application logic (main window, configuration, project management)
Result metadata adapters (
TableAdapter,GeometryAdapterfor storing results in object metadata)
Package renaming#
The DataLab package was renamed from cdl to datalab for clarity:
# 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
Plugin file naming convention#
Plugin filenames must follow the new naming convention:
Important
Plugin files must be renamed from cdl_*.py to datalab_*.py.
Examples:
cdl_myplugin.py→datalab_myplugin.pycdl_custom_filters.py→datalab_custom_filters.pycdl_example.py→datalab_example.py
This naming convention is required for DataLab to automatically discover and load plugins at startup. Plugins that don’t follow this naming pattern will not be loaded.
Note
The plugin discovery mechanism looks for Python files matching the pattern datalab_*.py in the plugins directory. Make sure to update your plugin filenames accordingly.
New result objects#
DataLab v1.0 introduces two new immutable result types:
TableResult: Replaces
ResultPropertiesfor tabular scalar results (e.g., statistics, measurements)GeometryResult: Replaces
ResultShapefor geometric results (e.g., detected features, contours)
These new result types are computation-oriented and free of application-specific logic (e.g., Qt, metadata). All metadata-related behaviors have been migrated to the DataLab application layer using adapter classes (TableAdapter, GeometryAdapter).
Object UUID access#
The way to access an object’s UUID has changed:
# 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")
This change applies to both SignalObj and ImageObj objects. The UUID is now stored in the object’s metadata rather than as a direct attribute.
Note
The get_uuid() function is defined in datalab.objectmodel, not as a method on the object itself. Import it before use: from datalab.objectmodel import get_uuid.
Unified processor interface#
The processor methods have been unified and renamed:
Most specific
compute_*methods now use the genericrun_feature()methodNaming conventions updated:
compute_11→compute_1_to_1,compute_10→compute_1_to_0, etc.
Updating the imports#
Import path changes#
The following table gives the equivalence between DataLab v0.20 and v1.0 imports.
The table below shows the mapping between DataLab v0.20 and v1.0 API. For most entries, only the import statement needs to be updated.
DataLab v0.20 |
DataLab v1.0 |
|---|---|
Module/package renames |
|
|
|
|
|
|
|
|
|
|
|
|
|
Object creation and manipulation |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameter classes |
|
|
|
|
|
|
|
|
|
|
|
Computation function wrappers |
|
|
|
|
|
|
|
|
|
Processor methods (p is a signal or image processor instance) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Result objects |
|
|
|
|
|
|
|
|
|
|
|
Test data functions |
|
|
|
|
|
|
|
|
|
|
|
New image parameters |
|
|
|
|
|
|
|
I/O functions |
|
|
|
|
|
|
|
|
|
Enumerations |
|
|
|
|
|
DataLab-specific features |
|
|
|
|
|
|
|
Migrating plugin code#
Example 1: Simple processing plugin#
Here’s how a simple custom filter plugin needs to be updated:
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),
)
Key changes:
cdl.computation.image→sigima.proc.imagecdl.plugins→datalab.pluginsWrap11Func→Wrap1to1Funccompute_11→compute_1_to_1
Example 2: Plugin using built-in features#
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)
Key changes:
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)
Example 3: Plugin creating objects and handling results#
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",
)
Key changes:
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
Working with result objects#
Changes to result objects#
The result object architecture has been completely redesigned in v1.0:
v0.20 Result Objects:
ResultProperties: For tabular results (statistics, etc.)ResultShape: For geometric results (detected features, etc.)These objects contained Qt-specific code and metadata handling logic
Methods like
add_to(obj)andfrom_metadata_entry()were part of the result class
v1.0 Result Objects:
TableResult: Immutable, computation-oriented table resultsGeometryResult: Immutable, computation-oriented geometry resultsNo Qt or metadata dependencies
Metadata handling moved to DataLab adapter classes
Using TableResult and GeometryResult#
In v1.0, when your plugin receives results from compute_1_to_0 operations, you’ll work with TableResult or GeometryResult objects.
Example 1: Computing signal dynamic parameters (TableResult)
This example shows how to compute dynamic parameters (ENOB, SINAD, THD, etc.) and work with the resulting TableResult:
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)
Example 2: Detecting image peaks (GeometryResult)
This example shows how to detect peaks in an image and work with the resulting GeometryResult:
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)
Example 3: Computing bandwidth (GeometryResult with segments)
This example shows how to compute signal bandwidth, which returns a segment geometry:
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)
Retrieving results from metadata#
To retrieve results stored in object metadata:
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
Processor method changes#
The processor interface has been unified in v1.0. Most specific compute_* methods now use the generic run_feature() method.
Using run_feature()#
The run_feature() method is a unified interface for running processing features:
# 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)
Method renaming#
Several processor methods have been renamed for consistency:
v0.20 |
v1.0 |
|---|---|
|
|
|
|
|
|
|
|
|
|
Built-in features using run_feature#
Most built-in processing features that previously had dedicated compute_* methods now use 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)
Some complex features still have dedicated methods:
# These still use dedicated methods in v1.0
proc.compute_roi_extraction(roi)
proc.compute_multigaussianfit()
proc.compute_peak_detection(param)
Testing your plugin#
After migration, thoroughly test your plugin:
Import checks: Verify all imports work correctly
Parameter creation: Check that parameter classes are correctly instantiated
Object creation: Test signal/image object creation functions
Processing operations: Verify all processing features work as expected
Result handling: Check that results are correctly generated and stored
GUI integration: Test that menus and actions appear correctly
Common issues and solutions#
Import errors#
Problem: ModuleNotFoundError: No module named 'cdl'
Solution: Update all imports from cdl.* to either datalab.* or sigima.* depending on the module.
Attribute errors on processor#
Problem: AttributeError: 'ImageProcessor' object has no attribute 'compute_binning'
Solution: Replace compute_* method calls with run_feature() calls:
# Wrong (v0.20 style)
proc.compute_binning(param)
# Correct (v1.0 style)
proc.run_feature("binning", param)
Result object incompatibility#
Problem: AttributeError: 'TableResult' object has no attribute 'add_to'
Solution: Use adapter classes for metadata operations:
# Wrong (v0.20 style)
result.add_to(obj)
# Correct (v1.0 style)
from datalab.adapters_metadata import TableAdapter
TableAdapter(result).add_to(obj)
Missing parameters#
Problem: ImportError: cannot import name 'SomeParam' from 'datalab'
Solution: Import parameters from sigima.params instead of cdl.param:
# Wrong
from cdl.param import GaussianParam
# Correct
from sigima.params import GaussianParam
Additional resources#
Getting help#
If you encounter issues during migration:
Check the GitHub issue tracker
Consult the built-in plugin examples in the
datalab/pluginsdirectory