datview 2.0.0__tar.gz → 2.1.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datview
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: GUI software for viewing images, text, cine, and HDF files.
5
5
  Home-page: https://github.com/algotom/datview
6
6
  Download-URL: https://github.com/algotom/datview.git
@@ -53,7 +53,7 @@ Motivation
53
53
  ==========
54
54
 
55
55
  For synchrotron-based tomography, users need convenient tools to view their
56
- data, typically in TIF, HDF, or CINE format during experiments, along with
56
+ data, typically in text, TIF, HDF, or CINE format during experiments, along with
57
57
  basic assessment tools such as contrast adjustment, zooming, line-profile
58
58
  viewing, histograms, image statistics, or percentile density. However, at synchrotron facilities,
59
59
  where Linux OS and open-source software are the primary tools, users often need to switch
@@ -64,12 +64,8 @@ This separation of tools is inconvenient, especially since many users are not fa
64
64
  with the Linux OS. **DatView** provides a unified GUI for all these tasks, improving
65
65
  efficiency and user experience. **DatView** runs across operating systems.
66
66
 
67
- Design Philosophy
68
- =================
69
-
70
- DatView has been developed following two key guidelines:
71
- - Minimize dependencies and the codebase.
72
- - Maximize functionality and maintainability.
67
+ Design
68
+ ======
73
69
 
74
70
  For distributing the software through Pip and Conda, the software is structured
75
71
  based on the RUI (Rendering-Utilities-Interactions) concept, which is a user-friendly
