From cb1c9637d2f036e3e48d852fbd5f62a8fafc63dd Mon Sep 17 00:00:00 2001 From: gao <81c52427-d970-4b21-86dc-fd526913f4c9@localhost> Date: Sat, 30 Jul 2022 21:50:53 +0200 Subject: [PATCH] Update for plotting function. --- .idea/workspace.xml | 139 ++++++++++-- {dylab => HelperClasses}/AbsorptionImaging.py | 19 +- {dylab => HelperClasses}/Camera.py | 0 {dylab => HelperClasses}/DyTransition.py | 2 +- {dylab => HelperClasses}/Fitting.py | 2 +- {dylab => HelperClasses}/FittingFunction.py | 0 HelperClasses/Plotting/DataExtractorLyse.py | 134 ++++++++++++ HelperClasses/Plotting/MainPlotPanel.py | 104 +++++++++ HelperClasses/Plotting/PlottingData.py | 172 +++++++++++++++ HelperClasses/Plotting/PlottingPanel.py | 42 ++++ HelperClasses/Plotting/Quick1DPlot.py | 128 ++++++++++++ HelperClasses/Plotting/Unknown.py | 188 +++++++++++++++++ HelperClasses/Plotting/WidgetDataSelector.py | 131 ++++++++++++ HelperClasses/Plotting/WidgetFakeColorPlot.py | 139 ++++++++++++ HelperClasses/Plotting/WidgetPlot.py | 63 ++++++ HelperClasses/Plotting/WidgetQuick2DPlot.py | 166 +++++++++++++++ .../Plotting/WidgetQuickPlotGenerator.py | 55 +++++ .../Plotting/WidgetQuickWaterFlowPlot.py | 197 ++++++++++++++++++ HelperClasses/Plotting/__init__.py | 0 {dylab => HelperClasses}/TransitionClass.py | 0 {dylab => HelperClasses}/__init__.py | 0 __init__.py | 2 +- build/lib/dylab/AbsorptionImaging.py | 2 +- build/lib/dylab/DyTransition.py | 2 +- build/lib/dylab/Fitting.py | 2 +- setup.py | 2 +- test_absorption_imaging.py | 15 +- test_fitting.py | 4 +- test_living_plot.py | 57 +++++ 29 files changed, 1733 insertions(+), 34 deletions(-) rename {dylab => HelperClasses}/AbsorptionImaging.py (95%) rename {dylab => HelperClasses}/Camera.py (100%) rename {dylab => HelperClasses}/DyTransition.py (91%) rename {dylab => HelperClasses}/Fitting.py (99%) rename {dylab => HelperClasses}/FittingFunction.py (100%) create mode 100644 HelperClasses/Plotting/DataExtractorLyse.py create mode 100644 HelperClasses/Plotting/MainPlotPanel.py create mode 100644 HelperClasses/Plotting/PlottingData.py create mode 100644 HelperClasses/Plotting/PlottingPanel.py create mode 100644 HelperClasses/Plotting/Quick1DPlot.py create mode 100644 HelperClasses/Plotting/Unknown.py create mode 100644 HelperClasses/Plotting/WidgetDataSelector.py create mode 100644 HelperClasses/Plotting/WidgetFakeColorPlot.py create mode 100644 HelperClasses/Plotting/WidgetPlot.py create mode 100644 HelperClasses/Plotting/WidgetQuick2DPlot.py create mode 100644 HelperClasses/Plotting/WidgetQuickPlotGenerator.py create mode 100644 HelperClasses/Plotting/WidgetQuickWaterFlowPlot.py create mode 100644 HelperClasses/Plotting/__init__.py rename {dylab => HelperClasses}/TransitionClass.py (100%) rename {dylab => HelperClasses}/__init__.py (100%) create mode 100644 test_living_plot.py diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 67fa883..16d701d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,13 +1,36 @@ - + + + + + + + + + + + + + + + + - - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + @@ -75,7 +172,12 @@ 1658317648754 - + + + + + + 1658494877565 @@ -91,7 +193,14 @@ - @@ -107,11 +216,13 @@ + - @@ -127,13 +238,14 @@ + + + \ No newline at end of file diff --git a/dylab/AbsorptionImaging.py b/HelperClasses/AbsorptionImaging.py similarity index 95% rename from dylab/AbsorptionImaging.py rename to HelperClasses/AbsorptionImaging.py index 9a5b313..ff6e45d 100644 --- a/dylab/AbsorptionImaging.py +++ b/HelperClasses/AbsorptionImaging.py @@ -2,7 +2,7 @@ from lyse import * from pylab import * import numpy as np import scipy.constants as constant -from dylab import DyTransition, Camera +from HelperClasses import DyTransition, Camera import matplotlib.pyplot as plt from matplotlib import colors as mcolors import colorsys @@ -54,6 +54,7 @@ class absorption_imaging: # select effective region self.image_absorption_cut = None + self.image_absorption_remove_background = None self.x_start = None self.y_start = None self.x_end = None @@ -134,6 +135,10 @@ class absorption_imaging: self.image_absorption_cut = self.image_absorption[self.y_start:self.y_end, self.x_start:self.x_end] + back_ground = self.corner_subtract(self.image_absorption_cut, 0.1, 0.1) + self.image_absorption_cut = self.image_absorption_cut - back_ground + self.image_absorption_remove_background = self.image_absorption - back_ground + return self.image_absorption_cut # The function do the analyzation for absorption imaging @@ -184,19 +189,11 @@ class absorption_imaging: if self.atom_number is not None and not force_to_run: return self.atom_number - if self.image_absorption is None or not force_to_run: + if self.image_absorption is None: self.image_absorption = self.get_image_absorption() if self.image_absorption_cut is None: - self.image_absorption_cut = self.image_absorption - self.x_start = 0 - self.x_end = self.image_absorption.shape[1] - self.y_start = 0 - self.y_end = self.image_absorption.shape[0] - - back_ground = self.corner_subtract(self.image_absorption_cut, 0.1, 0.1) - self.image_absorption_cut = self.image_absorption_cut - back_ground - self.image_absorption = self.image_absorption - back_ground + self.select_effective_data((0, 0), np.shape(self.image_absorption)) OD_act = self.image_absorption_cut diff --git a/dylab/Camera.py b/HelperClasses/Camera.py similarity index 100% rename from dylab/Camera.py rename to HelperClasses/Camera.py diff --git a/dylab/DyTransition.py b/HelperClasses/DyTransition.py similarity index 91% rename from dylab/DyTransition.py rename to HelperClasses/DyTransition.py index d203361..aabef80 100644 --- a/dylab/DyTransition.py +++ b/HelperClasses/DyTransition.py @@ -1,7 +1,7 @@ # Functions create constant dictionary storing transition information of Dy. import scipy.constants as constant -from dylab import TransitionClass +from HelperClasses import TransitionClass def creat_Dy421(): diff --git a/dylab/Fitting.py b/HelperClasses/Fitting.py similarity index 99% rename from dylab/Fitting.py rename to HelperClasses/Fitting.py index 5dc8f5e..e909591 100644 --- a/dylab/Fitting.py +++ b/HelperClasses/Fitting.py @@ -1,5 +1,5 @@ import numpy as np -from dylab import FittingFunction +from HelperClasses import FittingFunction import laserbeamsize as lbs import lmfit from lmfit.minimizer import MinimizerResult diff --git a/dylab/FittingFunction.py b/HelperClasses/FittingFunction.py similarity index 100% rename from dylab/FittingFunction.py rename to HelperClasses/FittingFunction.py diff --git a/HelperClasses/Plotting/DataExtractorLyse.py b/HelperClasses/Plotting/DataExtractorLyse.py new file mode 100644 index 0000000..58228b7 --- /dev/null +++ b/HelperClasses/Plotting/DataExtractorLyse.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 15 15:08:46 2021 + +@author: Nick Sauerwein +""" + +import lyse +import numpy as np + +import os.path +import time + +import h5py + + +def get_mtime(filename): + return time.ctime(os.path.getmtime(filename)) + + +class DataExtractorManager: + + def __init__(self, data_path=None): + + self.data_extractors = {} + self.h5_file = None + self.data_path = data_path + self.local_data_changed = False + + def update_local_data(self, h5_path): + with h5py.File(h5_path, 'r') as h5_file: + self.local_data_changed = False + for key in self.data_extractors: + self.data_extractors[key].update_local_data(h5_path, h5_file=h5_file) + if self.data_extractors[key].local_data_changed: + self.local_data_changed = True + + def clean_memory(self, h5_paths): + for key in self.data_extractors: + self.data_extractors[key].clean_memory(h5_paths) + + def __getitem__(self, key): + return self.data_extractors[key] + + def __setitem__(self, key, de): + self.data_extractors[key] = de + + def add_data_extractor(self, label, type, data_path): + + result = DataExtractor(label, type, data_path) + self.data_extractors[label] = result + + return result + + def get_data(self, h5_path, h5_file=None): + result = {} + for key in self.data_extractors: + result[key] = self.data_extractors[key].get_data(h5_path, h5_file=h5_file) + + return result + + +class DataExtractor: + def __init__(self, label, type, data_path, load_to_ram=True): + + self.load_to_ram = load_to_ram + + self.local_datas = {} + self.local_mtimes = {} + + if self.load_to_ram: + self.local_data_changed = True + + # The path of data in hdf5 file + self.data_path = data_path + self.data_type = type + self.data_label = label + + def extract_data(self, h5_path, h5_file=None): + + result = None + + data_handle = lyse.Run(h5_path, no_write=True) + + if self.data_type == 'array': + try: + result = data_handle.get_result_array(self.data_path[0], self.data_path[1], + h5_file=h5_file) + result = np.array(result) + except: + result = None + + elif self.data_type == 'single': + try: + if len(self.data_path) == 1: + result = data_handle.get_globals(self.data_path[0], h5_file=h5_file) + else: + result = data_handle.get_result(self.data_path[0], self.data_path[1], + h5_file=h5_file) + except: + result = None + + return result + + def update_local_data(self, h5_path, h5_file=None): + if h5_path in self.local_datas and self.local_mtimes[h5_path] == get_mtime(h5_path): + self.local_data_changed = False + elif self.load_to_ram: + self.local_datas[h5_path] = self.extract_data(h5_path, h5_file=h5_file) + self.local_mtimes[h5_path] = get_mtime(h5_path) + self.local_data_changed = True + + def update_local_datas(self): + for key in self.local_datas: + self.update_local_data(key) + + def get_data(self, h5_path, h5_file=None): + + if self.load_to_ram: + + self.update_local_data(h5_path) + + return self.local_datas[h5_path] + else: + return self.extract_data(h5_path, h5_file=h5_file) + + def clean_memory(self, h5_paths): + for key in list(self.local_datas): + if key not in h5_paths.to_list(): + del self.local_datas[key] + del self.local_mtimes[key] + + self.local_data_changed = True + self.update_local_datas() diff --git a/HelperClasses/Plotting/MainPlotPanel.py b/HelperClasses/Plotting/MainPlotPanel.py new file mode 100644 index 0000000..874e5f3 --- /dev/null +++ b/HelperClasses/Plotting/MainPlotPanel.py @@ -0,0 +1,104 @@ +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtGui +import numpy as np + +from pyqtgraph.dockarea import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +import lyse +from .WidgetDataSelector import ShotSelector + + +class MainPlotPanel(QtGui.QMainWindow): + + def __init__(self, h5_paths, n_rows=3, **kwargs): + + self.h5_paths = h5_paths + + super().__init__(**kwargs) + + pg.mkQApp() + + self.n_rows = n_rows + + self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) + self.area = DockArea() + + self.setCentralWidget(self.area) + self.resize(1000, 500) + + self.dshotselector = Dock("Shot selector") + self.shotselector = ShotSelector() + + self.dshotselector.addWidget(self.shotselector) + self.area.addDock(self.dshotselector, 'bottom') + + # self.qpg_dock = Dock("Quick Plot Generator") + # self.qpg_dock.addWidget(QuickPlotGenerator(self)) + # self.qpg_dock.setMinimumSize(self.qpg_dock.minimumSizeHint()) + # self.area.addDock(self.qpg_dock) + + self.show() + + self.plots = {} + + self.shotselector.valueChanged.connect(self.refresh) + self.shotselector.selectionChanged.connect(self.refresh) + + self.df = lyse.data() + + def add_plot_dock(self, plot_name, plot_widget, **kwargs): + if plot_name not in self.plots: + plot_widget.plot_name = plot_name + + dock = Dock(plot_name, **kwargs) + + dock.sigClosed.connect(self.remove_plot) + dock.addWidget(plot_widget) + if len(self.plots): + if len(self.plots) % self.n_rows == 2: + position = 'right' + self.area.addDock(dock, position) + if len(self.plots) % (self.n_rows * 2) in [0, 1]: + position = 'bottom' + self.area.addDock(dock, position, relativeTo=self.area.docks[list(self.plots.keys())[-1]]) + if len(self.plots) % (self.n_rows * 2) in [3, 4]: + position = 'top' + self.area.addDock(dock, position, relativeTo=self.area.docks[list(self.plots.keys())[-1]]) + print(len(self.plots) % (self.n_rows * 2), len(self.plots) % self.n_rows) + else: + self.area.addDock(dock, 'right') + + self.plots[plot_name] = plot_widget + else: + print(f'Plot {plot_name} already exists. Please choose different name.') + + def remove_plot(self, dock): + del self.plots[dock.title()] + + def update_h5_paths(self, h5_paths): + self.h5_paths = h5_paths + + self.shotselector.update_nshots(len(h5_paths)) + + for plot_name in self.plots: + self.plots[plot_name].data_extractor_manager.clean_memory(h5_paths) + + def refresh(self, h5_path=None): + if len(self.h5_paths): + + self.h5_paths_selected = self.h5_paths.iloc[self.shotselector.get_selected_indices()] + + if h5_path == None: + i = self.shotselector.get_current_index() + h5_path = self.h5_paths.iloc[i] + + for plot_name in self.plots: + self.plots[plot_name].data_extractor_manager.update_local_data(h5_path) + + # self.data_extractor_manager.update_local_data(h5_path) + + for plot_name, plot in self.plots.items(): + plot.update_from_h5(h5_path) + else: + pass \ No newline at end of file diff --git a/HelperClasses/Plotting/PlottingData.py b/HelperClasses/Plotting/PlottingData.py new file mode 100644 index 0000000..3fbe40c --- /dev/null +++ b/HelperClasses/Plotting/PlottingData.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 15 15:08:46 2021 + +@author: Nick Sauerwein +""" + +import lyse +import numpy as np + +import os.path +import time + +import h5py + + +def get_mtime(filename): + return time.ctime(os.path.getmtime(filename)) + + +class DataExtractorManager: + + def __init__(self): + + self.data_extractors = {} + + def update_local_data(self, h5_path): + with h5py.File(h5_path, 'r') as h5_file: + for key in self.data_extractors: + self.data_extractors[key].update_local_data(h5_path, h5_file=h5_file) + + def clean_memory(self, h5_paths): + for key in self.data_extractors: + self.data_extractors[key].clean_memory(h5_paths) + + def __getitem__(self, key): + return self.data_extractors[key] + + def __setitem__(self, key, de): + self.data_extractors[key] = de + + +class DataExtractor: + def __init__(self, load_to_ram=True): + + self.load_to_ram = load_to_ram + + self.local_datas = {} + self.local_mtimes = {} + + if self.load_to_ram: + self.local_data_changed = True + + def update_local_data(self, h5_path, h5_file=None): + if h5_path in self.local_datas and self.local_mtimes[h5_path] == get_mtime(h5_path): + self.local_data_changed = False + elif self.load_to_ram: + self.local_datas[h5_path] = self.extract_data(h5_path, h5_file=h5_file) + self.local_mtimes[h5_path] = get_mtime(h5_path) + self.local_data_changed = True + + def update_local_datas(self): + for key in self.local_datas: + self.update_local_data(key) + + def get_data(self, h5_path, h5_file=None): + + if self.load_to_ram: + + self.update_local_data(h5_path) + + return self.local_datas[h5_path] + else: + return self.extract_data(h5_path, h5_file=h5_file) + + def clean_memory(self, h5_paths): + for key in list(self.local_datas): + if key not in h5_paths.to_list(): + del self.local_datas[key] + del self.local_mtimes[key] + + self.local_data_changed = True + self.update_local_datas() + + +class MultiDataExtractor(DataExtractor): + def __init__(self, **kwargs): + + super().__init__(load_to_ram=False, **kwargs) + + self.data_extractors = {} + self.children_changed = False + + def extract_data(self, h5_path, h5_file=None): + + data = {} + + for key in self.data_extractors: + data[key] = self.data_extractors[key].get_data(h5_path, h5_file=h5_file) + + self.children_changed = False + + return [data] + + def clean_children(self, keys): + self.children_changed = False + for key in list(self.data_extractors): + if key not in keys: + del self.data_extractors[key] + self.children_changed = True + + def clean_memory(self, h5_paths): + for key in self.data_extractors: + self.data_extractors[key].clean_memory(h5_paths) + + def __getitem__(self, key): + return self.data_extractors[key] + + def __setitem__(self, key, de): + self.children_changed = True + self.data_extractors[key] = de + + @property + def local_data_changed(self): + return any( + [self.data_extractors[key].local_data_changed for key in self.data_extractors]) or self.children_changed + + +class EmptyDataExtractor(DataExtractor): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def extract_data(self, h5_path, h5_file=None): + return [] + + +class ArrayDataExtractor(DataExtractor): + + def __init__(self, idx, **kwargs): + super().__init__(**kwargs) + self.idx = idx + + def extract_data(self, h5_path, h5_file=None): + run = lyse.Run(h5_path, no_write=True) + try: + res = run.get_result_array(self.idx[0], self.idx[1], h5_file=h5_file) + except: + res = None + + return res + + +class SingleDataExtractor(DataExtractor): + + def __init__(self, idx, **kwargs): + super().__init__(**kwargs) + self.idx = idx + print(len(idx)) + + def extract_data(self, h5_path, h5_file=None): + run = lyse.Run(h5_path, no_write=True) + try: + if len(self.idx) == 1: + res = run.get_global(self.idx[0], h5_file=h5_file) + else: + res = run.get_result(self.idx[0], self.idx[1], h5_file=h5_file) + except: + raise + res = None + + return res diff --git a/HelperClasses/Plotting/PlottingPanel.py b/HelperClasses/Plotting/PlottingPanel.py new file mode 100644 index 0000000..631ac19 --- /dev/null +++ b/HelperClasses/Plotting/PlottingPanel.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 10 15:35:59 2021 + +@author: nicks +""" + +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtGui +import numpy as np + +from pyqtgraph.dockarea import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +from PIL import ImageColor + +import lyse + +import collections + +import h5py +from pandas.api.types import is_numeric_dtype +from sortedcontainers import SortedSet +from scipy.interpolate import griddata + +# from data_extractors import MultiDataExtractor, SingleDataExtractor, ArrayDataExtractor, EmptyDataExtractor, \ +# DataExtractorManager + +color_palette_html = ['#1f77b4', + '#ff7f0e', + '#2ca02c', + '#d62728', + '#9467bd', + '#8c564b', + '#e377c2', + '#7f7f7f', + '#bcbd22', + '#17becf'] + +color_palette = [ImageColor.getcolor(color, "RGB") for color in color_palette_html] + diff --git a/HelperClasses/Plotting/Quick1DPlot.py b/HelperClasses/Plotting/Quick1DPlot.py new file mode 100644 index 0000000..96b4a72 --- /dev/null +++ b/HelperClasses/Plotting/Quick1DPlot.py @@ -0,0 +1,128 @@ +class Quick1DPlot(QuickDataPlot): + + def __init__(self, ap, **kwargs): + + super().__init__(ap, **kwargs) + self.nplots = 0 + + self.table.setColumnCount(5) + self.table.setRowCount(1) + self.table.setColumnWidth(0, 200) + self.table.setColumnWidth(1, 200) + self.table.setColumnWidth(2, 30) + self.table.setColumnWidth(3, 30) + self.table.setColumnWidth(4, 40) + self.table.setHorizontalHeaderLabels(['xvalue', 'yvalue', 'color', 'show', 'scatter']) + + self.combos = [] + self.curves = [] + self.show_cbs = [] + self.scatter_cbs = [] + + self.mk_buttons() + + def mk_buttons(self): + self.bt_add_plot = QPushButton('Add plot', self) + self.bt_add_plot.clicked.connect(self.add_plot) + self.table.setCellWidget(self.nplots, 0, self.bt_add_plot) + + self.bt_update = QPushButton('Update', self) + self.bt_update.clicked.connect(self.update_from_h5) + self.table.setCellWidget(self.nplots, 1, self.bt_update) + + def add_plot(self): + self.nplots += 1 + self.table.setRowCount(self.nplots + 1) + combox = NumericDataCombo(self.ap.df) + comboy = NumericDataCombo(self.ap.df) + + self.combos += [[combox, comboy]] + self.table.setCellWidget(self.nplots - 1, 0, combox) + self.table.setCellWidget(self.nplots - 1, 1, comboy) + + self.table.setItem(self.nplots - 1, 2, QtGui.QTableWidgetItem()) + self.table.item(self.nplots - 1, 2).setBackground(QtGui.QColor(*color_palette[self.nplots - 1])) + + # self.table.setFixedSize(self.table.sizeHint()) + + self.curves += [ + self.plot.plot(pen=pg.mkPen(color=color_palette[self.nplots - 1], width=1.5), symbol='x', symbolPen=None, + symbolBrush=None)] + + self.show_cbs += [QCheckBox()] + self.show_cbs[self.nplots - 1].setChecked(True) + self.table.setCellWidget(self.nplots - 1, 3, self.show_cbs[self.nplots - 1]) + self.show_cbs[self.nplots - 1].stateChanged.connect(self.update_shows) + + self.scatter_cbs += [QCheckBox()] + self.scatter_cbs[self.nplots - 1].setChecked(False) + self.table.setCellWidget(self.nplots - 1, 4, self.scatter_cbs[self.nplots - 1]) + self.scatter_cbs[self.nplots - 1].stateChanged.connect(self.update_scatters) + + self.mk_buttons() + + def update_shows(self): + for k, cb in enumerate(self.show_cbs): + if cb.isChecked(): + self.curves[k].show() + else: + self.curves[k].hide() + + def update_scatters(self): + for k, cb in enumerate(self.scatter_cbs): + pen = pg.mkPen(color=color_palette[k], width=1.5) + brush = pg.mkBrush(color=color_palette[k]) + if cb.isChecked(): + self.curves[k].setSymbolBrush(brush) + self.curves[k].setPen(None) + else: + self.curves[k].setSymbolBrush(None) + self.curves[k].setPen(pen) + + def update_data_extractor(self): + + idxxs = [combo[0].get_idx() for combo in self.combos] + idxys = [combo[1].get_idx() for combo in self.combos] + + for idxx in idxxs: + if idxx not in self.data_extractor.data_extractors: + if idxx[0] == 'shot number': + self.data_extractor[idxx] = EmptyDataExtractor() + else: + self.data_extractor[idxx] = SingleDataExtractor(idxx) + + for idxy in idxys: + if idxy not in self.data_extractor.data_extractors: + if idxy[0] == 'shot number': + self.data_extractor[idxy] = EmptyDataExtractor() + else: + self.data_extractor[idxy] = SingleDataExtractor(idxy) + + self.data_extractor.clean_children(idxxs + idxys) + self.data_extractor.clean_memory(self.ap.h5_paths) + + def update(self, data=None): + + Xs = np.zeros((self.nplots, len(self.ap.h5_paths_selected))) + Ys = np.zeros((self.nplots, len(self.ap.h5_paths_selected))) + + for i, h5_path in enumerate(self.ap.h5_paths_selected): + + data = self.data_extractor.get_data(h5_path)[0] + + for k in range(self.nplots): + idxx = self.combos[k][0].get_idx() + idxy = self.combos[k][1].get_idx() + + if idxx[0] == 'shot number': + Xs[k, i] = self.ap.shotselector.get_selected_indices()[0][i] + else: + Xs[k, i] = data[idxx] + + if idxy[0] == 'shot number': + Ys[k, i] = self.ap.shotselector.get_selected_indices()[0][i] + else: + Ys[k, i] = data[idxy] + + for k in range(self.nplots): + self.curves[k].setData(Xs[k], Ys[k]) \ No newline at end of file diff --git a/HelperClasses/Plotting/Unknown.py b/HelperClasses/Plotting/Unknown.py new file mode 100644 index 0000000..4adb2e2 --- /dev/null +++ b/HelperClasses/Plotting/Unknown.py @@ -0,0 +1,188 @@ +class DataPlot(QSplitter): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.setOrientation(Qt.Vertical) + + self.plots = pg.GraphicsLayoutWidget() + + self.addWidget(self.plots) + + self.bottom = QSplitter() + self.bottom.setOrientation(Qt.Horizontal) + + self.addWidget(self.bottom) + + self.h5_path_shown = None + + def update_from_h5(self, h5_path): + if self.h5_path_shown != h5_path or self.data_extractor.local_data_changed: + self.h5_path_shown = h5_path + self.update(*self.data_extractor.extract_data(h5_path)) + + +class QuickDataPlot(DataPlot): + def __init__(self, ap, **kwargs): + + super().__init__(**kwargs) + + self.plot = self.plots.addPlot() + + for key in self.plot.axes: + ax = self.plot.getAxis(key) + # Fix Z value making the grid on top of the image + ax.setZValue(1) + self.ap = ap + self.h5_paths_shown = [] + + self.table = QTableWidget() + self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + + self.bottom.addWidget(self.table) + + self.plot_setting = PlotSettings(self.plot) + + self.bottom.addWidget(self.plot_setting) + + def update_from_h5(self, h5_path=None): + self.update_data_extractor() + if self.data_extractor.local_data_changed or collections.Counter(self.h5_paths_shown) != collections.Counter( + self.ap.h5_paths_selected): + self.h5_paths_shown = self.ap.h5_paths_selected + self.update() + + +class ExtendedCombo(QComboBox): + def __init__(self, parent=None): + super().__init__(parent) + + self.setFocusPolicy(Qt.StrongFocus) + self.setEditable(True) + self.completer = QCompleter(self) + + # always show all completions + self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) + self.pFilterModel = QSortFilterProxyModel(self) + self.pFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) + + self.completer.setPopup(self.view()) + + self.setCompleter(self.completer) + + self.lineEdit().textEdited[str].connect(self.pFilterModel.setFilterFixedString) + self.completer.activated.connect(self.setTextIfCompleterIsClicked) + + def setModel(self, model): + super().setModel(model) + self.pFilterModel.setSourceModel(model) + self.completer.setModel(self.pFilterModel) + + def setModelColumn(self, column): + self.completer.setCompletionColumn(column) + self.pFilterModel.setFilterKeyColumn(column) + super().setModelColumn(column) + + def view(self): + return self.completer.popup() + + def index(self): + return self.currentIndex() + + def setTextIfCompleterIsClicked(self, text): + if text: + index = self.findText(text) + self.setCurrentIndex(index) + + +class PlotSettings(QTableWidget): + def __init__(self, plot, **kwargs): + super().__init__(**kwargs) + + self.plot = plot + self.setColumnCount(3) + self.setColumnWidth(0, 100) + self.setColumnWidth(1, 100) + + self.setHorizontalHeaderLabels(['parameter', 'setting', 'units']) + + self.setRowCount(4) + + # title + i = 0 + self.le_title = QLineEdit() + + self.setCellWidget(i, 0, QLabel('title')) + self.setCellWidget(i, 1, self.le_title) + + self.le_title.textChanged[str].connect(self.set_title) + + # xlabel + i += 1 + self.le_xlabel = QLineEdit() + self.le_xlabel_unit = QLineEdit() + + self.setCellWidget(i, 0, QLabel('xlabel')) + self.setCellWidget(i, 1, self.le_xlabel) + self.setCellWidget(i, 2, self.le_xlabel_unit) + + self.le_xlabel.textChanged[str].connect(self.set_xlabel) + self.le_xlabel_unit.textChanged[str].connect(self.set_xlabel) + + # ylabel + i += 1 + self.le_ylabel = QLineEdit() + self.le_ylabel_unit = QLineEdit() + + self.setCellWidget(i, 0, QLabel('ylabel')) + self.setCellWidget(i, 1, self.le_ylabel) + self.setCellWidget(i, 2, self.le_ylabel_unit) + + self.le_ylabel.textChanged[str].connect(self.set_ylabel) + self.le_ylabel_unit.textChanged[str].connect(self.set_ylabel) + + # grid + i += 1 + self.cb_grid = QCheckBox() + self.setCellWidget(i, 0, QLabel('grid')) + self.setCellWidget(i, 1, self.cb_grid) + + self.cb_grid.stateChanged.connect(self.set_grid) + + def set_title(self): + self.plot.setTitle(self.le_title.text()) + + def set_xlabel(self): + self.plot.setLabel('bottom', self.le_xlabel.text(), units=self.le_xlabel_unit.text()) + + def set_ylabel(self): + self.plot.setLabel('left', self.le_ylabel.text(), units=self.le_ylabel_unit.text()) + + def set_grid(self): + self.plot.showGrid(x=self.cb_grid.isChecked(), y=self.cb_grid.isChecked(), alpha=0.3) + + +class NumericDataCombo(ExtendedCombo): + + def __init__(self, df, **kwargs): + + super().__init__(**kwargs) + self.update_model(df) + + def update_model(self, df): + model = QtGui.QStandardItemModel() + + item = QtGui.QStandardItem('shot number') + model.setItem(0, 0, item) + + i = 1 + for midx in df.columns: + if is_numeric_dtype(df.dtypes[midx]): + column_name = ','.join([x for x in midx if x]) + item = QtGui.QStandardItem(column_name) + model.setItem(i, 0, item) + i += 1 + + self.setModel(model) + + def get_idx(self): + return tuple(str(self.currentText()).split(',')) \ No newline at end of file diff --git a/HelperClasses/Plotting/WidgetDataSelector.py b/HelperClasses/Plotting/WidgetDataSelector.py new file mode 100644 index 0000000..2b65830 --- /dev/null +++ b/HelperClasses/Plotting/WidgetDataSelector.py @@ -0,0 +1,131 @@ +import pyqtgraph as pg +from pyqtgraph.Qt import QtGui +import numpy as np + +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +from sortedcontainers import SortedSet + + +# ShotSelector class for choosing which the hdf5 files are import for plotting +class ShotSelector(pg.LayoutWidget): + valueChanged = pyqtSignal() + selectionChanged = pyqtSignal() + + def __init__(self, **kwargs): + + super().__init__(**kwargs) + + self.nshots = 1 + + self.setFixedHeight(100) + + self.current_idx_le = QLineEdit(self) + + self.current_idx_le.setMaximumWidth(30) + self.current_idx_le.setValidator(QtGui.QIntValidator()) + + self.current_idx_le.setText(str(-1)) + + self.addWidget(self.current_idx_le) + + self.slider = QSlider(self) + + self.slider.setPageStep(1) + self.slider.setOrientation(Qt.Horizontal) + + self.addWidget(self.slider, colspan=2) + + self.nextRow() + + self.addWidget(QLabel('index selector')) + + self.idx_select_le = QLineEdit(self) + self.idx_select_le.setText(':') + self.addWidget(self.idx_select_le) + + self.warning = QLabel() + self.warning.setMargin(5) + self.update_warning() + self.addWidget(self.warning) + + self.update_nshots(self.nshots) + + self.idx_select_le.editingFinished.connect(self.update_selection) + self.current_idx_le.editingFinished.connect(self.setSliderValue) + self.slider.valueChanged.connect(self.setLabelValue) + + def update_nshots(self, nshots): + self.nshots = nshots + self.idx = np.arange(self.nshots) + self.slider.setRange(0, self.nshots - 1) + + self.update_selection() + self.setSliderValue() + + def update_warning(self, warning=''): + if warning == '': + self.warning.setStyleSheet("background-color: lightgreen") + warning = 'all good' + else: + self.warning.setStyleSheet("background-color: red") + + self.warning.setText(warning) + + def update_selection(self): + self.update_warning() + + slice_text = self.idx_select_le.text() + slices = slice_text.split(',') + + self.idx_selected = SortedSet([]) + for s in slices: + try: + scope = locals() + + select = eval('self.idx[' + s + ']', scope) + + if isinstance(select, np.ndarray): + for idx in select: + self.idx_selected.add(idx) + else: + self.idx_selected.add(select) + + except: + self.update_warning('problem in selected indeces') + return 0 + + self.slider.setRange(0, len(self.idx_selected) - 1) + + if int(self.current_idx_le.text()) % self.nshots not in self.idx_selected: + self.current_idx_le.setText(self.idx_selected[-1]) + + self.update_warning('last index not in selection
-> setting last selected') + + self.selectionChanged.emit() + + def setLabelValue(self, value): + newval = self.idx_selected[value] + + if newval != self.get_current_index(): + self.current_idx_le.setText(str(newval)) + + self.valueChanged.emit() + + def setSliderValue(self): + self.update_warning() + + value = int(self.current_idx_le.text()) + + try: + value_sl = self.idx_selected.index(value % len(self.idx)) + self.slider.setValue(value_sl) + except ValueError: + self.update_warning('set index not in selection
ignore') + + def get_current_index(self): + return int(self.current_idx_le.text()) % self.nshots + + def get_selected_indices(self): + return (np.array(self.idx_selected),) \ No newline at end of file diff --git a/HelperClasses/Plotting/WidgetFakeColorPlot.py b/HelperClasses/Plotting/WidgetFakeColorPlot.py new file mode 100644 index 0000000..cc7b4a7 --- /dev/null +++ b/HelperClasses/Plotting/WidgetFakeColorPlot.py @@ -0,0 +1,139 @@ +import pyqtgraph as pg +from pyqtgraph import Qt +from pyqtgraph.Qt import QtCore, QtGui +import numpy as np + +from pyqtgraph.dockarea import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +from .DataExtractorLyse import DataExtractorManager +# import DataExtractorLyse +from .WidgetPlot import DataPlot +# import WidgetPlot + +from PIL import ImageColor + +color_palette_html = ['#1f77b4', + '#ff7f0e', + '#2ca02c', + '#d62728', + '#9467bd', + '#8c564b', + '#e377c2', + '#7f7f7f', + '#bcbd22', + '#17becf'] + +color_palette = [ImageColor.getcolor(color, "RGB") for color in color_palette_html] + + +class FakeColorPlot(DataPlot): + def __init__(self, title, data_path=None, **kwargs): + + super().__init__(title, **kwargs) + + # set data extractor + self.data_extractor_manager = DataExtractorManager(data_path) + self.data_extractor_manager.add_data_extractor('data_img', 'array', data_path) + self.data_extractor_manager.add_data_extractor('x', 'array', (data_path[0], 'x')) + self.data_extractor_manager.add_data_extractor('y', 'array', (data_path[0], 'y')) + self.data_extractor_manager.add_data_extractor('warning', 'single', (data_path[0], 'warning')) + + self.setMinimumHeight(550) + self.setMinimumWidth(550) + + # Add imaging + self.img = pg.ImageItem() + self.aximg = self.plots.addPlot(title="") + self.aximg.addItem(self.img) + + # Isocurve draplotsg + self.iso = pg.IsocurveItem(level=1000, pen=color_palette[2]) + self.iso.setParentItem(self.img) + self.iso.setZValue(5) + + # Contrast/color control + self.hist = pg.HistogramLUTItem() + self.hist.setImageItem(self.img) + self.plots.addItem(self.hist) + + # Draggable line for setting isocurve level + self.isoLine = pg.InfiniteLine(angle=0, movable=True, pen=color_palette[2]) + self.hist.vb.addItem(self.isoLine) + self.hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier + self.isoLine.setValue(1000) + self.isoLine.setZValue(1000) # bring iso line above contrast controls + self.isoLine.sigDragged.connect(self.updateIsocurve) + + # Monkey-patch the image to use our custom hover function. + # This is generally discouraged (you should subclass ImageItem instead), + # but it works for a very simple use like this. + self.img.hoverEvent = self.imageHoverEvent + + self.plots.nextRow() + self.plots.nextColumn() + self.img.translate(-0.5, -0.5) + + self.scalex = 1 + self.scaley = 1 + + self.cx = 0 + self.cy = 0 + + self.data_image = None + + def update(self, h5_path): + + data = self.data_extractor_manager.get_data(h5_path) + data_img = data['data_img'] + x = data['x'] + y = data['y'] + if x is None: + x = np.linspace(0, data_img.shape[0], data_img.shape[0]) + if y is None: + y = np.linspace(0, data_img.shape[1], data_img.shape[1]) + warning = data['warning'] + + # update plots + self.img.setImage(data_img.T) + self.iso.setData(data_img.T) + + self.data_img = data_img + + # set position and scale of image + newscalex = x[1] - x[0] + newscaley = y[1] - y[0] + + transx = (x[0] - (self.cx - 0.5 * self.scalex)) / newscalex - 0.5 + transy = (y[0] - (self.cy - 0.5 * self.scaley)) / newscaley - 0.5 + + self.img.scale(newscalex / self.scalex, newscaley / self.scaley) + self.img.translate(transx, transy) + + self.scalex = newscalex + self.scaley = newscaley + + self.cx = x[0] + self.cy = y[0] + + # update table and warning + self.update_warning(warning) + + def updateIsocurve(self): + self.iso.setLevel(self.isoLine.value()) + + def imageHoverEvent(self, event): + """Show the position, pixel, and value under the mouse cursor. + """ + if event.isExit(): + self.aximg.setTitle("") + return + pos = event.pos() + i, j = pos.y(), pos.x() + i = int(np.clip(i, 0, self.data_img.shape[0] - 1)) + j = int(np.clip(j, 0, self.data_img.shape[1] - 1)) + val = self.data_img[i, j] + ppos = self.img.mapToParent(pos) + x, y = ppos.x(), ppos.y() + self.aximg.setTitle("pos: (%0.1f, %0.1f) value: %g" % (x, y, val)) \ No newline at end of file diff --git a/HelperClasses/Plotting/WidgetPlot.py b/HelperClasses/Plotting/WidgetPlot.py new file mode 100644 index 0000000..49a073e --- /dev/null +++ b/HelperClasses/Plotting/WidgetPlot.py @@ -0,0 +1,63 @@ +import pyqtgraph as pg +from pyqtgraph import Qt +from pyqtgraph.Qt import QtCore, QtGui +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +from .DataExtractorLyse import DataExtractorManager + + +class DataPlot(QSplitter): + def __init__(self, title, **kwargs): + super().__init__(**kwargs) + + # Orientation of plot and title (and other information) + self.setOrientation(Qt.Vertical) + + # Add plot + self.plots = pg.GraphicsLayoutWidget() + self.addWidget(self.plots) + + # Claim bottom part (title) + self.bottom = QSplitter() + self.bottom.setOrientation(Qt.Horizontal) + self.addWidget(self.bottom) + + # Add title + self.title = QtGui.QLabel() + self.title.setAlignment(QtCore.Qt.AlignCenter) + self.title.setText('

