Skip to content

Commit 0d2a98a

Browse files
committed
Merge branch 'develop' into feature/history-panel
# Conflicts: # datalab/gui/newobject.py # datalab/gui/processor/base.py # datalab/gui/processor/signal.py # datalab/tests/features/common/interactive_processing_test.py # datalab/tests/features/signal/fitdialog_unit_test.py # datalab/widgets/fitdialog.py
2 parents ad4101f + e53f245 commit 0d2a98a

40 files changed

Lines changed: 2719 additions & 830 deletions

.github/copilot-instructions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ def my_processing_func(src: SignalObj, param: MyParam) -> SignalObj:
184184
# ... computation logic ...
185185
return dst
186186

187+
187188
# 2. Register in DataLab processor (datalab/gui/processor/signal.py)
188189
def register_processing(self) -> None:
189190
self.register_1_to_1(
@@ -308,6 +309,7 @@ generated with `scripts/print_datalab_menus.py`).
308309
```python
309310
from datalab.plugins import PluginBase, PluginInfo
310311

312+
311313
class MyPlugin(PluginBase):
312314
"""My custom plugin."""
313315

@@ -455,6 +457,7 @@ and remote control from any HTTP client (including WASM/Pyodide environments).
455457
```python
456458
from __future__ import annotations
457459

460+
458461
def process_signal(src: SignalObj) -> SignalObj:
459462
"""Process signal."""
460463
pass
@@ -546,6 +549,7 @@ menu_title = "Processing" # Not translatable!
546549
2. **Export from Sigima** (`sigima/proc/signal/__init__.py`):
547550
```python
548551
from sigima.proc.signal.processing import my_feature # Import
552+
549553
__all__ = [..., "my_feature"] # Export
550554
```
551555

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,11 @@
2828
"python.testing.pytestPath": "pytest",
2929
"python.testing.unittestEnabled": false,
3030
"terminal.integrated.tabs.description": "${workspaceFolder}",
31+
"python-envs.pythonProjects": [
32+
{
33+
"path": ".",
34+
"envManager": "ms-python.python:venv",
35+
"packageManager": "ms-python.python:pip"
36+
}
37+
],
3138
}

