datalab-platform 1.0.2__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 (41) hide show
  1. datalab/__init__.py +1 -1
  2. datalab/adapters_metadata/common.py +2 -2
  3. datalab/config.py +86 -26
  4. datalab/control/baseproxy.py +70 -0
  5. datalab/control/proxy.py +33 -0
  6. datalab/control/remote.py +35 -0
  7. datalab/data/doc/DataLab_en.pdf +0 -0
  8. datalab/data/doc/DataLab_fr.pdf +0 -0
  9. datalab/data/icons/create/linear_chirp.svg +1 -1
  10. datalab/data/icons/create/logistic.svg +1 -1
  11. datalab/gui/actionhandler.py +13 -0
  12. datalab/gui/h5io.py +25 -0
  13. datalab/gui/macroeditor.py +19 -5
  14. datalab/gui/main.py +60 -5
  15. datalab/gui/objectview.py +18 -3
  16. datalab/gui/panel/base.py +24 -18
  17. datalab/gui/panel/macro.py +26 -0
  18. datalab/gui/plothandler.py +10 -1
  19. datalab/gui/processor/base.py +43 -10
  20. datalab/gui/processor/image.py +6 -2
  21. datalab/locale/fr/LC_MESSAGES/datalab.mo +0 -0
  22. datalab/locale/fr/LC_MESSAGES/datalab.po +3288 -0
  23. datalab/objectmodel.py +1 -1
  24. datalab/tests/features/common/auto_analysis_recompute_unit_test.py +81 -0
  25. datalab/tests/features/common/coordutils_unit_test.py +1 -1
  26. datalab/tests/features/common/result_deletion_unit_test.py +121 -1
  27. datalab/tests/features/common/update_tree_robustness_test.py +65 -0
  28. datalab/tests/features/control/remoteclient_unit.py +10 -0
  29. datalab/tests/features/hdf5/h5workspace_unit_test.py +133 -0
  30. datalab/tests/features/image/roigrid_unit_test.py +75 -0
  31. datalab/tests/features/macro/macroeditor_unit_test.py +2 -2
  32. datalab/widgets/imagebackground.py +13 -4
  33. datalab/widgets/instconfviewer.py +2 -2
  34. datalab/widgets/signalcursor.py +7 -2
  35. datalab/widgets/signaldeltax.py +4 -1
  36. {datalab_platform-1.0.2.dist-info → datalab_platform-1.0.3.dist-info}/METADATA +2 -2
  37. {datalab_platform-1.0.2.dist-info → datalab_platform-1.0.3.dist-info}/RECORD +41 -37
  38. {datalab_platform-1.0.2.dist-info → datalab_platform-1.0.3.dist-info}/WHEEL +0 -0
  39. {datalab_platform-1.0.2.dist-info → datalab_platform-1.0.3.dist-info}/entry_points.txt +0 -0
  40. {datalab_platform-1.0.2.dist-info → datalab_platform-1.0.3.dist-info}/licenses/LICENSE +0 -0
  41. {datalab_platform-1.0.2.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.2"
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()
datalab/config.py CHANGED
@@ -487,6 +487,8 @@ initialize()
487
487
 
488
488
  ROI_LINE_COLOR = "#5555ff"
489
489
  ROI_SEL_LINE_COLOR = "#9393ff"
490
+ MARKER_LINE_COLOR = "#A11818"
491
+ MARKER_TEXT_COLOR = "#440909"
490
492
 
491
493
  PLOTPY_DEFAULTS = {
492
494
  "plot": {
@@ -508,11 +510,27 @@ PLOTPY_DEFAULTS = {
508
510
  "selected_curve_symbol/alpha": 0.3,
509
511
  "selected_curve_symbol/size": 5,
510
512
  "marker/curve/text/textcolor": "black",
511
- "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",
512
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,
513
531
  # Cursor line and symbol style
514
532
  "marker/cursor/line/style": "SolidLine",
515
- "marker/cursor/line/color": "#A11818",
533
+ "marker/cursor/line/color": MARKER_LINE_COLOR,
516
534
  "marker/cursor/line/width": 1.0,
517
535
  "marker/cursor/symbol/marker": "NoSymbol",
518
536
  "marker/cursor/symbol/size": 11,
@@ -520,41 +538,83 @@ PLOTPY_DEFAULTS = {
520
538
  "marker/cursor/symbol/facecolor": "#ff9393",
521
539
  "marker/cursor/symbol/alpha": 1.0,
522
540
  "marker/cursor/sel_line/style": "SolidLine",
523
- "marker/cursor/sel_line/color": "#A11818",
541
+ "marker/cursor/sel_line/color": MARKER_LINE_COLOR,
524
542
  "marker/cursor/sel_line/width": 2.0,
525
543
  "marker/cursor/sel_symbol/marker": "NoSymbol",
526
544
  "marker/cursor/sel_symbol/size": 11,
527
545
  "marker/cursor/sel_symbol/edgecolor": MAIN_BG_COLOR,
528
- "marker/cursor/sel_symbol/facecolor": "#A11818",
546
+ "marker/cursor/sel_symbol/facecolor": MARKER_LINE_COLOR,
529
547
  "marker/cursor/sel_symbol/alpha": 0.8,
530
548
  "marker/cursor/text/font/size": 9,
531
549
  "marker/cursor/text/font/family": "default",
532
550
  "marker/cursor/text/font/bold": False,
533
551
  "marker/cursor/text/font/italic": False,
534
- "marker/cursor/text/textcolor": "#440909",
552
+ "marker/cursor/text/textcolor": MARKER_TEXT_COLOR,
535
553
  "marker/cursor/text/background_color": "#ffffff",
536
554
  "marker/cursor/text/background_alpha": 0.7,
537
555
  "marker/cursor/sel_text/font/size": 9,
538
556
  "marker/cursor/sel_text/font/family": "default",
539
557
  "marker/cursor/sel_text/font/bold": False,
540
558
  "marker/cursor/sel_text/font/italic": False,
541
- "marker/cursor/sel_text/textcolor": "#440909",
559
+ "marker/cursor/sel_text/textcolor": MARKER_TEXT_COLOR,
542
560
  "marker/cursor/sel_text/background_color": "#ffffff",
543
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,
544
604
  },
545
605
  "results": {
546
606
  # Annotated shape style for result shapes:
547
607
  # Signals:
548
608
  "s/annotation/line/style": "SolidLine",
549
- "s/annotation/line/color": MAIN_FG_COLOR,
550
- "s/annotation/line/width": 1,
551
- "s/annotation/fill/style": "SolidPattern",
609
+ "s/annotation/line/color": "#00aa00",
610
+ "s/annotation/line/width": 2,
611
+ "s/annotation/fill/style": "NoBrush",
552
612
  "s/annotation/fill/color": MAIN_BG_COLOR,
553
613
  "s/annotation/fill/alpha": 0.1,
554
614
  "s/annotation/symbol/marker": "XCross",
555
615
  "s/annotation/symbol/size": 7,
556
- "s/annotation/symbol/edgecolor": MAIN_FG_COLOR,
557
- "s/annotation/symbol/facecolor": MAIN_FG_COLOR,
616
+ "s/annotation/symbol/edgecolor": "#00aa00",
617
+ "s/annotation/symbol/facecolor": "#00aa00",
558
618
  "s/annotation/symbol/alpha": 1.0,
559
619
  "s/annotation/sel_line/style": "DashLine",
560
620
  "s/annotation/sel_line/color": "#00ff00",
@@ -593,65 +653,65 @@ PLOTPY_DEFAULTS = {
593
653
  # Marker styles for results:
594
654
  # Signals:
595
655
  "s/marker/cursor/line/style": "DashLine",
596
- "s/marker/cursor/line/color": "#ffff00",
656
+ "s/marker/cursor/line/color": MARKER_LINE_COLOR,
597
657
  "s/marker/cursor/line/width": 1.0,
598
658
  "s/marker/cursor/symbol/marker": "Ellipse",
599
659
  "s/marker/cursor/symbol/size": 11,
600
660
  "s/marker/cursor/symbol/edgecolor": MAIN_BG_COLOR,
601
- "s/marker/cursor/symbol/facecolor": "#ffff00",
661
+ "s/marker/cursor/symbol/facecolor": MARKER_LINE_COLOR,
602
662
  "s/marker/cursor/symbol/alpha": 0.7,
603
663
  "s/marker/cursor/sel_line/style": "DashLine",
604
- "s/marker/cursor/sel_line/color": "#ffff00",
664
+ "s/marker/cursor/sel_line/color": MARKER_LINE_COLOR,
605
665
  "s/marker/cursor/sel_line/width": 2.0,
606
666
  "s/marker/cursor/sel_symbol/marker": "Ellipse",
607
667
  "s/marker/cursor/sel_symbol/size": 11,
608
- "s/marker/cursor/sel_symbol/edgecolor": "#ffff00",
609
- "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,
610
670
  "s/marker/cursor/sel_symbol/alpha": 0.7,
611
671
  "s/marker/cursor/text/font/size": 9,
612
672
  "s/marker/cursor/text/font/family": "default",
613
673
  "s/marker/cursor/text/font/bold": False,
614
674
  "s/marker/cursor/text/font/italic": False,
615
- "s/marker/cursor/text/textcolor": "#440909",
675
+ "s/marker/cursor/text/textcolor": MARKER_TEXT_COLOR,
616
676
  "s/marker/cursor/text/background_color": "#ffffff",
617
677
  "s/marker/cursor/text/background_alpha": 0.7,
618
678
  "s/marker/cursor/sel_text/font/size": 9,
619
679
  "s/marker/cursor/sel_text/font/family": "default",
620
680
  "s/marker/cursor/sel_text/font/bold": False,
621
681
  "s/marker/cursor/sel_text/font/italic": False,
622
- "s/marker/cursor/sel_text/textcolor": "#440909",
682
+ "s/marker/cursor/sel_text/textcolor": MARKER_TEXT_COLOR,
623
683
  "s/marker/cursor/sel_text/background_color": "#ffffff",
624
684
  "s/marker/cursor/sel_text/background_alpha": 0.7,
625
685
  "s/marker/cursor/markerstyle": "Cross",
626
686
  # Images:
627
687
  "i/marker/cursor/line/style": "DashLine",
628
- "i/marker/cursor/line/color": "#ffff00",
688
+ "i/marker/cursor/line/color": MARKER_LINE_COLOR,
629
689
  "i/marker/cursor/line/width": 1.0,
630
690
  "i/marker/cursor/symbol/marker": "Diamond",
631
691
  "i/marker/cursor/symbol/size": 11,
632
- "i/marker/cursor/symbol/edgecolor": "#ffff00",
633
- "i/marker/cursor/symbol/facecolor": "#ffff00",
692
+ "i/marker/cursor/symbol/edgecolor": MARKER_LINE_COLOR,
693
+ "i/marker/cursor/symbol/facecolor": MARKER_LINE_COLOR,
634
694
  "i/marker/cursor/symbol/alpha": 0.7,
635
695
  "i/marker/cursor/sel_line/style": "DashLine",
636
- "i/marker/cursor/sel_line/color": "#ffff00",
696
+ "i/marker/cursor/sel_line/color": MARKER_LINE_COLOR,
637
697
  "i/marker/cursor/sel_line/width": 2.0,
638
698
  "i/marker/cursor/sel_symbol/marker": "Diamond",
639
699
  "i/marker/cursor/sel_symbol/size": 11,
640
- "i/marker/cursor/sel_symbol/edgecolor": "#ffff00",
641
- "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,
642
702
  "i/marker/cursor/sel_symbol/alpha": 0.7,
643
703
  "i/marker/cursor/text/font/size": 9,
644
704
  "i/marker/cursor/text/font/family": "default",
645
705
  "i/marker/cursor/text/font/bold": False,
646
706
  "i/marker/cursor/text/font/italic": False,
647
- "i/marker/cursor/text/textcolor": "#440909",
707
+ "i/marker/cursor/text/textcolor": MARKER_TEXT_COLOR,
648
708
  "i/marker/cursor/text/background_color": "#ffffff",
649
709
  "i/marker/cursor/text/background_alpha": 0.7,
650
710
  "i/marker/cursor/sel_text/font/size": 9,
651
711
  "i/marker/cursor/sel_text/font/family": "default",
652
712
  "i/marker/cursor/sel_text/font/bold": False,
653
713
  "i/marker/cursor/sel_text/font/italic": False,
654
- "i/marker/cursor/sel_text/textcolor": "#440909",
714
+ "i/marker/cursor/sel_text/textcolor": MARKER_TEXT_COLOR,
655
715
  "i/marker/cursor/sel_text/background_color": "#ffffff",
656
716
  "i/marker/cursor/sel_text/background_alpha": 0.7,
657
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
 
datalab/control/proxy.py CHANGED
@@ -300,6 +300,39 @@ class LocalProxy(BaseProxy):
300
300
  """
301
301
  self._datalab.add_annotations_from_items(items, refresh_plot, panel)
302
302
 
303
+ def load_h5_workspace(
304
+ self, h5files: list[str] | str, reset_all: bool = False
305
+ ) -> None:
306
+ """Load HDF5 workspace files without showing file dialog.
307
+
308
+ This method loads one or more DataLab native HDF5 files directly, bypassing
309
+ the file dialog. It is safe to call from the internal console or any context
310
+ where Qt dialogs would cause threading issues.
311
+
312
+ Args:
313
+ h5files: Path(s) to HDF5 file(s). Can be a single path string or a list
314
+ of paths
315
+ reset_all: If True, reset workspace before loading. Defaults to False.
316
+
317
+ Raises:
318
+ ValueError: if file is not a DataLab native HDF5 file
319
+ """
320
+ if isinstance(h5files, str):
321
+ h5files = [h5files]
322
+ self._datalab.load_h5_workspace(h5files, reset_all)
323
+
324
+ def save_h5_workspace(self, filename: str) -> None:
325
+ """Save workspace to HDF5 file without showing file dialog.
326
+
327
+ This method saves the current workspace to a DataLab native HDF5 file
328
+ directly, bypassing the file dialog. It is safe to call from the internal
329
+ console or any context where Qt dialogs would cause threading issues.
330
+
331
+ Args:
332
+ filename: Path to the output HDF5 file
333
+ """
334
+ self._datalab.save_h5_workspace(filename)
335
+
303
336
 
304
337
  @contextmanager
305
338
  def proxy_context(what: str) -> Generator[LocalProxy | RemoteProxy, None, None]:
datalab/control/remote.py CHANGED
@@ -84,6 +84,8 @@ class RemoteServer(QC.QThread):
84
84
  SIG_SAVE_TO_H5 = QC.Signal(str)
85
85
  SIG_OPEN_H5 = QC.Signal(list, bool, bool)
86
86
  SIG_IMPORT_H5 = QC.Signal(str, bool)
87
+ SIG_LOAD_H5_WORKSPACE = QC.Signal(list, bool)
88
+ SIG_SAVE_H5_WORKSPACE = QC.Signal(str)
87
89
  SIG_CALC = QC.Signal(str, object)
88
90
  SIG_RUN_MACRO = QC.Signal(str)
89
91
  SIG_STOP_MACRO = QC.Signal(str)
@@ -114,6 +116,8 @@ class RemoteServer(QC.QThread):
114
116
  self.SIG_SAVE_TO_H5.connect(win.save_to_h5_file)
115
117
  self.SIG_OPEN_H5.connect(win.open_h5_files)
116
118
  self.SIG_IMPORT_H5.connect(win.import_h5_file)
119
+ self.SIG_LOAD_H5_WORKSPACE.connect(win.load_h5_workspace)
120
+ self.SIG_SAVE_H5_WORKSPACE.connect(win.save_h5_workspace)
117
121
  self.SIG_CALC.connect(win.calc)
118
122
  self.SIG_RUN_MACRO.connect(win.run_macro)
119
123
  self.SIG_STOP_MACRO.connect(win.stop_macro)
@@ -268,6 +272,37 @@ class RemoteServer(QC.QThread):
268
272
  reset_all = False if reset_all is None else reset_all
269
273
  self.SIG_IMPORT_H5.emit(filename, reset_all)
270
274
 
275
+ @remote_call
276
+ def load_h5_workspace(self, h5files: list[str], reset_all: bool = False) -> None:
277
+ """Load native DataLab HDF5 workspace files without any GUI elements.
278
+
279
+ This method can be safely called from scripts as it does not create
280
+ any Qt widgets, dialogs, or progress bars.
281
+
282
+ Args:
283
+ h5files: List of native DataLab HDF5 filenames
284
+ reset_all: Reset all application data before importing. Defaults to False.
285
+
286
+ Raises:
287
+ ValueError: If a file is not a valid native DataLab HDF5 file
288
+ """
289
+ self.SIG_LOAD_H5_WORKSPACE.emit(h5files, reset_all)
290
+
291
+ @remote_call
292
+ def save_h5_workspace(self, filename: str) -> None:
293
+ """Save current workspace to a native DataLab HDF5 file without GUI elements.
294
+
295
+ This method can be safely called from scripts as it does not create
296
+ any Qt widgets, dialogs, or progress bars.
297
+
298
+ Args:
299
+ filename: HDF5 filename to save to
300
+
301
+ Raises:
302
+ IOError: If file cannot be saved
303
+ """
304
+ self.SIG_SAVE_H5_WORKSPACE.emit(filename)
305
+
271
306
  @remote_call
272
307
  def load_from_files(self, filenames: list[str]) -> None:
273
308
  """Open objects from files in current panel (signals/images).
Binary file
Binary file
@@ -3,5 +3,5 @@
3
3
  xmlns="http://www.w3.org/2000/svg">
4
4
  <rect style="fill:#696969;fill-opacity:1;stroke-width:0.547874" width="0.53186357" height="12.584304" x="6.7340684" y="0.70784807" />
5
5
  <rect style="fill:#696969;fill-opacity:1;stroke-width:0.54642" width="0.53186357" height="12.5176" x="6.7340684" y="-13.258801" transform="rotate(90)" />
6
- <path style="fill:none;stroke:#009966;stroke-width:0.35;stroke-linejoin:round" d="M 1,7 C 1.5,5.5 2,4.5 2.5,5.5 3,7 3.3,8.5 3.6,7.5 3.9,6 4.2,4 4.5,5 4.8,6.5 5,8.5 5.3,7.5 5.6,6 5.9,3.5 6.3,5 6.7,7 6.9,9.5 7.2,7.5 7.5,5 7.8,2 8.2,5 8.6,8 8.9,11 9.3,8 9.7,5 10.1,2 10.6,5.5 11.1,9 11.6,12 12.2,8.5 12.8,5 13,2" />
6
+ <path style="fill:none;stroke:#009966;stroke-width:0.35;stroke-linejoin:round" d="M 1,7 C 1.5,5.5 2,4.5 2.5,5.5 C 3,7 3.3,8.5 3.6,7.5 C 3.9,6 4.2,4 4.5,5 C 4.8,6.5 5,8.5 5.3,7.5 C 5.6,6 5.9,3.5 6.3,5 C 6.7,7 6.9,9.5 7.2,7.5 C 7.5,5 7.8,2 8.2,5 C 8.6,8 8.9,11 9.3,8 C 9.7,5 10.1,2 10.6,5.5 C 11.1,9 11.6,12 12.2,8.5 C 12.8,5 13,3.5 13,2" />
7
7
  </svg>
@@ -3,5 +3,5 @@
3
3
  xmlns="http://www.w3.org/2000/svg">
4
4
  <rect style="fill:#696969;fill-opacity:1;stroke-width:0.547874" width="0.53186357" height="12.584304" x="6.7340684" y="0.70784807" />
5
5
  <rect style="fill:#696969;fill-opacity:1;stroke-width:0.54642" width="0.53186357" height="12.5176" x="6.7340684" y="-13.258801" transform="rotate(90)" />
6
- <path style="fill:none;stroke:#9900cc;stroke-width:0.35;stroke-linejoin:round" d="M 1.5,12 C 2,12 2.5,12 3,11.9 3.5,11.8 4,11.6 4.5,11.2 5,10.8 5.5,10.2 6,9.5 6.5,8.5 7,7.2 7.5,5.5 8,4 8.5,3 9,2.5 9.5,2.2 10,2.1 10.5,2.05 11,2.02 11.5,2.01 12,2 12.5,2" />
6
+ <path style="fill:none;stroke:#9900cc;stroke-width:0.35;stroke-linejoin:round" d="M 1.5,12 C 2,12 2.5,12 3,11.9 C 3.5,11.8 4,11.6 4.5,11.2 C 5,10.8 5.5,10.2 6,9.5 C 6.5,8.5 7,7.2 7.5,5.5 C 8,4 8.5,3 9,2.5 C 9.5,2.2 10,2.1 10.5,2.05 C 11,2.02 11.5,2.01 12,2 L 12.5,2" />
7
7
  </svg>
@@ -50,6 +50,10 @@ from qtpy import QtWidgets as QW
50
50
  from datalab.adapters_metadata import GeometryAdapter, TableAdapter, have_results
51
51
  from datalab.config import Conf, _
52
52
  from datalab.gui import newobject
53
+ from datalab.gui.processor.base import (
54
+ clear_analysis_parameters,
55
+ extract_analysis_parameters,
56
+ )
53
57
  from datalab.widgets import fitdialog
54
58
 
55
59
  if TYPE_CHECKING:
@@ -361,6 +365,15 @@ class BaseActionHandler(metaclass=abc.ABCMeta):
361
365
  obj: Object containing the result
362
366
  adapter: Adapter for the result to delete
363
367
  """
368
+ # Check if this result matches the stored analysis parameters
369
+ # If so, clear them to prevent auto-recompute from attempting to
370
+ # recompute the deleted analysis when ROI changes
371
+ analysis_params = extract_analysis_parameters(obj)
372
+ if (
373
+ analysis_params is not None
374
+ and analysis_params.func_name == adapter.func_name
375
+ ):
376
+ clear_analysis_parameters(obj)
364
377
  adapter.remove_from(obj)
365
378
  # Update properties panel to reflect the removal
366
379
  if obj is self.panel.objview.get_current_object():
datalab/gui/h5io.py CHANGED
@@ -54,6 +54,31 @@ class H5InputOutput:
54
54
  panel.serialize_to_hdf5(writer)
55
55
  writer.close()
56
56
 
57
+ def open_file_headless(self, filename: str, reset_all: bool) -> bool:
58
+ """Open native DataLab HDF5 file without any GUI elements.
59
+
60
+ This method can be safely called from any thread (e.g., the console thread)
61
+ as it does not create any Qt widgets or dialogs.
62
+
63
+ Args:
64
+ filename: HDF5 filename
65
+ reset_all: Reset all application data before importing
66
+
67
+ Returns:
68
+ True if file was successfully opened as a native DataLab file,
69
+ False if the file format is not compatible (KeyError was raised)
70
+ """
71
+ try:
72
+ reader = NativeH5Reader(filename)
73
+ if reset_all:
74
+ self.mainwindow.reset_all()
75
+ for panel in self.mainwindow.panels:
76
+ panel.deserialize_from_hdf5(reader, reset_all)
77
+ reader.close()
78
+ return True
79
+ except KeyError:
80
+ return False
81
+
57
82
  def open_file(self, filename: str, import_all: bool, reset_all: bool) -> None:
58
83
  """Open HDF5 file"""
59
84
  progress = None
@@ -186,8 +186,20 @@ print("All done!")
186
186
  """
187
187
  global UNTITLED_NB # pylint: disable=global-statement
188
188
  UNTITLED_NB += 1
189
- untitled = _("Untitled")
190
- return f"{untitled} {UNTITLED_NB:02d}"
189
+ return f"macro_{UNTITLED_NB:02d}"
190
+
191
+ @staticmethod
192
+ def set_untitled_number(number: int) -> None:
193
+ """Set the untitled number counter
194
+
195
+ This is useful when loading macros from HDF5 or files to ensure
196
+ that the next untitled macro has a unique name.
197
+
198
+ Args:
199
+ number: New untitled number
200
+ """
201
+ global UNTITLED_NB # pylint: disable=global-statement
202
+ UNTITLED_NB = number
191
203
 
192
204
  def modification_changed(self, state: bool) -> None:
193
205
  """Method called when macro's editor modification state changed
@@ -208,8 +220,10 @@ print("All done!")
208
220
  Returns:
209
221
  Locale str
210
222
  """
211
- locale_codec = QC.QTextCodec.codecForLocale()
212
- return locale_codec.toUnicode(bytearr.data())
223
+ # Python 3 outputs UTF-8 by default, so we need to decode as UTF-8
224
+ # instead of using the system locale codec (which might be cp1252 on Windows)
225
+ utf8_codec = QC.QTextCodec.codecForName(b"UTF-8")
226
+ return utf8_codec.toUnicode(bytearr.data())
213
227
 
214
228
  def get_stdout(self) -> str:
215
229
  """Return standard output str
@@ -258,7 +272,7 @@ print("All done!")
258
272
  def run(self) -> None:
259
273
  """Run macro"""
260
274
  self.process = QC.QProcess()
261
- code = self.get_code().replace('"', "'")
275
+ code = self.get_code()
262
276
  datalab_path = osp.abspath(osp.join(osp.dirname(datalab.__file__), os.pardir))
263
277
  # Reconfigure stdout/stderr to use UTF-8 encoding to avoid UnicodeEncodeError
264
278
  # on Windows with locales that don't support all Unicode characters
datalab/gui/main.py CHANGED
@@ -1360,6 +1360,10 @@ class DLMainWindow(QW.QMainWindow, AbstractDLControl, metaclass=DLMainWindowMeta
1360
1360
  title += f" [{datalab.__version__}]"
1361
1361
  self.setWindowTitle(title)
1362
1362
 
1363
+ def is_modified(self) -> bool:
1364
+ """Return True if mainwindow is modified"""
1365
+ return self.__is_modified
1366
+
1363
1367
  def __add_dockwidget(self, child, title: str) -> QW.QDockWidget:
1364
1368
  """Add QDockWidget and toggleViewAction"""
1365
1369
  dockwidget, location = child.create_dockwidget(title)
@@ -1559,9 +1563,7 @@ class DLMainWindow(QW.QMainWindow, AbstractDLControl, metaclass=DLMainWindowMeta
1559
1563
  if not filename:
1560
1564
  return
1561
1565
  with qth.qt_try_loadsave_file(self, filename, "save"):
1562
- filename = self.__check_h5file(filename, "save")
1563
- self.h5inputoutput.save_file(filename)
1564
- self.set_modified(False)
1566
+ self.save_h5_workspace(filename)
1565
1567
 
1566
1568
  @remote_controlled
1567
1569
  def open_h5_files(
@@ -1667,6 +1669,59 @@ class DLMainWindow(QW.QMainWindow, AbstractDLControl, metaclass=DLMainWindowMeta
1667
1669
  self.__check_h5file(filename, "load")
1668
1670
  self.h5inputoutput.import_files(filenames, False, reset_all)
1669
1671
 
1672
+ @remote_controlled
1673
+ def load_h5_workspace(self, h5files: list[str], reset_all: bool = False) -> None:
1674
+ """Load native DataLab HDF5 workspace files without any GUI elements.
1675
+
1676
+ This method can be safely called from the internal console as it does not
1677
+ create any Qt widgets, dialogs, or progress bars. It is designed for
1678
+ programmatic use when loading DataLab workspace files.
1679
+
1680
+ .. warning::
1681
+
1682
+ This method only supports native DataLab HDF5 files. For importing
1683
+ arbitrary HDF5 files (non-native), use the GUI menu or macros with
1684
+ :class:`datalab.control.proxy.RemoteProxy`.
1685
+
1686
+ Args:
1687
+ h5files: List of native DataLab HDF5 filenames
1688
+ reset_all: Reset all application data before importing. Defaults to False.
1689
+
1690
+ Raises:
1691
+ ValueError: If a file is not a valid native DataLab HDF5 file
1692
+ """
1693
+ for idx, filename in enumerate(h5files):
1694
+ filename = self.__check_h5file(filename, "load")
1695
+ success = self.h5inputoutput.open_file_headless(
1696
+ filename, reset_all=(reset_all and idx == 0)
1697
+ )
1698
+ if not success:
1699
+ raise ValueError(
1700
+ f"File '{filename}' is not a native DataLab HDF5 file. "
1701
+ f"Use the GUI menu or a macro with RemoteProxy to import "
1702
+ f"arbitrary HDF5 files."
1703
+ )
1704
+ # Refresh panel trees after loading
1705
+ self.repopulate_panel_trees()
1706
+
1707
+ @remote_controlled
1708
+ def save_h5_workspace(self, filename: str) -> None:
1709
+ """Save current workspace to a native DataLab HDF5 file without GUI elements.
1710
+
1711
+ This method can be safely called from the internal console as it does not
1712
+ create any Qt widgets, dialogs, or progress bars. It is designed for
1713
+ programmatic use when saving DataLab workspace files.
1714
+
1715
+ Args:
1716
+ filename: HDF5 filename to save to
1717
+
1718
+ Raises:
1719
+ IOError: If file cannot be saved
1720
+ """
1721
+ filename = self.__check_h5file(filename, "save")
1722
+ self.h5inputoutput.save_file(filename)
1723
+ self.set_modified(False)
1724
+
1670
1725
  @remote_controlled
1671
1726
  def import_h5_file(self, filename: str, reset_all: bool | None = None) -> None:
1672
1727
  """Import HDF5 file into DataLab
@@ -2027,7 +2082,7 @@ class DLMainWindow(QW.QMainWindow, AbstractDLControl, metaclass=DLMainWindowMeta
2027
2082
  Returns:
2028
2083
  True if closed properly, False otherwise
2029
2084
  """
2030
- if not env.execenv.unattended and self.__is_modified:
2085
+ if not env.execenv.unattended and self.is_modified():
2031
2086
  answer = QW.QMessageBox.warning(
2032
2087
  self,
2033
2088
  _("Quit"),
@@ -2039,7 +2094,7 @@ class DLMainWindow(QW.QMainWindow, AbstractDLControl, metaclass=DLMainWindowMeta
2039
2094
  )
2040
2095
  if answer == QW.QMessageBox.Yes:
2041
2096
  self.save_to_h5_file()
2042
- if self.__is_modified:
2097
+ if self.is_modified():
2043
2098
  return False
2044
2099
  elif answer == QW.QMessageBox.Cancel:
2045
2100
  return False