@@ -107,7 +103,6 @@ Features
107
103
  ![Fig5](https://github.com/algotom/datview/raw/main/figs/fig5.png)
108
104
 
109
105
  - Interactive viewing of TIF files in a folder or frames of a CINE file.
110
-
111
106
  - Interactive viewing of common image formats (JPG, PNG, TIF, ...).
112
107
  - Viewing 1D or 2D datasets of an HDF file in table format.
113
108
  - Opening multiple interactive viewers simultaneously.
@@ -13,7 +13,7 @@ Motivation
13
13
  ==========
14
14
 
15
15
  For synchrotron-based tomography, users need convenient tools to view their
16
- data, typically in TIF, HDF, or CINE format during experiments, along with
16
+ data, typically in text, TIF, HDF, or CINE format during experiments, along with
17
17
  basic assessment tools such as contrast adjustment, zooming, line-profile
18
18
  viewing, histograms, image statistics, or percentile density. However, at synchrotron facilities,
19
19
  where Linux OS and open-source software are the primary tools, users often need to switch
@@ -24,12 +24,8 @@ This separation of tools is inconvenient, especially since many users are not fa
24
24
  with the Linux OS. **DatView** provides a unified GUI for all these tasks, improving
25
25
  efficiency and user experience. **DatView** runs across operating systems.
26
26
 
27
- Design Philosophy
28
- =================
29
-
30
- DatView has been developed following two key guidelines:
31
- - Minimize dependencies and the codebase.
32
- - Maximize functionality and maintainability.
27
+ Design
28
+ ======
33
29
 
34
30
  For distributing the software through Pip and Conda, the software is structured
35
31
  based on the RUI (Rendering-Utilities-Interactions) concept, which is a user-friendly
@@ -67,7 +63,6 @@ Features
67
63
  ![Fig5](https://github.com/algotom/datview/raw/main/figs/fig5.png)
68
64
 
69
65
  - Interactive viewing of TIF files in a folder or frames of a CINE file.
70
-
71
66
  - Interactive viewing of common image formats (JPG, PNG, TIF, ...).
72
67
  - Viewing 1D or 2D datasets of an HDF file in table format.
73
68
  - Opening multiple interactive viewers simultaneously.
@@ -0,0 +1 @@
1
+ __version__ = "2.1.0"
@@ -9,12 +9,13 @@ import sys
9
9
  import json
10
10
  import threading
11
11
  from pathlib import Path
12
- import numpy as np
13
12
  import h5py
13
+ import numpy as np
14
14
  from PIL import Image
15
+ import pyqtgraph as pg
15
16
  from PySide6.QtCore import QObject, Qt, QTimer
16
- from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, \
17
- QTreeWidgetItem, QInputDialog, QLineEdit
17
+ from PySide6.QtWidgets import (QApplication, QFileDialog, QMessageBox,
18
+ QTreeWidgetItem, QInputDialog, QLineEdit)
18
19
 
19
20
  import datview.lib.rendering as ren
20
21
  import datview.lib.utilities as util
@@ -29,12 +30,16 @@ class DatviewInteraction(QObject):
29
30
  self.app.setApplicationName(util.APP_NAME)
30
31
  self.app.setFont(ren.select_ui_font(point_size=util.FONT_SIZE,
31
32
  weight=util.FONT_WEIGHT))
32
- ren.apply_app_theme(self.app)
33
+ # Load saved theme (default Light)
34
+ config_data = util.load_config()
35
+ saved_theme = "Light"
36
+ if config_data and "theme" in config_data:
37
+ if config_data["theme"] in ren.THEMES:
38
+ saved_theme = config_data["theme"]
39
+ ren.apply_theme(self.app, saved_theme)
33
40
 
34
41
  if base_folder is None:
35
- cfg = util.load_config()
36
- if cfg:
37
- base_folder = cfg.get("last_folder")
42
+ base_folder = config_data.get("last_folder") if config_data else None
38
43
  if not base_folder:
39
44
  base_folder = os.path.expanduser("~")
40
45
 
@@ -43,6 +48,10 @@ class DatviewInteraction(QObject):
43
48
  self.base_folder = Path.home()
44
49
 
45
50
  self.main_win = ren.DatviewMainWindow(str(self.base_folder))
51
+ # Restore theme combo
52
+ self.main_win.combo_theme.blockSignals(True)
53
+ self.main_win.combo_theme.setCurrentText(saved_theme)
54
+ self.main_win.combo_theme.blockSignals(False)
46
55
  self.active_viewer = None
47
56
  self.viewers = []
48
57
  self.current_table = None
@@ -57,6 +66,7 @@ class DatviewInteraction(QObject):
57
66
  def _wire_signals(self):
58
67
  """Wire UI signals to controller slots."""
59
68
  self.main_win.sig_browse_requested.connect(self.select_base_folder)
69
+ self.main_win.sig_theme_changed.connect(self.on_theme_changed)
60
70
  self.main_win.sig_folder_selected.connect(self.on_folder_select)
61
71
  self.main_win.sig_file_select_changed.connect(
62
72
  self.on_file_select_change)
@@ -83,7 +93,23 @@ class DatviewInteraction(QObject):
83
93
  self.populate_tree_root()
84
94
  self.main_win.combo_hdf.clear()
85
95
  self.main_win.combo_hdf.setEnabled(False)
86
- util.save_config({"last_folder": str(self.base_folder)})
96
+ theme_name = self.main_win.combo_theme.currentText()
97
+ util.save_config({"last_folder": str(self.base_folder), "theme": theme_name})
98
+
99
+ def on_theme_changed(self, theme_name: str):
100
+ """Apply the selected theme and refresh open viewer backgrounds."""
101
+ if not self.app:
102
+ return
103
+ ren.apply_theme(self.app, theme_name)
104
+ pg_bg = pg.mkColor(ren.sel_theme["SURFACE"])
105
+ for v in self.viewers:
106
+ if hasattr(v, "imv") and v.imv is not None:
107
+ try:
108
+ v.imv.getHistogramWidget().setBackground(pg_bg)
109
+ except Exception:
110
+ pass
111
+ cfg = {"last_folder": str(self.base_folder), "theme": theme_name}
112
+ util.save_config(cfg)
87
113
 
88
114
  def populate_tree_root(self):
89
115
  self.main_win.tree.clear()
@@ -379,8 +405,8 @@ class DatviewInteraction(QObject):
379
405
  try:
380
406
  img = util.load_image(path)
381
407
  win = ren.Viewer2DWindow(self,
382
- title=f"Viewing: {os.path.basename(path)}. "
383
- f"(Height, Width) = {img.shape}",
408
+ title=f"Viewing: {os.path.basename(path)}."
409
+ f" (Height, Width) = {img.shape}",
384
410
  image=img, file_path=path)
385
411
  win.parent_app = self
386
412
  self._show_window(win)
@@ -599,7 +625,7 @@ class DatviewInteraction(QObject):
599
625
  "2D arrays.")
600
626
  return
601
627
  # Too large -> show as image instead of table
602
- if data.size > util.TABLE_SIZE_CUTOFF:
628
+ if data.size > util.TABLE_SIZE_CUTOFF and len(data.shape) == 2:
603
629
  info = " !!!Display as image instead table due to size!!!"
604
630
  win = ren.Viewer2DWindow(
605
631
  self,
@@ -677,6 +703,28 @@ class DatviewInteraction(QObject):
677
703
  self.active_viewer = viewer
678
704
  self.main_win.statusBar().showMessage(f"Active: {viewer.windowTitle()}")
679
705
 
706
+ def _get_save_start_path(self, default_ext: str):
707
+ """
708
+ Return a default save path based on the current active viewer file.
709
+ """
710
+ source_path = None
711
+
712
+ if self.active_viewer and hasattr(self.active_viewer, "file_path"):
713
+ source_path = self.active_viewer.file_path
714
+
715
+ if source_path:
716
+ source_path = os.path.normpath(source_path)
717
+
718
+ if os.path.isfile(source_path):
719
+ base_dir = os.path.dirname(source_path)
720
+ base_name = os.path.splitext(os.path.basename(source_path))[0]
721
+ return os.path.join(base_dir, base_name + default_ext)
722
+
723
+ if os.path.isdir(source_path):
724
+ return os.path.join(source_path, "output" + default_ext)
725
+
726
+ return os.path.join(os.getcwd(), "output" + default_ext)
727
+
680
728
  def save_to_image(self):
681
729
  if self.active_viewer and hasattr(self.active_viewer, 'viewer_state'):
682
730
  img = self.active_viewer.viewer_state.get("image")
@@ -688,13 +736,28 @@ class DatviewInteraction(QObject):
688
736
  QMessageBox.information(self.main_win, "Input needed",
689
737
  "No active image. Use Interactive-Viewer!")
690
738
  return
739
+ default_path = self._get_save_start_path(".tif")
740
+ path, selected_filter = QFileDialog.getSaveFileName(
741
+ self.main_win,
742
+ "Save Image As",
743
+ default_path,
744
+ "TIFF (*.tif);;PNG (*.png);;JPEG (*.jpg)")
745
+
746
+ if not path:
747
+ return
691
748
 
692
- path, _ = QFileDialog.getSaveFileName(self.main_win,
693
- "Save Image As", "",
694
- "TIFF (*.tif);;PNG (*.png);;"
695
- "JPEG (*.jpg)")
696
- if path:
697
- util.save_image(path, img)
749
+ if not os.path.splitext(path)[1]:
750
+ if "PNG" in selected_filter:
751
+ path += ".png"
752
+ elif "JPEG" in selected_filter:
753
+ path += ".jpg"
754
+ else:
755
+ path += ".tif"
756
+
757
+ err = util.save_image(path, img)
758
+ if err:
759
+ QMessageBox.critical(self, "Save failed", err)
760
+ else:
698
761
  self.main_win.statusBar().showMessage(f"Image saved to: {path}")
699
762
 
700
763
  def save_to_table(self):
@@ -718,11 +781,19 @@ class DatviewInteraction(QObject):
718
781
  f"({util.MAX_TABLE_SAVE} elements).")
719
782
  return
720
783
 
721
- path, _ = QFileDialog.getSaveFileName(self.main_win, "Save Data As", "",
722
- "CSV (*.csv)")
784
+ default_path = self._get_save_start_path(".csv")
785
+
786
+ path, _ = QFileDialog.getSaveFileName(self.main_win, "Save Data As",
787
+ default_path, "CSV (*.csv)")
788
+
723
789
  if path:
724
- util.save_table(path, data)
725
- self.main_win.statusBar().showMessage(f"Data saved to: {path}")
790
+ if not os.path.splitext(path)[1]:
791
+ path += ".csv"
792
+ err = util.save_table(path, data)
793
+ if err:
794
+ QMessageBox.critical(self, "Save failed", err)
795
+ else:
796
+ self.main_win.statusBar().showMessage(f"Data saved to: {path}")
726
797
 
727
798
  def _show_window(self, win):
728
799
  self.viewers.append(win)