.vscode/tasks.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,13 @@
576576
"label": "🖼️ Refresh doc screenshots",
577577
"detail": "Maintainer task: regenerate PNG screenshots under doc/images/ by launching DataLab for each language (fr + en). Commit the resulting diff in a dedicated 'docs: refresh screenshots' commit.",
578578
"type": "shell",
579-
"command": "scripts\\update_screenshots.bat",
579+
"command": "${command:python.interpreterPath}",
580+
"args": [
581+
"scripts/run_with_env.py",
582+
"cmd",
583+
"/c",
584+
"scripts\\update_screenshots.bat",
585+
],
580586
"options": {
581587
"cwd": "${workspaceFolder}",
582588
"env": {

datalab/gui/creation.py

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,53 @@
44

55
from __future__ import annotations
66

7+
import json
78
from typing import TYPE_CHECKING
89

910
import guidata.dataset as gds
1011
import numpy as np
1112
from sigima.objects import (
13+
PEAK_PARAMETERIZATION,
1214
CustomSignalParam,
1315
Gauss2DParam,
1416
ImageDatatypes,
1517
ImageObj,
1618
NewImageParam,
1719
NewSignalParam,
1820
SignalObj,
21+
convert_legacy_peak_creation_params,
1922
create_signal,
23+
validate_peak_creation_params,
2024
)
2125
from sigima.objects import create_image_from_param as create_image_headless
2226
from sigima.objects import create_signal_from_param as create_signal_headless
2327
from sigima.objects.base import BaseProcParam
24-
from sigima.objects.signal import DEFAULT_TITLE as SIGNAL_DEFAULT_TITLE
28+
from sigima.objects.signal import (
29+
DEFAULT_TITLE as SIGNAL_DEFAULT_TITLE,
30+
)
31+
from sigima.objects.signal import (
32+
BaseGaussLorentzVoigtParam,
33+
)
2534

2635
from datalab.config import _
2736

2837
if TYPE_CHECKING:
2938
from qtpy import QtWidgets as QW
3039

31-
CREATION_PARAMETERS_OPTION = "creation_param_json"
40+
CREATION_PARAMETERS_OPTION = "creation_parameters"
41+
LEGACY_CREATION_PARAMETERS_OPTION = "creation_param_json"
42+
CREATION_PARAMETERS_FORMAT_VERSION = 1
43+
44+
45+
def _decode_dataset_json(dataset_json: str) -> dict[str, object]:
46+
"""Decode a DataSet JSON payload without instantiating its class."""
47+
try:
48+
payload = json.loads(dataset_json)
49+
except (TypeError, json.JSONDecodeError) as exc:
50+
raise ValueError("Invalid creation parameter JSON") from exc
51+
if not isinstance(payload, dict):
52+
raise ValueError("Creation parameter JSON must contain an object")
53+
return payload
3254

3355

3456
def insert_creation_parameters(obj: SignalObj | ImageObj, param: gds.DataSet) -> None:
@@ -38,7 +60,17 @@ def insert_creation_parameters(obj: SignalObj | ImageObj, param: gds.DataSet) ->
3860
obj: Object receiving the serialized parameters.
3961
param: Creation parameters.
4062
"""
41-
obj.set_metadata_option(CREATION_PARAMETERS_OPTION, gds.dataset_to_json(param))
63+
dataset_json = gds.dataset_to_json(param)
64+
raw_params = _decode_dataset_json(dataset_json)
65+
envelope: dict[str, object] = {
66+
"format_version": CREATION_PARAMETERS_FORMAT_VERSION,
67+
"dataset_json": dataset_json,
68+
}
69+
if isinstance(param, BaseGaussLorentzVoigtParam):
70+
validate_peak_creation_params(raw_params)
71+
envelope["peak_parameterization"] = PEAK_PARAMETERIZATION
72+
obj.set_metadata_option(CREATION_PARAMETERS_OPTION, envelope)
73+
obj.metadata.pop(f"__{LEGACY_CREATION_PARAMETERS_OPTION}", None)
4274

4375

4476
def extract_creation_parameters(obj: SignalObj | ImageObj) -> gds.DataSet | None:
@@ -50,11 +82,77 @@ def extract_creation_parameters(obj: SignalObj | ImageObj) -> gds.DataSet | None
5082
Returns:
5183
Creation parameters or None if not found.
5284
"""
53-
try:
54-
param_json = obj.get_metadata_option(CREATION_PARAMETERS_OPTION)
55-
except ValueError:
85+
options = obj.get_metadata_options()
86+
has_current = CREATION_PARAMETERS_OPTION in options
87+
has_legacy = LEGACY_CREATION_PARAMETERS_OPTION in options
88+
if has_current and has_legacy:
89+
raise ValueError("Conflicting creation parameter formats")
90+
if not has_current and not has_legacy:
5691
return None
57-
return gds.json_to_dataset(param_json)
92+
93+
if has_current:
94+
envelope = options[CREATION_PARAMETERS_OPTION]
95+
if not isinstance(envelope, dict):
96+
raise ValueError("Creation parameters must use a versioned envelope")
97+
version = envelope.get("format_version")
98+
if version != CREATION_PARAMETERS_FORMAT_VERSION:
99+
raise ValueError(f"Unsupported creation parameter format: {version!r}")
100+
dataset_json = envelope.get("dataset_json")
101+
if not isinstance(dataset_json, str):
102+
raise ValueError("Creation parameter envelope has no dataset_json")
103+
raw_params = _decode_dataset_json(dataset_json)
104+
is_peak = raw_params.get("class_name") in {
105+
"GaussParam",
106+
"LorentzParam",
107+
"VoigtParam",
108+
}
109+
parameterization = envelope.get("peak_parameterization")
110+
if is_peak:
111+
if parameterization != PEAK_PARAMETERIZATION:
112+
raise ValueError(
113+
f"Unsupported peak parameterization: {parameterization!r}"
114+
)
115+
validate_peak_creation_params(raw_params)
116+
elif parameterization is not None:
117+
raise ValueError(
118+
"Peak parameterization set on non-peak creation parameters"
119+
)
120+
return gds.json_to_dataset(dataset_json)
121+
122+
dataset_json = options[LEGACY_CREATION_PARAMETERS_OPTION]
123+
if not isinstance(dataset_json, str):
124+
raise ValueError("Legacy creation parameters must contain DataSet JSON")
125+
raw_params = _decode_dataset_json(dataset_json)
126+
if raw_params.get("class_name") in {"GaussParam", "LorentzParam", "VoigtParam"}:
127+
validate_peak_creation_params(raw_params)
128+
return gds.json_to_dataset(dataset_json)
129+
130+
131+
def convert_legacy_creation_parameters(
132+
obj: SignalObj | ImageObj,
133+
) -> gds.DataSet:
134+
"""Explicitly convert legacy peak creation metadata to version 2.
135+
136+
The object data is not regenerated; only its reusable creation parameters
137+
are converted and stored under the new metadata option.
138+
139+
Args:
140+
obj: Object carrying historical creation metadata.
141+
142+
Returns:
143+
Converted peak creation parameters.
144+
"""
145+
options = obj.get_metadata_options()
146+
if CREATION_PARAMETERS_OPTION in options:
147+
raise ValueError("Current creation parameters already exist")
148+
dataset_json = options.get(LEGACY_CREATION_PARAMETERS_OPTION)
149+
if not isinstance(dataset_json, str):
150+
raise ValueError("Object has no legacy creation parameters")
151+
raw_params = _decode_dataset_json(dataset_json)
152+
converted = convert_legacy_peak_creation_params(raw_params)
153+
param = gds.json_to_dataset(json.dumps(converted))
154+
insert_creation_parameters(obj, param)
155+
return param
58156

59157

60158
def create_signal_from_param(param: NewSignalParam) -> SignalObj:

datalab/gui/newobject.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929

3030
from datalab.config import _
3131
from datalab.gui.creation import (
32+
CREATION_PARAMETERS_FORMAT_VERSION,
3233
CREATION_PARAMETERS_OPTION,
34+
LEGACY_CREATION_PARAMETERS_OPTION,
35+
convert_legacy_creation_parameters,
3336
create_image_from_param,
3437
create_signal_from_param,
3538
extract_creation_parameters,
@@ -39,7 +42,10 @@
3942
)
4043

4144
__all__ = [
45+
"CREATION_PARAMETERS_FORMAT_VERSION",
4246
"CREATION_PARAMETERS_OPTION",
47+
"LEGACY_CREATION_PARAMETERS_OPTION",
48+
"convert_legacy_creation_parameters",
4349
"create_image_gui",
4450
"create_signal_gui",
4551
"extract_creation_parameters",

0 commit comments

Comments
 (0)