' + title + ' <\h2>') + self.desciption = pg.LayoutWidget() + self.desciption.addWidget(self.title) + + # Add warning information + self.warning = QtGui.QLabel() + self.warning.setAlignment(QtCore.Qt.AlignCenter) + self.desciption.nextRow() + self.desciption.addWidget(self.warning) + self.bottom.addWidget(self.desciption) + + # Add a table for fitting information + self.table = pg.TableWidget() + self.bottom.addWidget(self.table) + + # Current hdf5 file path + self.h5_path_shown = None + + # data extractor + self.data_extractor_manager = DataExtractorManager() + + def update_from_h5(self, h5_path): + if self.h5_path_shown != h5_path or self.data_extractor_manager.local_data_changed: + self.h5_path_shown = h5_path + self.update(h5_path) + + def update_warning(self, warning): + if warning == '': + self.warning.setStyleSheet("background-color: lightgreen") + warning = 'all good' + else: + self.warning.setStyleSheet("background-color: red") + + self.warning.setText(warning) + diff --git a/HelperClasses/Plotting/WidgetQuick2DPlot.py b/HelperClasses/Plotting/WidgetQuick2DPlot.py new file mode 100644 index 0000000..174146f --- /dev/null +++ b/HelperClasses/Plotting/WidgetQuick2DPlot.py @@ -0,0 +1,166 @@ +class Quick2DPlot(QuickDataPlot): + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + self.img = pg.ImageItem() + self.plot.addItem(self.img) + + # Isocurve draplotsg + self.iso = pg.IsocurveItem(level=1000, pen=color_palette[2]) + self.iso.setParentItem(self.img) + self.iso.setZValue(5) + + # Contrast/color control + self.hist = pg.HistogramLUTItem() + self.hist.setImageItem(self.img) + self.plots.addItem(self.hist) + + # Draggable line for setting isocurve level + self.isoLine = pg.InfiniteLine(angle=0, movable=True, pen=color_palette[2]) + self.hist.vb.addItem(self.isoLine) + self.hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier + self.isoLine.setValue(1000) + self.isoLine.setZValue(1000) # bring iso line above contrast controls + self.isoLine.sigDragged.connect(self.updateIsocurve) + + # Monkey-patch the image to use our custom hover function. + # This is generally discouraged (you should subclass ImageItem instead), + # but it works for a very simple use like this. + self.img.hoverEvent = self.imageHoverEvent + + self.img.translate(-0.5, -0.5) + + self.scalex = 1 + self.scaley = 1 + + self.cx = 0 + self.cy = 0 + + self.nplots = 0 + + self.table.setColumnCount(3) + self.table.setRowCount(2) + self.table.setColumnWidth(0, 200) + self.table.setColumnWidth(1, 150) + self.table.setColumnWidth(2, 150) + self.table.setHorizontalHeaderLabels(['xvalue', 'yarray', 'zarray']) + + self.combox = NumericDataCombo(self.ap.df) + self.comboy = NumericDataCombo(self.ap.df) + self.comboz = NumericDataCombo(self.ap.df) + + self.table.setCellWidget(0, 0, self.combox) + self.table.setCellWidget(0, 1, self.comboy) + self.table.setCellWidget(0, 2, self.comboz) + + self.mk_buttons() + + def mk_buttons(self): + self.bt_update = QPushButton('Update', self) + self.bt_update.clicked.connect(self.update_from_h5) + self.table.setCellWidget(1, 1, self.bt_update) + + def update_data_extractor(self): + + idxx = self.combox.get_idx() + idxy = self.comboy.get_idx() + idxz = self.comboz.get_idx() + + if idxx not in self.data_extractor.data_extractors: + if idxx[0] == 'shot number': + self.data_extractor[idxx] = EmptyDataExtractor() + else: + self.data_extractor[idxx] = SingleDataExtractor(idxx) + + if idxy not in self.data_extractor.data_extractors: + if idxy[0] == 'shot number': + self.data_extractor[idxy] = EmptyDataExtractor() + else: + self.data_extractor[idxy] = SingleDataExtractor(idxy) + + if idxz not in self.data_extractor.data_extractors: + if idxz[0] == 'shot number': + self.data_extractor[idxz] = EmptyDataExtractor() + else: + self.data_extractor[idxz] = SingleDataExtractor(idxz) + + self.data_extractor.clean_children([idxx, idxy, idxz]) + self.data_extractor.clean_memory(self.ap.h5_paths) + + def update(self, murks=None): + + xs = np.array([]) + ys = np.array([]) + zs = np.array([]) + + idxx = self.combox.get_idx() + idxy = self.comboy.get_idx() + idxz = self.comboz.get_idx() + + for i, h5_path in enumerate(self.ap.h5_paths_selected): + + data = self.data_extractor.get_data(h5_path)[0] + if idxx[0] == 'shot number': + xs = np.append(xs, self.ap.shotselector.get_selected_indices()[0][i]) + else: + xs = np.append(xs, data[idxx]) + + if idxy[0] == 'shot number': + ys = np.append(ys, self.ap.shotselector.get_selected_indices()[0][i]) + else: + ys = np.append(ys, data[idxy]) + + if idxz[0] == 'shot number': + zs = np.append(zs, self.ap.shotselector.get_selected_indices()[0][i]) + else: + zs = np.append(zs, data[idxz]) + + # here we don't want to assume that the data is on a grid + # this can happen if the parameters where changed between two sweeps + + xi = np.linspace(xs.min(), xs.max(), 200) + yi = np.linspace(ys.min(), ys.max(), 200) + + Xi, Yi = np.meshgrid(xi, yi) + + Zi = griddata((xs, ys), zs, (Xi, Yi), 'nearest') + + self.img.setImage(Zi.T) + self.iso.setData(Zi.T) + self.data_img = Zi + + # set position and scale of image + newscalex = xi[1] - xi[0] + newscaley = yi[1] - yi[0] + + transx = (xi[0] - (self.cx - 0.5 * self.scalex)) / newscalex - 0.5 + transy = (yi[0] - (self.cy - 0.5 * self.scaley)) / newscaley - 0.5 + + self.img.scale(newscalex / self.scalex, newscaley / self.scaley) + self.img.translate(transx, transy) + + self.scalex = newscalex + self.scaley = newscaley + + self.cx = xi[0] + self.cy = yi[0] + + def updateIsocurve(self): + self.iso.setLevel(self.isoLine.value()) + + def imageHoverEvent(self, event): + """Show the position, pixel, and value under the mouse cursor. + """ + if event.isExit(): + self.plot.setTitle("") + return + pos = event.pos() + i, j = pos.y(), pos.x() + i = int(np.clip(i, 0, self.data_img.shape[0] - 1)) + j = int(np.clip(j, 0, self.data_img.shape[1] - 1)) + val = self.data_img[i, j] + ppos = self.img.mapToParent(pos) + x, y = ppos.x(), ppos.y() + self.plot.setTitle("pos: (%0.1f, %0.1f) value: %g" % (x, y, val)) \ No newline at end of file diff --git a/HelperClasses/Plotting/WidgetQuickPlotGenerator.py b/HelperClasses/Plotting/WidgetQuickPlotGenerator.py new file mode 100644 index 0000000..9d661e7 --- /dev/null +++ b/HelperClasses/Plotting/WidgetQuickPlotGenerator.py @@ -0,0 +1,55 @@ +import pyqtgraph as pg +from PyQt5.QtWidgets import * + + +class QuickPlotGenerator(pg.LayoutWidget): + + def __init__(self, ap, **kwargs): + super().__init__(**kwargs) + + self.ap = ap + + self.title = QLabel('

