From 7470f1388b4909355109c6341d9d6db2518f7f1d Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 14 Apr 2023 14:17:46 +0200 Subject: [PATCH 01/21] first try on adding 2D image support --- grum/imgdesc.py | 48 +++++++++++++++++++++++++++++++++++++++++++ grum/mainwin.py | 35 +++++++++++++++++++++++++------ grum/mdi/__init__.py | 1 + grum/mdi/mdisubimg.py | 38 ++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 grum/imgdesc.py create mode 100644 grum/mdi/mdisubimg.py diff --git a/grum/imgdesc.py b/grum/imgdesc.py new file mode 100644 index 0000000..5b171d5 --- /dev/null +++ b/grum/imgdesc.py @@ -0,0 +1,48 @@ +import numpy as np + + +class ImageDescription: + + def __init__(self, name, title=None, xlabel=None, ylabel=None, image=None): + self.name = name + self.title = title + self.xlabel = xlabel + self.ylabel = ylabel + self.image = image + + @property + def data(self): + return np.asarray(self.image) + + @data.setter + def data(self, value): + self.image = value + + + def append(self, xy): + print("ignored image append") + + def extend(self, data): + print("ignored image extend") + + + def make_plot(self, plotwidget, style): + res = plotwidget.setImage(self.data) + + if self.title: + plotwidget.setTitle(self.title) + + if self.xlabel: + plotwidget.setLabel("bottom", self.xlabel) + + if self.ylabel: + plotwidget.setLabel("left", self.ylabel) + + return res + + + def to_dict(self): + return {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None} + + + diff --git a/grum/mainwin.py b/grum/mainwin.py index a2e4fd9..a80e250 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -6,9 +6,10 @@ from .dictlist import DictList from .exampledata import exampledata from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog from .io import write_dict, read_dict -from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot, MDIWindowMode +from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot, MDISubImage, MDIWindowMode from .menus import BarMenu from .plotdesc import PlotDescription +from .imgdesc import ImageDescription from .rpc import RPCServerThread from .shortcut import shortcut from .webview import WebView @@ -16,7 +17,8 @@ from .webview import WebView class MainWindow(QMainWindow): - sig_make_new_plot = pyqtSignal(str, PlotDescription) + sig_make_new_plot = pyqtSignal(str, PlotDescription) + sig_make_new_image = pyqtSignal(str, ImageDescription) def __init__(self, *args, title="grum", host="localhost", port=8000, offline=False, add_examples=False, window_mode=MDIWindowMode.MULTI, **kwargs): super().__init__(*args, **kwargs) @@ -88,8 +90,10 @@ class MainWindow(QMainWindow): rst.server.register_function(self.append_data) rst.server.register_function(self.extend_data) rst.server.register_function(self.set_data) + rst.server.register_function(self.new_image) self.sig_make_new_plot.connect(self.on_make_new_plot) + self.sig_make_new_image.connect(self.on_make_new_image) def keyPressEvent(self, event): @@ -140,12 +144,29 @@ class MainWindow(QMainWindow): desc.data = data self.sync_item_and_plots(item) + def new_image(self, name, cfg): + """ + Create a new image using the configuration dict . + The configuration is forwarded to the constructor of ImageDescription. + Allowed keys are: title, xlabel, ylabel, image. + """ + desc = self.add_new_desc_to_list(name, cfg, Desc=ImageDescription) #TODO: clean up Desc argument + if self.menu_settings.checkboxes["Open new plots"].isChecked(): + sub = self.mdi.findSubWindow(name) + if sub: + sub.pw.setImage(desc.data) #TODO lacks the list sync + else: + self.sig_make_new_image.emit(name, desc) + # Signal callbacks def on_make_new_plot(self, *args, **kwargs): self.make_subwin(MDISubPlot, *args, **kwargs) + def on_make_new_image(self, *args, **kwargs): + self.make_subwin(MDISubImage, *args, **kwargs) + def on_dclick_list_item(self, item): self.plot_single_item(item) @@ -188,7 +209,8 @@ class MainWindow(QMainWindow): for fn in fns: data = read_dict(fn) for k, v in data.items(): - self.add_new_desc_to_list(k, v) + Desc = ImageDescription if "image" in v else PlotDescription #TODO + self.add_new_desc_to_list(k, v, Desc=Desc) def on_file_save(self): @@ -206,8 +228,8 @@ class MainWindow(QMainWindow): # Plumbing - def add_new_desc_to_list(self, name, cfg): - desc = PlotDescription(name, **cfg) + def add_new_desc_to_list(self, name, cfg, Desc=PlotDescription): #TODO + desc = Desc(name, **cfg) self.lst.set(name, desc) return desc @@ -226,7 +248,8 @@ class MainWindow(QMainWindow): item.timestamps.access.update() item.set_alarm(False) name, desc = item.key, item.value - self.activate_or_make_subwin(MDISubPlot, name, desc) + MDISubType = MDISubImage if isinstance(desc, ImageDescription) else MDISubPlot #TODO + self.activate_or_make_subwin(MDISubType, name, desc) def plot_multiple_items(self, items): for i in items: diff --git a/grum/mdi/__init__.py b/grum/mdi/__init__.py index 4820247..e754999 100644 --- a/grum/mdi/__init__.py +++ b/grum/mdi/__init__.py @@ -1,5 +1,6 @@ from .mdiarea import MDIArea, MDIWindowMode from .mdisubplot import MDISubPlot, MDISubMultiPlot +from .mdisubimg import MDISubImage diff --git a/grum/mdi/mdisubimg.py b/grum/mdi/mdisubimg.py new file mode 100644 index 0000000..a3adce1 --- /dev/null +++ b/grum/mdi/mdisubimg.py @@ -0,0 +1,38 @@ +import pyqtgraph as pg +from .mdisubwin import MDISubWindow +from ..theme import pg_plot_style + + +class MDISubImage(MDISubWindow): + + def __init__(self, name, desc, *args, **kwargs): + super().__init__(name, *args, **kwargs) + self.pw = pw = pg.ImageView() + self.setWidget(pw) + + # connect to plot mouse-over event + pw.scene.sigMouseMoved.connect(self.on_hover) + + style = pg_plot_style() + + plot = desc.make_plot(self.pw, style) + self.plots = {name: plot} + + self.image = desc.data + + + def on_hover(self, event): + coord = self.pw.imageItem.mapFromScene(event) + x = coord.x() + y = coord.y() + x = int(x) + y = int(y) + try: + z = self.image[x, y] + except IndexError: + return + z = round(z, 3) + self.setToolTip(f"x = {x}\ny = {y}\nz = {z}") + + + From da8d7b0a2bf9a481de3948ba1b2027f1b773c532 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 14 Apr 2023 17:57:33 +0200 Subject: [PATCH 02/21] better add_new_desc_to_list; fixed multi plots with marked image; typo --- grum/mainwin.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index a80e250..c0c1a93 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -109,7 +109,7 @@ class MainWindow(QMainWindow): The configuration is forwarded to the constructor of PlotDescription. Allowed keys are: title, xlabel, ylabel, xs, ys. """ - desc = self.add_new_desc_to_list(name, cfg) + desc = self.add_new_desc_to_list(PlotDescription, name, cfg) if self.menu_settings.checkboxes["Open new plots"].isChecked(): if not self.mdi.findSubWindow(name): self.sig_make_new_plot.emit(name, desc) @@ -150,7 +150,7 @@ class MainWindow(QMainWindow): The configuration is forwarded to the constructor of ImageDescription. Allowed keys are: title, xlabel, ylabel, image. """ - desc = self.add_new_desc_to_list(name, cfg, Desc=ImageDescription) #TODO: clean up Desc argument + desc = self.add_new_desc_to_list(ImageDescription, name, cfg) if self.menu_settings.checkboxes["Open new plots"].isChecked(): sub = self.mdi.findSubWindow(name) if sub: @@ -209,8 +209,8 @@ class MainWindow(QMainWindow): for fn in fns: data = read_dict(fn) for k, v in data.items(): - Desc = ImageDescription if "image" in v else PlotDescription #TODO - self.add_new_desc_to_list(k, v, Desc=Desc) + DescType = ImageDescription if "image" in v else PlotDescription #TODO + self.add_new_desc_to_list(DescType, k, v) def on_file_save(self): @@ -228,8 +228,8 @@ class MainWindow(QMainWindow): # Plumbing - def add_new_desc_to_list(self, name, cfg, Desc=PlotDescription): #TODO - desc = Desc(name, **cfg) + def add_new_desc_to_list(self, DescType, name, cfg): + desc = DescType(name, **cfg) self.lst.set(name, desc) return desc @@ -255,13 +255,14 @@ class MainWindow(QMainWindow): for i in items: i.timestamps.access.update() i.set_alarm(False) + items = (i for i in items if isinstance(i.value, PlotDescription)) #TODO descs = {i.key: i.value for i in items} names = descs.keys() name = " | ".join(names) self.activate_or_make_subwin(MDISubMultiPlot, name, descs) - #TODO: the following two could be methods to MDIArea? + #TODO: the following two could be methods of MDIArea? def activate_or_make_subwin(self, MDISubType, name, *args, **kwargs): sub = self.mdi.findSubWindow(name) From a7c86eb3b12df55ca3a995e159551346ec051bdb Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 14 Apr 2023 18:25:10 +0200 Subject: [PATCH 03/21] moved *Description classes into descs folder --- README.md | 4 ++-- grum/descs/__init__.py | 5 +++++ grum/{ => descs}/imgdesc.py | 0 grum/{ => descs}/plotdesc.py | 0 grum/exampledata.py | 2 +- grum/mainwin.py | 3 +-- tests/test_mainwin.py | 2 +- tests/test_plotdesc.py | 2 +- 8 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 grum/descs/__init__.py rename grum/{ => descs}/imgdesc.py (100%) rename grum/{ => descs}/plotdesc.py (100%) diff --git a/README.md b/README.md index e5b5c12..e64dff6 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,11 @@ Via the RPC server, new plots can be created and new data appended to existing p - `new_plot(name, cfg)` -Creates a new plot named `name` in the grum list. The configuration dict `cfg` is used as arguments for the constructor of [`PlotDescription`](https://gitlab.psi.ch/augustin_s/grum/-/blob/master/grum/plotdesc.py#L4). +Creates a new plot named `name` in the grum list. The configuration dict `cfg` is used as arguments for the constructor of [`PlotDescription`](https://gitlab.psi.ch/augustin_s/grum/-/blob/master/grum/descs/plotdesc.py#L4). - `append_data(name, point)` -Append data point to the plot named `name`. The new `point` is forwarded to [`PlotDescription.append()`](https://gitlab.psi.ch/augustin_s/grum/-/blob/master/grum/plotdesc.py#L18). +Append data point to the plot named `name`. The new `point` is forwarded to [`PlotDescription.append()`](https://gitlab.psi.ch/augustin_s/grum/-/blob/master/grum/descs/plotdesc.py#L18). ### Utility functions diff --git a/grum/descs/__init__.py b/grum/descs/__init__.py new file mode 100644 index 0000000..278a64b --- /dev/null +++ b/grum/descs/__init__.py @@ -0,0 +1,5 @@ + +from .imgdesc import ImageDescription +from .plotdesc import PlotDescription + + diff --git a/grum/imgdesc.py b/grum/descs/imgdesc.py similarity index 100% rename from grum/imgdesc.py rename to grum/descs/imgdesc.py diff --git a/grum/plotdesc.py b/grum/descs/plotdesc.py similarity index 100% rename from grum/plotdesc.py rename to grum/descs/plotdesc.py diff --git a/grum/exampledata.py b/grum/exampledata.py index 8eb609c..1925b55 100644 --- a/grum/exampledata.py +++ b/grum/exampledata.py @@ -1,6 +1,6 @@ import numpy as np -from .plotdesc import PlotDescription +from .descs import PlotDescription X = np.arange(100) / 10 diff --git a/grum/mainwin.py b/grum/mainwin.py index c0c1a93..ed43aa1 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -8,8 +8,7 @@ from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog from .io import write_dict, read_dict from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot, MDISubImage, MDIWindowMode from .menus import BarMenu -from .plotdesc import PlotDescription -from .imgdesc import ImageDescription +from .descs import PlotDescription, ImageDescription from .rpc import RPCServerThread from .shortcut import shortcut from .webview import WebView diff --git a/tests/test_mainwin.py b/tests/test_mainwin.py index 6a73af4..2ec4d2a 100644 --- a/tests/test_mainwin.py +++ b/tests/test_mainwin.py @@ -14,7 +14,7 @@ from grum.mainwin import MainWindow from grum.mdi import MDIArea, MDISubMultiPlot, MDISubPlot from grum.menus import BarMenu from grum.menus.rclickmenu import RClickMenu -from grum.plotdesc import PlotDescription +from grum.descs import PlotDescription from grum.rpc import RPCServerThread diff --git a/tests/test_plotdesc.py b/tests/test_plotdesc.py index 06fd68c..5cf2263 100644 --- a/tests/test_plotdesc.py +++ b/tests/test_plotdesc.py @@ -6,7 +6,7 @@ import pyqtgraph as pg from grum import theme from grum.mainwin import MainWindow from grum.mdi.mdisubplot import MDISubPlot -from grum.plotdesc import PlotDescription +from grum.descs import PlotDescription from grum.theme import pg_plot_style From 00b12d800f3f54e7223e975bec653b0121e8ece6 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 14 Apr 2023 18:33:22 +0200 Subject: [PATCH 04/21] import order --- grum/mainwin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index ed43aa1..d0854b0 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -2,13 +2,13 @@ from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QMainWindow, QSplitter from . import assets +from .descs import PlotDescription, ImageDescription from .dictlist import DictList from .exampledata import exampledata from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog from .io import write_dict, read_dict from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot, MDISubImage, MDIWindowMode from .menus import BarMenu -from .descs import PlotDescription, ImageDescription from .rpc import RPCServerThread from .shortcut import shortcut from .webview import WebView From 4e95c1f8fd668bce907b2a3de7925ef5e2974a1d Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 14 Apr 2023 23:17:02 +0200 Subject: [PATCH 05/21] added a Description base class --- grum/descs/__init__.py | 1 + grum/descs/desc.py | 8 ++++++++ grum/descs/imgdesc.py | 7 ++----- grum/descs/plotdesc.py | 8 +++----- 4 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 grum/descs/desc.py diff --git a/grum/descs/__init__.py b/grum/descs/__init__.py index 278a64b..d7ca373 100644 --- a/grum/descs/__init__.py +++ b/grum/descs/__init__.py @@ -1,4 +1,5 @@ +from .desc import Description from .imgdesc import ImageDescription from .plotdesc import PlotDescription diff --git a/grum/descs/desc.py b/grum/descs/desc.py new file mode 100644 index 0000000..0c52278 --- /dev/null +++ b/grum/descs/desc.py @@ -0,0 +1,8 @@ + +class Description: + + def to_dict(self): + return {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None} + + + diff --git a/grum/descs/imgdesc.py b/grum/descs/imgdesc.py index 5b171d5..be990ec 100644 --- a/grum/descs/imgdesc.py +++ b/grum/descs/imgdesc.py @@ -1,7 +1,8 @@ import numpy as np +from .desc import Description -class ImageDescription: +class ImageDescription(Description): def __init__(self, name, title=None, xlabel=None, ylabel=None, image=None): self.name = name @@ -41,8 +42,4 @@ class ImageDescription: return res - def to_dict(self): - return {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None} - - diff --git a/grum/descs/plotdesc.py b/grum/descs/plotdesc.py index 45832f3..86a399d 100644 --- a/grum/descs/plotdesc.py +++ b/grum/descs/plotdesc.py @@ -1,5 +1,7 @@ +from .desc import Description -class PlotDescription: + +class PlotDescription(Description): def __init__(self, name, title=None, xlabel=None, ylabel=None, xs=None, ys=None): self.name = name @@ -45,8 +47,4 @@ class PlotDescription: return res - def to_dict(self): - return {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None} - - From 3c951910b0bee17bcb8ff0c9d149281972a18911 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Sat, 15 Apr 2023 13:44:00 +0200 Subject: [PATCH 06/21] extract description type and add it to dict representation --- grum/descs/desc.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/grum/descs/desc.py b/grum/descs/desc.py index 0c52278..8a85046 100644 --- a/grum/descs/desc.py +++ b/grum/descs/desc.py @@ -2,7 +2,22 @@ class Description: def to_dict(self): - return {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None} + res = {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None} + tn = self.get_type() + res.setdefault("type", tn) + return res + + + @classmethod + def get_type(cls): + tn = cls.__name__ + suffix = "Description" + if not tn.endswith(suffix): + raise ValueError(f'"{tn}" does not end with "{suffix}"') + tn = tn[:-len(suffix)] + tn = tn.casefold() + tn = tn or None + return tn From 4bb052d66509da1b734168a06e0176292793867f Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Sat, 15 Apr 2023 13:44:41 +0200 Subject: [PATCH 07/21] added description type to class mapping --- grum/descs/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/grum/descs/__init__.py b/grum/descs/__init__.py index d7ca373..d641fb3 100644 --- a/grum/descs/__init__.py +++ b/grum/descs/__init__.py @@ -4,3 +4,10 @@ from .imgdesc import ImageDescription from .plotdesc import PlotDescription +DESC_TYPES = { + ImageDescription.get_type(): ImageDescription, + PlotDescription.get_type(): PlotDescription +} + + + From 839b1a1e21421d564b9073a7ae8e99b647a12570 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Sat, 15 Apr 2023 13:45:35 +0200 Subject: [PATCH 08/21] use DESC_TYPES and stored type to choose DescType on file load --- grum/mainwin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index d0854b0..7fa4758 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -2,7 +2,7 @@ from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QMainWindow, QSplitter from . import assets -from .descs import PlotDescription, ImageDescription +from .descs import DESC_TYPES, PlotDescription, ImageDescription from .dictlist import DictList from .exampledata import exampledata from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog @@ -208,7 +208,8 @@ class MainWindow(QMainWindow): for fn in fns: data = read_dict(fn) for k, v in data.items(): - DescType = ImageDescription if "image" in v else PlotDescription #TODO + tn = v.pop("type") + DescType = DESC_TYPES[tn] self.add_new_desc_to_list(DescType, k, v) From 593241d66f9ed4cd892a4a15b859897d998d727a Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Sat, 15 Apr 2023 13:51:18 +0200 Subject: [PATCH 09/21] added and use a mapping DescType to MDISubType --- grum/mainwin.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index 7fa4758..fdb23c9 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -14,6 +14,12 @@ from .shortcut import shortcut from .webview import WebView +DESC_TYPE_TO_MDI_SUB_TYPE = { + ImageDescription: MDISubImage, + PlotDescription: MDISubPlot +} + + class MainWindow(QMainWindow): sig_make_new_plot = pyqtSignal(str, PlotDescription) @@ -248,7 +254,8 @@ class MainWindow(QMainWindow): item.timestamps.access.update() item.set_alarm(False) name, desc = item.key, item.value - MDISubType = MDISubImage if isinstance(desc, ImageDescription) else MDISubPlot #TODO + DescType = type(desc) + MDISubType = DESC_TYPE_TO_MDI_SUB_TYPE[DescType] self.activate_or_make_subwin(MDISubType, name, desc) def plot_multiple_items(self, items): From 023ecd76188f9e952fae1b6db47a62b09ca8d503 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Sat, 15 Apr 2023 13:54:08 +0200 Subject: [PATCH 10/21] added a comment --- grum/mainwin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index fdb23c9..24895c4 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -262,7 +262,7 @@ class MainWindow(QMainWindow): for i in items: i.timestamps.access.update() i.set_alarm(False) - items = (i for i in items if isinstance(i.value, PlotDescription)) #TODO + items = (i for i in items if isinstance(i.value, PlotDescription)) #TODO: for now, only overlay plots descs = {i.key: i.value for i in items} names = descs.keys() name = " | ".join(names) From f80a7759fb900952e94a51a92359461b35068eb9 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Mon, 17 Apr 2023 22:46:56 +0200 Subject: [PATCH 11/21] use Description base class; change callbacks to concrete arguments --- grum/mainwin.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index 24895c4..6d51f55 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -2,7 +2,7 @@ from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QMainWindow, QSplitter from . import assets -from .descs import DESC_TYPES, PlotDescription, ImageDescription +from .descs import DESC_TYPES, Description, PlotDescription, ImageDescription from .dictlist import DictList from .exampledata import exampledata from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog @@ -22,8 +22,8 @@ DESC_TYPE_TO_MDI_SUB_TYPE = { class MainWindow(QMainWindow): - sig_make_new_plot = pyqtSignal(str, PlotDescription) - sig_make_new_image = pyqtSignal(str, ImageDescription) + sig_make_new_plot = pyqtSignal(str, Description) + sig_make_new_image = pyqtSignal(str, Description) def __init__(self, *args, title="grum", host="localhost", port=8000, offline=False, add_examples=False, window_mode=MDIWindowMode.MULTI, **kwargs): super().__init__(*args, **kwargs) @@ -166,11 +166,11 @@ class MainWindow(QMainWindow): # Signal callbacks - def on_make_new_plot(self, *args, **kwargs): - self.make_subwin(MDISubPlot, *args, **kwargs) + def on_make_new_plot(self, name, desc): + self.make_subwin(MDISubPlot, name, desc) - def on_make_new_image(self, *args, **kwargs): - self.make_subwin(MDISubImage, *args, **kwargs) + def on_make_new_image(self, name, desc): + self.make_subwin(MDISubImage, name, desc) def on_dclick_list_item(self, item): self.plot_single_item(item) From 3ef9032499e449cfbabd2663958d3431731b1624 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Mon, 17 Apr 2023 23:09:50 +0200 Subject: [PATCH 12/21] use single signal for creating new subwins --- grum/mainwin.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index 6d51f55..a0eaf0f 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -22,8 +22,7 @@ DESC_TYPE_TO_MDI_SUB_TYPE = { class MainWindow(QMainWindow): - sig_make_new_plot = pyqtSignal(str, Description) - sig_make_new_image = pyqtSignal(str, Description) + sig_make_new_subwin = pyqtSignal(str, Description) def __init__(self, *args, title="grum", host="localhost", port=8000, offline=False, add_examples=False, window_mode=MDIWindowMode.MULTI, **kwargs): super().__init__(*args, **kwargs) @@ -97,8 +96,7 @@ class MainWindow(QMainWindow): rst.server.register_function(self.set_data) rst.server.register_function(self.new_image) - self.sig_make_new_plot.connect(self.on_make_new_plot) - self.sig_make_new_image.connect(self.on_make_new_image) + self.sig_make_new_subwin.connect(self.on_make_new_subwin) def keyPressEvent(self, event): @@ -117,7 +115,7 @@ class MainWindow(QMainWindow): desc = self.add_new_desc_to_list(PlotDescription, name, cfg) if self.menu_settings.checkboxes["Open new plots"].isChecked(): if not self.mdi.findSubWindow(name): - self.sig_make_new_plot.emit(name, desc) + self.sig_make_new_subwin.emit(name, desc) def append_data(self, name, point): """ @@ -161,16 +159,15 @@ class MainWindow(QMainWindow): if sub: sub.pw.setImage(desc.data) #TODO lacks the list sync else: - self.sig_make_new_image.emit(name, desc) + self.sig_make_new_subwin.emit(name, desc) # Signal callbacks - def on_make_new_plot(self, name, desc): - self.make_subwin(MDISubPlot, name, desc) - - def on_make_new_image(self, name, desc): - self.make_subwin(MDISubImage, name, desc) + def on_make_new_subwin(self, name, desc): + DescType = type(desc) + MDISubType = DESC_TYPE_TO_MDI_SUB_TYPE[DescType] + self.make_subwin(MDISubType, name, desc) def on_dclick_list_item(self, item): self.plot_single_item(item) From dee9cbb934fba45ef9098b48a2781c4ef08d5f61 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Mon, 17 Apr 2023 23:11:04 +0200 Subject: [PATCH 13/21] naming --- grum/mainwin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index a0eaf0f..652d669 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -210,10 +210,10 @@ class MainWindow(QMainWindow): for fn in fns: data = read_dict(fn) - for k, v in data.items(): - tn = v.pop("type") + for name, cfg in data.items(): + tn = cfg.pop("type") DescType = DESC_TYPES[tn] - self.add_new_desc_to_list(DescType, k, v) + self.add_new_desc_to_list(DescType, name, cfg) def on_file_save(self): From 2d11fe4ff9ccf32102fd6b8d1dc67990f984dee1 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Mon, 17 Apr 2023 23:36:47 +0200 Subject: [PATCH 14/21] order --- grum/mainwin.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/grum/mainwin.py b/grum/mainwin.py index 652d669..a37de16 100644 --- a/grum/mainwin.py +++ b/grum/mainwin.py @@ -2,7 +2,7 @@ from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QMainWindow, QSplitter from . import assets -from .descs import DESC_TYPES, Description, PlotDescription, ImageDescription +from .descs import DESC_TYPES, Description, ImageDescription, PlotDescription from .dictlist import DictList from .exampledata import exampledata from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog @@ -90,11 +90,11 @@ class MainWindow(QMainWindow): if not offline: self.rst = rst = RPCServerThread(host, port, doc_title_suffix=title) rst.start() + rst.server.register_function(self.new_image) rst.server.register_function(self.new_plot) rst.server.register_function(self.append_data) rst.server.register_function(self.extend_data) rst.server.register_function(self.set_data) - rst.server.register_function(self.new_image) self.sig_make_new_subwin.connect(self.on_make_new_subwin) @@ -106,6 +106,20 @@ class MainWindow(QMainWindow): # Remote API calls + def new_image(self, name, cfg): + """ + Create a new image using the configuration dict . + The configuration is forwarded to the constructor of ImageDescription. + Allowed keys are: title, xlabel, ylabel, image. + """ + desc = self.add_new_desc_to_list(ImageDescription, name, cfg) + if self.menu_settings.checkboxes["Open new plots"].isChecked(): + sub = self.mdi.findSubWindow(name) + if sub: + sub.pw.setImage(desc.data) #TODO lacks the list sync + else: + self.sig_make_new_subwin.emit(name, desc) + def new_plot(self, name, cfg): """ Create a new plot using the configuration dict . @@ -147,20 +161,6 @@ class MainWindow(QMainWindow): desc.data = data self.sync_item_and_plots(item) - def new_image(self, name, cfg): - """ - Create a new image using the configuration dict . - The configuration is forwarded to the constructor of ImageDescription. - Allowed keys are: title, xlabel, ylabel, image. - """ - desc = self.add_new_desc_to_list(ImageDescription, name, cfg) - if self.menu_settings.checkboxes["Open new plots"].isChecked(): - sub = self.mdi.findSubWindow(name) - if sub: - sub.pw.setImage(desc.data) #TODO lacks the list sync - else: - self.sig_make_new_subwin.emit(name, desc) - # Signal callbacks From 438d5c1eeaa052d50130a7ba864d2b60dff77382 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 12 May 2023 11:17:18 +0200 Subject: [PATCH 15/21] added image example --- grum/exampledata.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/grum/exampledata.py b/grum/exampledata.py index 1925b55..c6edab9 100644 --- a/grum/exampledata.py +++ b/grum/exampledata.py @@ -1,6 +1,6 @@ import numpy as np -from .descs import PlotDescription +from .descs import PlotDescription, ImageDescription X = np.arange(100) / 10 @@ -31,4 +31,18 @@ for name, (xs, ys) in exampledata_raw.items(): ) +name = "image" +xdim = ydim = 100 +size = xdim * ydim +shape = (xdim, ydim) + +img = np.arange(size).reshape(shape) / size +img += np.random.random(shape) / 10 + +exampledata[name] = ImageDescription( + name, + image=img +) + + From ae70ac86a89adbd121c1341c15d582fa1d56a1c0 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 12 May 2023 14:28:07 +0200 Subject: [PATCH 16/21] added labels to example image --- grum/exampledata.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/grum/exampledata.py b/grum/exampledata.py index c6edab9..2a59d7f 100644 --- a/grum/exampledata.py +++ b/grum/exampledata.py @@ -41,7 +41,9 @@ img += np.random.random(shape) / 10 exampledata[name] = ImageDescription( name, - image=img + image=img, + xlabel="x", + ylabel="y" ) From 0742b9de0f96ab5310844de633c0066046e17ea1 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 12 May 2023 14:28:40 +0200 Subject: [PATCH 17/21] fixed axis labels --- grum/descs/imgdesc.py | 4 ++-- grum/mdi/mdisubimg.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/grum/descs/imgdesc.py b/grum/descs/imgdesc.py index be990ec..599eeb6 100644 --- a/grum/descs/imgdesc.py +++ b/grum/descs/imgdesc.py @@ -34,10 +34,10 @@ class ImageDescription(Description): plotwidget.setTitle(self.title) if self.xlabel: - plotwidget.setLabel("bottom", self.xlabel) + plotwidget.getView().setLabel("bottom", self.xlabel) if self.ylabel: - plotwidget.setLabel("left", self.ylabel) + plotwidget.getView().setLabel("left", self.ylabel) return res diff --git a/grum/mdi/mdisubimg.py b/grum/mdi/mdisubimg.py index a3adce1..9f78974 100644 --- a/grum/mdi/mdisubimg.py +++ b/grum/mdi/mdisubimg.py @@ -7,7 +7,7 @@ class MDISubImage(MDISubWindow): def __init__(self, name, desc, *args, **kwargs): super().__init__(name, *args, **kwargs) - self.pw = pw = pg.ImageView() + self.pw = pw = pg.ImageView(view=pg.PlotItem()) # for axis ticks and labels, view needs to be a PlotItem self.setWidget(pw) # connect to plot mouse-over event From cf24d54b62116186c10385edec68c4c3424a3fdd Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 12 May 2023 14:35:04 +0200 Subject: [PATCH 18/21] allow to set levels --- grum/descs/imgdesc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/grum/descs/imgdesc.py b/grum/descs/imgdesc.py index 599eeb6..4706638 100644 --- a/grum/descs/imgdesc.py +++ b/grum/descs/imgdesc.py @@ -4,12 +4,13 @@ from .desc import Description class ImageDescription(Description): - def __init__(self, name, title=None, xlabel=None, ylabel=None, image=None): + def __init__(self, name, title=None, xlabel=None, ylabel=None, image=None, levels=None): self.name = name self.title = title self.xlabel = xlabel self.ylabel = ylabel self.image = image + self.levels = levels #TODO: might be better to use vmin and vmax @property def data(self): @@ -28,7 +29,7 @@ class ImageDescription(Description): def make_plot(self, plotwidget, style): - res = plotwidget.setImage(self.data) + res = plotwidget.setImage(self.data, levels=self.levels) if self.title: plotwidget.setTitle(self.title) From dcc554eab84b025cfeac3211d128bd177849b090 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 12 May 2023 14:59:18 +0200 Subject: [PATCH 19/21] allow to set color map --- grum/descs/imgdesc.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/grum/descs/imgdesc.py b/grum/descs/imgdesc.py index 4706638..0d29e75 100644 --- a/grum/descs/imgdesc.py +++ b/grum/descs/imgdesc.py @@ -1,16 +1,18 @@ import numpy as np +import pyqtgraph as pg from .desc import Description class ImageDescription(Description): - def __init__(self, name, title=None, xlabel=None, ylabel=None, image=None, levels=None): + def __init__(self, name, title=None, xlabel=None, ylabel=None, image=None, levels=None, cmap="viridis"): self.name = name self.title = title self.xlabel = xlabel self.ylabel = ylabel self.image = image self.levels = levels #TODO: might be better to use vmin and vmax + self.cmap = cmap @property def data(self): @@ -40,6 +42,10 @@ class ImageDescription(Description): if self.ylabel: plotwidget.getView().setLabel("left", self.ylabel) + if self.cmap: + cm = pg.colormap.get(self.cmap) + plotwidget.setColorMap(cm) + return res From d958d89da45196a567fc95c4d19f90695f5510b5 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 23 May 2023 10:19:52 +0200 Subject: [PATCH 20/21] added and use constants for default sub window size --- grum/mdi/mdisubwin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/grum/mdi/mdisubwin.py b/grum/mdi/mdisubwin.py index 64c45a1..03c5518 100644 --- a/grum/mdi/mdisubwin.py +++ b/grum/mdi/mdisubwin.py @@ -4,12 +4,17 @@ from PyQt5.QtWidgets import QMdiSubWindow from .. import assets +SUB_WIN_WIDTH = 640 +SUB_WIN_HEIGHT = 480 + + class MDISubWindow(QMdiSubWindow): def __init__(self, title, *args, **kwargs): super().__init__(*args, **kwargs) self.setWindowTitle(title) self.setWindowIcon(assets.char()) + self.resize(SUB_WIN_WIDTH, SUB_WIN_HEIGHT) # without this, the SubWindow is not removed from the subWindowList self.setAttribute(Qt.WA_DeleteOnClose) From fcca087b51efd6431f172427af364c1be9fd2336 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 23 May 2023 10:41:06 +0200 Subject: [PATCH 21/21] adjusted tests --- tests/test_mainwin.py | 32 +++++++++++++++++--------------- tests/test_plotdesc.py | 1 + 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/test_mainwin.py b/tests/test_mainwin.py index 2ec4d2a..53751d9 100644 --- a/tests/test_mainwin.py +++ b/tests/test_mainwin.py @@ -14,7 +14,7 @@ from grum.mainwin import MainWindow from grum.mdi import MDIArea, MDISubMultiPlot, MDISubPlot from grum.menus import BarMenu from grum.menus.rclickmenu import RClickMenu -from grum.descs import PlotDescription +from grum.descs import Description, PlotDescription from grum.rpc import RPCServerThread @@ -47,7 +47,7 @@ class TestMainWin: for key in mw.lst.lst.items: assert isinstance(mw.lst.lst.get(key), DictListItem) - assert isinstance(mw.lst.lst.get(key).value, PlotDescription) + assert isinstance(mw.lst.lst.get(key).value, Description) assert isinstance(mw.lst.menu, RClickMenu) assert isinstance(mw.menu_settings, BarMenu) @@ -65,7 +65,7 @@ class TestMainWin: xlabel = "xlabel" ylabel = "ylabel" cfg = {"title": title, "xlabel": xlabel, "ylabel": ylabel} - spy_sig_make_new_plot = QSignalSpy(mw.sig_make_new_plot) + spy_sig_make_new_subwin = QSignalSpy(mw.sig_make_new_subwin) mw.new_plot(name, cfg=cfg) @@ -76,28 +76,28 @@ class TestMainWin: assert mw.lst.lst.get(name).value.ylabel == ylabel assert mw.menu_settings.checkboxes["Open new plots"].isChecked() - assert len(spy_sig_make_new_plot) == 1 # assert called once - assert spy_sig_make_new_plot[0][0] == name # assert called with name - assert isinstance(spy_sig_make_new_plot[0][1], PlotDescription) + assert len(spy_sig_make_new_subwin) == 1 # assert called once + assert spy_sig_make_new_subwin[0][0] == name # assert called with name + assert isinstance(spy_sig_make_new_subwin[0][1], PlotDescription) mw.menu_settings.checkboxes["Open new plots"].setChecked(False) assert mw.menu_settings.checkboxes["Open new plots"].isChecked() == False - spy_sig_make_new_plot = QSignalSpy(mw.sig_make_new_plot) + spy_sig_make_new_subwin = QSignalSpy(mw.sig_make_new_subwin) mw.new_plot("new_name", cfg) - assert len(spy_sig_make_new_plot) == 0 # assert not called + assert len(spy_sig_make_new_subwin) == 0 # assert not called mw.menu_settings.checkboxes["Open new plots"].setChecked(True) assert mw.menu_settings.checkboxes["Open new plots"].isChecked() == True - spy_sig_make_new_plot = QSignalSpy(mw.sig_make_new_plot) + spy_sig_make_new_subwin = QSignalSpy(mw.sig_make_new_subwin) new_name_item = mw.lst.lst.get("new_name") sub = MDISubPlot("new_name", new_name_item.value) mw.mdi.add(sub) mw.new_plot("new_name", cfg) - assert len(spy_sig_make_new_plot) == 0 # assert not called + assert len(spy_sig_make_new_subwin) == 0 # assert not called def test_append_data(self): @@ -122,17 +122,19 @@ class TestMainWin: assert sine_item.set_alarm.call_args[0][0] == False - def test_on_make_new_plot(self): + def test_on_make_new_subwin(self): mw = self.mw mw.make_subwin = mock.MagicMock() - args = (1, 2, "name") - kwargs = {"title": "plot_title"} + name = "test" + cfg = {"title": "title"} - mw.on_make_new_plot(args, kwargs) + desc = PlotDescription(name, *cfg) - mw.make_subwin.assert_called_once_with(MDISubPlot, args, kwargs) + mw.on_make_new_subwin(name, desc) + + mw.make_subwin.assert_called_once_with(MDISubPlot, name, desc) def test_on_dclick_list_item(self): diff --git a/tests/test_plotdesc.py b/tests/test_plotdesc.py index 5cf2263..0d46110 100644 --- a/tests/test_plotdesc.py +++ b/tests/test_plotdesc.py @@ -78,6 +78,7 @@ def test_to_dict(): "xs": [1, 2], "ylabel": "plot_ylabel", "ys": [3, 4], + "type": "plot" }