datalab-platform 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datalab/__init__.py +1 -1
- datalab/adapters_metadata/common.py +2 -2
- datalab/adapters_plotpy/converters.py +3 -1
- datalab/adapters_plotpy/coordutils.py +157 -0
- datalab/adapters_plotpy/roi/image.py +35 -6
- datalab/adapters_plotpy/roi/signal.py +8 -1
- datalab/config.py +88 -26
- datalab/control/baseproxy.py +70 -0
- datalab/control/proxy.py +33 -0
- datalab/control/remote.py +35 -0
- datalab/data/doc/DataLab_en.pdf +0 -0
- datalab/data/doc/DataLab_fr.pdf +0 -0
- datalab/data/icons/create/linear_chirp.svg +1 -1
- datalab/data/icons/create/logistic.svg +1 -1
- datalab/gui/actionhandler.py +16 -2
- datalab/gui/h5io.py +25 -0
- datalab/gui/macroeditor.py +37 -6
- datalab/gui/main.py +62 -5
- datalab/gui/newobject.py +7 -0
- datalab/gui/objectview.py +18 -3
- datalab/gui/panel/base.py +89 -16
- datalab/gui/panel/macro.py +26 -0
- datalab/gui/plothandler.py +20 -2
- datalab/gui/processor/base.py +72 -26
- datalab/gui/processor/image.py +6 -2
- datalab/gui/processor/signal.py +10 -0
- datalab/gui/roieditor.py +2 -2
- datalab/locale/fr/LC_MESSAGES/datalab.mo +0 -0
- datalab/locale/fr/LC_MESSAGES/datalab.po +3288 -0
- datalab/objectmodel.py +1 -1
- datalab/tests/features/common/auto_analysis_recompute_unit_test.py +81 -0
- datalab/tests/features/common/coordutils_unit_test.py +212 -0
- datalab/tests/features/common/result_deletion_unit_test.py +121 -1
- datalab/tests/features/common/roi_plotitem_unit_test.py +4 -2
- datalab/tests/features/common/update_tree_robustness_test.py +65 -0
- datalab/tests/features/control/remoteclient_unit.py +10 -0
- datalab/tests/features/hdf5/h5workspace_unit_test.py +133 -0
- datalab/tests/features/image/roigrid_unit_test.py +75 -0
- datalab/tests/features/macro/macroeditor_unit_test.py +104 -3
- datalab/tests/features/signal/custom_signal_bug_unit_test.py +96 -0
- datalab/widgets/imagebackground.py +13 -4
- datalab/widgets/instconfviewer.py +2 -2
- datalab/widgets/signalcursor.py +7 -2
- datalab/widgets/signaldeltax.py +4 -1
- {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/METADATA +3 -3
- {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/RECORD +50 -43
- {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/WHEEL +0 -0
- {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/entry_points.txt +0 -0
- {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/top_level.txt +0 -0
datalab/gui/plothandler.py
CHANGED
|
@@ -55,7 +55,16 @@ if TYPE_CHECKING:
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def calc_data_hash(obj: SignalObj | ImageObj) -> str:
|
|
58
|
-
"""Calculate a hash for a SignalObj | ImageObj object's data
|
|
58
|
+
"""Calculate a hash for a SignalObj | ImageObj object's data
|
|
59
|
+
|
|
60
|
+
For signals, this includes both X and Y data to detect axis changes.
|
|
61
|
+
For images, this includes only the Z data.
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(obj, SignalObj):
|
|
64
|
+
# For signals, hash both X and Y data to detect axis changes
|
|
65
|
+
# (e.g., when xmin/xmax is modified without changing Y values)
|
|
66
|
+
return hashlib.sha1(np.ascontiguousarray(obj.xydata)).hexdigest()
|
|
67
|
+
# For images, hash only the image data
|
|
59
68
|
return hashlib.sha1(np.ascontiguousarray(obj.data)).hexdigest()
|
|
60
69
|
|
|
61
70
|
|
|
@@ -153,6 +162,7 @@ class BasePlotHandler(Generic[TypeObj, TypePlotItem]): # type: ignore
|
|
|
153
162
|
self.__merged_result_adapters = {}
|
|
154
163
|
self.cleanup_dataview()
|
|
155
164
|
self.remove_all_shape_items()
|
|
165
|
+
self.plot.replot()
|
|
156
166
|
|
|
157
167
|
def add_shapes(self, oid: str, do_autoscale: bool = False) -> None:
|
|
158
168
|
"""Add geometric shape items associated to computed results and annotations,
|
|
@@ -382,7 +392,10 @@ class BasePlotHandler(Generic[TypeObj, TypePlotItem]): # type: ignore
|
|
|
382
392
|
if what == "selected":
|
|
383
393
|
# Refresh selected objects
|
|
384
394
|
oids = self.panel.objview.get_sel_object_uuids(include_groups=True)
|
|
385
|
-
if len(oids)
|
|
395
|
+
if len(oids) <= 1:
|
|
396
|
+
# Cleanup data view when there is 0 or 1 selected object.
|
|
397
|
+
# This removes stray plot items (like XRangeSelection, DataInfoLabel)
|
|
398
|
+
# that were created by PlotPy tools but are not managed by DataLab.
|
|
386
399
|
self.cleanup_dataview()
|
|
387
400
|
self.remove_all_shape_items()
|
|
388
401
|
for item in self:
|
|
@@ -797,4 +810,9 @@ class ImagePlotHandler(BasePlotHandler[ImageObj, MaskedXYImageItem]):
|
|
|
797
810
|
options = super().get_plot_options()
|
|
798
811
|
options.zlabel = self.plot.get_axis_title("right")
|
|
799
812
|
options.zunit = self.plot.get_axis_unit("right")
|
|
813
|
+
# Include aspect ratio configuration so that separate plot dialogs
|
|
814
|
+
# (e.g. "View in a new window", ROI editors, profile dialogs) use the same
|
|
815
|
+
# settings as the integrated plot handler:
|
|
816
|
+
options.aspect_ratio = self.plot.get_aspect_ratio()
|
|
817
|
+
options.lock_aspect_ratio = self.plot.lock_aspect_ratio
|
|
800
818
|
return options
|
datalab/gui/processor/base.py
CHANGED
|
@@ -42,6 +42,7 @@ from datalab.adapters_metadata import (
|
|
|
42
42
|
TableAdapter,
|
|
43
43
|
show_resultdata,
|
|
44
44
|
)
|
|
45
|
+
from datalab.adapters_plotpy import coordutils
|
|
45
46
|
from datalab.config import Conf, _
|
|
46
47
|
from datalab.gui.processor.catcher import CompOut, wng_err_func
|
|
47
48
|
from datalab.objectmodel import get_short_id, get_uuid, patch_title_with_ids
|
|
@@ -194,6 +195,38 @@ def insert_processing_parameters(
|
|
|
194
195
|
obj.set_metadata_option(PROCESSING_PARAMETERS_OPTION, pp.to_dict())
|
|
195
196
|
|
|
196
197
|
|
|
198
|
+
def clear_analysis_parameters(obj: SignalObj | ImageObj) -> None:
|
|
199
|
+
"""Clear analysis parameters from object metadata.
|
|
200
|
+
|
|
201
|
+
This removes the stored analysis parameters (1-to-0 operations) from the object.
|
|
202
|
+
Should be called when all analysis results are deleted to prevent the
|
|
203
|
+
auto_recompute_analysis function from attempting to recompute deleted analyses.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
obj: Signal or Image object
|
|
207
|
+
"""
|
|
208
|
+
key = f"__{ANALYSIS_PARAMETERS_OPTION}"
|
|
209
|
+
if key in obj.metadata:
|
|
210
|
+
del obj.metadata[key]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def run_with_env(func: Callable, args: tuple, env_json: str) -> CompOut:
|
|
214
|
+
"""Wrapper to apply environment config before calling func
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
func: function to call
|
|
218
|
+
args: function arguments
|
|
219
|
+
env_json: JSON string with environment configuration
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Computation output object containing the result, error message,
|
|
223
|
+
or warning message.
|
|
224
|
+
"""
|
|
225
|
+
sigima_options.set_env(env_json)
|
|
226
|
+
sigima_options.ensure_loaded_from_env() # recharge depuis l'env
|
|
227
|
+
return wng_err_func(func, args)
|
|
228
|
+
|
|
229
|
+
|
|
197
230
|
# Enable multiprocessing support for Windows, with frozen executable (e.g. PyInstaller)
|
|
198
231
|
multiprocessing.freeze_support()
|
|
199
232
|
|
|
@@ -220,22 +253,6 @@ COMPUTATION_TIP = _(
|
|
|
220
253
|
POOL: Pool | None = None
|
|
221
254
|
|
|
222
255
|
|
|
223
|
-
def run_with_env(func: Callable, args: tuple, env_json: str) -> CompOut:
|
|
224
|
-
"""Wrapper to apply environment config before calling func
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
func: function to call
|
|
228
|
-
args: function arguments
|
|
229
|
-
|
|
230
|
-
Returns:
|
|
231
|
-
Computation output object containing the result, error message,
|
|
232
|
-
or warning message.
|
|
233
|
-
"""
|
|
234
|
-
sigima_options.set_env(env_json)
|
|
235
|
-
sigima_options.ensure_loaded_from_env() # recharge depuis l'env
|
|
236
|
-
return wng_err_func(func, args)
|
|
237
|
-
|
|
238
|
-
|
|
239
256
|
class WorkerState(Enum):
|
|
240
257
|
"""Worker states for computation lifecycle."""
|
|
241
258
|
|
|
@@ -943,7 +960,9 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
943
960
|
TableAdapter.remove_all_from(result_obj)
|
|
944
961
|
GeometryAdapter.remove_all_from(result_obj)
|
|
945
962
|
|
|
946
|
-
def auto_recompute_analysis(
|
|
963
|
+
def auto_recompute_analysis(
|
|
964
|
+
self, obj: SignalObj | ImageObj, refresh_plot: bool = True
|
|
965
|
+
) -> None:
|
|
947
966
|
"""Automatically recompute analysis (1-to-0) operations after data changes.
|
|
948
967
|
|
|
949
968
|
This method checks if the object has 1-to-0 analysis parameters (analysis
|
|
@@ -960,6 +979,7 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
960
979
|
|
|
961
980
|
Args:
|
|
962
981
|
obj: The object whose data was modified
|
|
982
|
+
refresh_plot: Whether to refresh the plot after recomputation
|
|
963
983
|
"""
|
|
964
984
|
# Check if object has 1-to-0 analysis parameters (analysis operations)
|
|
965
985
|
proc_params = extract_analysis_parameters(obj)
|
|
@@ -972,14 +992,16 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
972
992
|
# Get the actual function from the function name
|
|
973
993
|
feature = self.get_feature(proc_params.func_name)
|
|
974
994
|
|
|
975
|
-
# Recompute the analysis operation silently
|
|
995
|
+
# Recompute the analysis operation silently, only for this specific object
|
|
996
|
+
# (not all selected objects, to avoid O(n²) behavior when called in a loop)
|
|
976
997
|
with Conf.proc.show_result_dialog.temp(False):
|
|
977
|
-
self.compute_1_to_0(feature.function, param, edit=False)
|
|
998
|
+
self.compute_1_to_0(feature.function, param, edit=False, target_objs=[obj])
|
|
978
999
|
|
|
979
1000
|
# Update the view
|
|
980
1001
|
obj_uuid = get_uuid(obj)
|
|
981
1002
|
self.panel.objview.update_item(obj_uuid)
|
|
982
|
-
|
|
1003
|
+
if refresh_plot:
|
|
1004
|
+
self.panel.refresh_plot(obj_uuid, update_items=True, force=True)
|
|
983
1005
|
|
|
984
1006
|
def __exec_func(
|
|
985
1007
|
self,
|
|
@@ -1121,6 +1143,9 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
1121
1143
|
)
|
|
1122
1144
|
insert_processing_parameters(new_obj, pp)
|
|
1123
1145
|
|
|
1146
|
+
# Mark object as freshly processed to show Processing tab
|
|
1147
|
+
self.panel.objprop.mark_as_freshly_processed(new_obj)
|
|
1148
|
+
|
|
1124
1149
|
new_gid = None
|
|
1125
1150
|
if grps:
|
|
1126
1151
|
# If groups are selected, then it means that there is no
|
|
@@ -1327,12 +1352,14 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
1327
1352
|
title: str | None = None,
|
|
1328
1353
|
comment: str | None = None,
|
|
1329
1354
|
edit: bool | None = None,
|
|
1355
|
+
target_objs: list[SignalObj | ImageObj] | None = None,
|
|
1330
1356
|
) -> ResultData:
|
|
1331
1357
|
"""Generic processing method: 1 object in → no object out.
|
|
1332
1358
|
|
|
1333
|
-
Applies a function to each selected object
|
|
1334
|
-
results (e.g. peak coordinates, statistical
|
|
1335
|
-
new objects. Results are stored in the object's
|
|
1359
|
+
Applies a function to each selected object (or specified target objects),
|
|
1360
|
+
returning metadata or measurement results (e.g. peak coordinates, statistical
|
|
1361
|
+
properties) without generating new objects. Results are stored in the object's
|
|
1362
|
+
metadata and returned as a
|
|
1336
1363
|
ResultData instance.
|
|
1337
1364
|
|
|
1338
1365
|
Args:
|
|
@@ -1344,6 +1371,8 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
1344
1371
|
title: Optional progress bar title.
|
|
1345
1372
|
comment: Optional comment for parameter dialog.
|
|
1346
1373
|
edit: Whether to open the parameter editor before execution.
|
|
1374
|
+
target_objs: Optional list of specific objects to process. If None,
|
|
1375
|
+
processes all currently selected objects.
|
|
1347
1376
|
|
|
1348
1377
|
Returns:
|
|
1349
1378
|
ResultData instance containing the results for all processed objects.
|
|
@@ -1360,7 +1389,11 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
1360
1389
|
if param is not None:
|
|
1361
1390
|
if edit and not param.edit(parent=self.mainwindow):
|
|
1362
1391
|
return None
|
|
1363
|
-
objs =
|
|
1392
|
+
objs = (
|
|
1393
|
+
target_objs
|
|
1394
|
+
if target_objs is not None
|
|
1395
|
+
else self.panel.objview.get_sel_objects(include_groups=True)
|
|
1396
|
+
)
|
|
1364
1397
|
current_obj = self.panel.objview.get_current_object()
|
|
1365
1398
|
title = func.__name__ if title is None else title
|
|
1366
1399
|
refresh_needed = False
|
|
@@ -1416,6 +1449,8 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
1416
1449
|
rdata.append(adapter, obj)
|
|
1417
1450
|
|
|
1418
1451
|
if obj is current_obj:
|
|
1452
|
+
# Mark object as having fresh analysis results to show Analysis tab
|
|
1453
|
+
self.panel.objprop.mark_as_fresh_analysis(obj)
|
|
1419
1454
|
self.panel.selection_changed(update_items=True)
|
|
1420
1455
|
else:
|
|
1421
1456
|
self.panel.refresh_plot(get_uuid(obj), True, False)
|
|
@@ -2374,8 +2409,13 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
2374
2409
|
)
|
|
2375
2410
|
# Auto-recompute analysis operations for objects with modified ROIs
|
|
2376
2411
|
if mode == "apply":
|
|
2377
|
-
|
|
2378
|
-
self.
|
|
2412
|
+
with create_progress_bar(
|
|
2413
|
+
self.panel, _("Recomputing..."), max_=len(objs)
|
|
2414
|
+
) as progress:
|
|
2415
|
+
for idx, obj_i in enumerate(objs):
|
|
2416
|
+
progress.setValue(idx)
|
|
2417
|
+
self.auto_recompute_analysis(obj_i, refresh_plot=False)
|
|
2418
|
+
self.panel.manual_refresh()
|
|
2379
2419
|
return edited_roi
|
|
2380
2420
|
|
|
2381
2421
|
def edit_roi_numerically(self) -> TypeROI:
|
|
@@ -2390,6 +2430,12 @@ class BaseProcessor(QC.QObject, Generic[TypeROI, TypeROIParam]):
|
|
|
2390
2430
|
obj = self.panel.objview.get_sel_objects()[0]
|
|
2391
2431
|
assert obj.roi is not None, _("No ROI selected for editing.")
|
|
2392
2432
|
params = obj.roi.to_params(obj)
|
|
2433
|
+
# Round coordinates to appropriate precision before displaying
|
|
2434
|
+
for param in params:
|
|
2435
|
+
if isinstance(obj, SignalObj):
|
|
2436
|
+
coordutils.round_signal_roi_param(obj, param)
|
|
2437
|
+
elif isinstance(obj, ImageObj):
|
|
2438
|
+
coordutils.round_image_roi_param(obj, param)
|
|
2393
2439
|
group = gds.DataSetGroup(params, title=_("Regions of Interest"))
|
|
2394
2440
|
if group.edit(parent=self.mainwindow):
|
|
2395
2441
|
edited_roi = obj.roi.__class__.from_params(obj, params)
|
datalab/gui/processor/image.py
CHANGED
|
@@ -779,7 +779,8 @@ class ImageProcessor(BaseProcessor[ImageROI, ROI2DParam]):
|
|
|
779
779
|
== QW.QMessageBox.No
|
|
780
780
|
):
|
|
781
781
|
return
|
|
782
|
-
|
|
782
|
+
options = self.panel.plothandler.get_plot_options()
|
|
783
|
+
editor = ImageGridROIEditor(parent=self.parent(), obj=obj0, options=options)
|
|
783
784
|
if exec_dialog(editor):
|
|
784
785
|
for obj in self.panel.objview.get_sel_objects():
|
|
785
786
|
obj.roi = editor.get_roi()
|
|
@@ -1014,7 +1015,10 @@ class ImageProcessor(BaseProcessor[ImageROI, ROI2DParam]):
|
|
|
1014
1015
|
with :py:func:`sigima.proc.image.offset_correction`"""
|
|
1015
1016
|
obj = self.panel.objview.get_sel_objects(include_groups=True)[0]
|
|
1016
1017
|
if param is None:
|
|
1017
|
-
|
|
1018
|
+
options = self.panel.plothandler.get_plot_options()
|
|
1019
|
+
dlg = imagebackground.ImageBackgroundDialog(
|
|
1020
|
+
obj, parent=self.mainwindow, options=options
|
|
1021
|
+
)
|
|
1018
1022
|
if exec_dialog(dlg):
|
|
1019
1023
|
x0, y0, x1, y1 = dlg.get_rect_coords()
|
|
1020
1024
|
param = ROI2DParam.create(
|
datalab/gui/processor/signal.py
CHANGED
|
@@ -164,6 +164,16 @@ class SignalProcessor(BaseProcessor[SignalROI, ROI1DParam]):
|
|
|
164
164
|
icon_name="convolution.svg",
|
|
165
165
|
obj2_name=_("signal to convolve with"),
|
|
166
166
|
)
|
|
167
|
+
self.register_2_to_1(
|
|
168
|
+
sips.replace_x_by_other_y,
|
|
169
|
+
_("Replace X by other signal's Y"),
|
|
170
|
+
comment=_(
|
|
171
|
+
"Replace X coordinates using Y values from another signal.\n"
|
|
172
|
+
"Useful for calibration: plot data vs wavelength scale."
|
|
173
|
+
),
|
|
174
|
+
obj2_name=_("signal providing Y values for X axis"),
|
|
175
|
+
skip_xarray_compat=True,
|
|
176
|
+
)
|
|
167
177
|
self.register_2_to_1(
|
|
168
178
|
sips.deconvolution,
|
|
169
179
|
_("Deconvolution"),
|
datalab/gui/roieditor.py
CHANGED
|
@@ -186,7 +186,7 @@ class ROIPolygonTool(PolygonTool):
|
|
|
186
186
|
|
|
187
187
|
def __init__(self, manager: PlotManager, obj: ImageObj) -> None:
|
|
188
188
|
super().__init__(manager, switch_to_default_tool=False, toolbar_id=None)
|
|
189
|
-
self.roi = PolygonalROI([
|
|
189
|
+
self.roi = PolygonalROI([0, 0, 1, 0, 1, 1, 0, 1], False)
|
|
190
190
|
self.obj = obj
|
|
191
191
|
|
|
192
192
|
def activate(self):
|
|
@@ -385,7 +385,7 @@ class BaseROIEditor(
|
|
|
385
385
|
"""Parent dialog was accepted: updating ROI Editor data"""
|
|
386
386
|
self.__roi.empty()
|
|
387
387
|
for roi_item in self.roi_items:
|
|
388
|
-
self.__roi.add_roi(plotitem_to_singleroi(roi_item))
|
|
388
|
+
self.__roi.add_roi(plotitem_to_singleroi(roi_item, self.obj))
|
|
389
389
|
if self.singleobj_btn is not None:
|
|
390
390
|
Conf.proc.extract_roi_singleobj.set(self.singleobj_btn.isChecked())
|
|
391
391
|
|
|
Binary file
|