Quick Plot Generator <\h2>') + + self.addWidget(self.title, colspan=2) + + self.nextRow() + + self.newlayout = pg.LayoutWidget() + + self.newlayout.addWidget(QLabel('Make new plot')) + self.newlayout.nextRow() + self.newlayout.addWidget(QLabel('Title: ')) + + self.title_le = QLineEdit('murks') + self.newlayout.addWidget(self.title_le) + + self.newlayout.nextRow() + self.bt1d = QPushButton('make 1D plot', self) + self.newlayout.addWidget(self.bt1d) + + self.bt1d.clicked.connect(self.mk1dplot) + + self.btwaterfall = QPushButton('make waterfall plot', self) + self.newlayout.addWidget(self.btwaterfall) + + self.btwaterfall.clicked.connect(self.mkwaterfallplot) + + self.bt2d = QPushButton('make 2d plot', self) + self.newlayout.addWidget(self.bt2d) + + self.bt2d.clicked.connect(self.mk2dplot) + + self.addWidget(self.newlayout) + + def mk1dplot(self): + title = self.title_le.text() + self.ap.add_plot_dock(title, Quick1DPlot(self.ap), MultiDataExtractor(), closable=True) + + def mkwaterfallplot(self): + title = self.title_le.text() + self.ap.add_plot_dock(title, QuickWaterfallPlot(self.ap), MultiDataExtractor(), closable=True) + + def mk2dplot(self): + title = self.title_le.text() + self.ap.add_plot_dock(title, Quick2DPlot(self.ap), MultiDataExtractor(), closable=True) \ No newline at end of file diff --git a/HelperClasses/Plotting/WidgetQuickWaterFlowPlot.py b/HelperClasses/Plotting/WidgetQuickWaterFlowPlot.py new file mode 100644 index 0000000..2b5f03b --- /dev/null +++ b/HelperClasses/Plotting/WidgetQuickWaterFlowPlot.py @@ -0,0 +1,197 @@ +class ArrayDataCombo(ExtendedCombo): + + def __init__(self, h5_paths, **kwargs): + + super().__init__(**kwargs) + self.update_model(h5_paths) + + def update_model(self, h5_paths): + results_array_labels = set([]) + for h5_path in h5_paths: + with h5py.File(h5_path, 'r') as h5_file: + analysis_names = h5_file['results'].keys() + + for analysis_name in analysis_names: + for key in h5_file['results'][analysis_name].keys(): + results_array_labels.add(analysis_name + ',' + key) + + model = QtGui.QStandardItemModel() + + item = QtGui.QStandardItem('shot number') + model.setItem(0, 0, item) + + for i, idx in enumerate(results_array_labels): + item = QtGui.QStandardItem(idx) + model.setItem(i, 0, item) + + self.setModel(model) + + def get_idx(self): + return tuple(str(self.currentText()).split(',')) + + +class QuickWaterfallPlot(QuickDataPlot): + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + self.img = pg.ImageItem() + self.plot.addItem(self.img) + + # Isocurve draplotsg + self.iso = pg.IsocurveItem(level=1000, pen=color_palette[2]) + self.iso.setParentItem(self.img) + self.iso.setZValue(5) + + # Contrast/color control + self.hist = pg.HistogramLUTItem() + self.hist.setImageItem(self.img) + self.plots.addItem(self.hist) + + # Draggable line for setting isocurve level + self.isoLine = pg.InfiniteLine(angle=0, movable=True, pen=color_palette[2]) + self.hist.vb.addItem(self.isoLine) + self.hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier + self.isoLine.setValue(1000) + self.isoLine.setZValue(1000) # bring iso line above contrast controls + self.isoLine.sigDragged.connect(self.updateIsocurve) + + # Monkey-patch the image to use our custom hover function. + # This is generally discouraged (you should subclass ImageItem instead), + # but it works for a very simple use like this. + self.img.hoverEvent = self.imageHoverEvent + + self.img.translate(-0.5, -0.5) + + self.scalex = 1 + self.scaley = 1 + + self.cx = 0 + self.cy = 0 + + self.nplots = 0 + + self.table.setColumnCount(3) + self.table.setRowCount(2) + self.table.setColumnWidth(0, 200) + self.table.setColumnWidth(1, 150) + self.table.setColumnWidth(2, 150) + self.table.setHorizontalHeaderLabels(['xvalue', 'yarray', 'zarray']) + + self.combox = NumericDataCombo(self.ap.df) + self.comboy = ArrayDataCombo(self.ap.h5_paths) + self.comboz = ArrayDataCombo(self.ap.h5_paths) + + self.table.setCellWidget(0, 0, self.combox) + self.table.setCellWidget(0, 1, self.comboy) + self.table.setCellWidget(0, 2, self.comboz) + + self.mk_buttons() + + def mk_buttons(self): + self.bt_update = QPushButton('Update', self) + self.bt_update.clicked.connect(self.update_from_h5) + self.table.setCellWidget(1, 1, self.bt_update) + + def update_data_extractor(self): + + idxx = self.combox.get_idx() + idxy = self.comboy.get_idx() + idxz = self.comboz.get_idx() + + if idxx not in self.data_extractor.data_extractors: + if idxx[0] == 'shot number': + self.data_extractor[idxx] = EmptyDataExtractor() + else: + self.data_extractor[idxx] = SingleDataExtractor(idxx) + + if idxy not in self.data_extractor.data_extractors: + self.data_extractor[idxy] = ArrayDataExtractor(idxy) + + if idxz not in self.data_extractor.data_extractors: + self.data_extractor[idxz] = ArrayDataExtractor(idxz) + + self.data_extractor.clean_children([idxx, idxy, idxz]) + self.data_extractor.clean_memory(self.ap.h5_paths) + + def update(self, data=None): + xs = np.array([]) + ys = np.array([]) + zs = np.array([]) + + idxx = self.combox.get_idx() + idxy = self.comboy.get_idx() + idxz = self.comboz.get_idx() + + for i, h5_path in enumerate(self.ap.h5_paths_selected): + + data = self.data_extractor.get_data(h5_path)[0] + + if idxx[0] == 'shot number': + x = self.ap.shotselector.get_selected_indices()[0][i] + else: + x = data[idxx] + + y = data[idxy] + z = data[idxz] + + if y is not None and z is not None: + xs = np.append(xs, x * np.ones_like(y)) + ys = np.append(ys, y) + zs = np.append(zs, z) + elif y is not None: + xs = np.append(xs, x * np.ones_like(y)) + ys = np.append(ys, y) + zs = np.append(zs, y * np.nan) + elif z is not None: + xs = np.append(xs, x * np.ones_like(z)) + ys = np.append(ys, z * np.nan) + zs = np.append(zs, z) + + # here we don't want to assume that the data is on a grid + # this can happen if the parameters where changed between two sweeps + xi = np.linspace(xs.min(), xs.max(), 200) + yi = np.linspace(ys.min(), ys.max(), 200) + + Xi, Yi = np.meshgrid(xi, yi) + + Zi = griddata((xs, ys), zs, (Xi, Yi), 'nearest') + + self.img.setImage(Zi.T) + self.iso.setData(Zi.T) + self.data_img = Zi + + # set position and scale of image + newscalex = xi[1] - xi[0] + newscaley = yi[1] - yi[0] + + transx = (xi[0] - (self.cx - 0.5 * self.scalex)) / newscalex - 0.5 + transy = (yi[0] - (self.cy - 0.5 * self.scaley)) / newscaley - 0.5 + + self.img.scale(newscalex / self.scalex, newscaley / self.scaley) + self.img.translate(transx, transy) + + self.scalex = newscalex + self.scaley = newscaley + + self.cx = xi[0] + self.cy = yi[0] + + def updateIsocurve(self): + self.iso.setLevel(self.isoLine.value()) + + def imageHoverEvent(self, event): + """Show the position, pixel, and value under the mouse cursor. + """ + if event.isExit(): + self.plot.setTitle("") + return + pos = event.pos() + i, j = pos.y(), pos.x() + i = int(np.clip(i, 0, self.data_img.shape[0] - 1)) + j = int(np.clip(j, 0, self.data_img.shape[1] - 1)) + val = self.data_img[i, j] + ppos = self.img.mapToParent(pos) + x, y = ppos.x(), ppos.y() + self.plot.setTitle("pos: (%0.1f, %0.1f) value: %g" % (x, y, val)) \ No newline at end of file diff --git a/HelperClasses/Plotting/__init__.py b/HelperClasses/Plotting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dylab/TransitionClass.py b/HelperClasses/TransitionClass.py similarity index 100% rename from dylab/TransitionClass.py rename to HelperClasses/TransitionClass.py diff --git a/dylab/__init__.py b/HelperClasses/__init__.py similarity index 100% rename from dylab/__init__.py rename to HelperClasses/__init__.py diff --git a/__init__.py b/__init__.py index 1a630b9..e119571 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1 @@ -name = "dylab" \ No newline at end of file +name = "HelperClasses" \ No newline at end of file diff --git a/build/lib/dylab/AbsorptionImaging.py b/build/lib/dylab/AbsorptionImaging.py index 2c4513d..bd4b188 100644 --- a/build/lib/dylab/AbsorptionImaging.py +++ b/build/lib/dylab/AbsorptionImaging.py @@ -2,7 +2,7 @@ from lyse import * from pylab import * import numpy as np import scipy.constants as constant -from dylab import DyTransition, Camera +from HelperClasses import DyTransition, Camera import matplotlib.pyplot as plt from matplotlib import colors as mcolors import colorsys diff --git a/build/lib/dylab/DyTransition.py b/build/lib/dylab/DyTransition.py index d203361..aabef80 100644 --- a/build/lib/dylab/DyTransition.py +++ b/build/lib/dylab/DyTransition.py @@ -1,7 +1,7 @@ # Functions create constant dictionary storing transition information of Dy. import scipy.constants as constant -from dylab import TransitionClass +from HelperClasses import TransitionClass def creat_Dy421(): diff --git a/build/lib/dylab/Fitting.py b/build/lib/dylab/Fitting.py index 5dc8f5e..e909591 100644 --- a/build/lib/dylab/Fitting.py +++ b/build/lib/dylab/Fitting.py @@ -1,5 +1,5 @@ import numpy as np -from dylab import FittingFunction +from HelperClasses import FittingFunction import laserbeamsize as lbs import lmfit from lmfit.minimizer import MinimizerResult diff --git a/setup.py b/setup.py index 2ab153f..24e6323 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ with open("README.md", "r") as fh: long_description = fh.read() setuptools.setup( - name="dylab", + name="HelperClasses", version="0.0.2", author="QF-group (AG Chomaz), Heidelberg university", author_email="gao@physi.uni-heidelberg.de", diff --git a/test_absorption_imaging.py b/test_absorption_imaging.py index e2dc29d..e262bc4 100644 --- a/test_absorption_imaging.py +++ b/test_absorption_imaging.py @@ -1,6 +1,12 @@ from lyse import * -from dylab import DyTransition, Camera, AbsorptionImaging +from HelperClasses import DyTransition, Camera, AbsorptionImaging, Plotting +import matplotlib.pyplot as plt +import matplotlib.style as mplstyle +import matplotlib +matplotlib.use("qt5agg") + +mplstyle.use('fast') absorption_imaging_transition = DyTransition.creat_Dy421() mot_3D_camera = Camera.c11440_36u(absorption_imaging_transition['wavelength']) @@ -10,6 +16,11 @@ with AbsorptionImaging.absorption_imaging(path, 'MOT_3D_Camera', 'in_situ_absorp absorption_image.select_effective_data((800, 500), (1000, 700)) absorption_image.get_atom_number() - absorption_image.plot_result(-0.05, 0.05) print(absorption_image.atom_number) + + +plotting = Plotting.plotting_absorption_imaging(absorption_image) +plotting.four_plots() +# plotting.absorption_plots() + diff --git a/test_fitting.py b/test_fitting.py index 41c8d27..a894f63 100644 --- a/test_fitting.py +++ b/test_fitting.py @@ -1,10 +1,10 @@ import laserbeamsize as lbs -from dylab import Fitting +from HelperClasses import Fitting import numpy as np import lmfit import matplotlib.pyplot as plt -from dylab.FittingFunction import twoD_Gaussian +from HelperClasses.FittingFunction import twoD_Gaussian xc = 300 yc = 200 diff --git a/test_living_plot.py b/test_living_plot.py new file mode 100644 index 0000000..471464a --- /dev/null +++ b/test_living_plot.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Mar 11 14:58:18 2021 + +@author: Nick Sauerwein +""" +import lyse +import numpy as np + +from HelperClasses.Plotting import WidgetFakeColorPlot, MainPlotPanel + +import cProfile +import pstats + +profile = cProfile.Profile() +profile.enable() + +fm = lyse.figure_manager + +h5_paths = lyse.h5_paths() + +if lyse.spinning_top: + # If so, use the filepath of the current shot + h5_path = lyse.path +else: + # If not, get the filepath of the last shot of the lyse DataFrame + h5_path = lyse.h5_paths().iloc[-1] + +print(h5_path) + +if len(h5_paths): + last_globals = run = lyse.Run(h5_paths.iloc[-1]).get_globals() + +if not hasattr(fm, 'ap'): + fm.ap = MainPlotPanel.MainPlotPanel(h5_paths) + +# Imaging Methods +imagings = ['absorption_imaging'] + +cams = {'absorption_imaging': ['Cam_absorption']} + +imaging = 'absorption_imaging' +for cam in cams[imaging]: + plot_name = imaging + ' ' + cam + + if not plot_name in fm.ap.plots: + ip = WidgetFakeColorPlot.FakeColorPlot(plot_name, ('absorption_imaging', 'absorption_imaging')) + + fm.ap.add_plot_dock(plot_name, ip) + +fm.ap.update_h5_paths(h5_paths) +fm.ap.refresh(h5_path) + +profile.disable() +ps = pstats.Stats(profile) +ps.sort_stats('cumtime') +ps.print_stats(10)