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.
Files changed (50) hide show
  1. datalab/__init__.py +1 -1
  2. datalab/adapters_metadata/common.py +2 -2
  3. datalab/adapters_plotpy/converters.py +3 -1
  4. datalab/adapters_plotpy/coordutils.py +157 -0
  5. datalab/adapters_plotpy/roi/image.py +35 -6
  6. datalab/adapters_plotpy/roi/signal.py +8 -1
  7. datalab/config.py +88 -26
  8. datalab/control/baseproxy.py +70 -0
  9. datalab/control/proxy.py +33 -0
  10. datalab/control/remote.py +35 -0
  11. datalab/data/doc/DataLab_en.pdf +0 -0
  12. datalab/data/doc/DataLab_fr.pdf +0 -0
  13. datalab/data/icons/create/linear_chirp.svg +1 -1
  14. datalab/data/icons/create/logistic.svg +1 -1
  15. datalab/gui/actionhandler.py +16 -2
  16. datalab/gui/h5io.py +25 -0
  17. datalab/gui/macroeditor.py +37 -6
  18. datalab/gui/main.py +62 -5
  19. datalab/gui/newobject.py +7 -0
  20. datalab/gui/objectview.py +18 -3
  21. datalab/gui/panel/base.py +89 -16
  22. datalab/gui/panel/macro.py +26 -0
  23. datalab/gui/plothandler.py +20 -2
  24. datalab/gui/processor/base.py +72 -26
  25. datalab/gui/processor/image.py +6 -2
  26. datalab/gui/processor/signal.py +10 -0
  27. datalab/gui/roieditor.py +2 -2
  28. datalab/locale/fr/LC_MESSAGES/datalab.mo +0 -0
  29. datalab/locale/fr/LC_MESSAGES/datalab.po +3288 -0
  30. datalab/objectmodel.py +1 -1
  31. datalab/tests/features/common/auto_analysis_recompute_unit_test.py +81 -0
  32. datalab/tests/features/common/coordutils_unit_test.py +212 -0
  33. datalab/tests/features/common/result_deletion_unit_test.py +121 -1
  34. datalab/tests/features/common/roi_plotitem_unit_test.py +4 -2
  35. datalab/tests/features/common/update_tree_robustness_test.py +65 -0
  36. datalab/tests/features/control/remoteclient_unit.py +10 -0
  37. datalab/tests/features/hdf5/h5workspace_unit_test.py +133 -0
  38. datalab/tests/features/image/roigrid_unit_test.py +75 -0
  39. datalab/tests/features/macro/macroeditor_unit_test.py +104 -3
  40. datalab/tests/features/signal/custom_signal_bug_unit_test.py +96 -0
  41. datalab/widgets/imagebackground.py +13 -4
  42. datalab/widgets/instconfviewer.py +2 -2
  43. datalab/widgets/signalcursor.py +7 -2
  44. datalab/widgets/signaldeltax.py +4 -1
  45. {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/METADATA +3 -3
  46. {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/RECORD +50 -43
  47. {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/WHEEL +0 -0
  48. {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/entry_points.txt +0 -0
  49. {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/licenses/LICENSE +0 -0
  50. {datalab_platform-1.0.1.dist-info → datalab_platform-1.0.3.dist-info}/top_level.txt +0 -0
datalab/__init__.py CHANGED
@@ -24,7 +24,7 @@ except RuntimeError:
24
24
  # this module is imported more than once, e.g. when running tests)
25
25
  pass
26
26
 
27
- __version__ = "1.0.1"
27
+ __version__ = "1.0.3"
28
28
  __docurl__ = __homeurl__ = "https://datalab-platform.com/"
29
29
  __supporturl__ = "https://github.com/DataLab-Platform/DataLab/issues/new/choose"
30
30
 
@@ -88,7 +88,7 @@ class ResultData:
88
88
  if "roi_index" in df.columns:
89
89
  i_roi = int(df.iloc[i_row_res]["roi_index"])
90
90
  roititle = ""
91
- if i_roi >= 0:
91
+ if i_roi >= 0 and obj.roi is not None:
92
92
  roititle = obj.roi.get_single_roi_title(i_roi)
93
93
  ylabel += f"|{roititle}"
94
94
  self.ylabels.append(ylabel)
@@ -365,7 +365,7 @@ def resultadapter_to_html(
365
365
  num_cols = max_display_cols
366
366
 
367
367
  # Calculate number of cells (rows × columns)
368
- num_rows = len(adapter.result)
368
+ num_rows = len(df)
369
369
  num_cells = num_rows * num_cols
370
370
 
371
371
  # Check if truncation is needed BEFORE calling to_html()
@@ -39,11 +39,13 @@ def plotitem_to_singleroi(
39
39
  | AnnotatedRectangle
40
40
  | AnnotatedCircle
41
41
  | AnnotatedPolygon,
42
+ obj: SignalObj | ImageObj | None = None,
42
43
  ) -> SegmentROI | RectangularROI | CircularROI | PolygonalROI:
43
44
  """Create a single ROI from the given PlotPy item to integrate with DataLab
44
45
 
45
46
  Args:
46
47
  plot_item: The PlotPy item for which to create a single ROI
48
+ obj: Optional signal or image object for coordinate rounding
47
49
 
48
50
  Returns:
49
51
  A single ROI instance
@@ -66,7 +68,7 @@ def plotitem_to_singleroi(
66
68
  adapter = PolygonalROIPlotPyAdapter
67
69
  else:
68
70
  raise TypeError(f"Unsupported PlotPy item type: {type(plot_item)}")
69
- return adapter.from_plot_item(plot_item)
71
+ return adapter.from_plot_item(plot_item, obj)
70
72
 
71
73
 
72
74
  def singleroi_to_plotitem(
@@ -0,0 +1,157 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ ROI Coordinate Utilities
5
+ =========================
6
+
7
+ This module provides utility functions for rounding ROI coordinates to appropriate
8
+ precision based on the sampling characteristics of signals and images.
9
+
10
+ These functions are used when converting interactive PlotPy shapes to ROI objects
11
+ to ensure coordinates are displayed with reasonable precision.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import numpy as np
17
+ from sigima.objects import ImageObj, ROI1DParam, ROI2DParam, SignalObj
18
+
19
+
20
+ def round_signal_coords(
21
+ obj: SignalObj, coords: list[float], precision_factor: float = 0.1
22
+ ) -> list[float]:
23
+ """Round signal coordinates to appropriate precision based on sampling period.
24
+
25
+ Rounds to a fraction of the median sampling period to avoid excessive decimal
26
+ places while maintaining reasonable precision.
27
+
28
+ Args:
29
+ obj: signal object
30
+ coords: coordinates to round
31
+ precision_factor: fraction of sampling period to use as rounding precision.
32
+ Default is 0.1 (1/10th of sampling period).
33
+
34
+ Returns:
35
+ Rounded coordinates
36
+ """
37
+ if len(obj.x) < 2:
38
+ # Cannot compute sampling period, return coords as-is
39
+ return coords
40
+ # Compute median sampling period
41
+ sampling_period = float(np.median(np.diff(obj.x)))
42
+ if sampling_period == 0:
43
+ # Avoid division by zero for constant x arrays
44
+ return coords
45
+ # Round to specified fraction of sampling period
46
+ precision = sampling_period * precision_factor
47
+ # Determine number of decimal places
48
+ if precision > 0:
49
+ decimals = max(0, int(-np.floor(np.log10(precision))))
50
+ return [round(c, decimals) for c in coords]
51
+ return coords
52
+
53
+
54
+ def round_image_coords(
55
+ obj: ImageObj, coords: list[float], precision_factor: float = 0.1
56
+ ) -> list[float]:
57
+ """Round image coordinates to appropriate precision based on pixel spacing.
58
+
59
+ Rounds to a fraction of the pixel spacing to avoid excessive decimal places
60
+ while maintaining reasonable precision. Uses separate precision for X and Y.
61
+
62
+ Args:
63
+ obj: image object
64
+ coords: flat list of coordinates [x0, y0, x1, y1, ...] to round
65
+ precision_factor: fraction of pixel spacing to use as rounding precision.
66
+ Default is 0.1 (1/10th of pixel spacing).
67
+
68
+ Returns:
69
+ Rounded coordinates
70
+
71
+ Raises:
72
+ ValueError: if coords does not contain an even number of elements
73
+ """
74
+ if len(coords) % 2 != 0:
75
+ raise ValueError("coords must contain an even number of elements (x, y pairs).")
76
+ if len(coords) == 0:
77
+ return coords
78
+
79
+ rounded = list(coords)
80
+ if obj.is_uniform_coords:
81
+ # Use dx, dy for uniform coordinates
82
+ precision_x = abs(obj.dx) * precision_factor
83
+ precision_y = abs(obj.dy) * precision_factor
84
+ else:
85
+ # Compute average spacing for non-uniform coordinates
86
+ if len(obj.xcoords) > 1:
87
+ avg_dx = float(np.mean(np.abs(np.diff(obj.xcoords))))
88
+ precision_x = avg_dx * precision_factor
89
+ else:
90
+ precision_x = 0
91
+ if len(obj.ycoords) > 1:
92
+ avg_dy = float(np.mean(np.abs(np.diff(obj.ycoords))))
93
+ precision_y = avg_dy * precision_factor
94
+ else:
95
+ precision_y = 0
96
+
97
+ # Round X coordinates (even indices)
98
+ if precision_x > 0:
99
+ decimals_x = max(0, int(-np.floor(np.log10(precision_x))))
100
+ for i in range(0, len(rounded), 2):
101
+ rounded[i] = round(rounded[i], decimals_x)
102
+
103
+ # Round Y coordinates (odd indices)
104
+ if precision_y > 0:
105
+ decimals_y = max(0, int(-np.floor(np.log10(precision_y))))
106
+ for i in range(1, len(rounded), 2):
107
+ rounded[i] = round(rounded[i], decimals_y)
108
+
109
+ return rounded
110
+
111
+
112
+ def round_signal_roi_param(
113
+ obj: SignalObj, param: ROI1DParam, precision_factor: float = 0.1
114
+ ) -> None:
115
+ """Round signal ROI parameter coordinates in-place.
116
+
117
+ Args:
118
+ obj: signal object
119
+ param: ROI parameter to round (modified in-place)
120
+ precision_factor: fraction of sampling period to use as rounding precision
121
+ """
122
+ coords = round_signal_coords(obj, [param.xmin, param.xmax], precision_factor)
123
+ param.xmin, param.xmax = coords
124
+
125
+
126
+ def round_image_roi_param(
127
+ obj: ImageObj, param: ROI2DParam, precision_factor: float = 0.1
128
+ ) -> None:
129
+ """Round image ROI parameter coordinates in-place.
130
+
131
+ Args:
132
+ obj: image object
133
+ param: ROI parameter to round (modified in-place)
134
+ precision_factor: fraction of pixel spacing to use as rounding precision
135
+ """
136
+ if param.geometry == "rectangle":
137
+ # Round x0, y0, dx, dy
138
+ x0, y0, x1, y1 = param.x0, param.y0, param.x0 + param.dx, param.y0 + param.dy
139
+ coords = round_image_coords(obj, [x0, y0, x1, y1], precision_factor)
140
+ param.x0, param.y0 = coords[0], coords[1]
141
+ # Round dx and dy to avoid floating-point errors in subtraction
142
+ dx_dy_rounded = round_image_coords(
143
+ obj, [coords[2] - coords[0], coords[3] - coords[1]], precision_factor
144
+ )
145
+ param.dx = dx_dy_rounded[0]
146
+ param.dy = dx_dy_rounded[1]
147
+ elif param.geometry == "circle":
148
+ # Round xc, yc, r
149
+ coords = round_image_coords(obj, [param.xc, param.yc], precision_factor)
150
+ param.xc, param.yc = coords
151
+ # Round radius using X precision
152
+ r_rounded = round_image_coords(obj, [param.r, 0], precision_factor)[0]
153
+ param.r = r_rounded
154
+ elif param.geometry == "polygon":
155
+ # Round polygon points
156
+ rounded = round_image_coords(obj, param.points.tolist(), precision_factor)
157
+ param.points = np.array(rounded)
@@ -13,6 +13,7 @@ from plotpy.items import AnnotatedCircle, AnnotatedPolygon, AnnotatedRectangle
13
13
  from sigima.objects import CircularROI, ImageObj, ImageROI, PolygonalROI, RectangularROI
14
14
  from sigima.tools import coordinates
15
15
 
16
+ from datalab.adapters_plotpy.coordutils import round_image_coords
16
17
  from datalab.adapters_plotpy.roi.base import (
17
18
  BaseROIPlotPyAdapter,
18
19
  BaseSingleROIPlotPyAdapter,
@@ -64,14 +65,21 @@ class PolygonalROIPlotPyAdapter(
64
65
  return item
65
66
 
66
67
  @classmethod
67
- def from_plot_item(cls, item: AnnotatedPolygon) -> PolygonalROI:
68
+ def from_plot_item(
69
+ cls, item: AnnotatedPolygon, obj: ImageObj | None = None
70
+ ) -> PolygonalROI:
68
71
  """Create ROI from plot item
69
72
 
70
73
  Args:
71
74
  item: plot item
75
+ obj: image object for coordinate rounding (optional)
72
76
  """
77
+ coords = item.get_points().flatten().tolist()
78
+ # Round coordinates to appropriate precision
79
+ if obj is not None:
80
+ coords = round_image_coords(obj, coords)
73
81
  title = str(item.title().text())
74
- return PolygonalROI(item.get_points().flatten(), False, title)
82
+ return PolygonalROI(coords, False, title)
75
83
 
76
84
 
77
85
  class RectangularROIPlotPyAdapter(
@@ -116,15 +124,22 @@ class RectangularROIPlotPyAdapter(
116
124
  return item
117
125
 
118
126
  @classmethod
119
- def from_plot_item(cls, item: AnnotatedRectangle) -> RectangularROI:
127
+ def from_plot_item(
128
+ cls, item: AnnotatedRectangle, obj: ImageObj | None = None
129
+ ) -> RectangularROI:
120
130
  """Create ROI from plot item
121
131
 
122
132
  Args:
123
133
  item: plot item
134
+ obj: image object for coordinate rounding (optional)
124
135
  """
125
136
  rect = item.get_rect()
137
+ coords = RectangularROI.rect_to_coords(*rect)
138
+ # Round coordinates to appropriate precision
139
+ if obj is not None:
140
+ coords = round_image_coords(obj, coords)
126
141
  title = str(item.title().text())
127
- return RectangularROI(RectangularROI.rect_to_coords(*rect), False, title)
142
+ return RectangularROI(coords, False, title)
128
143
 
129
144
 
130
145
  class CircularROIPlotPyAdapter(
@@ -166,15 +181,29 @@ class CircularROIPlotPyAdapter(
166
181
  return item
167
182
 
168
183
  @classmethod
169
- def from_plot_item(cls, item: AnnotatedCircle) -> CircularROI:
184
+ def from_plot_item(
185
+ cls, item: AnnotatedCircle, obj: ImageObj | None = None
186
+ ) -> CircularROI:
170
187
  """Create ROI from plot item
171
188
 
172
189
  Args:
173
190
  item: plot item
191
+ obj: image object for coordinate rounding (optional)
174
192
  """
175
193
  rect = item.get_rect()
194
+ coords = CircularROI.rect_to_coords(*rect)
195
+ # Round coordinates to appropriate precision
196
+ # For circular ROI: [xc, yc, r] - round center (xc, yc) as pair, then radius
197
+ if obj is not None:
198
+ xc, yc, r = coords
199
+ # Round center coordinates
200
+ xc_rounded, yc_rounded = round_image_coords(obj, [xc, yc])
201
+ # Round radius using average of X and Y precision
202
+ # For radius, we use the X precision (could also average X and Y)
203
+ r_rounded = round_image_coords(obj, [r, 0])[0]
204
+ coords = [xc_rounded, yc_rounded, r_rounded]
176
205
  title = str(item.title().text())
177
- return CircularROI(CircularROI.rect_to_coords(*rect), False, title)
206
+ return CircularROI(coords, False, title)
178
207
 
179
208
 
180
209
  class ImageROIPlotPyAdapter(BaseROIPlotPyAdapter[ImageROI]):
@@ -11,6 +11,7 @@ from plotpy.builder import make
11
11
  from plotpy.items import AnnotatedXRange
12
12
  from sigima.objects import SegmentROI, SignalObj, SignalROI
13
13
 
14
+ from datalab.adapters_plotpy.coordutils import round_signal_coords
14
15
  from datalab.adapters_plotpy.roi.base import (
15
16
  BaseROIPlotPyAdapter,
16
17
  BaseSingleROIPlotPyAdapter,
@@ -36,11 +37,14 @@ class SegmentROIPlotPyAdapter(BaseSingleROIPlotPyAdapter[SegmentROI, AnnotatedXR
36
37
  return item
37
38
 
38
39
  @classmethod
39
- def from_plot_item(cls, item: AnnotatedXRange) -> SegmentROI:
40
+ def from_plot_item(
41
+ cls, item: AnnotatedXRange, obj: SignalObj | None = None
42
+ ) -> SegmentROI:
40
43
  """Create ROI from plot item
41
44
 
42
45
  Args:
43
46
  item: plot item
47
+ obj: signal object for coordinate rounding (optional)
44
48
 
45
49
  Returns:
46
50
  ROI
@@ -48,6 +52,9 @@ class SegmentROIPlotPyAdapter(BaseSingleROIPlotPyAdapter[SegmentROI, AnnotatedXR
48
52
  if not isinstance(item, AnnotatedXRange):
49
53
  raise TypeError("Invalid plot item type")
50
54
  coords = sorted(item.get_range())
55
+ # Round coordinates to appropriate precision
56
+ if obj is not None:
57
+ coords = round_signal_coords(obj, coords)
51
58
  title = str(item.title().text())
52
59
  return SegmentROI(coords, False, title)
53
60
 
datalab/config.py CHANGED
@@ -301,6 +301,7 @@ class ViewSection(conf.Section, metaclass=conf.SectionMeta):
301
301
  ima_def_interpolation = conf.Option()
302
302
  ima_def_alpha = conf.Option()
303
303
  ima_def_alpha_function = conf.Option()
304
+ ima_def_keep_lut_range = conf.Option()
304
305
 
305
306
  # Annotated shape and marker visualization settings for signals
306
307
  sig_shape_param = conf.DataSetOption()
@@ -459,6 +460,7 @@ def initialize():
459
460
  Conf.view.ima_def_interpolation.get(5)
460
461
  Conf.view.ima_def_alpha.get(1.0)
461
462
  Conf.view.ima_def_alpha_function.get(LUTAlpha.NONE.value)
463
+ Conf.view.ima_def_keep_lut_range.get(False)
462
464
 
463
465
  # Datetime format strings: % must be escaped as %% for ConfigParser
464
466
  Conf.view.sig_datetime_format_s.get("%%H:%%M:%%S")
@@ -485,6 +487,8 @@ initialize()
485
487
 
486
488
  ROI_LINE_COLOR = "#5555ff"
487
489
  ROI_SEL_LINE_COLOR = "#9393ff"
490
+ MARKER_LINE_COLOR = "#A11818"
491
+ MARKER_TEXT_COLOR = "#440909"
488
492
 
489
493
  PLOTPY_DEFAULTS = {
490
494
  "plot": {
@@ -506,11 +510,27 @@ PLOTPY_DEFAULTS = {
506
510
  "selected_curve_symbol/alpha": 0.3,
507
511
  "selected_curve_symbol/size": 5,
508
512
  "marker/curve/text/textcolor": "black",
509
- "marker/cross/text/textcolor": "black",
513
+ # Cross marker style (shown when pressing Alt key on plot)
514
+ "marker/cross/symbol/marker": "Cross",
515
+ "marker/cross/symbol/edgecolor": MAIN_FG_COLOR,
516
+ "marker/cross/symbol/facecolor": "#ff0000",
517
+ "marker/cross/symbol/alpha": 1.0,
518
+ "marker/cross/symbol/size": 8,
519
+ "marker/cross/text/font/family": "default",
520
+ "marker/cross/text/font/size": 8,
521
+ "marker/cross/text/font/bold": False,
522
+ "marker/cross/text/font/italic": False,
523
+ "marker/cross/text/textcolor": "#000000",
524
+ "marker/cross/text/background_color": "#ffffff",
510
525
  "marker/cross/text/background_alpha": 0.7,
526
+ "marker/cross/line/style": "DashLine",
527
+ "marker/cross/line/color": MARKER_LINE_COLOR,
528
+ "marker/cross/line/width": 1.0,
529
+ "marker/cross/markerstyle": "Cross",
530
+ "marker/cross/spacing": 7,
511
531
  # Cursor line and symbol style
512
532
  "marker/cursor/line/style": "SolidLine",
513
- "marker/cursor/line/color": "#A11818",
533
+ "marker/cursor/line/color": MARKER_LINE_COLOR,
514
534
  "marker/cursor/line/width": 1.0,
515
535
  "marker/cursor/symbol/marker": "NoSymbol",
516
536
  "marker/cursor/symbol/size": 11,
@@ -518,41 +538,83 @@ PLOTPY_DEFAULTS = {
518
538
  "marker/cursor/symbol/facecolor": "#ff9393",
519
539
  "marker/cursor/symbol/alpha": 1.0,
520
540
  "marker/cursor/sel_line/style": "SolidLine",
521
- "marker/cursor/sel_line/color": "#A11818",
541
+ "marker/cursor/sel_line/color": MARKER_LINE_COLOR,
522
542
  "marker/cursor/sel_line/width": 2.0,
523
543
  "marker/cursor/sel_symbol/marker": "NoSymbol",
524
544
  "marker/cursor/sel_symbol/size": 11,
525
545
  "marker/cursor/sel_symbol/edgecolor": MAIN_BG_COLOR,
526
- "marker/cursor/sel_symbol/facecolor": "#A11818",
546
+ "marker/cursor/sel_symbol/facecolor": MARKER_LINE_COLOR,
527
547
  "marker/cursor/sel_symbol/alpha": 0.8,
528
548
  "marker/cursor/text/font/size": 9,
529
549
  "marker/cursor/text/font/family": "default",
530
550
  "marker/cursor/text/font/bold": False,
531
551
  "marker/cursor/text/font/italic": False,
532
- "marker/cursor/text/textcolor": "#440909",
552
+ "marker/cursor/text/textcolor": MARKER_TEXT_COLOR,
533
553
  "marker/cursor/text/background_color": "#ffffff",
534
554
  "marker/cursor/text/background_alpha": 0.7,
535
555
  "marker/cursor/sel_text/font/size": 9,
536
556
  "marker/cursor/sel_text/font/family": "default",
537
557
  "marker/cursor/sel_text/font/bold": False,
538
558
  "marker/cursor/sel_text/font/italic": False,
539
- "marker/cursor/sel_text/textcolor": "#440909",
559
+ "marker/cursor/sel_text/textcolor": MARKER_TEXT_COLOR,
540
560
  "marker/cursor/sel_text/background_color": "#ffffff",
541
561
  "marker/cursor/sel_text/background_alpha": 0.7,
562
+ # Default annotation text style for segments:
563
+ "shape/segment/line/style": "SolidLine",
564
+ "shape/segment/line/color": "#00ff55",
565
+ "shape/segment/line/width": 1.0,
566
+ "shape/segment/sel_line/style": "SolidLine",
567
+ "shape/segment/sel_line/color": "#00ff55",
568
+ "shape/segment/sel_line/width": 2.0,
569
+ "shape/segment/fill/style": "NoBrush",
570
+ "shape/segment/sel_fill/style": "NoBrush",
571
+ "shape/segment/symbol/marker": "XCross",
572
+ "shape/segment/symbol/size": 9,
573
+ "shape/segment/symbol/edgecolor": "#00ff55",
574
+ "shape/segment/symbol/facecolor": "#00ff55",
575
+ "shape/segment/symbol/alpha": 1.0,
576
+ "shape/segment/sel_symbol/marker": "XCross",
577
+ "shape/segment/sel_symbol/size": 12,
578
+ "shape/segment/sel_symbol/edgecolor": "#00ff55",
579
+ "shape/segment/sel_symbol/facecolor": "#00ff55",
580
+ "shape/segment/sel_symbol/alpha": 0.7,
581
+ # Default style for drag shapes: (global annotations style)
582
+ "shape/drag/line/style": "SolidLine",
583
+ "shape/drag/line/color": "#00ff55",
584
+ "shape/drag/line/width": 1.0,
585
+ "shape/drag/fill/style": "SolidPattern",
586
+ "shape/drag/fill/color": MAIN_BG_COLOR,
587
+ "shape/drag/fill/alpha": 0.1,
588
+ "shape/drag/symbol/marker": "Rect",
589
+ "shape/drag/symbol/size": 3,
590
+ "shape/drag/symbol/edgecolor": "#00ff55",
591
+ "shape/drag/symbol/facecolor": "#00ff55",
592
+ "shape/drag/symbol/alpha": 1.0,
593
+ "shape/drag/sel_line/style": "SolidLine",
594
+ "shape/drag/sel_line/color": "#00ff55",
595
+ "shape/drag/sel_line/width": 2.0,
596
+ "shape/drag/sel_fill/style": "SolidPattern",
597
+ "shape/drag/sel_fill/color": MAIN_BG_COLOR,
598
+ "shape/drag/sel_fill/alpha": 0.1,
599
+ "shape/drag/sel_symbol/marker": "Rect",
600
+ "shape/drag/sel_symbol/size": 7,
601
+ "shape/drag/sel_symbol/edgecolor": "#00ff55",
602
+ "shape/drag/sel_symbol/facecolor": "#00ff00",
603
+ "shape/drag/sel_symbol/alpha": 0.7,
542
604
  },
543
605
  "results": {
544
606
  # Annotated shape style for result shapes:
545
607
  # Signals:
546
608
  "s/annotation/line/style": "SolidLine",
547
- "s/annotation/line/color": MAIN_FG_COLOR,
548
- "s/annotation/line/width": 1,
549
- "s/annotation/fill/style": "SolidPattern",
609
+ "s/annotation/line/color": "#00aa00",
610
+ "s/annotation/line/width": 2,
611
+ "s/annotation/fill/style": "NoBrush",
550
612
  "s/annotation/fill/color": MAIN_BG_COLOR,
551
613
  "s/annotation/fill/alpha": 0.1,
552
614
  "s/annotation/symbol/marker": "XCross",
553
615
  "s/annotation/symbol/size": 7,
554
- "s/annotation/symbol/edgecolor": MAIN_FG_COLOR,
555
- "s/annotation/symbol/facecolor": MAIN_FG_COLOR,
616
+ "s/annotation/symbol/edgecolor": "#00aa00",
617
+ "s/annotation/symbol/facecolor": "#00aa00",
556
618
  "s/annotation/symbol/alpha": 1.0,
557
619
  "s/annotation/sel_line/style": "DashLine",
558
620
  "s/annotation/sel_line/color": "#00ff00",
@@ -591,65 +653,65 @@ PLOTPY_DEFAULTS = {
591
653
  # Marker styles for results:
592
654
  # Signals:
593
655
  "s/marker/cursor/line/style": "DashLine",
594
- "s/marker/cursor/line/color": "#ffff00",
656
+ "s/marker/cursor/line/color": MARKER_LINE_COLOR,
595
657
  "s/marker/cursor/line/width": 1.0,
596
658
  "s/marker/cursor/symbol/marker": "Ellipse",
597
659
  "s/marker/cursor/symbol/size": 11,
598
660
  "s/marker/cursor/symbol/edgecolor": MAIN_BG_COLOR,
599
- "s/marker/cursor/symbol/facecolor": "#ffff00",
661
+ "s/marker/cursor/symbol/facecolor": MARKER_LINE_COLOR,
600
662
  "s/marker/cursor/symbol/alpha": 0.7,
601
663
  "s/marker/cursor/sel_line/style": "DashLine",
602
- "s/marker/cursor/sel_line/color": "#ffff00",
664
+ "s/marker/cursor/sel_line/color": MARKER_LINE_COLOR,
603
665
  "s/marker/cursor/sel_line/width": 2.0,
604
666
  "s/marker/cursor/sel_symbol/marker": "Ellipse",
605
667
  "s/marker/cursor/sel_symbol/size": 11,
606
- "s/marker/cursor/sel_symbol/edgecolor": "#ffff00",
607
- "s/marker/cursor/sel_symbol/facecolor": "#ffff00",
668
+ "s/marker/cursor/sel_symbol/edgecolor": MARKER_LINE_COLOR,
669
+ "s/marker/cursor/sel_symbol/facecolor": MARKER_LINE_COLOR,
608
670
  "s/marker/cursor/sel_symbol/alpha": 0.7,
609
671
  "s/marker/cursor/text/font/size": 9,
610
672
  "s/marker/cursor/text/font/family": "default",
611
673
  "s/marker/cursor/text/font/bold": False,
612
674
  "s/marker/cursor/text/font/italic": False,
613
- "s/marker/cursor/text/textcolor": "#440909",
675
+ "s/marker/cursor/text/textcolor": MARKER_TEXT_COLOR,
614
676
  "s/marker/cursor/text/background_color": "#ffffff",
615
677
  "s/marker/cursor/text/background_alpha": 0.7,
616
678
  "s/marker/cursor/sel_text/font/size": 9,
617
679
  "s/marker/cursor/sel_text/font/family": "default",
618
680
  "s/marker/cursor/sel_text/font/bold": False,
619
681
  "s/marker/cursor/sel_text/font/italic": False,
620
- "s/marker/cursor/sel_text/textcolor": "#440909",
682
+ "s/marker/cursor/sel_text/textcolor": MARKER_TEXT_COLOR,
621
683
  "s/marker/cursor/sel_text/background_color": "#ffffff",
622
684
  "s/marker/cursor/sel_text/background_alpha": 0.7,
623
685
  "s/marker/cursor/markerstyle": "Cross",
624
686
  # Images:
625
687
  "i/marker/cursor/line/style": "DashLine",
626
- "i/marker/cursor/line/color": "#ffff00",
688
+ "i/marker/cursor/line/color": MARKER_LINE_COLOR,
627
689
  "i/marker/cursor/line/width": 1.0,
628
690
  "i/marker/cursor/symbol/marker": "Diamond",
629
691
  "i/marker/cursor/symbol/size": 11,
630
- "i/marker/cursor/symbol/edgecolor": "#ffff00",
631
- "i/marker/cursor/symbol/facecolor": "#ffff00",
692
+ "i/marker/cursor/symbol/edgecolor": MARKER_LINE_COLOR,
693
+ "i/marker/cursor/symbol/facecolor": MARKER_LINE_COLOR,
632
694
  "i/marker/cursor/symbol/alpha": 0.7,
633
695
  "i/marker/cursor/sel_line/style": "DashLine",
634
- "i/marker/cursor/sel_line/color": "#ffff00",
696
+ "i/marker/cursor/sel_line/color": MARKER_LINE_COLOR,
635
697
  "i/marker/cursor/sel_line/width": 2.0,
636
698
  "i/marker/cursor/sel_symbol/marker": "Diamond",
637
699
  "i/marker/cursor/sel_symbol/size": 11,
638
- "i/marker/cursor/sel_symbol/edgecolor": "#ffff00",
639
- "i/marker/cursor/sel_symbol/facecolor": "#ffff00",
700
+ "i/marker/cursor/sel_symbol/edgecolor": MARKER_LINE_COLOR,
701
+ "i/marker/cursor/sel_symbol/facecolor": MARKER_LINE_COLOR,
640
702
  "i/marker/cursor/sel_symbol/alpha": 0.7,
641
703
  "i/marker/cursor/text/font/size": 9,
642
704
  "i/marker/cursor/text/font/family": "default",
643
705
  "i/marker/cursor/text/font/bold": False,
644
706
  "i/marker/cursor/text/font/italic": False,
645
- "i/marker/cursor/text/textcolor": "#440909",
707
+ "i/marker/cursor/text/textcolor": MARKER_TEXT_COLOR,
646
708
  "i/marker/cursor/text/background_color": "#ffffff",
647
709
  "i/marker/cursor/text/background_alpha": 0.7,
648
710
  "i/marker/cursor/sel_text/font/size": 9,
649
711
  "i/marker/cursor/sel_text/font/family": "default",
650
712
  "i/marker/cursor/sel_text/font/bold": False,
651
713
  "i/marker/cursor/sel_text/font/italic": False,
652
- "i/marker/cursor/sel_text/textcolor": "#440909",
714
+ "i/marker/cursor/sel_text/textcolor": MARKER_TEXT_COLOR,
653
715
  "i/marker/cursor/sel_text/background_color": "#ffffff",
654
716
  "i/marker/cursor/sel_text/background_alpha": 0.7,
655
717
  "i/marker/cursor/markerstyle": "Cross",
@@ -198,6 +198,41 @@ class AbstractDLControl(abc.ABC):
198
198
  reset_all: Reset all application data. Defaults to None.
199
199
  """
200
200
 
201
+ @abc.abstractmethod
202
+ def load_h5_workspace(self, h5files: list[str], reset_all: bool = False) -> None:
203
+ """Load native DataLab HDF5 workspace files without any GUI elements.
204
+
205
+ This method can be safely called from scripts (e.g., internal console,
206
+ macros) as it does not create any Qt widgets, dialogs, or progress bars.
207
+
208
+ .. warning::
209
+
210
+ This method only supports native DataLab HDF5 files. For importing
211
+ arbitrary HDF5 files (non-native), use :meth:`open_h5_files` or
212
+ :meth:`import_h5_file` instead.
213
+
214
+ Args:
215
+ h5files: List of native DataLab HDF5 filenames
216
+ reset_all: Reset all application data before importing. Defaults to False.
217
+
218
+ Raises:
219
+ ValueError: If a file is not a valid native DataLab HDF5 file
220
+ """
221
+
222
+ @abc.abstractmethod
223
+ def save_h5_workspace(self, filename: str) -> None:
224
+ """Save current workspace to a native DataLab HDF5 file without GUI elements.
225
+
226
+ This method can be safely called from scripts (e.g., internal console,
227
+ macros) as it does not create any Qt widgets, dialogs, or progress bars.
228
+
229
+ Args:
230
+ filename: HDF5 filename to save to
231
+
232
+ Raises:
233
+ IOError: If file cannot be saved
234
+ """
235
+
201
236
  @abc.abstractmethod
202
237
  def load_from_files(self, filenames: list[str]) -> None:
203
238
  """Open objects from files in current panel (signals/images).
@@ -607,6 +642,41 @@ class BaseProxy(AbstractDLControl, metaclass=abc.ABCMeta):
607
642
  """
608
643
  self._datalab.import_h5_file(filename, reset_all)
609
644
 
645
+ def load_h5_workspace(self, h5files: list[str], reset_all: bool = False) -> None:
646
+ """Load native DataLab HDF5 workspace files without any GUI elements.
647
+
648
+ This method can be safely called from scripts (e.g., internal console,
649
+ macros) as it does not create any Qt widgets, dialogs, or progress bars.
650
+
651
+ .. warning::
652
+
653
+ This method only supports native DataLab HDF5 files. For importing
654
+ arbitrary HDF5 files (non-native), use :meth:`open_h5_files` or
655
+ :meth:`import_h5_file` instead.
656
+
657
+ Args:
658
+ h5files: List of native DataLab HDF5 filenames
659
+ reset_all: Reset all application data before importing. Defaults to False.
660
+
661
+ Raises:
662
+ ValueError: If a file is not a valid native DataLab HDF5 file
663
+ """
664
+ self._datalab.load_h5_workspace(h5files, reset_all)
665
+
666
+ def save_h5_workspace(self, filename: str) -> None:
667
+ """Save current workspace to a native DataLab HDF5 file without GUI elements.
668
+
669
+ This method can be safely called from scripts (e.g., internal console,
670
+ macros) as it does not create any Qt widgets, dialogs, or progress bars.
671
+
672
+ Args:
673
+ filename: HDF5 filename to save to
674
+
675
+ Raises:
676
+ IOError: If file cannot be saved
677
+ """
678
+ self._datalab.save_h5_workspace(filename)
679
+
610
680
  def load_from_files(self, filenames: list[str]) -> None:
611
681
  """Open objects from files in current panel (signals/images).
612
682