qcodespp 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qcodespp/__init__.py +77 -0
- qcodespp/actions.py +237 -0
- qcodespp/data/__init__.py +0 -0
- qcodespp/data/data_array.py +555 -0
- qcodespp/data/data_set.py +926 -0
- qcodespp/data/format.py +338 -0
- qcodespp/data/gnuplot_format.py +425 -0
- qcodespp/data/hdf5_format.py +537 -0
- qcodespp/data/hdf5_format_hickle.py +63 -0
- qcodespp/data/io.py +241 -0
- qcodespp/data/location.py +171 -0
- qcodespp/instrument_drivers/Keysight/Keysight_34450A.py +9 -0
- qcodespp/instrument_drivers/Keysight/__init__.py +0 -0
- qcodespp/instrument_drivers/Keysight/private/Keysight_344xxA.py +456 -0
- qcodespp/instrument_drivers/Keysight/private/__init__.py +0 -0
- qcodespp/instrument_drivers/Lakeshore/Model_340.py +483 -0
- qcodespp/instrument_drivers/Lakeshore/__init__.py +0 -0
- qcodespp/instrument_drivers/QDevil/159fit_params_high_latest.json +386 -0
- qcodespp/instrument_drivers/QDevil/159fit_params_low_latest.json +386 -0
- qcodespp/instrument_drivers/QDevil/QDAC2.py +3229 -0
- qcodespp/instrument_drivers/QDevil/QDAC2_Array.py +300 -0
- qcodespp/instrument_drivers/QDevil/QSwitch_elab.py +652 -0
- qcodespp/instrument_drivers/QDevil/__init__.py +0 -0
- qcodespp/instrument_drivers/ZI/ZIHF2LI.py +415 -0
- qcodespp/instrument_drivers/ZI/ZIMFLI.py +764 -0
- qcodespp/instrument_drivers/ZI/ZIMFLI_sync.py +108 -0
- qcodespp/instrument_drivers/ZI/ZIUHFLI.py +2179 -0
- qcodespp/instrument_drivers/ZI/__init__.py +1 -0
- qcodespp/instrument_drivers/__init__.py +0 -0
- qcodespp/instrument_drivers/cryogenic/CryogenicSMS120C.py +599 -0
- qcodespp/instrument_drivers/cryogenic/__init__.py +0 -0
- qcodespp/instrument_drivers/instrument_mocks.py +379 -0
- qcodespp/instrument_drivers/multiplexer_instrument.py +287 -0
- qcodespp/instrument_drivers/national_instruments/PXI_4461.py +451 -0
- qcodespp/instrument_drivers/national_instruments/PXI_4462.py +290 -0
- qcodespp/instrument_drivers/national_instruments/PXI_6259.py +436 -0
- qcodespp/instrument_drivers/national_instruments/PXI_6733.py +230 -0
- qcodespp/instrument_drivers/national_instruments/PXI_6733_e.py +243 -0
- qcodespp/instrument_drivers/national_instruments/__init__.py +0 -0
- qcodespp/instrument_drivers/oxford/__init__.py +0 -0
- qcodespp/instrument_drivers/oxford/mercuryiPS_serial.py +658 -0
- qcodespp/instrument_drivers/oxford/triton.py +434 -0
- qcodespp/instrument_drivers/tektronix/Keithley_2000_Scan.py +65 -0
- qcodespp/instrument_drivers/tektronix/Keithley_2182a.py +190 -0
- qcodespp/instrument_drivers/tektronix/Keithley_2400.py +158 -0
- qcodespp/instrument_drivers/tektronix/Keithley_2450.py +242 -0
- qcodespp/instrument_drivers/tektronix/Keithley_2700.py +397 -0
- qcodespp/instrument_drivers/tektronix/Keithley_6500.py +161 -0
- qcodespp/instrument_drivers/tektronix/__init__.py +0 -0
- qcodespp/instrument_drivers/yokogawa/GS200.py +513 -0
- qcodespp/instrument_drivers/yokogawa/__init__.py +0 -0
- qcodespp/loops.py +1342 -0
- qcodespp/measure.py +310 -0
- qcodespp/parameters.py +311 -0
- qcodespp/plotting/RemotePlot.py +469 -0
- qcodespp/plotting/RemotePlotClient.py +367 -0
- qcodespp/plotting/RemoteQtPlotWidgets.py +871 -0
- qcodespp/plotting/__init__.py +0 -0
- qcodespp/plotting/analysis_tools.py +195 -0
- qcodespp/plotting/colors.py +135 -0
- qcodespp/plotting/offline/__init__.py +0 -0
- qcodespp/plotting/offline/datatypes.py +999 -0
- qcodespp/plotting/offline/design.py +906 -0
- qcodespp/plotting/offline/design.ui +1578 -0
- qcodespp/plotting/offline/editor.py +2966 -0
- qcodespp/plotting/offline/filters.py +561 -0
- qcodespp/plotting/offline/fits.py +1171 -0
- qcodespp/plotting/offline/helpers.py +288 -0
- qcodespp/plotting/offline/iconGadget.png +0 -0
- qcodespp/plotting/offline/main.py +55 -0
- qcodespp/plotting/offline/popupwindows.py +1848 -0
- qcodespp/plotting/offline/qcodes_pp_extension.py +328 -0
- qcodespp/plotting/offline/sidebars.py +1108 -0
- qcodespp/station.py +290 -0
- qcodespp/utils/__init__.py +0 -0
- qcodespp/utils/helpers.py +618 -0
- qcodespp/utils/visa_helpers.py +61 -0
- qcodespp/version.py +12 -0
- qcodespp-0.1.0.dist-info/METADATA +91 -0
- qcodespp-0.1.0.dist-info/RECORD +82 -0
- qcodespp-0.1.0.dist-info/WHEEL +4 -0
- qcodespp-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,2966 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Inspectra-Gadget
|
|
4
|
+
|
|
5
|
+
Author: Joeri de Bruijckere
|
|
6
|
+
|
|
7
|
+
Adapted for qcodes++ by: Dags Olsteins and Damon Carrad
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from PyQt5 import QtWidgets, QtCore, QtGui
|
|
12
|
+
import os
|
|
13
|
+
import copy
|
|
14
|
+
import io
|
|
15
|
+
import tarfile
|
|
16
|
+
from webbrowser import open as href_open
|
|
17
|
+
from csv import writer as csvwriter
|
|
18
|
+
from json import load as jsonload
|
|
19
|
+
from json import dump as jsondump
|
|
20
|
+
from stat import ST_CTIME
|
|
21
|
+
import numpy as np
|
|
22
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
23
|
+
|
|
24
|
+
import matplotlib.pyplot as plt
|
|
25
|
+
from matplotlib.figure import Figure
|
|
26
|
+
from matplotlib import rcParams
|
|
27
|
+
from matplotlib import colormaps as cm
|
|
28
|
+
from cycler import cycler
|
|
29
|
+
from collections import OrderedDict
|
|
30
|
+
try:
|
|
31
|
+
import qdarkstyle # pip install qdarkstyle
|
|
32
|
+
qdarkstyle_imported = True
|
|
33
|
+
except ModuleNotFoundError:
|
|
34
|
+
qdarkstyle_imported = False
|
|
35
|
+
|
|
36
|
+
import matplotlib.style as mplstyle
|
|
37
|
+
mplstyle.use('fast')
|
|
38
|
+
|
|
39
|
+
from lmfit.model import save_modelresult, load_modelresult
|
|
40
|
+
|
|
41
|
+
import qcodespp.plotting.offline.design as design
|
|
42
|
+
from qcodespp.plotting.offline.popupwindows import LineCutWindow, MetadataWindow, StatsWindow
|
|
43
|
+
from qcodespp.plotting.offline.sidebars import Sidebar1D
|
|
44
|
+
from qcodespp.plotting.offline.helpers import (cmaps, MidpointNormalize,NavigationToolbarMod,
|
|
45
|
+
rcParams_to_dark_theme,rcParams_to_light_theme,
|
|
46
|
+
NoScrollQComboBox,DraggablePoint)
|
|
47
|
+
from qcodespp.plotting.offline.filters import Filter
|
|
48
|
+
from qcodespp.plotting.offline.datatypes import DataItem, BaseClassData, NumpyData, InternalData, MixedInternalData
|
|
49
|
+
from qcodespp.plotting.offline.qcodes_pp_extension import qcodesppData
|
|
50
|
+
|
|
51
|
+
from qcodespp.data.data_set import DataSetPP
|
|
52
|
+
|
|
53
|
+
# UI settings
|
|
54
|
+
DARK_THEME = True
|
|
55
|
+
AUTO_REFRESH_INTERVAL_2D = 1
|
|
56
|
+
AUTO_REFRESH_INTERVAL_3D = 30
|
|
57
|
+
|
|
58
|
+
# List of custom presets
|
|
59
|
+
PRESETS = [{'title': '', 'labelsize': '9', 'ticksize': '9', 'spinewidth': '0.5',
|
|
60
|
+
'titlesize': '9',
|
|
61
|
+
'canvas_bounds': (0.425,0.4,0.575,0.6), # (left, bottom, right, top)
|
|
62
|
+
'show_meta_settings': False},
|
|
63
|
+
{'title': '<label>', 'labelsize': '16', 'ticksize': '16',
|
|
64
|
+
'spinewidth': '0.8', 'titlesize': '16',
|
|
65
|
+
'canvas_bounds': (0.425,0.4,0.575,0.6), # (left, bottom, right, top)
|
|
66
|
+
'show_meta_settings': True},
|
|
67
|
+
{'title': '', 'labelsize': '9', 'ticksize': '9', 'spinewidth': '0.5'},
|
|
68
|
+
{'title': '', 'labelsize': '9', 'ticksize': '9', 'spinewidth': '0.5'}]
|
|
69
|
+
|
|
70
|
+
# Matplotlib settings; font type is chosen such that text (labels, ticks, ...)
|
|
71
|
+
# can be recognized by Illustrator
|
|
72
|
+
rcParams['pdf.fonttype'] = 42
|
|
73
|
+
rcParams['ps.fonttype'] = 42
|
|
74
|
+
rcParams['font.family'] = 'sans-serif'
|
|
75
|
+
rcParams['font.sans-serif'] = ['Arial']
|
|
76
|
+
rcParams['font.cursive'] = ['Arial']
|
|
77
|
+
rcParams['mathtext.fontset'] = 'custom'
|
|
78
|
+
if DARK_THEME and qdarkstyle_imported:
|
|
79
|
+
DARK_COLOR = '#19232D'
|
|
80
|
+
GREY_COLOR = '#505F69'
|
|
81
|
+
LIGHT_COLOR = '#F0F0F0'
|
|
82
|
+
BLUE_COLOR = '#148CD2'
|
|
83
|
+
rcParams['figure.facecolor'] = DARK_COLOR
|
|
84
|
+
rcParams['axes.facecolor'] = DARK_COLOR
|
|
85
|
+
rcParams['axes.edgecolor'] = GREY_COLOR
|
|
86
|
+
rcParams['text.color'] = LIGHT_COLOR
|
|
87
|
+
rcParams['xtick.color'] = LIGHT_COLOR
|
|
88
|
+
rcParams['ytick.color'] = LIGHT_COLOR
|
|
89
|
+
rcParams['axes.labelcolor'] = LIGHT_COLOR
|
|
90
|
+
rcParams['savefig.facecolor'] = 'white'
|
|
91
|
+
color_cycle = [BLUE_COLOR, 'ff7f0e', '2ca02c', 'd62728', '9467bd',
|
|
92
|
+
'8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf']
|
|
93
|
+
rcParams['axes.prop_cycle'] = cycler('color', color_cycle)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Add custom hybrid colormaps to matplotlib register
|
|
97
|
+
# cmaps['Hybrid'] = ['magma+bone_r','inferno+bone_r']
|
|
98
|
+
# for cmap in cmaps['Hybrid']:
|
|
99
|
+
# n_colors = 512
|
|
100
|
+
# top = cm.get_cmap(cmap.split('+')[1], n_colors)
|
|
101
|
+
# bottom = cm.get_cmap(cmap.split('+')[0], n_colors)
|
|
102
|
+
# newcolors = np.vstack((top(np.linspace(0, 1, n_colors)),
|
|
103
|
+
# bottom(np.linspace(0, 1, n_colors))))
|
|
104
|
+
# newcolors_r = newcolors[::-1]
|
|
105
|
+
# newcmp = ListedColormap(newcolors, name=cmap)
|
|
106
|
+
# newcmp_r = ListedColormap(newcolors_r, name=cmap+'_r')
|
|
107
|
+
# cm.register_cmap(cmap=newcmp)
|
|
108
|
+
# cm.register_cmap(cmap=newcmp_r)
|
|
109
|
+
|
|
110
|
+
# Only include colormaps that are in the matplotlib register
|
|
111
|
+
for cmap_type in cmaps.copy():
|
|
112
|
+
cmaps[cmap_type][:] = [cmap for cmap in cmaps[cmap_type]
|
|
113
|
+
if cmap in plt.colormaps()]
|
|
114
|
+
if cmaps[cmap_type] == []:
|
|
115
|
+
del cmaps[cmap_type]
|
|
116
|
+
|
|
117
|
+
FONT_SIZES = ['8', '9', '10', '12', '14', '16', '18', '24']
|
|
118
|
+
SETTINGS_MENU_OPTIONS = OrderedDict()
|
|
119
|
+
SETTINGS_MENU_OPTIONS['title'] = [' ','<label>']
|
|
120
|
+
SETTINGS_MENU_OPTIONS['xlabel'] = ['Gate voltage (V)',
|
|
121
|
+
'$V_g$ (V)',
|
|
122
|
+
'Bias voltage (mV)',
|
|
123
|
+
'$V$ (mV)',
|
|
124
|
+
'Magnetic Field (T)',
|
|
125
|
+
'$B$ (T)',
|
|
126
|
+
'Angle (degrees)',
|
|
127
|
+
'Temperature (K)']
|
|
128
|
+
SETTINGS_MENU_OPTIONS['ylabel'] = ['Bias voltage (mV)',
|
|
129
|
+
'$V$ (mV)',
|
|
130
|
+
'Gate voltage (V)',
|
|
131
|
+
'$V_g$ (V)',
|
|
132
|
+
'$I$ (A)',
|
|
133
|
+
'$I$ (nA)',
|
|
134
|
+
'Current (A)',
|
|
135
|
+
'Current (nA)',
|
|
136
|
+
'd$I$/d$V$ (μS)',
|
|
137
|
+
'd$I$/d$V$ $(e^{2}/h)$',
|
|
138
|
+
'(d$I$/d$V$ $(e^{2}/h)$)$^{1/4}$',
|
|
139
|
+
'Angle (degrees)',
|
|
140
|
+
'Temperature (K)',
|
|
141
|
+
'Magnetic Field (T)',
|
|
142
|
+
'$B$ (T)', ]
|
|
143
|
+
SETTINGS_MENU_OPTIONS['clabel'] = ['$I$ (A)',
|
|
144
|
+
'$I$ (nA)',
|
|
145
|
+
'Current (A)',
|
|
146
|
+
'Current (nA)',
|
|
147
|
+
'Voltage (V)',
|
|
148
|
+
'Voltage (mV)',
|
|
149
|
+
'd$I$/d$V$ (S)',
|
|
150
|
+
'd$V$/d$I$ (Ohm)',
|
|
151
|
+
'd$I$/d$V$ (a.u.)',
|
|
152
|
+
'd$I$/d$V$ $(2e^{2}/h)$',
|
|
153
|
+
'd$I$/d$V$ $(e^{2}/h)$',
|
|
154
|
+
'd$I$/d$V$ ($G_0$)',
|
|
155
|
+
'(d$I$/d$V$ $(e^{2}/h)$)$^{1/4}$',
|
|
156
|
+
'log$^{10}$(d$I$/d$V$ $(e^{2}/h)$)',
|
|
157
|
+
'd$^2I$/d$V^2$ (a.u.)',
|
|
158
|
+
'|d$^2I$/d$V^2$| (a.u.)']
|
|
159
|
+
SETTINGS_MENU_OPTIONS['transpose'] = ['True', 'False']
|
|
160
|
+
SETTINGS_MENU_OPTIONS['delimiter'] = [' ',',']
|
|
161
|
+
SETTINGS_MENU_OPTIONS['titlesize'] = FONT_SIZES
|
|
162
|
+
SETTINGS_MENU_OPTIONS['labelsize'] = FONT_SIZES
|
|
163
|
+
SETTINGS_MENU_OPTIONS['ticksize'] = FONT_SIZES
|
|
164
|
+
SETTINGS_MENU_OPTIONS['colorbar'] = ['True', 'False']
|
|
165
|
+
SETTINGS_MENU_OPTIONS['minorticks'] = ['True','False']
|
|
166
|
+
SETTINGS_MENU_OPTIONS['maskcolor'] = ['black','white']
|
|
167
|
+
SETTINGS_MENU_OPTIONS['cmap levels'] = ['128','256','512','1024']
|
|
168
|
+
SETTINGS_MENU_OPTIONS['rasterized'] = ['True','False']
|
|
169
|
+
SETTINGS_MENU_OPTIONS['dpi'] = ['figure','300']
|
|
170
|
+
SETTINGS_MENU_OPTIONS['transparent'] = ['True', 'False']
|
|
171
|
+
SETTINGS_MENU_OPTIONS['shading'] = ['auto', 'flat', 'gouraud', 'nearest']
|
|
172
|
+
SETTINGS_MENU_OPTIONS['columns'] = ['0,1,2','0,1,3','0,2,3','1,2,4']
|
|
173
|
+
|
|
174
|
+
AXIS_SCALING_OPTIONS = ['linear', 'log', 'symlog', 'logit']
|
|
175
|
+
|
|
176
|
+
class Editor(QtWidgets.QMainWindow, design.Ui_MainWindow):
|
|
177
|
+
def __init__(self,folder=None,link_to_default=True):
|
|
178
|
+
super().__init__()
|
|
179
|
+
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
|
180
|
+
self.window_title = 'Inspectra Gadget'
|
|
181
|
+
self.window_title_auto_refresh = ''
|
|
182
|
+
self.setupUi(self)
|
|
183
|
+
self.init_plot_settings()
|
|
184
|
+
self.init_view_settings()
|
|
185
|
+
self.init_axis_scaling()
|
|
186
|
+
self.init_filters()
|
|
187
|
+
self.init_connections()
|
|
188
|
+
self.init_canvas()
|
|
189
|
+
self.linked_folder = None
|
|
190
|
+
self.linked_files = []
|
|
191
|
+
self.resize(1400,1000)
|
|
192
|
+
|
|
193
|
+
# Hide widgets that shouldn't be shown at startup
|
|
194
|
+
self.mixeddata_filter_box.hide()
|
|
195
|
+
self.binsX_label.hide()
|
|
196
|
+
self.binsX_lineedit.hide()
|
|
197
|
+
self.binsY_label.hide()
|
|
198
|
+
self.binsY_lineedit.hide()
|
|
199
|
+
self.metadata_button.hide()
|
|
200
|
+
self.stats_button.hide()
|
|
201
|
+
self.show_2d_data_checkbox.hide()
|
|
202
|
+
|
|
203
|
+
#... and some that are not currently in use.
|
|
204
|
+
self.track_button.hide()
|
|
205
|
+
self.refresh_label.hide()
|
|
206
|
+
self.refresh_line_edit.hide()
|
|
207
|
+
self.refreshunit_label.hide()
|
|
208
|
+
|
|
209
|
+
self.global_text_size='12'
|
|
210
|
+
self.global_text_lineedit.setText(self.global_text_size)
|
|
211
|
+
|
|
212
|
+
if folder:
|
|
213
|
+
print(f'Linking to {folder}... This may take some time.')
|
|
214
|
+
try:
|
|
215
|
+
self.update_link_to_folder(folder=folder)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
print(f'Failed to link to folder {folder}:', e)
|
|
218
|
+
elif link_to_default and os.path.isdir(DataSetPP.default_folder):
|
|
219
|
+
try:
|
|
220
|
+
print(f'Linking to qcodespp data folder at {DataSetPP.default_folder}... This may take some time.')
|
|
221
|
+
self.update_link_to_folder(folder=DataSetPP.default_folder)
|
|
222
|
+
except:
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
def init_plot_settings(self):
|
|
226
|
+
self.settings_table.setColumnCount(2)
|
|
227
|
+
self.settings_table.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked)
|
|
228
|
+
for col in range(2):
|
|
229
|
+
h = self.settings_table.horizontalHeader()
|
|
230
|
+
h.setSectionResizeMode(col, QtWidgets.QHeaderView.ResizeToContents)
|
|
231
|
+
self.settings_table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
|
232
|
+
# Opens options menu with right-click
|
|
233
|
+
self.settings_table.customContextMenuRequested.connect(self.open_plot_settings_menu)
|
|
234
|
+
|
|
235
|
+
def init_view_settings(self):
|
|
236
|
+
self.cmaps = cmaps
|
|
237
|
+
for cmap_type in self.cmaps:
|
|
238
|
+
self.colormap_type_box.addItem(cmap_type)
|
|
239
|
+
self.colormap_box.addItems(list(self.cmaps.values())[0])
|
|
240
|
+
self.min_line_edit.setAlignment(QtCore.Qt.AlignRight |
|
|
241
|
+
QtCore.Qt.AlignVCenter)
|
|
242
|
+
self.max_line_edit.setAlignment(QtCore.Qt.AlignRight |
|
|
243
|
+
QtCore.Qt.AlignVCenter)
|
|
244
|
+
self.mid_line_edit.setAlignment(QtCore.Qt.AlignRight |
|
|
245
|
+
QtCore.Qt.AlignVCenter)
|
|
246
|
+
|
|
247
|
+
def init_axis_scaling(self):
|
|
248
|
+
self.xaxis_combobox.addItems(AXIS_SCALING_OPTIONS)
|
|
249
|
+
self.yaxis_combobox.addItems(AXIS_SCALING_OPTIONS)
|
|
250
|
+
|
|
251
|
+
def init_filters(self):
|
|
252
|
+
self.filters_combobox.addItem('<Add Filter>')
|
|
253
|
+
self.filters_combobox.addItems(Filter.DEFAULT_SETTINGS.keys())
|
|
254
|
+
self.filters_table.setColumnCount(4)
|
|
255
|
+
self.filters_table.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked)
|
|
256
|
+
h = self.filters_table.horizontalHeader()
|
|
257
|
+
h.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
|
258
|
+
for col in range(1,4):
|
|
259
|
+
h.setSectionResizeMode(col, QtWidgets.QHeaderView.ResizeToContents)
|
|
260
|
+
self.filters_table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
|
261
|
+
# Opens options menu with right-click
|
|
262
|
+
self.filters_table.customContextMenuRequested.connect(self.open_filter_settings_menu)
|
|
263
|
+
|
|
264
|
+
def init_connections(self):
|
|
265
|
+
self.open_files_button.clicked.connect(self.open_files)
|
|
266
|
+
self.open_folder_button.clicked.connect(self.open_files_from_folder)
|
|
267
|
+
self.link_folder_button.clicked.connect(lambda: self.update_link_to_folder(new_folder=True))
|
|
268
|
+
self.delete_files_button.clicked.connect(lambda: self.remove_files('current'))
|
|
269
|
+
self.clear_files_button.clicked.connect(lambda: self.remove_files('all'))
|
|
270
|
+
self.unlink_folder_button.clicked.connect(self.unlink_folder)
|
|
271
|
+
self.file_list.itemChanged.connect(self.file_checked)
|
|
272
|
+
self.file_list.itemClicked.connect(self.file_clicked)
|
|
273
|
+
self.file_list.itemDoubleClicked.connect(self.file_double_clicked)
|
|
274
|
+
self.plot_type_box.currentIndexChanged.connect(self.plot_type_changed)
|
|
275
|
+
self.binsX_lineedit.editingFinished.connect(lambda: self.bins_changed('X'))
|
|
276
|
+
self.binsY_lineedit.editingFinished.connect(lambda: self.bins_changed('Y'))
|
|
277
|
+
self.show_2d_data_checkbox.clicked.connect(self.show_2d_data_checkbox_changed)
|
|
278
|
+
self.global_text_lineedit.editingFinished.connect(self.global_text_changed)
|
|
279
|
+
self.stats_button.clicked.connect(self.show_stats)
|
|
280
|
+
self.metadata_button.clicked.connect(self.show_metadata)
|
|
281
|
+
self.settings_table.itemChanged.connect(self.plot_setting_edited)
|
|
282
|
+
self.filters_table.itemChanged.connect(self.filters_table_edited)
|
|
283
|
+
self.copy_settings_button.clicked.connect(self.copy_plot_settings)
|
|
284
|
+
self.paste_settings_button.clicked.connect(lambda: self.paste_plot_settings('copied'))
|
|
285
|
+
self.reset_settings_button.clicked.connect(lambda: self.paste_plot_settings('default'))
|
|
286
|
+
self.filters_combobox.currentIndexChanged.connect(self.filters_box_changed)
|
|
287
|
+
self.mixeddata_filter_box.currentIndexChanged.connect(self.show_current_filters)
|
|
288
|
+
self.xaxis_combobox.currentIndexChanged.connect(self.axis_scaling_changed)
|
|
289
|
+
self.yaxis_combobox.currentIndexChanged.connect(self.axis_scaling_changed)
|
|
290
|
+
self.delete_filters_button.clicked.connect(lambda: self.remove_filters('current'))
|
|
291
|
+
self.clear_filters_button.clicked.connect(lambda: self.remove_filters('all'))
|
|
292
|
+
self.copy_filters_button.clicked.connect(self.copy_filters)
|
|
293
|
+
self.paste_filters_button.clicked.connect(lambda: self.paste_filters('copied'))
|
|
294
|
+
self.up_filters_button.clicked.connect(lambda: self.move_filter(-1))
|
|
295
|
+
self.down_filters_button.clicked.connect(lambda: self.move_filter(1))
|
|
296
|
+
self.filtXtocol_button.clicked.connect(lambda: self.filttocol_clicked('X'))
|
|
297
|
+
self.filtYtocol_button.clicked.connect(lambda: self.filttocol_clicked('Y'))
|
|
298
|
+
self.filtZtocol_button.clicked.connect(lambda: self.filttocol_clicked('Z'))
|
|
299
|
+
# self.previous_button.clicked.connect(self.to_previous_file)
|
|
300
|
+
# self.next_button.clicked.connect(self.to_next_file)
|
|
301
|
+
self.new_plot_button.clicked.connect(lambda: self.duplicate_item(new_plot_button=True))
|
|
302
|
+
self.copy_view_button.clicked.connect(self.copy_view_settings)
|
|
303
|
+
self.paste_view_button.clicked.connect(lambda: self.paste_view_settings('copied'))
|
|
304
|
+
self.colormap_type_box.currentIndexChanged.connect(self.colormap_type_edited)
|
|
305
|
+
self.colormap_box.currentIndexChanged.connect(self.colormap_edited)
|
|
306
|
+
self.cbar_hist_checkbox.clicked.connect(lambda: self.view_setting_edited('CBarHist'))
|
|
307
|
+
self.reverse_colors_box.clicked.connect(self.colormap_edited)
|
|
308
|
+
self.xmin_line_edit.editingFinished.connect(lambda: self.axlim_setting_edited('Xmin'))
|
|
309
|
+
self.xmax_line_edit.editingFinished.connect(lambda: self.axlim_setting_edited('Xmax'))
|
|
310
|
+
self.ymin_line_edit.editingFinished.connect(lambda: self.axlim_setting_edited('Ymin'))
|
|
311
|
+
self.ymax_line_edit.editingFinished.connect(lambda: self.axlim_setting_edited('Ymax'))
|
|
312
|
+
self.copy_xy_button.clicked.connect(self.copy_axlim_settings)
|
|
313
|
+
self.paste_xy_button.clicked.connect(lambda: self.paste_axlim_settings('copied'))
|
|
314
|
+
self.reset_xy_button.clicked.connect(self.reset_axlim_settings)
|
|
315
|
+
self.min_line_edit.editingFinished.connect(lambda: self.view_setting_edited('Minimum'))
|
|
316
|
+
self.max_line_edit.editingFinished.connect(lambda: self.view_setting_edited('Maximum'))
|
|
317
|
+
self.mid_line_edit.editingFinished.connect(lambda: self.view_setting_edited('Midpoint'))
|
|
318
|
+
self.lock_checkbox.clicked.connect(lambda: self.view_setting_edited('Locked'))
|
|
319
|
+
self.mid_checkbox.clicked.connect(lambda: self.view_setting_edited('MidLock'))
|
|
320
|
+
self.reset_limits_button.clicked.connect(self.reset_color_limits)
|
|
321
|
+
self.tight_layout_button.clicked.connect(self.tight_layout)
|
|
322
|
+
self.save_image_button.clicked.connect(self.save_image)
|
|
323
|
+
self.copy_image_button.clicked.connect(self.copy_canvas_to_clipboard)
|
|
324
|
+
self.load_filters_button.clicked.connect(self.load_filters)
|
|
325
|
+
self.action_filters.triggered.connect(self.save_filters)
|
|
326
|
+
self.action_save_session.triggered.connect(lambda: self.save_session('all'))
|
|
327
|
+
self.action_restore_session.triggered.connect(self.load_session)
|
|
328
|
+
self.action_combine_files.triggered.connect(self.combine_plots)
|
|
329
|
+
self.action_duplicate_file.triggered.connect(self.duplicate_item)
|
|
330
|
+
self.action_export_data_columns.triggered.connect(lambda: self.export_processed_data('current'))
|
|
331
|
+
self.action_export_data_Z.triggered.connect(lambda: self.export_processed_data('Z'))
|
|
332
|
+
self.track_button.clicked.connect(self.track_button_clicked)
|
|
333
|
+
self.refresh_line_edit.editingFinished.connect(lambda: self.refresh_interval_changed(self.refresh_line_edit.text()))
|
|
334
|
+
self.actionSave_plot_s_as.triggered.connect(self.save_image)
|
|
335
|
+
self.action_open_file.triggered.connect(self.open_files)
|
|
336
|
+
self.action_open_files_from_folder.triggered.connect(self.open_files_from_folder)
|
|
337
|
+
self.action_copy_plot_as_image.triggered.connect(self.copy_canvas_to_clipboard)
|
|
338
|
+
self.action_save_files_as_PNG.triggered.connect(lambda: self.save_images_as('.png'))
|
|
339
|
+
self.action_save_files_as_PDF.triggered.connect(lambda: self.save_images_as('.pdf'))
|
|
340
|
+
self.action_load_preset.triggered.connect(self.load_preset)
|
|
341
|
+
self.action_save_preset.triggered.connect(self.save_preset)
|
|
342
|
+
self.action_refresh_stop.setEnabled(False)
|
|
343
|
+
self.action_link_to_folder.triggered.connect(lambda: self.update_link_to_folder(new_folder=True))
|
|
344
|
+
self.action_unlink_folder.triggered.connect(self.unlink_folder)
|
|
345
|
+
self.action_track_data.triggered.connect(self.track_button_clicked)
|
|
346
|
+
self.refresh_file_button.clicked.connect(self.refresh_files)
|
|
347
|
+
self.up_file_button.clicked.connect(lambda: self.move_file('up'))
|
|
348
|
+
self.down_file_button.clicked.connect(lambda: self.move_file('down'))
|
|
349
|
+
self.file_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
|
350
|
+
self.file_list.customContextMenuRequested.connect(self.open_item_menu)
|
|
351
|
+
self.actionOnline_help.triggered.connect(lambda: href_open('https://qcodespp.github.io/offline_plotting.html'))
|
|
352
|
+
|
|
353
|
+
# Keyboard shortcuts
|
|
354
|
+
self.open_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+O"), self)
|
|
355
|
+
self.open_shortcut.activated.connect(self.open_files)
|
|
356
|
+
self.open_folder_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Shift+O"), self)
|
|
357
|
+
self.open_folder_shortcut.activated.connect(self.open_files_from_folder)
|
|
358
|
+
self.link_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+L"), self)
|
|
359
|
+
self.link_shortcut.activated.connect(lambda: self.update_link_to_folder(new_folder=True))
|
|
360
|
+
self.unlink_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Shift+L"), self)
|
|
361
|
+
self.unlink_shortcut.activated.connect(self.unlink_folder)
|
|
362
|
+
self.track_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+T"), self)
|
|
363
|
+
self.track_shortcut.activated.connect(self.track_button_clicked)
|
|
364
|
+
self.save_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Shift+S"), self)
|
|
365
|
+
self.save_shortcut.activated.connect(self.save_image)
|
|
366
|
+
# self.copy_image_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+C"), self)
|
|
367
|
+
# self.copy_image_shortcut.activated.connect(self.copy_canvas_to_clipboard)
|
|
368
|
+
# Problem with the above is that you then can't copy text! or anything else.
|
|
369
|
+
self.duplicate_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+D"), self)
|
|
370
|
+
self.duplicate_shortcut.activated.connect(self.duplicate_item)
|
|
371
|
+
self.save_session_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+S"),self)
|
|
372
|
+
self.save_session_shortcut.activated.connect(lambda: self.save_session('all'))
|
|
373
|
+
self.load_session_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+R"),self)
|
|
374
|
+
self.load_session_shortcut.activated.connect(self.load_session)
|
|
375
|
+
|
|
376
|
+
def init_canvas(self):
|
|
377
|
+
self.figure = Figure()
|
|
378
|
+
self.canvas = FigureCanvas(self.figure)
|
|
379
|
+
self.canvas.mpl_connect('button_press_event', self.mouse_click_canvas)
|
|
380
|
+
self.canvas.mpl_connect('scroll_event', self.mouse_scroll_canvas)
|
|
381
|
+
self.canvas.mpl_connect('button_release_event', self.on_release)
|
|
382
|
+
self.canvas.mpl_connect('motion_notify_event', self.on_motion)
|
|
383
|
+
self.canvas.mpl_connect('pick_event', self.on_pick)
|
|
384
|
+
self.navi_toolbar = NavigationToolbarMod(self.canvas, self)
|
|
385
|
+
self.graph_layout.addWidget(self.navi_toolbar)
|
|
386
|
+
self.graph_layout.addWidget(self.canvas)
|
|
387
|
+
self.subplot_grid = [(1,1),(1,2),(2,2),(2,2),(2,3),(2,3),(2,4),(2,4),
|
|
388
|
+
(3,4),(3,4),(3,4),(3,4),(4,4),(4,4),(4,4),(4,4),
|
|
389
|
+
(4,5),(4,5),(4,5),(4,5),(4,5),(5,5),(5,5),(5,5),
|
|
390
|
+
(5,5)]
|
|
391
|
+
self.figure.subplots_adjust(top=0.893, bottom=0.137,
|
|
392
|
+
left=0.121, right=0.86)
|
|
393
|
+
|
|
394
|
+
def load_data_item(self,filepath,load_the_data=True):
|
|
395
|
+
filename, extension = os.path.splitext(filepath)
|
|
396
|
+
if extension == '.npy': # Numpy files (old saved sessions)
|
|
397
|
+
dataset_list = np.load(filepath, allow_pickle=True)
|
|
398
|
+
for dataset in dataset_list:
|
|
399
|
+
try:
|
|
400
|
+
item = DataItem(NumpyData(filepath, self.canvas, dataset))
|
|
401
|
+
return item
|
|
402
|
+
except Exception as e:
|
|
403
|
+
print(f'Failed to add NumPy dataset inside {filepath}:', e)
|
|
404
|
+
|
|
405
|
+
elif (extension == '.dat' and # qcodes++ files
|
|
406
|
+
os.path.isfile(os.path.dirname(filepath)+'/snapshot.json')):
|
|
407
|
+
metapath = os.path.dirname(filepath)+'/snapshot.json'
|
|
408
|
+
try:
|
|
409
|
+
item = DataItem(qcodesppData(filepath, self.canvas, metapath,load_the_data))
|
|
410
|
+
return item
|
|
411
|
+
except Exception as e:
|
|
412
|
+
print(f'Failed to add qcodes++ dataset {filepath}:', e)
|
|
413
|
+
|
|
414
|
+
else: # bare column-based data file
|
|
415
|
+
item = DataItem(BaseClassData(filepath, self.canvas))
|
|
416
|
+
return item
|
|
417
|
+
|
|
418
|
+
def open_files(self, filepaths=None, load_the_data=True, attr_dicts=None, dirpath=None,overrideautocheck=False):
|
|
419
|
+
self.file_list.itemClicked.disconnect(self.file_clicked)
|
|
420
|
+
self.file_list.itemChanged.disconnect(self.file_checked)
|
|
421
|
+
if not filepaths:
|
|
422
|
+
filepaths, _ = QtWidgets.QFileDialog.getOpenFileNames(
|
|
423
|
+
self, 'Open File', '', 'Data Files (*.dat *.npy *.csv)')
|
|
424
|
+
if filepaths:
|
|
425
|
+
for i,filepath in enumerate(filepaths):
|
|
426
|
+
try:
|
|
427
|
+
if filepath == 'internal_data': # Should only happen when loading a session, therefore rely on attr_dicts
|
|
428
|
+
item=DataItem(InternalData(self.canvas,
|
|
429
|
+
attr_dicts[i]['loaded_data'],
|
|
430
|
+
attr_dicts[i]['label'],
|
|
431
|
+
attr_dicts[i]['all_parameter_names'],
|
|
432
|
+
attr_dicts[i]['dim']))
|
|
433
|
+
|
|
434
|
+
elif filepath == 'mixed_internal_data': # Should only happen when loading a session, therefore rely on attr_dicts
|
|
435
|
+
type_dictionary={'qcodesppData': qcodesppData,
|
|
436
|
+
'BaseClassData': BaseClassData,
|
|
437
|
+
'InternalData': InternalData}
|
|
438
|
+
for key in type_dictionary.keys():
|
|
439
|
+
if key in attr_dicts[i]['dataset1d_type']:
|
|
440
|
+
dataset1d_type = type_dictionary[key]
|
|
441
|
+
if key in attr_dicts[i]['dataset2d_type']:
|
|
442
|
+
dataset2d_type = type_dictionary[key]
|
|
443
|
+
label_name = attr_dicts[i]['label']
|
|
444
|
+
kwargs={}
|
|
445
|
+
if dataset1d_type in [qcodesppData,BaseClassData]:
|
|
446
|
+
kwargs['dataset1d_filepath'] = attr_dicts[i]['dataset1d_filepath']
|
|
447
|
+
elif dataset1d_type == InternalData:
|
|
448
|
+
kwargs['dataset1d_loaded_data'] = attr_dicts[i]['dataset1d_loaded_data']
|
|
449
|
+
kwargs['dataset1d_label'] = attr_dicts[i]['dataset1d_label']
|
|
450
|
+
kwargs['dataset1d_all_parameter_names'] = attr_dicts[i]['dataset1d_all_parameter_names']
|
|
451
|
+
kwargs['dataset1d_dim']= attr_dicts[i]['dataset1d_dim']
|
|
452
|
+
if dataset2d_type in [qcodesppData,BaseClassData]:
|
|
453
|
+
kwargs['dataset2d_filepath'] = attr_dicts[i]['dataset2d_filepath']
|
|
454
|
+
elif dataset2d_type == InternalData:
|
|
455
|
+
kwargs['dataset2d_loaded_data'] = attr_dicts[i]['dataset2d_loaded_data']
|
|
456
|
+
kwargs['dataset2d_label'] = attr_dicts[i]['dataset2d_label']
|
|
457
|
+
kwargs['dataset2d_all_parameter_names'] = attr_dicts[i]['dataset2d_all_parameter_names']
|
|
458
|
+
kwargs['dataset2d_dim']= attr_dicts[i]['dataset2d_dim']
|
|
459
|
+
item=DataItem(MixedInternalData(self.canvas,label_name,dataset2d_type,dataset1d_type,**kwargs))
|
|
460
|
+
|
|
461
|
+
else:
|
|
462
|
+
item=self.load_data_item(filepath,load_the_data)
|
|
463
|
+
|
|
464
|
+
item.filepath=filepath
|
|
465
|
+
self.file_list.addItem(item)
|
|
466
|
+
if attr_dicts is not None: #then a previous session is being loaded
|
|
467
|
+
for attr in attr_dicts[i]:
|
|
468
|
+
if attr not in ['filename','checkState',
|
|
469
|
+
'dataset1d_type','dataset2d_type',
|
|
470
|
+
'dataset1d_plotted_lines','dataset2d_linecuts']:
|
|
471
|
+
setattr(item.data,attr,attr_dicts[i][attr])
|
|
472
|
+
|
|
473
|
+
elif attr=='checkState':
|
|
474
|
+
item.setCheckState(attr_dicts[i][attr])
|
|
475
|
+
if attr_dicts[i][attr]==2:
|
|
476
|
+
self.file_checked(item)
|
|
477
|
+
overrideautocheck=True #If any item is checked, override autochecking. But if NONE of them are checked, let autocheck do its thing.
|
|
478
|
+
# The below is kind of dumb... but for anything at all to work, 1D data has to be inited by
|
|
479
|
+
# actually plotting _something_, which makes a sidebar1D with the default params plotted.
|
|
480
|
+
# So we need to check if the sidebar1D exists, and if it does, delete it.
|
|
481
|
+
if hasattr(item.data,'sidebar1D'):
|
|
482
|
+
item.data.sidebar1D.hide()
|
|
483
|
+
del item.data.sidebar1D
|
|
484
|
+
|
|
485
|
+
elif attr=='dataset1d_plotted_lines':
|
|
486
|
+
item.data.dataset1d.plotted_lines= attr_dicts[i][attr]
|
|
487
|
+
self.reload_plotted_lines(item.data.dataset1d,dirpath,item)
|
|
488
|
+
|
|
489
|
+
elif attr=='dataset2d_linecuts':
|
|
490
|
+
item.data.dataset2d.linecuts = attr_dicts[i][attr]
|
|
491
|
+
self.reload_linecuts(item.data.dataset2d,dirpath,item.checkState())
|
|
492
|
+
|
|
493
|
+
if attr=='linecuts':
|
|
494
|
+
self.reload_linecuts(item.data,dirpath,item.checkState())
|
|
495
|
+
|
|
496
|
+
if attr=='plotted_lines':
|
|
497
|
+
self.reload_plotted_lines(item.data,dirpath,item)
|
|
498
|
+
|
|
499
|
+
else:
|
|
500
|
+
for setting in ['titlesize','labelsize','ticksize']:
|
|
501
|
+
if hasattr(item.data,'settings'):
|
|
502
|
+
item.data.settings[setting]=self.global_text_size
|
|
503
|
+
|
|
504
|
+
except Exception as e:
|
|
505
|
+
print(f'Failed to open {filepath}:', e)
|
|
506
|
+
|
|
507
|
+
if self.file_list.count() > 0:
|
|
508
|
+
last_item = self.file_list.item(self.file_list.count()-1)
|
|
509
|
+
self.file_list.setCurrentItem(last_item)
|
|
510
|
+
if not overrideautocheck:
|
|
511
|
+
for item_index in range(self.file_list.count()-1):
|
|
512
|
+
self.file_list.item(item_index).setCheckState(QtCore.Qt.Unchecked)
|
|
513
|
+
last_item.setCheckState(QtCore.Qt.Checked)
|
|
514
|
+
self.file_checked(last_item)
|
|
515
|
+
self.file_list.itemChanged.connect(self.file_checked)
|
|
516
|
+
self.file_list.itemClicked.connect(self.file_clicked)
|
|
517
|
+
|
|
518
|
+
def reload_plotted_lines(self,data,dirpath,item):
|
|
519
|
+
if len(data.plotted_lines) > 0:
|
|
520
|
+
item.data.sidebar1D = Sidebar1D(data,self)
|
|
521
|
+
for line in data.plotted_lines.keys():
|
|
522
|
+
if 'fit' in data.plotted_lines[line].keys():
|
|
523
|
+
data.plotted_lines[line]['fit']['fit_result'] = load_modelresult(dirpath+'/igtemp/'+data.plotted_lines[line]['fit']['fit_result']+'.sav')
|
|
524
|
+
item.data.sidebar1D.append_trace_to_table(line)
|
|
525
|
+
if item.checkState():
|
|
526
|
+
item.data.sidebar1D.update()
|
|
527
|
+
|
|
528
|
+
def reload_linecuts(self,data,dirpath,item_checkState):
|
|
529
|
+
for orientation in data.linecuts.keys():
|
|
530
|
+
if len(data.linecuts[orientation]['lines']) > 0:
|
|
531
|
+
for line in data.linecuts[orientation]['lines'].keys():
|
|
532
|
+
if 'fit' in data.linecuts[orientation]['lines'][line].keys():
|
|
533
|
+
data.linecuts[orientation]['lines'][line]['fit']['fit_result'] = load_modelresult(dirpath+'/igtemp/'+data.linecuts[orientation]['lines'][line]['fit']['fit_result']+'.sav')
|
|
534
|
+
if 'draggable_points' in data.linecuts[orientation]['lines'][line].keys():
|
|
535
|
+
points=data.linecuts[orientation]['lines'][line]['points']
|
|
536
|
+
data.linecuts[orientation]['lines'][line]['draggable_points'] = [DraggablePoint(data,points[0][0],points[0][1],line,orientation),
|
|
537
|
+
DraggablePoint(data,points[1][0],points[1][1],line,orientation,draw_line=True)]
|
|
538
|
+
#Then make the linecut window
|
|
539
|
+
data.linecuts[orientation]['linecut_window'] = LineCutWindow(data,orientation=orientation,init_cmap='plasma',editor_window=self)
|
|
540
|
+
data.linecuts[orientation]['linecut_window'].running = True
|
|
541
|
+
for line in data.linecuts[orientation]['lines']:
|
|
542
|
+
data.linecuts[orientation]['linecut_window'].append_cut_to_table(line)
|
|
543
|
+
if item_checkState:
|
|
544
|
+
data.linecuts[orientation]['linecut_window'].activateWindow()
|
|
545
|
+
data.linecuts[orientation]['linecut_window'].update()
|
|
546
|
+
data.linecuts[orientation]['linecut_window'].show()
|
|
547
|
+
|
|
548
|
+
def add_internal_data(self,item,check_item=True,uncheck_others=True):
|
|
549
|
+
# Add internal data to the file list (from combined plots/files, fitting dependency, etc)
|
|
550
|
+
#item.filepath='internal_data'
|
|
551
|
+
self.file_list.itemChanged.disconnect(self.file_checked)
|
|
552
|
+
self.file_list.addItem(item)
|
|
553
|
+
self.file_list.setCurrentItem(item)
|
|
554
|
+
if uncheck_others:
|
|
555
|
+
for item_index in range(self.file_list.count()-1):
|
|
556
|
+
self.file_list.item(item_index).setCheckState(QtCore.Qt.Unchecked)
|
|
557
|
+
if check_item:
|
|
558
|
+
self.clear_sidebar1D()
|
|
559
|
+
item.setCheckState(QtCore.Qt.Checked)
|
|
560
|
+
self.file_checked(item)
|
|
561
|
+
self.file_list.itemChanged.connect(self.file_checked)
|
|
562
|
+
|
|
563
|
+
def remove_files(self, which='current'):
|
|
564
|
+
if self.file_list.count() > 0:
|
|
565
|
+
if which == 'current':
|
|
566
|
+
items = [self.file_list.currentItem()]
|
|
567
|
+
elif which == 'all':
|
|
568
|
+
items = [self.file_list.item(n) for n in range(self.file_list.count())]
|
|
569
|
+
elif which == 'unchecked':
|
|
570
|
+
items = self.get_unchecked_items()
|
|
571
|
+
update_plots = any([item.checkState() == 2 for item in items]) # only update plots if any of the items are checked
|
|
572
|
+
for item in items:
|
|
573
|
+
if hasattr(item.data,'sidebar1D'):
|
|
574
|
+
item.data.sidebar1D.close()
|
|
575
|
+
if hasattr(item.data, 'linecuts'):
|
|
576
|
+
for orientation in item.data.linecuts.keys():
|
|
577
|
+
if item.data.linecuts[orientation]['linecut_window']:
|
|
578
|
+
item.data.linecuts[orientation]['linecut_window'].close()
|
|
579
|
+
if isinstance(item.data, MixedInternalData):
|
|
580
|
+
self.mixeddata_filter_box.hide()
|
|
581
|
+
if (item.data.filepath in self.linked_files
|
|
582
|
+
and not hasattr(item, 'duplicate')):
|
|
583
|
+
self.linked_files.remove(item.data.filepath)
|
|
584
|
+
index = self.file_list.row(item)
|
|
585
|
+
self.file_list.takeItem(index)
|
|
586
|
+
del item
|
|
587
|
+
self.show_current_all()
|
|
588
|
+
if update_plots:
|
|
589
|
+
self.update_plots()
|
|
590
|
+
|
|
591
|
+
def open_files_from_folder(self):
|
|
592
|
+
rootdir = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Directory to Open")
|
|
593
|
+
filepaths = []
|
|
594
|
+
for subdir, dirs, files in os.walk(rootdir):
|
|
595
|
+
for file in files:
|
|
596
|
+
filename, file_extension = os.path.splitext(file)
|
|
597
|
+
if file_extension == '.dat':
|
|
598
|
+
already_loaded=self.check_already_loaded(subdir,[file[1] for file in filepaths])
|
|
599
|
+
if not already_loaded:
|
|
600
|
+
filepath = os.path.join(subdir, file)
|
|
601
|
+
try: # on Windows
|
|
602
|
+
st_ctime = os.path.getctime(filepath)
|
|
603
|
+
except Exception:
|
|
604
|
+
try: # on Mac
|
|
605
|
+
st_ctime = os.stat(filepath).st_birthtime
|
|
606
|
+
except Exception as e:
|
|
607
|
+
print(e)
|
|
608
|
+
filepaths.append((st_ctime,filepath,subdir))
|
|
609
|
+
if not os.path.split(filepaths[0][2])[1].startswith('#'): #If it's qcodespp data, it's already sorted. If not, sort by time
|
|
610
|
+
filepaths.sort(key=lambda tup: tup[0])
|
|
611
|
+
self.open_files([file[1] for file in filepaths],load_the_data=False)
|
|
612
|
+
|
|
613
|
+
def check_already_loaded(self, subdir, filepaths):
|
|
614
|
+
loaded=False
|
|
615
|
+
for filepath in filepaths:
|
|
616
|
+
if subdir in filepath:
|
|
617
|
+
loaded=True
|
|
618
|
+
return loaded
|
|
619
|
+
|
|
620
|
+
def update_link_to_folder(self, new_folder=True, folder=None):
|
|
621
|
+
if folder is not None:
|
|
622
|
+
self.linked_folder = folder
|
|
623
|
+
elif new_folder:
|
|
624
|
+
self.linked_folder = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Directory to Link")
|
|
625
|
+
if self.linked_folder:
|
|
626
|
+
self.window_title = f'InSpectra Gadget - Linked to folder {self.linked_folder}'
|
|
627
|
+
self.setWindowTitle(self.window_title+self.window_title_auto_refresh)
|
|
628
|
+
new_files = []
|
|
629
|
+
for subdir, dirs, files in os.walk(self.linked_folder):
|
|
630
|
+
for file in files:
|
|
631
|
+
filename, file_extension = os.path.splitext(file)
|
|
632
|
+
if file_extension == '.dat':
|
|
633
|
+
already_loaded=self.check_already_loaded(subdir,[file[1] for file in new_files])
|
|
634
|
+
if not already_loaded:
|
|
635
|
+
filepath = os.path.join(subdir, file)
|
|
636
|
+
# Need to deal with qcodespp data differently during refresh since multiple
|
|
637
|
+
# .dat files may belong to the same dataset
|
|
638
|
+
if os.path.isfile(subdir+'/snapshot.json'):
|
|
639
|
+
already_linked=False
|
|
640
|
+
for file in self.linked_files:
|
|
641
|
+
if subdir in file:
|
|
642
|
+
already_linked=True
|
|
643
|
+
if not already_linked:
|
|
644
|
+
try: # on Windows
|
|
645
|
+
st_ctime = os.path.getctime(filepath)
|
|
646
|
+
except Exception:
|
|
647
|
+
try: # on Mac
|
|
648
|
+
st_ctime = os.stat(filepath).st_birthtime
|
|
649
|
+
except Exception as e:
|
|
650
|
+
print(e)
|
|
651
|
+
new_files.append((st_ctime,filepath,subdir))
|
|
652
|
+
|
|
653
|
+
else:
|
|
654
|
+
if filepath not in self.linked_files:
|
|
655
|
+
try: # on Windows
|
|
656
|
+
st_ctime = os.path.getctime(filepath)
|
|
657
|
+
except Exception:
|
|
658
|
+
try: # on Mac
|
|
659
|
+
st_ctime = os.stat(filepath).st_birthtime
|
|
660
|
+
except Exception as e:
|
|
661
|
+
print(e)
|
|
662
|
+
new_files.append((st_ctime,filepath,subdir))
|
|
663
|
+
if new_files:
|
|
664
|
+
if not os.path.split(new_files[0][2])[1].startswith('#'): #If it's qcodespp data, it's already sorted. If not, sort by time
|
|
665
|
+
new_files.sort(key=lambda tup: tup[0])
|
|
666
|
+
new_filepaths = [new_file[1] for new_file in new_files]
|
|
667
|
+
self.open_files(new_filepaths,load_the_data=False)
|
|
668
|
+
for new_filepath in new_filepaths:
|
|
669
|
+
self.linked_files.append(new_filepath)
|
|
670
|
+
|
|
671
|
+
def unlink_folder(self):
|
|
672
|
+
if self.linked_folder:
|
|
673
|
+
self.linked_folder = None
|
|
674
|
+
self.window_title = 'InSpectra Gadget'
|
|
675
|
+
self.setWindowTitle(self.window_title+
|
|
676
|
+
self.window_title_auto_refresh)
|
|
677
|
+
|
|
678
|
+
def save_session(self, which='current'):
|
|
679
|
+
current_item = self.file_list.currentItem()
|
|
680
|
+
if current_item:
|
|
681
|
+
|
|
682
|
+
filepath, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
683
|
+
self, 'Save Session As...', '', '*.igs')
|
|
684
|
+
items = [self.file_list.item(n) for n in range(self.file_list.count())]
|
|
685
|
+
|
|
686
|
+
if filepath:
|
|
687
|
+
dirpath = os.path.dirname(filepath)
|
|
688
|
+
dictionary_list = []
|
|
689
|
+
os.makedirs(dirpath+'/igtemp', exist_ok=True)
|
|
690
|
+
self.i=0 # Used to iterate over total number of lmfit results across all datafiles.
|
|
691
|
+
for item in items:
|
|
692
|
+
item_dictionary = {}
|
|
693
|
+
if hasattr(item,'filepath'):
|
|
694
|
+
item_dictionary['filepath']=item.filepath
|
|
695
|
+
if hasattr(item,'checkState'):
|
|
696
|
+
item_dictionary['checkState']=item.checkState()
|
|
697
|
+
attributes=['label','settings','filters','view_settings','axlim_settings','plot_type','dim','labels_changed'
|
|
698
|
+
'raw_data','processed_data'] # These two are potentially not necessary.
|
|
699
|
+
if isinstance(item.data, InternalData):
|
|
700
|
+
attributes.append('loaded_data')
|
|
701
|
+
attributes.append('label')
|
|
702
|
+
attributes.append('all_parameter_names')
|
|
703
|
+
attributes.append('dim')
|
|
704
|
+
elif isinstance(item.data, MixedInternalData):
|
|
705
|
+
item_dictionary['dataset2d_type'] = str(item.data.dataset2d_type)
|
|
706
|
+
item_dictionary['dataset1d_type'] = str(item.data.dataset2d_type)
|
|
707
|
+
attributes.append('label')
|
|
708
|
+
attributes.append('dataset1d_filepath')
|
|
709
|
+
attributes.append('dataset2d_filepath')
|
|
710
|
+
attributes.append('dataset1d_loaded_data')
|
|
711
|
+
attributes.append('dataset2d_loaded_data')
|
|
712
|
+
attributes.append('dataset1d_label')
|
|
713
|
+
attributes.append('dataset2d_label')
|
|
714
|
+
attributes.append('dataset1d_all_parameter_names')
|
|
715
|
+
attributes.append('dataset2d_all_parameter_names')
|
|
716
|
+
attributes.append('dataset1d_dim')
|
|
717
|
+
attributes.append('dataset2d_dim')
|
|
718
|
+
attributes.append('show_2d_data')
|
|
719
|
+
if hasattr(item.data.dataset2d,'linecuts'):
|
|
720
|
+
item_dictionary['dataset2d_linecuts'] = self.remove_linecutwindows_and_fits(item.data.dataset2d.linecuts,dirpath)
|
|
721
|
+
if hasattr(item.data.dataset1d,'plotted_lines'):
|
|
722
|
+
item_dictionary['dataset1d_plotted_lines'] = self.remove_linecutwindows_and_fits(item.data.dataset1d.plotted_lines,dirpath)
|
|
723
|
+
|
|
724
|
+
for attribute in attributes:
|
|
725
|
+
if hasattr(item.data,attribute):
|
|
726
|
+
item_dictionary[attribute]=getattr(item.data,attribute)
|
|
727
|
+
|
|
728
|
+
if hasattr(item.data,'linecuts'):
|
|
729
|
+
item_dictionary['linecuts'] = self.remove_linecutwindows_and_fits(item.data.linecuts,dirpath)
|
|
730
|
+
|
|
731
|
+
if hasattr(item.data,'plotted_lines'):
|
|
732
|
+
item_dictionary['plotted_lines'] = self.remove_linecutwindows_and_fits(item.data.plotted_lines,dirpath)
|
|
733
|
+
|
|
734
|
+
dictionary_list.append(item_dictionary)
|
|
735
|
+
|
|
736
|
+
# Save all needed files to a temperorary directory and add them to the tarball
|
|
737
|
+
np.save(dirpath+'/igtemp/numpyfile.npy', dictionary_list)
|
|
738
|
+
with tarfile.open(filepath, 'w:gz') as tar:
|
|
739
|
+
for filename in os.listdir(dirpath+'/igtemp'):
|
|
740
|
+
tar.add('./igtemp/'+filename, recursive=False)
|
|
741
|
+
|
|
742
|
+
print(f'Session saved as {filepath}')
|
|
743
|
+
|
|
744
|
+
# Delete unnecessary information
|
|
745
|
+
for filename in os.listdir(dirpath+'/igtemp'):
|
|
746
|
+
file_path = os.path.join(dirpath+'/igtemp', filename)
|
|
747
|
+
os.remove(file_path)
|
|
748
|
+
os.rmdir(dirpath+'/igtemp')
|
|
749
|
+
del dictionary_list
|
|
750
|
+
|
|
751
|
+
def remove_linecutwindows_and_fits(self,d,dirpath,exclude_key='linecut_window',exclude_key2='fit_result',exclude_key3='draggable_points'):
|
|
752
|
+
# Remove linecut window object and lmfit object from the dictionary. Neither can be pickled. lmfit fit results are saved to
|
|
753
|
+
# file, added to the tarball, and loaded again when the session is loaded.
|
|
754
|
+
new_dict = {}
|
|
755
|
+
for key, value in d.items():
|
|
756
|
+
if isinstance(value, dict):
|
|
757
|
+
# Recurse into nested dictionaries
|
|
758
|
+
new_dict[key] = self.remove_linecutwindows_and_fits(value, dirpath,exclude_key,exclude_key2,exclude_key3)
|
|
759
|
+
else:
|
|
760
|
+
# For non-dictionary values, just copy them
|
|
761
|
+
new_dict[key] = value
|
|
762
|
+
|
|
763
|
+
if exclude_key in new_dict:
|
|
764
|
+
new_dict[exclude_key] = None # Remove the linecut window object
|
|
765
|
+
if exclude_key3 in new_dict:
|
|
766
|
+
new_dict[exclude_key3] = None # Remove the draggable points object
|
|
767
|
+
|
|
768
|
+
if exclude_key2 in new_dict:
|
|
769
|
+
try:
|
|
770
|
+
save_modelresult(new_dict[exclude_key2], dirpath+'/igtemp/lmfit_result'+str(self.i).zfill(4)+'.sav') # Save the lmfit object to a file
|
|
771
|
+
new_dict[exclude_key2]='lmfit_result'+str(self.i).zfill(4) # Replace the lmfit model with the name.
|
|
772
|
+
self.i+=1
|
|
773
|
+
except Exception as e:
|
|
774
|
+
print('Error saving lmfit object:', e)
|
|
775
|
+
return new_dict
|
|
776
|
+
|
|
777
|
+
def load_session(self):
|
|
778
|
+
filepath, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Open Session', '', 'Inspectra Gadget session (*.igs)')
|
|
779
|
+
if filepath:
|
|
780
|
+
dirpath = os.path.dirname(filepath)
|
|
781
|
+
if self.file_list.count() > 0:
|
|
782
|
+
self.remove_files('all')
|
|
783
|
+
with tarfile.open(filepath, 'r') as tar:
|
|
784
|
+
tar.extractall(dirpath)
|
|
785
|
+
try:
|
|
786
|
+
data=np.load(dirpath+'/igtemp/numpyfile.npy', allow_pickle=True)
|
|
787
|
+
file_list=[]
|
|
788
|
+
for attr_dict in data:
|
|
789
|
+
file_list.append(attr_dict['filepath'])
|
|
790
|
+
self.open_files(file_list,load_the_data=False,attr_dicts=data,dirpath=dirpath)
|
|
791
|
+
except Exception as e:
|
|
792
|
+
print('Error loading session:', e)
|
|
793
|
+
|
|
794
|
+
for filename in os.listdir(dirpath+'/igtemp'):
|
|
795
|
+
file_path = os.path.join(dirpath+'/igtemp', filename)
|
|
796
|
+
os.remove(file_path)
|
|
797
|
+
os.rmdir(dirpath+'/igtemp')
|
|
798
|
+
|
|
799
|
+
self.update_plots() # Necessary to ensure some settings are applied to the final plot.
|
|
800
|
+
|
|
801
|
+
def export_processed_data(self, which='current'):
|
|
802
|
+
current_item = self.file_list.currentItem()
|
|
803
|
+
if current_item:
|
|
804
|
+
formats='Numpy text (*.dat);;CSV (*.csv);;JSON (*.json)'
|
|
805
|
+
suggested_filename = current_item.data.label
|
|
806
|
+
filepath, ext = QtWidgets.QFileDialog.getSaveFileName(
|
|
807
|
+
self, 'Export Data As', suggested_filename, formats)
|
|
808
|
+
if which == 'current':
|
|
809
|
+
if hasattr(current_item.data,'processed_data'):
|
|
810
|
+
if '.dat' in ext:
|
|
811
|
+
header=''
|
|
812
|
+
with open(filepath, "w") as dat_file:
|
|
813
|
+
try:
|
|
814
|
+
if current_item.data.dim==3:
|
|
815
|
+
labels=['xlabel','ylabel','clabel']
|
|
816
|
+
for label in labels:
|
|
817
|
+
if current_item.data.settings[label] != '':
|
|
818
|
+
header+=f'{current_item.data.settings[label]}\t'
|
|
819
|
+
header=header.strip('\t')
|
|
820
|
+
dat_file.write(f'# {header}\n')
|
|
821
|
+
for j in range(np.shape(current_item.data.processed_data[2])[0]):
|
|
822
|
+
for k in range(np.shape(current_item.data.processed_data[2])[1]):
|
|
823
|
+
dat_file.write('{}\t{}\t{}\n'.format(current_item.data.processed_data[0][j,k],current_item.data.processed_data[1][j,k],current_item.data.processed_data[2][j,k]))
|
|
824
|
+
elif current_item.data.dim == 2:
|
|
825
|
+
if current_item.data.plot_type == 'Histogram':
|
|
826
|
+
print('Cannot save 1D histogram data as .dat. Use .json or .csv instead.')
|
|
827
|
+
else:
|
|
828
|
+
processed_data=[]
|
|
829
|
+
for line in current_item.data.plotted_lines.keys():
|
|
830
|
+
if current_item.data.plotted_lines[line]['checkstate']:
|
|
831
|
+
header+=f'{line}_{current_item.data.plotted_lines[line]["X data"]}\t{line}_{current_item.data.plotted_lines[line]["Y data"]}\t'
|
|
832
|
+
processed_data.append(current_item.data.plotted_lines[line]['processed_data'][0])
|
|
833
|
+
processed_data.append(current_item.data.plotted_lines[line]['processed_data'][1])
|
|
834
|
+
np.savetxt(filepath, np.column_stack(processed_data),header=header)
|
|
835
|
+
except Exception as e:
|
|
836
|
+
print('Error saving processed data as .dat:', e)
|
|
837
|
+
|
|
838
|
+
else:
|
|
839
|
+
data={}
|
|
840
|
+
if current_item.data.dim==3:
|
|
841
|
+
if current_item.data.settings['xlabel'] != '':
|
|
842
|
+
data[current_item.data.settings['xlabel']]=current_item.data.processed_data[0].flatten().tolist()
|
|
843
|
+
else:
|
|
844
|
+
data['X']=current_item.data.processed_data[0].flatten().tolist()
|
|
845
|
+
if current_item.data.settings['ylabel'] != '':
|
|
846
|
+
data[current_item.data.settings['ylabel']]=current_item.data.processed_data[1].flatten().tolist()
|
|
847
|
+
else:
|
|
848
|
+
data['Y']=current_item.data.processed_data[1].flatten().tolist()
|
|
849
|
+
if current_item.data.settings['clabel'] != '':
|
|
850
|
+
data[current_item.data.settings['clabel']]=current_item.data.processed_data[2].flatten().tolist()
|
|
851
|
+
else:
|
|
852
|
+
data['Z']=current_item.data.processed_data[2].flatten().tolist()
|
|
853
|
+
|
|
854
|
+
elif current_item.data.dim == 2:
|
|
855
|
+
for line in current_item.data.plotted_lines.keys():
|
|
856
|
+
if current_item.data.plotted_lines[line]['checkstate']:
|
|
857
|
+
if current_item.data.plot_type == 'Histogram':
|
|
858
|
+
headers=[f'{line}_{current_item.data.plotted_lines[line]['Y data']}_bins',
|
|
859
|
+
f'{line}_{current_item.data.plotted_lines[line]['Y data']}_counts',
|
|
860
|
+
f'{line}_{current_item.data.plotted_lines[line]['Y data']}_bins_fit']
|
|
861
|
+
else:
|
|
862
|
+
headers=[f'{line}_{current_item.data.plotted_lines[line]['X data']}',
|
|
863
|
+
f'{line}_{current_item.data.plotted_lines[line]['Y data']}',
|
|
864
|
+
f'{line}_{current_item.data.plotted_lines[line]['X data']}_fit']
|
|
865
|
+
data[headers[0]]=current_item.data.plotted_lines[line]['processed_data'][0].tolist()
|
|
866
|
+
data[headers[1]]=current_item.data.plotted_lines[line]['processed_data'][1].tolist()
|
|
867
|
+
if 'fit' in current_item.data.plotted_lines[line].keys() and current_item.data.plotted_lines[line]['fit']['fit_checkstate']:
|
|
868
|
+
data[headers[2]]=current_item.data.plotted_lines[line]['fit']['xdata'].tolist()
|
|
869
|
+
fit_result=current_item.data.plotted_lines[line]['fit']['fit_result']
|
|
870
|
+
data[f'{line}_{current_item.data.plotted_lines[line]['Y data']}'+'_fit']=fit_result.best_fit.tolist()
|
|
871
|
+
data[f'{line}_{current_item.data.plotted_lines[line]['Y data']}'+'_fit_error']=fit_result.eval_uncertainty().tolist()
|
|
872
|
+
fit_components=fit_result.eval_components()
|
|
873
|
+
for component in fit_components:
|
|
874
|
+
data[f'{line}_{current_item.data.plotted_lines[line]['Y data']}'+'_'+component]=fit_components[component].tolist()
|
|
875
|
+
if '.json' in ext:
|
|
876
|
+
with open(filepath, 'w') as json_file:
|
|
877
|
+
jsondump(data, json_file,ensure_ascii=False, indent=4)
|
|
878
|
+
|
|
879
|
+
elif '.csv' in ext:
|
|
880
|
+
with open(filepath, 'w', newline='') as f:
|
|
881
|
+
writer = csvwriter(f)
|
|
882
|
+
if current_item.data.dim==3:
|
|
883
|
+
header=[]
|
|
884
|
+
for label in ['xlabel','ylabel','clabel']:
|
|
885
|
+
if current_item.data.settings[label] != '':
|
|
886
|
+
header.append(f'#{current_item.data.settings[label]}')
|
|
887
|
+
writer.writerow(header)
|
|
888
|
+
for j in range(np.shape(current_item.data.processed_data[2])[0]):
|
|
889
|
+
for k in range(np.shape(current_item.data.processed_data[2])[1]):
|
|
890
|
+
writer.writerow([current_item.data.processed_data[0][j,k],current_item.data.processed_data[1][j,k],current_item.data.processed_data[2][j,k]])
|
|
891
|
+
elif current_item.data.dim == 2:
|
|
892
|
+
writer.writerow([key for key in data.keys()])
|
|
893
|
+
lengths = [len(data[key]) for key in data.keys()]
|
|
894
|
+
for i in range(np.max(lengths)):
|
|
895
|
+
row = []
|
|
896
|
+
for key in data.keys():
|
|
897
|
+
try:
|
|
898
|
+
row.append(data[key][i])
|
|
899
|
+
except IndexError:
|
|
900
|
+
row.append('')
|
|
901
|
+
writer.writerow(row)
|
|
902
|
+
else:
|
|
903
|
+
print('No processed data to export')
|
|
904
|
+
|
|
905
|
+
elif which=='Z':
|
|
906
|
+
if hasattr(current_item.data,'processed_data'):
|
|
907
|
+
if '.dat' in ext:
|
|
908
|
+
with open(filepath,'w') as dat_file:
|
|
909
|
+
np.savetxt(filepath, current_item.data.processed_data[-1])
|
|
910
|
+
else:
|
|
911
|
+
print('No processed data to export')
|
|
912
|
+
|
|
913
|
+
def file_checked(self, item):
|
|
914
|
+
|
|
915
|
+
if item.checkState() == 2:
|
|
916
|
+
self.file_list.setCurrentItem(item)
|
|
917
|
+
self.show_or_hide_mixeddata_widgets()
|
|
918
|
+
self.update_plots()
|
|
919
|
+
|
|
920
|
+
def plot_type_changed(self):
|
|
921
|
+
current_item = self.file_list.currentItem()
|
|
922
|
+
plot_type = self.plot_type_box.currentText()
|
|
923
|
+
current_item.data.plot_type=plot_type
|
|
924
|
+
|
|
925
|
+
lineedits={'X':self.binsX_lineedit,
|
|
926
|
+
'Y':self.binsY_lineedit}
|
|
927
|
+
labels={'X':self.binsX_label,
|
|
928
|
+
'Y':self.binsY_label}
|
|
929
|
+
|
|
930
|
+
self.binsX_lineedit.hide()
|
|
931
|
+
self.binsY_lineedit.hide()
|
|
932
|
+
self.binsX_label.hide()
|
|
933
|
+
self.binsY_label.hide()
|
|
934
|
+
if hasattr(current_item.data,'sidebar1D'):
|
|
935
|
+
# current_row = current_item.data.sidebar1D.trace_table.currentRow()
|
|
936
|
+
# line = int(current_item.data.sidebar1D.trace_table.item(current_row,0).text())
|
|
937
|
+
current_item.data.sidebar1D.plot_type_changed()
|
|
938
|
+
self.update_plots(update_data=False)
|
|
939
|
+
|
|
940
|
+
else:
|
|
941
|
+
if 'Histogram' in plot_type:
|
|
942
|
+
try:
|
|
943
|
+
which=plot_type.split(' ')[1]
|
|
944
|
+
|
|
945
|
+
labels[which].show()
|
|
946
|
+
lineedits[which].show()
|
|
947
|
+
|
|
948
|
+
if f'bins{which}' not in current_item.data.settings.keys():
|
|
949
|
+
current_item.data.settings[f'bins{which}'] = 100
|
|
950
|
+
|
|
951
|
+
lineedits[which].setText(str(current_item.data.settings[f'bins{which}']))
|
|
952
|
+
except Exception as e:
|
|
953
|
+
print('Error in plot_type_changed:', e)
|
|
954
|
+
|
|
955
|
+
self.update_plots(update_data=True,update_color_limits=True)
|
|
956
|
+
|
|
957
|
+
self.reset_axlim_settings()
|
|
958
|
+
|
|
959
|
+
def bins_changed(self,which='X'):
|
|
960
|
+
lineedits={'X':self.binsX_lineedit,'Y':self.binsY_lineedit}
|
|
961
|
+
current_item = self.file_list.currentItem()
|
|
962
|
+
current_item.data.settings[f'bins{which}'] = int(lineedits[which].text())
|
|
963
|
+
self.update_plots()
|
|
964
|
+
|
|
965
|
+
def show_2d_data_checkbox_changed(self):
|
|
966
|
+
current_item = self.file_list.currentItem()
|
|
967
|
+
if isinstance(current_item.data, MixedInternalData):
|
|
968
|
+
current_item.data.show_2d_data = self.show_2d_data_checkbox.isChecked()
|
|
969
|
+
self.update_plots()
|
|
970
|
+
|
|
971
|
+
def show_or_hide_view_settings(self):
|
|
972
|
+
current_item = self.file_list.currentItem()
|
|
973
|
+
if current_item:
|
|
974
|
+
if any([current_item.data.dim==3,isinstance(current_item.data, MixedInternalData)]):
|
|
975
|
+
self.stats_button.show()
|
|
976
|
+
for i in range(self.view_layout.rowCount()):
|
|
977
|
+
for j in range(self.view_layout.columnCount()):
|
|
978
|
+
try:
|
|
979
|
+
self.view_layout.itemAtPosition(i,j).widget().show()
|
|
980
|
+
except:
|
|
981
|
+
pass
|
|
982
|
+
else:
|
|
983
|
+
self.stats_button.hide()
|
|
984
|
+
for i in range(self.view_layout.rowCount()):
|
|
985
|
+
for j in range(self.view_layout.columnCount()):
|
|
986
|
+
try:
|
|
987
|
+
self.view_layout.itemAtPosition(i,j).widget().hide()
|
|
988
|
+
except:
|
|
989
|
+
pass
|
|
990
|
+
|
|
991
|
+
def file_clicked(self):
|
|
992
|
+
current_item = self.file_list.currentItem()
|
|
993
|
+
self.show_current_all()
|
|
994
|
+
self.clear_sidebar1D()
|
|
995
|
+
if hasattr(current_item.data,'sidebar1D'):
|
|
996
|
+
self.oneD_layout.addWidget(current_item.data.sidebar1D)
|
|
997
|
+
self.show_or_hide_mixeddata_widgets()
|
|
998
|
+
|
|
999
|
+
def show_or_hide_mixeddata_widgets(self):
|
|
1000
|
+
current_item = self.file_list.currentItem()
|
|
1001
|
+
if isinstance(current_item.data,MixedInternalData):
|
|
1002
|
+
self.show_2d_data_checkbox.show()
|
|
1003
|
+
self.show_2d_data_checkbox.setChecked(current_item.data.show_2d_data)
|
|
1004
|
+
self.plot_type_box.hide()
|
|
1005
|
+
self.plot_type_label.hide()
|
|
1006
|
+
self.mixeddata_filter_box.clear()
|
|
1007
|
+
self.mixeddata_filter_box.show()
|
|
1008
|
+
self.mixeddata_filter_box.addItem(f'Applied to 2D data: {current_item.data.dataset2d.label}')
|
|
1009
|
+
self.mixeddata_filter_box.addItem(f'Applied to 1D data: {current_item.data.dataset1d.label}')
|
|
1010
|
+
else:
|
|
1011
|
+
self.plot_type_box.show()
|
|
1012
|
+
self.plot_type_label.show()
|
|
1013
|
+
self.show_2d_data_checkbox.hide()
|
|
1014
|
+
self.mixeddata_filter_box.clear()
|
|
1015
|
+
self.mixeddata_filter_box.hide()
|
|
1016
|
+
|
|
1017
|
+
def file_double_clicked(self, item):
|
|
1018
|
+
self.file_list.itemChanged.disconnect(self.file_checked)
|
|
1019
|
+
for item_index in range(self.file_list.count()):
|
|
1020
|
+
self.file_list.item(item_index).setCheckState(QtCore.Qt.Unchecked)
|
|
1021
|
+
item.setCheckState(QtCore.Qt.Checked)
|
|
1022
|
+
self.file_list.itemChanged.connect(self.file_checked)
|
|
1023
|
+
self.update_plots()
|
|
1024
|
+
|
|
1025
|
+
def reinstate_markers(self, item, orientation):
|
|
1026
|
+
orientations={'horizontal':'horimarkers','vertical':'vertmarkers'}
|
|
1027
|
+
arrayname=orientations[orientation]
|
|
1028
|
+
setattr(item.data,arrayname,[])
|
|
1029
|
+
if orientation == 'horizontal':
|
|
1030
|
+
for line in item.data.linecuts['horizontal']['lines']:
|
|
1031
|
+
z=item.data.linecuts[orientation]['lines'][line]['cut_axis_value']
|
|
1032
|
+
item.data.horimarkers.append(item.data.axes.axhline(y=z, linestyle='dashed', linewidth=1, xmax=0.1,
|
|
1033
|
+
color=item.data.linecuts[orientation]['lines'][line]['linecolor']))
|
|
1034
|
+
item.data.horimarkers.append(item.data.axes.axhline(y=z, linestyle='dashed', linewidth=1, xmin=0.9,
|
|
1035
|
+
color=item.data.linecuts[orientation]['lines'][line]['linecolor']))
|
|
1036
|
+
|
|
1037
|
+
elif orientation == 'vertical':
|
|
1038
|
+
for line in item.data.linecuts['vertical']['lines']:
|
|
1039
|
+
z=item.data.linecuts[orientation]['lines'][line]['cut_axis_value']
|
|
1040
|
+
item.data.vertmarkers.append(item.data.axes.axvline(x=z, linestyle='dashed', linewidth=1, ymax=0.1,
|
|
1041
|
+
color=item.data.linecuts[orientation]['lines'][line]['linecolor']))
|
|
1042
|
+
item.data.vertmarkers.append(item.data.axes.axvline(x=z, linestyle='dashed', linewidth=1, ymin=0.9,
|
|
1043
|
+
color=item.data.linecuts[orientation]['lines'][line]['linecolor']))
|
|
1044
|
+
|
|
1045
|
+
def clear_sidebar1D(self):
|
|
1046
|
+
# clear the sidebar1D
|
|
1047
|
+
for i in reversed(range(self.oneD_layout.count())):
|
|
1048
|
+
widgetToRemove = self.oneD_layout.itemAt(i).widget()
|
|
1049
|
+
# remove it from the layout list
|
|
1050
|
+
self.oneD_layout.removeWidget(widgetToRemove)
|
|
1051
|
+
# remove it from the gui
|
|
1052
|
+
widgetToRemove.setParent(None)
|
|
1053
|
+
|
|
1054
|
+
def update_plots(self, item=None,update_data=True,clear_figure=True,update_color_limits=False):
|
|
1055
|
+
if clear_figure:
|
|
1056
|
+
self.figure.clf()
|
|
1057
|
+
|
|
1058
|
+
self.clear_sidebar1D()
|
|
1059
|
+
|
|
1060
|
+
checked_items = self.get_checked_items()
|
|
1061
|
+
if checked_items:
|
|
1062
|
+
rows, cols = self.subplot_grid[len(checked_items)-1]
|
|
1063
|
+
for index, item in enumerate(checked_items):
|
|
1064
|
+
try:
|
|
1065
|
+
if not hasattr(item.data, 'processed_data'):
|
|
1066
|
+
item.data.prepare_data_for_plot(reload_data=True)
|
|
1067
|
+
elif hasattr(item.data, 'dim'):
|
|
1068
|
+
if item.data.dim == 3 and update_data==True: # This should only be called when updating 2D data: updating 1D data is taken care of in the datatype and sidebar
|
|
1069
|
+
item.data.prepare_data_for_plot(update_color_limits=update_color_limits) #reload_data=False by default
|
|
1070
|
+
elif item.data.dim == 'mixed' and update_data==True: #If MixedInternalData
|
|
1071
|
+
item.data.dataset2d.prepare_data_for_plot(update_color_limits=update_color_limits)
|
|
1072
|
+
|
|
1073
|
+
item.data.figure = self.figure
|
|
1074
|
+
item.data.axes = item.data.figure.add_subplot(rows, cols, index+1)
|
|
1075
|
+
item.data.add_plot(editor_window=self)
|
|
1076
|
+
if hasattr(item.data, 'linecuts'):
|
|
1077
|
+
for orientation in ['horizontal','vertical']:#,'diagonal']:#,'circular']:
|
|
1078
|
+
if len(item.data.linecuts[orientation]['lines']) > 0:
|
|
1079
|
+
self.reinstate_markers(item,orientation)
|
|
1080
|
+
if item.data.linecuts[orientation]['linecut_window'] is not None and item==self.file_list.currentItem():
|
|
1081
|
+
# This can be really heavy if there's lots of linecuts and fits. Only run if file actually in focus.
|
|
1082
|
+
item.data.linecuts[orientation]['linecut_window'].update()
|
|
1083
|
+
if hasattr(item.data, 'sidebar1D') and self.file_list.currentItem() == item:
|
|
1084
|
+
self.oneD_layout.addWidget(item.data.sidebar1D)
|
|
1085
|
+
except Exception as e:
|
|
1086
|
+
print(f'Could not plot {item.data.filepath}:', e)
|
|
1087
|
+
raise
|
|
1088
|
+
try:
|
|
1089
|
+
self.show_current_all()
|
|
1090
|
+
self.figure.tight_layout()
|
|
1091
|
+
self.canvas.draw()
|
|
1092
|
+
except Exception as e:
|
|
1093
|
+
print('Error in update_plots:', e)
|
|
1094
|
+
|
|
1095
|
+
if hasattr(self, 'live_track_item') and self.live_track_item:
|
|
1096
|
+
if (self.live_track_item.checkState() and
|
|
1097
|
+
self.track_button.text() == 'Stop tracking' and
|
|
1098
|
+
hasattr(self.live_track_item.data, 'remaining_time_string')):
|
|
1099
|
+
self.remaining_time_label.setText(self.live_track_item.data.remaining_time_string)
|
|
1100
|
+
else:
|
|
1101
|
+
self.remaining_time_label.setText('')
|
|
1102
|
+
|
|
1103
|
+
def refresh_files(self):
|
|
1104
|
+
checked_items = self.get_checked_items()
|
|
1105
|
+
if checked_items:
|
|
1106
|
+
for item in checked_items:
|
|
1107
|
+
item.data.prepare_data_for_plot(reload_data=True, reload_from_file=True)
|
|
1108
|
+
self.update_plots()
|
|
1109
|
+
if self.linked_folder:
|
|
1110
|
+
old_number_of_items = self.file_list.count()
|
|
1111
|
+
self.update_link_to_folder(new_folder=False)
|
|
1112
|
+
if self.file_list.count() > old_number_of_items:
|
|
1113
|
+
last_item = self.file_list.item(self.file_list.count()-1)
|
|
1114
|
+
self.file_double_clicked(last_item)
|
|
1115
|
+
self.file_list.setCurrentItem(last_item)
|
|
1116
|
+
#self.track_button_clicked()
|
|
1117
|
+
|
|
1118
|
+
# The below is not working and not implemented, but could be useful to fix and include.
|
|
1119
|
+
# def to_next_file(self):
|
|
1120
|
+
# checked_items, indices = self.get_checked_items(return_indices=True)
|
|
1121
|
+
# if (len(checked_items) == 1 and self.file_list.count() > 1 and
|
|
1122
|
+
# indices[0]+1 < self.file_list.count()):
|
|
1123
|
+
# item = checked_items[0]
|
|
1124
|
+
# next_item = self.file_list.item(indices[0]+1)
|
|
1125
|
+
# self.file_list.itemChanged.disconnect(self.file_checked)
|
|
1126
|
+
# item.setCheckState(QtCore.Qt.Unchecked)
|
|
1127
|
+
# next_item.setCheckState(QtCore.Qt.Checked)
|
|
1128
|
+
# self.file_list.setCurrentItem(next_item)
|
|
1129
|
+
# self.file_list.itemChanged.connect(self.file_checked)
|
|
1130
|
+
# self.update_plots()
|
|
1131
|
+
|
|
1132
|
+
# def to_previous_file(self):
|
|
1133
|
+
# checked_items, indices = self.get_checked_items(return_indices=True)
|
|
1134
|
+
# if (len(checked_items) == 1 and self.file_list.count() > 1
|
|
1135
|
+
# and indices[0] > 0):
|
|
1136
|
+
# item = checked_items[0]
|
|
1137
|
+
# previous_item = self.file_list.item(indices[0]-1)
|
|
1138
|
+
# self.file_list.itemChanged.disconnect(self.file_checked)
|
|
1139
|
+
# item.setCheckState(QtCore.Qt.Unchecked)
|
|
1140
|
+
# previous_item.setCheckState(QtCore.Qt.Checked)
|
|
1141
|
+
# self.file_list.setCurrentItem(previous_item)
|
|
1142
|
+
# self.file_list.itemChanged.connect(self.file_checked)
|
|
1143
|
+
# self.update_plots()
|
|
1144
|
+
|
|
1145
|
+
def get_checked_items(self, return_indices = False):
|
|
1146
|
+
indices = [index for index in range(self.file_list.count())
|
|
1147
|
+
if self.file_list.item(index).checkState() == 2]
|
|
1148
|
+
checked_items = [self.file_list.item(index) for index in indices]
|
|
1149
|
+
if return_indices:
|
|
1150
|
+
return checked_items, indices
|
|
1151
|
+
else:
|
|
1152
|
+
return checked_items
|
|
1153
|
+
|
|
1154
|
+
def get_unchecked_items(self, return_indices = False):
|
|
1155
|
+
indices = [index for index in range(self.file_list.count())
|
|
1156
|
+
if self.file_list.item(index).checkState() != 2]
|
|
1157
|
+
unchecked_items = [self.file_list.item(index) for index in indices]
|
|
1158
|
+
if return_indices:
|
|
1159
|
+
return unchecked_items, indices
|
|
1160
|
+
else:
|
|
1161
|
+
return unchecked_items
|
|
1162
|
+
|
|
1163
|
+
def get_all_items(self, return_indices = False):
|
|
1164
|
+
indices = [index for index in range(self.file_list.count())]
|
|
1165
|
+
all_items = [self.file_list.item(index) for index in indices]
|
|
1166
|
+
if return_indices:
|
|
1167
|
+
return all_items, indices
|
|
1168
|
+
else:
|
|
1169
|
+
return all_items
|
|
1170
|
+
|
|
1171
|
+
def refresh_interval_changed(self, interval):
|
|
1172
|
+
AUTO_REFRESH_INTERVAL_3D=float(interval)
|
|
1173
|
+
if self.track_button.text() == 'Stop tracking':
|
|
1174
|
+
self.track_button_clicked()
|
|
1175
|
+
|
|
1176
|
+
def track_button_clicked(self):
|
|
1177
|
+
current_item = self.file_list.currentItem()
|
|
1178
|
+
if (self.track_button.text() == 'Track data' and
|
|
1179
|
+
current_item and current_item.checkState() and
|
|
1180
|
+
not current_item.data.file_finished()):
|
|
1181
|
+
self.live_track_item = current_item
|
|
1182
|
+
self.live_track_item.setText('[LIVE] '+self.live_track_item.data.label)
|
|
1183
|
+
self.live_track_item.data.prepare_data_for_plot(reload_data=True)
|
|
1184
|
+
if self.live_track_item.data.raw_data:
|
|
1185
|
+
if len(self.live_track_item.data.get_columns()) == 3: # if file is 3D
|
|
1186
|
+
self.start_auto_refresh(AUTO_REFRESH_INTERVAL_3D)
|
|
1187
|
+
elif len(self.live_track_item.data.get_columns()) == 2: # if file is 2D
|
|
1188
|
+
self.start_auto_refresh(AUTO_REFRESH_INTERVAL_2D)
|
|
1189
|
+
else:
|
|
1190
|
+
self.start_auto_refresh(AUTO_REFRESH_INTERVAL_2D,
|
|
1191
|
+
wait_for_file=True)
|
|
1192
|
+
elif self.track_button.text() == 'Stop tracking':
|
|
1193
|
+
self.stop_auto_refresh()
|
|
1194
|
+
|
|
1195
|
+
def start_auto_refresh(self, time_interval, wait_for_file=False):
|
|
1196
|
+
self.track_button.setText('Stop tracking')
|
|
1197
|
+
self.auto_refresh_timer = QtCore.QTimer()
|
|
1198
|
+
self.auto_refresh_timer.setInterval(time_interval*1000)
|
|
1199
|
+
if wait_for_file:
|
|
1200
|
+
self.auto_refresh_timer.timeout.connect(self.wait_for_file_call)
|
|
1201
|
+
else:
|
|
1202
|
+
self.auto_refresh_timer.timeout.connect(self.auto_refresh_call)
|
|
1203
|
+
self.action_refresh_stop.setEnabled(True)
|
|
1204
|
+
self.auto_refresh_timer.start()
|
|
1205
|
+
self.window_title_auto_refresh = ' - Auto-Refreshing Enabled'
|
|
1206
|
+
self.setWindowTitle(self.window_title+self.window_title_auto_refresh)
|
|
1207
|
+
self.auto_refresh_call()
|
|
1208
|
+
|
|
1209
|
+
def wait_for_file_call(self):
|
|
1210
|
+
if self.live_track_item and self.live_track_item.checkState():
|
|
1211
|
+
self.live_track_item.data.prepare_data_for_plot(reload_data=True)
|
|
1212
|
+
if self.live_track_item.data.raw_data:
|
|
1213
|
+
self.auto_refresh_timer.stop()
|
|
1214
|
+
self.track_button.setText('Track data')
|
|
1215
|
+
self.track_button_clicked()
|
|
1216
|
+
|
|
1217
|
+
def auto_refresh_call(self):
|
|
1218
|
+
if self.live_track_item and self.live_track_item.checkState():
|
|
1219
|
+
if self.live_track_item.data.file_finished():
|
|
1220
|
+
print('Stop auto refresh...')
|
|
1221
|
+
self.live_track_item.data.remaining_time_string = ''
|
|
1222
|
+
self.stop_auto_refresh()
|
|
1223
|
+
else:
|
|
1224
|
+
if hasattr(self.live_track_item.data, 'remaining_time_string'):
|
|
1225
|
+
self.remaining_time_label.setText(self.live_track_item.data.remaining_time_string)
|
|
1226
|
+
else:
|
|
1227
|
+
self.remaining_time_label.setText('')
|
|
1228
|
+
self.window_title_auto_refresh = ' - Auto-Refreshing Enabled (Refreshing...)'
|
|
1229
|
+
self.setWindowTitle(self.window_title+self.window_title_auto_refresh)
|
|
1230
|
+
self.refresh_files()
|
|
1231
|
+
self.window_title_auto_refresh = ' - Auto-Refreshing Enabled'
|
|
1232
|
+
self.setWindowTitle(self.window_title+self.window_title_auto_refresh)
|
|
1233
|
+
|
|
1234
|
+
def stop_auto_refresh(self):
|
|
1235
|
+
self.track_button.setText('Track data')
|
|
1236
|
+
self.auto_refresh_timer.stop()
|
|
1237
|
+
self.action_refresh_stop.setEnabled(False)
|
|
1238
|
+
self.window_title_auto_refresh = ''
|
|
1239
|
+
self.setWindowTitle(self.window_title+self.window_title_auto_refresh)
|
|
1240
|
+
self.remaining_time_label.setText('')
|
|
1241
|
+
self.live_track_item.setText(self.live_track_item.data.label)
|
|
1242
|
+
|
|
1243
|
+
def move_file(self, direction):
|
|
1244
|
+
current_item = self.file_list.currentItem()
|
|
1245
|
+
if current_item:
|
|
1246
|
+
current_row = self.file_list.currentRow()
|
|
1247
|
+
if direction == 'up' and current_row > 0:
|
|
1248
|
+
new_row = current_row-1
|
|
1249
|
+
elif direction == 'down' and current_row < self.file_list.count()-1:
|
|
1250
|
+
new_row = current_row+1
|
|
1251
|
+
else:
|
|
1252
|
+
new_row = current_row
|
|
1253
|
+
if new_row != current_row:
|
|
1254
|
+
if (current_item.checkState() == 2 and
|
|
1255
|
+
self.file_list.item(new_row).checkState() == 2):
|
|
1256
|
+
update_canvas = True
|
|
1257
|
+
else:
|
|
1258
|
+
update_canvas = False
|
|
1259
|
+
self.file_list.takeItem(current_row)
|
|
1260
|
+
self.file_list.insertItem(new_row, current_item)
|
|
1261
|
+
self.file_list.setCurrentRow(new_row)
|
|
1262
|
+
if update_canvas:
|
|
1263
|
+
self.update_plots()
|
|
1264
|
+
|
|
1265
|
+
def show_current_all(self):
|
|
1266
|
+
self.populate_new_plot_settings()
|
|
1267
|
+
self.show_current_plot_settings()
|
|
1268
|
+
self.show_current_view_settings()
|
|
1269
|
+
self.show_current_filters()
|
|
1270
|
+
self.show_current_axscale_settings()
|
|
1271
|
+
self.show_current_axlim_settings()
|
|
1272
|
+
self.show_or_hide_view_settings()
|
|
1273
|
+
self.show_data_shape()
|
|
1274
|
+
|
|
1275
|
+
def show_data_shape(self):
|
|
1276
|
+
current_item = self.file_list.currentItem()
|
|
1277
|
+
if current_item:
|
|
1278
|
+
try:
|
|
1279
|
+
self.data_shape_label.setText(f'Data shape: {current_item.data.processed_data[-1].shape}')
|
|
1280
|
+
except:
|
|
1281
|
+
self.data_shape_label.setText('Data shape:')
|
|
1282
|
+
else:
|
|
1283
|
+
self.data_shape_label.setText('Data shape:')
|
|
1284
|
+
|
|
1285
|
+
def populate_new_plot_settings(self):
|
|
1286
|
+
self.plot_type_box.currentIndexChanged.disconnect(self.plot_type_changed)
|
|
1287
|
+
try:
|
|
1288
|
+
boxes= [self.new_plot_X_box, self.new_plot_Y_box, self.new_plot_Z_box, self.plot_type_box]
|
|
1289
|
+
for combobox in boxes:
|
|
1290
|
+
combobox.clear()
|
|
1291
|
+
|
|
1292
|
+
current_item = self.file_list.currentItem()
|
|
1293
|
+
if current_item:
|
|
1294
|
+
if hasattr(current_item.data, 'all_parameter_names'):
|
|
1295
|
+
dim = len(current_item.data.get_columns())
|
|
1296
|
+
if dim == 2:
|
|
1297
|
+
self.new_plot_Z_label.hide()
|
|
1298
|
+
self.new_plot_Z_box.hide()
|
|
1299
|
+
boxes= [self.new_plot_X_box, self.new_plot_Y_box]
|
|
1300
|
+
else:
|
|
1301
|
+
self.new_plot_Z_label.show()
|
|
1302
|
+
self.new_plot_Z_box.show()
|
|
1303
|
+
boxes= [self.new_plot_X_box, self.new_plot_Y_box, self.new_plot_Z_box]
|
|
1304
|
+
for combobox in boxes:
|
|
1305
|
+
combobox.addItems(current_item.data.all_parameter_names)
|
|
1306
|
+
self.new_plot_X_box.setCurrentIndex(0)
|
|
1307
|
+
self.new_plot_Y_box.setCurrentIndex(1)
|
|
1308
|
+
if dim == 3:
|
|
1309
|
+
self.new_plot_Z_box.setCurrentIndex(2)
|
|
1310
|
+
|
|
1311
|
+
if current_item.data.dim == 2:
|
|
1312
|
+
plot_types=['X,Y','Histogram','FFT']
|
|
1313
|
+
elif current_item.data.dim == 3:
|
|
1314
|
+
plot_types=['X,Y,Z', 'Histogram Y', 'Histogram X', 'FFT Y', 'FFT X', 'FFT X/Y']
|
|
1315
|
+
self.plot_type_box.addItems(plot_types)
|
|
1316
|
+
if hasattr(current_item.data, 'plot_type'):
|
|
1317
|
+
if current_item.data.plot_type in plot_types:
|
|
1318
|
+
self.plot_type_box.setCurrentText(current_item.data.plot_type)
|
|
1319
|
+
|
|
1320
|
+
lineedits={'X':[self.binsX_lineedit],
|
|
1321
|
+
'Y':[self.binsY_lineedit],
|
|
1322
|
+
'X/Y':[self.binsX_lineedit,self.binsY_lineedit]}
|
|
1323
|
+
labels={'X':[self.binsX_label],
|
|
1324
|
+
'Y':[self.binsY_label],
|
|
1325
|
+
'X/Y':[self.binsX_label,self.binsY_label]}
|
|
1326
|
+
|
|
1327
|
+
# Have to first hide to make sure the only correct one(s) shown
|
|
1328
|
+
for label in labels['X/Y']:
|
|
1329
|
+
label.hide()
|
|
1330
|
+
for lineedit in lineedits['X/Y']:
|
|
1331
|
+
lineedit.hide()
|
|
1332
|
+
|
|
1333
|
+
if self.plot_type_box.currentText() in ['Histogram X', 'Histogram Y', 'Histogram X/Y']:
|
|
1334
|
+
which=self.plot_type_box.currentText().split(' ')[1]
|
|
1335
|
+
for label in labels[which]:
|
|
1336
|
+
label.show()
|
|
1337
|
+
for lineedit in lineedits[which]:
|
|
1338
|
+
lineedit.show()
|
|
1339
|
+
|
|
1340
|
+
if isinstance(current_item.data, qcodesppData):
|
|
1341
|
+
self.metadata_button.show()
|
|
1342
|
+
else:
|
|
1343
|
+
self.metadata_button.hide()
|
|
1344
|
+
|
|
1345
|
+
except:
|
|
1346
|
+
pass
|
|
1347
|
+
self.plot_type_box.currentIndexChanged.connect(self.plot_type_changed)
|
|
1348
|
+
|
|
1349
|
+
def show_current_plot_settings(self):
|
|
1350
|
+
self.settings_table.clear()
|
|
1351
|
+
self.settings_table.setRowCount(0)
|
|
1352
|
+
current_item = self.file_list.currentItem()
|
|
1353
|
+
if current_item:
|
|
1354
|
+
self.settings_table.itemChanged.disconnect(self.plot_setting_edited)
|
|
1355
|
+
self.settings_table.setRowCount(0)
|
|
1356
|
+
old_settings = current_item.data.settings
|
|
1357
|
+
preferred_order= ['X data', 'Y data', 'Z data',
|
|
1358
|
+
'title', 'xlabel', 'ylabel', 'clabel']
|
|
1359
|
+
settings = OrderedDict()
|
|
1360
|
+
for key in preferred_order:
|
|
1361
|
+
if key in old_settings:
|
|
1362
|
+
settings[key] = old_settings[key]
|
|
1363
|
+
for key, value in old_settings.items():
|
|
1364
|
+
if key not in preferred_order:
|
|
1365
|
+
settings[key] = value
|
|
1366
|
+
for key, value in list(settings.items()):
|
|
1367
|
+
row = self.settings_table.rowCount()
|
|
1368
|
+
self.settings_table.insertRow(row)
|
|
1369
|
+
property_item = QtWidgets.QTableWidgetItem(key)
|
|
1370
|
+
property_item.setFlags(QtCore.Qt.ItemIsSelectable |
|
|
1371
|
+
QtCore.Qt.ItemIsEnabled)
|
|
1372
|
+
self.settings_table.setItem(row, 0, property_item)
|
|
1373
|
+
self.settings_table.setItem(row, 1, QtWidgets.QTableWidgetItem(value))
|
|
1374
|
+
self.settings_table.itemChanged.connect(self.plot_setting_edited)
|
|
1375
|
+
|
|
1376
|
+
def show_current_view_settings(self):
|
|
1377
|
+
current_item = self.file_list.currentItem()
|
|
1378
|
+
if current_item:
|
|
1379
|
+
settings = current_item.data.view_settings
|
|
1380
|
+
self.min_line_edit.setText(f'{settings["Minimum"]:.4g}')
|
|
1381
|
+
self.max_line_edit.setText(f'{settings["Maximum"]:.4g}')
|
|
1382
|
+
self.mid_line_edit.setText(f'{settings["Midpoint"]:.4g}')
|
|
1383
|
+
if settings['Locked']:
|
|
1384
|
+
self.lock_checkbox.setCheckState(QtCore.Qt.Checked)
|
|
1385
|
+
else:
|
|
1386
|
+
self.lock_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
1387
|
+
if settings['MidLock']:
|
|
1388
|
+
self.mid_checkbox.setCheckState(QtCore.Qt.Checked)
|
|
1389
|
+
else:
|
|
1390
|
+
self.mid_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
1391
|
+
self.colormap_type_box.currentIndexChanged.disconnect(self.colormap_type_edited)
|
|
1392
|
+
self.colormap_type_box.setCurrentText(settings['Colormap Type'])
|
|
1393
|
+
self.colormap_type_box.currentIndexChanged.connect(self.colormap_type_edited)
|
|
1394
|
+
self.fill_colormap_box()
|
|
1395
|
+
self.colormap_box.currentIndexChanged.disconnect(self.colormap_edited)
|
|
1396
|
+
self.colormap_box.setCurrentText(settings['Colormap'])
|
|
1397
|
+
self.colormap_box.currentIndexChanged.connect(self.colormap_edited)
|
|
1398
|
+
if settings['Reverse']:
|
|
1399
|
+
self.reverse_colors_box.setCheckState(QtCore.Qt.Checked)
|
|
1400
|
+
else:
|
|
1401
|
+
self.reverse_colors_box.setCheckState(QtCore.Qt.Unchecked)
|
|
1402
|
+
if settings['CBarHist']:
|
|
1403
|
+
self.cbar_hist_checkbox.setCheckState(QtCore.Qt.Checked)
|
|
1404
|
+
else:
|
|
1405
|
+
self.cbar_hist_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
1406
|
+
else:
|
|
1407
|
+
self.min_line_edit.setText('')
|
|
1408
|
+
self.max_line_edit.setText('')
|
|
1409
|
+
self.lock_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
1410
|
+
self.mid_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
1411
|
+
|
|
1412
|
+
def show_current_axlim_settings(self):
|
|
1413
|
+
current_item = self.file_list.currentItem()
|
|
1414
|
+
if current_item:
|
|
1415
|
+
axlim_settings = current_item.data.axlim_settings
|
|
1416
|
+
if axlim_settings['Xmin'] is None:
|
|
1417
|
+
self.xmin_line_edit.setText('')
|
|
1418
|
+
else:
|
|
1419
|
+
self.xmin_line_edit.setText(f'{axlim_settings["Xmin"]:.5g}')
|
|
1420
|
+
if axlim_settings['Xmax'] is None:
|
|
1421
|
+
self.xmax_line_edit.setText('')
|
|
1422
|
+
else:
|
|
1423
|
+
self.xmax_line_edit.setText(f'{axlim_settings["Xmax"]:.5g}')
|
|
1424
|
+
if axlim_settings['Ymin'] is None:
|
|
1425
|
+
self.ymin_line_edit.setText('')
|
|
1426
|
+
else:
|
|
1427
|
+
self.ymin_line_edit.setText(f'{axlim_settings["Ymin"]:.5g}')
|
|
1428
|
+
if axlim_settings['Ymax'] is None:
|
|
1429
|
+
self.ymax_line_edit.setText('')
|
|
1430
|
+
else:
|
|
1431
|
+
self.ymax_line_edit.setText(f'{axlim_settings["Ymax"]:.5g}')
|
|
1432
|
+
|
|
1433
|
+
def show_current_axscale_settings(self):
|
|
1434
|
+
current_item = self.file_list.currentItem()
|
|
1435
|
+
if current_item:
|
|
1436
|
+
axlim_settings = current_item.data.axlim_settings
|
|
1437
|
+
|
|
1438
|
+
self.xaxis_combobox.currentIndexChanged.disconnect(self.axis_scaling_changed)
|
|
1439
|
+
self.xaxis_combobox.setCurrentText(axlim_settings['Xscale'])
|
|
1440
|
+
self.xaxis_combobox.currentIndexChanged.connect(self.axis_scaling_changed)
|
|
1441
|
+
|
|
1442
|
+
self.yaxis_combobox.currentIndexChanged.disconnect(self.axis_scaling_changed)
|
|
1443
|
+
self.yaxis_combobox.setCurrentText(axlim_settings['Yscale'])
|
|
1444
|
+
self.yaxis_combobox.currentIndexChanged.connect(self.axis_scaling_changed)
|
|
1445
|
+
|
|
1446
|
+
def which_filters(self,item,filters=None,filt=None):
|
|
1447
|
+
# This function works out which filters should be addressed based on the datatype (e.g. belonging to 1D line or 2D dataset)
|
|
1448
|
+
# and either returns them, sets them, or appends to them.
|
|
1449
|
+
if filters is None and filt is None:
|
|
1450
|
+
# Return the filters to operate on
|
|
1451
|
+
if isinstance(item.data,MixedInternalData) and self.mixeddata_filter_box.currentIndex() == 0:
|
|
1452
|
+
filters = item.data.dataset2d.filters
|
|
1453
|
+
elif hasattr(item.data, 'sidebar1D'):
|
|
1454
|
+
current_1D_row = item.data.sidebar1D.trace_table.currentRow()
|
|
1455
|
+
current_line = int(item.data.sidebar1D.trace_table.item(current_1D_row,0).text())
|
|
1456
|
+
if isinstance(item.data,MixedInternalData):
|
|
1457
|
+
filters= item.data.dataset1d.plotted_lines[current_line]['filters']
|
|
1458
|
+
else:
|
|
1459
|
+
filters= item.data.plotted_lines[current_line]['filters']
|
|
1460
|
+
else:
|
|
1461
|
+
filters = item.data.filters
|
|
1462
|
+
return filters
|
|
1463
|
+
|
|
1464
|
+
elif filt is None: # The filters are being set.
|
|
1465
|
+
if isinstance(item.data,MixedInternalData) and self.mixeddata_filter_box.currentIndex() == 0:
|
|
1466
|
+
item.data.dataset2d.filters=filters
|
|
1467
|
+
elif hasattr(item.data, 'sidebar1D'):
|
|
1468
|
+
current_1D_row = item.data.sidebar1D.trace_table.currentRow()
|
|
1469
|
+
current_line = int(item.data.sidebar1D.trace_table.item(current_1D_row,0).text())
|
|
1470
|
+
if isinstance(item.data,MixedInternalData):
|
|
1471
|
+
item.data.dataset1d.plotted_lines[current_line]['filters']=filters
|
|
1472
|
+
else:
|
|
1473
|
+
item.data.plotted_lines[current_line]['filters']=filters
|
|
1474
|
+
else:
|
|
1475
|
+
item.data.filters = filters
|
|
1476
|
+
|
|
1477
|
+
else: # A filter is being appended
|
|
1478
|
+
if isinstance(item.data,MixedInternalData) and self.mixeddata_filter_box.currentIndex() == 0:
|
|
1479
|
+
item.data.dataset2d.filters.append(filt)
|
|
1480
|
+
elif hasattr(item.data, 'sidebar1D'):
|
|
1481
|
+
current_1D_row = item.data.sidebar1D.trace_table.currentRow()
|
|
1482
|
+
current_line = int(item.data.sidebar1D.trace_table.item(current_1D_row,0).text())
|
|
1483
|
+
if hasattr(filt, 'method_list') and 'Z' in filt.method_list:
|
|
1484
|
+
filt.method_list=copy.copy(filt.method_list)
|
|
1485
|
+
filt.method_list.remove('Z')
|
|
1486
|
+
if hasattr(filt, 'method') and filt.method == 'Z':
|
|
1487
|
+
filt.method=filt.method_list[0]
|
|
1488
|
+
if isinstance(item.data,MixedInternalData):
|
|
1489
|
+
item.data.dataset1d.plotted_lines[current_line]['filters'].append(filt)
|
|
1490
|
+
else:
|
|
1491
|
+
item.data.plotted_lines[current_line]['filters'].append(filt)
|
|
1492
|
+
else:
|
|
1493
|
+
item.data.filters.append(filt)
|
|
1494
|
+
|
|
1495
|
+
def show_current_filters(self):
|
|
1496
|
+
self.filters_table.setRowCount(0)
|
|
1497
|
+
current_item = self.file_list.currentItem()
|
|
1498
|
+
if current_item:
|
|
1499
|
+
for _ in self.which_filters(current_item):
|
|
1500
|
+
try:
|
|
1501
|
+
self.append_filter_to_table()
|
|
1502
|
+
except Exception as e:
|
|
1503
|
+
print('Error appending filter to table:', e)
|
|
1504
|
+
|
|
1505
|
+
def global_text_changed(self):
|
|
1506
|
+
self.global_text_size=self.global_text_lineedit.text()
|
|
1507
|
+
for item in range(self.file_list.count()):
|
|
1508
|
+
if hasattr(self.file_list.item(item),'data') and hasattr(self.file_list.item(item).data,'settings'):
|
|
1509
|
+
for setting in ['titlesize','labelsize','ticksize']:
|
|
1510
|
+
if setting in self.file_list.item(item).data.settings.keys():
|
|
1511
|
+
self.file_list.item(item).data.settings[setting] = self.global_text_size
|
|
1512
|
+
if self.file_list.item(item).checkState():
|
|
1513
|
+
self.file_list.item(item).data.apply_plot_settings()
|
|
1514
|
+
self.show_current_plot_settings()
|
|
1515
|
+
self.canvas.draw()
|
|
1516
|
+
|
|
1517
|
+
def plot_setting_edited(self,setting_item=None,setting_name=None):
|
|
1518
|
+
current_item = self.file_list.currentItem()
|
|
1519
|
+
if current_item:
|
|
1520
|
+
current_item.data.old_settings = current_item.data.settings.copy()
|
|
1521
|
+
if setting_name is None:
|
|
1522
|
+
setting_name = self.settings_table.item(setting_item.row(), 0).text()
|
|
1523
|
+
for row in range(self.settings_table.rowCount()):
|
|
1524
|
+
value = self.settings_table.item(row, 1).text()
|
|
1525
|
+
name = self.settings_table.item(row, 0).text()
|
|
1526
|
+
current_item.data.settings[name] = value
|
|
1527
|
+
self.settings_table.clearFocus()
|
|
1528
|
+
try:
|
|
1529
|
+
if setting_name == 'X data' or setting_name == 'Y data' or setting_name == 'Z data':
|
|
1530
|
+
if isinstance(current_item.data,MixedInternalData):
|
|
1531
|
+
current_item.data.dataset2d.prepare_data_for_plot(reload_data=True,reload_from_file=False)
|
|
1532
|
+
else:
|
|
1533
|
+
current_item.data.prepare_data_for_plot(reload_data=True,reload_from_file=False)
|
|
1534
|
+
# IF the plotted data has happened, all fits are likely to be irrelevant. Not sure whether to remove by force or not.
|
|
1535
|
+
# I think keep the fits, because it's easy for the user to remove them, but could be a total pain to re-do if the user has
|
|
1536
|
+
# changed an axis by mistake. It's also the case for applying a filter, but in this case we uncheck them.
|
|
1537
|
+
self.check_all_filters(signal=None,manual_signal='Uncheck all')
|
|
1538
|
+
self.update_plots()
|
|
1539
|
+
self.reset_axlim_settings()
|
|
1540
|
+
elif 'label' in setting_name:
|
|
1541
|
+
current_item.data.labels_changed=True
|
|
1542
|
+
if f'default_{setting_name}' in current_item.data.settings.keys():
|
|
1543
|
+
current_item.data.settings[f'default_{setting_name}'] = current_item.data.settings[f'{setting_name}']
|
|
1544
|
+
self.show_current_plot_settings()
|
|
1545
|
+
elif setting_name == 'transpose':
|
|
1546
|
+
current_item.data.prepare_data_for_plot(reload_data=True,reload_from_file=True)
|
|
1547
|
+
self.update_plots()
|
|
1548
|
+
elif setting_name == 'columns' or setting_name == 'delimiter':
|
|
1549
|
+
current_item.data.prepare_data_for_plot(reload_data=True,reload_from_file=False)
|
|
1550
|
+
self.update_plots()
|
|
1551
|
+
self.reset_axlim_settings()
|
|
1552
|
+
elif setting_name == 'maskcolor' or setting_name == 'cmap levels':
|
|
1553
|
+
current_item.data.apply_colormap()
|
|
1554
|
+
elif (setting_name == 'rasterized' or setting_name == 'colorbar'
|
|
1555
|
+
or setting_name == 'minorticks'):
|
|
1556
|
+
self.update_plots()
|
|
1557
|
+
elif setting_name == 'shading':
|
|
1558
|
+
self.update_plots()
|
|
1559
|
+
current_item.data.extension_setting_edited(self, setting_name)
|
|
1560
|
+
current_item.data.apply_plot_settings()
|
|
1561
|
+
self.canvas.draw()
|
|
1562
|
+
except Exception as e: # if invalid value is typed: reset to previous settings
|
|
1563
|
+
print('Invalid value of plot setting!', e)
|
|
1564
|
+
self.paste_plot_settings(which='old')
|
|
1565
|
+
|
|
1566
|
+
def axlim_setting_edited(self, edited_setting):
|
|
1567
|
+
current_item = self.file_list.currentItem()
|
|
1568
|
+
axlim_settings = current_item.data.axlim_settings
|
|
1569
|
+
current_item.data.old_axlim_settings = axlim_settings.copy()
|
|
1570
|
+
if current_item:
|
|
1571
|
+
try:
|
|
1572
|
+
if edited_setting == 'Xmin':
|
|
1573
|
+
text_box = self.xmin_line_edit
|
|
1574
|
+
elif edited_setting == 'Xmax':
|
|
1575
|
+
text_box = self.xmax_line_edit
|
|
1576
|
+
elif edited_setting == 'Ymin':
|
|
1577
|
+
text_box = self.ymin_line_edit
|
|
1578
|
+
else:
|
|
1579
|
+
text_box = self.ymax_line_edit
|
|
1580
|
+
|
|
1581
|
+
if text_box.text() == '':
|
|
1582
|
+
new_value=None
|
|
1583
|
+
else:
|
|
1584
|
+
new_value = float(text_box.text())
|
|
1585
|
+
axlim_settings[edited_setting] = new_value
|
|
1586
|
+
if new_value is None:
|
|
1587
|
+
text_box.setText('')
|
|
1588
|
+
else:
|
|
1589
|
+
text_box.setText(f'{new_value:.4g}')
|
|
1590
|
+
text_box.clearFocus()
|
|
1591
|
+
current_item.data.apply_axlim_settings()
|
|
1592
|
+
self.canvas.draw()
|
|
1593
|
+
except Exception as e:
|
|
1594
|
+
print('Invalid axis limit!', e)
|
|
1595
|
+
self.paste_axlim_settings(which='old')
|
|
1596
|
+
|
|
1597
|
+
def reset_axlim_settings(self):
|
|
1598
|
+
current_item = self.file_list.currentItem()
|
|
1599
|
+
if current_item:
|
|
1600
|
+
current_item.data.reset_axlim_settings()
|
|
1601
|
+
self.show_current_axlim_settings()
|
|
1602
|
+
self.canvas.draw()
|
|
1603
|
+
|
|
1604
|
+
def axis_scaling_changed(self):
|
|
1605
|
+
current_item = self.file_list.currentItem()
|
|
1606
|
+
if current_item:
|
|
1607
|
+
settings = current_item.data.axlim_settings
|
|
1608
|
+
settings['Xscale'] = self.xaxis_combobox.currentText()
|
|
1609
|
+
settings['Yscale'] = self.yaxis_combobox.currentText()
|
|
1610
|
+
#if current_item.checkState():
|
|
1611
|
+
current_item.data.apply_axscale_settings()
|
|
1612
|
+
self.canvas.draw()
|
|
1613
|
+
|
|
1614
|
+
def view_setting_edited(self, edited_setting):
|
|
1615
|
+
current_item = self.file_list.currentItem()
|
|
1616
|
+
view_settings = current_item.data.view_settings
|
|
1617
|
+
current_item.data.old_view_settings = view_settings.copy()
|
|
1618
|
+
if current_item:
|
|
1619
|
+
try:
|
|
1620
|
+
if edited_setting == 'Minimum' or edited_setting == 'Maximum':
|
|
1621
|
+
if edited_setting == 'Minimum':
|
|
1622
|
+
text_box = self.min_line_edit
|
|
1623
|
+
else:
|
|
1624
|
+
text_box = self.max_line_edit
|
|
1625
|
+
new_value = float(text_box.text())
|
|
1626
|
+
view_settings[edited_setting] = new_value
|
|
1627
|
+
text_box.setText(f'{new_value:.4g}')
|
|
1628
|
+
text_box.clearFocus()
|
|
1629
|
+
current_item.data.reset_midpoint()
|
|
1630
|
+
self.mid_line_edit.setText(f'{view_settings["Midpoint"]:.4g}')
|
|
1631
|
+
elif edited_setting == 'Midpoint':
|
|
1632
|
+
if self.mid_line_edit.text():
|
|
1633
|
+
new_value = float(self.mid_line_edit.text())
|
|
1634
|
+
view_settings[edited_setting] = new_value
|
|
1635
|
+
else:
|
|
1636
|
+
current_item.data.reset_midpoint()
|
|
1637
|
+
new_value = view_settings[edited_setting]
|
|
1638
|
+
self.mid_line_edit.setText(f'{new_value:.4g}')
|
|
1639
|
+
self.mid_line_edit.clearFocus()
|
|
1640
|
+
elif edited_setting == 'Locked':
|
|
1641
|
+
if self.lock_checkbox.isChecked():
|
|
1642
|
+
view_settings[edited_setting] = True
|
|
1643
|
+
else:
|
|
1644
|
+
view_settings[edited_setting] = False
|
|
1645
|
+
elif edited_setting == 'MidLock':
|
|
1646
|
+
if self.mid_checkbox.isChecked():
|
|
1647
|
+
view_settings[edited_setting] = True
|
|
1648
|
+
else:
|
|
1649
|
+
view_settings[edited_setting] = False
|
|
1650
|
+
elif edited_setting == 'CBarHist':
|
|
1651
|
+
if self.cbar_hist_checkbox.isChecked():
|
|
1652
|
+
view_settings[edited_setting] = True
|
|
1653
|
+
if not hasattr(current_item.data,'hax'):
|
|
1654
|
+
current_item.data.add_cbar_hist()
|
|
1655
|
+
self.figure.tight_layout()
|
|
1656
|
+
else:
|
|
1657
|
+
view_settings[edited_setting] = False
|
|
1658
|
+
if hasattr(current_item.data, 'hax'):
|
|
1659
|
+
current_item.data.hax.clear()
|
|
1660
|
+
try:
|
|
1661
|
+
current_item.data.hax.remove()
|
|
1662
|
+
except:
|
|
1663
|
+
pass
|
|
1664
|
+
del current_item.data.hax
|
|
1665
|
+
self.figure.tight_layout()
|
|
1666
|
+
|
|
1667
|
+
current_item.data.apply_view_settings()
|
|
1668
|
+
self.canvas.draw()
|
|
1669
|
+
except Exception as e:
|
|
1670
|
+
print('Invalid value of view setting!', e)
|
|
1671
|
+
self.paste_view_settings(which='old')
|
|
1672
|
+
|
|
1673
|
+
def fill_colormap_box(self):
|
|
1674
|
+
self.colormap_box.currentIndexChanged.disconnect(self.colormap_edited)
|
|
1675
|
+
self.colormap_box.clear()
|
|
1676
|
+
self.colormap_box.addItems(self.cmaps[self.colormap_type_box.currentText()])
|
|
1677
|
+
self.colormap_box.currentIndexChanged.connect(self.colormap_edited)
|
|
1678
|
+
|
|
1679
|
+
def colormap_type_edited(self):
|
|
1680
|
+
self.fill_colormap_box()
|
|
1681
|
+
self.colormap_edited()
|
|
1682
|
+
|
|
1683
|
+
def colormap_edited(self):
|
|
1684
|
+
current_item = self.file_list.currentItem()
|
|
1685
|
+
if current_item:
|
|
1686
|
+
settings = current_item.data.view_settings
|
|
1687
|
+
settings['Colormap Type'] = self.colormap_type_box.currentText()
|
|
1688
|
+
settings['Colormap'] = self.colormap_box.currentText()
|
|
1689
|
+
settings['Reverse'] = self.reverse_colors_box.isChecked()
|
|
1690
|
+
if current_item.checkState():
|
|
1691
|
+
current_item.data.apply_colormap()
|
|
1692
|
+
self.canvas.draw()
|
|
1693
|
+
|
|
1694
|
+
def filters_table_edited(self, item):
|
|
1695
|
+
current_item = self.file_list.currentItem()
|
|
1696
|
+
self.old_filters=copy.deepcopy(self.which_filters(current_item))
|
|
1697
|
+
if current_item:
|
|
1698
|
+
filters=self.which_filters(current_item)
|
|
1699
|
+
try:
|
|
1700
|
+
row = item.row()
|
|
1701
|
+
filt = filters[row]
|
|
1702
|
+
filter_item = self.filters_table.item(row, 0)
|
|
1703
|
+
filt.method = self.filters_table.cellWidget(row, 1).currentText()
|
|
1704
|
+
filt.settings = [self.filters_table.item(row, 2).text(),
|
|
1705
|
+
self.filters_table.item(row, 3).text()]
|
|
1706
|
+
filt.checkstate = filter_item.checkState()
|
|
1707
|
+
self.filters_table.clearFocus()
|
|
1708
|
+
current_item.data.apply_all_filters(filter_box_index=self.mixeddata_filter_box.currentIndex())
|
|
1709
|
+
if current_item.checkState():
|
|
1710
|
+
self.update_plots(update_color_limits=True)
|
|
1711
|
+
self.show_current_filters()
|
|
1712
|
+
self.show_current_view_settings()
|
|
1713
|
+
self.reset_axlim_settings()
|
|
1714
|
+
except Exception as e:
|
|
1715
|
+
print('Invalid value of filter!', e)
|
|
1716
|
+
self.paste_filters(which='old')
|
|
1717
|
+
|
|
1718
|
+
def copy_plot_settings(self):
|
|
1719
|
+
current_item = self.file_list.currentItem()
|
|
1720
|
+
if current_item:
|
|
1721
|
+
self.copied_settings = current_item.data.settings.copy()
|
|
1722
|
+
|
|
1723
|
+
def copy_filters(self):
|
|
1724
|
+
current_item = self.file_list.currentItem()
|
|
1725
|
+
if current_item:
|
|
1726
|
+
self.copied_filters = copy.deepcopy(self.which_filters(current_item))
|
|
1727
|
+
|
|
1728
|
+
def copy_view_settings(self):
|
|
1729
|
+
current_item = self.file_list.currentItem()
|
|
1730
|
+
if current_item:
|
|
1731
|
+
self.copied_view_settings = current_item.data.view_settings.copy()
|
|
1732
|
+
|
|
1733
|
+
def copy_axlim_settings(self):
|
|
1734
|
+
current_item = self.file_list.currentItem()
|
|
1735
|
+
if current_item:
|
|
1736
|
+
self.copied_axlim_settings = current_item.data.axlim_settings.copy()
|
|
1737
|
+
|
|
1738
|
+
def paste_plot_settings(self, which='copied'):
|
|
1739
|
+
current_item = self.file_list.currentItem()
|
|
1740
|
+
if current_item:
|
|
1741
|
+
if which == 'copied':
|
|
1742
|
+
if self.copied_settings:
|
|
1743
|
+
current_item.data.settings = self.copied_settings.copy()
|
|
1744
|
+
elif which == 'default':
|
|
1745
|
+
current_item.data.settings = current_item.data.DEFAULT_PLOT_SETTINGS.copy()
|
|
1746
|
+
elif which == 'old':
|
|
1747
|
+
current_item.data.settings = current_item.data.old_settings.copy()
|
|
1748
|
+
self.show_current_plot_settings()
|
|
1749
|
+
if current_item.checkState():
|
|
1750
|
+
current_item.data.apply_plot_settings()
|
|
1751
|
+
self.canvas.draw()
|
|
1752
|
+
|
|
1753
|
+
def paste_filters(self, which='copied'):
|
|
1754
|
+
current_item = self.file_list.currentItem()
|
|
1755
|
+
if current_item:
|
|
1756
|
+
if which == 'copied':
|
|
1757
|
+
if self.copied_filters:
|
|
1758
|
+
filters = copy.deepcopy(self.copied_filters)
|
|
1759
|
+
elif which == 'old':
|
|
1760
|
+
filters = copy.deepcopy(self.old_filters)
|
|
1761
|
+
self.which_filters(current_item,filters=filters)
|
|
1762
|
+
|
|
1763
|
+
#self.show_current_filters() Now show_current_all below
|
|
1764
|
+
#current_item.data.apply_all_filters() <<-- seems unnecessary; this will be done at update_plots on next call.
|
|
1765
|
+
|
|
1766
|
+
if current_item.checkState():
|
|
1767
|
+
self.update_plots(update_color_limits=True)
|
|
1768
|
+
self.reset_axlim_settings()
|
|
1769
|
+
self.show_current_all()
|
|
1770
|
+
|
|
1771
|
+
def paste_view_settings(self, which='copied'):
|
|
1772
|
+
current_item = self.file_list.currentItem()
|
|
1773
|
+
if current_item:
|
|
1774
|
+
if which == 'copied':
|
|
1775
|
+
if self.copied_view_settings:
|
|
1776
|
+
current_item.data.view_settings = self.copied_view_settings.copy()
|
|
1777
|
+
elif which == 'old':
|
|
1778
|
+
current_item.data.view_settings = current_item.data.old_view_settings.copy()
|
|
1779
|
+
self.show_current_view_settings()
|
|
1780
|
+
if current_item.checkState():
|
|
1781
|
+
current_item.data.apply_view_settings()
|
|
1782
|
+
current_item.data.apply_colormap()
|
|
1783
|
+
self.canvas.draw()
|
|
1784
|
+
|
|
1785
|
+
def paste_axlim_settings(self, which='copied'):
|
|
1786
|
+
current_item = self.file_list.currentItem()
|
|
1787
|
+
if current_item:
|
|
1788
|
+
if which == 'copied':
|
|
1789
|
+
if self.copied_axlim_settings:
|
|
1790
|
+
current_item.data.axlim_settings = self.copied_axlim_settings.copy()
|
|
1791
|
+
elif which == 'old':
|
|
1792
|
+
current_item.data.axlim_settings = current_item.data.old_axlim_settings.copy()
|
|
1793
|
+
self.show_current_axlim_settings()
|
|
1794
|
+
if current_item.checkState():
|
|
1795
|
+
current_item.data.apply_axlim_settings()
|
|
1796
|
+
self.canvas.draw()
|
|
1797
|
+
|
|
1798
|
+
def open_item_menu(self):
|
|
1799
|
+
current_item = self.file_list.currentItem()
|
|
1800
|
+
if current_item:
|
|
1801
|
+
checked_items = self.get_checked_items()
|
|
1802
|
+
menu = QtWidgets.QMenu(self)
|
|
1803
|
+
menu.addAction('Duplicate (Ctrl+D)')
|
|
1804
|
+
menu.addSeparator()
|
|
1805
|
+
actions = ['Remove file','Check all','Uncheck all','Clear list','Remove unchecked']
|
|
1806
|
+
for entry in actions:
|
|
1807
|
+
action = QtWidgets.QAction(entry, self)
|
|
1808
|
+
menu.addAction(action)
|
|
1809
|
+
if len(checked_items) > 1:
|
|
1810
|
+
menu.addSeparator()
|
|
1811
|
+
menu.addAction('Combine checked files')
|
|
1812
|
+
|
|
1813
|
+
menu.triggered[QtWidgets.QAction].connect(self.do_item_action)
|
|
1814
|
+
menu.popup(QtGui.QCursor.pos())
|
|
1815
|
+
|
|
1816
|
+
def do_item_action(self, signal):
|
|
1817
|
+
current_item = self.file_list.currentItem()
|
|
1818
|
+
if current_item:
|
|
1819
|
+
if signal.text() == 'Duplicate (Ctrl+D)':
|
|
1820
|
+
self.duplicate_item()
|
|
1821
|
+
elif signal.text() == 'Remove file':
|
|
1822
|
+
self.remove_files(which='current')
|
|
1823
|
+
elif signal.text() == 'Check all':
|
|
1824
|
+
self.file_list.itemChanged.disconnect(self.file_checked)
|
|
1825
|
+
for item_index in range(self.file_list.count()):
|
|
1826
|
+
self.file_list.item(item_index).setCheckState(QtCore.Qt.Checked)
|
|
1827
|
+
self.file_list.itemChanged.connect(self.file_checked)
|
|
1828
|
+
self.update_plots()
|
|
1829
|
+
elif signal.text() == 'Uncheck all':
|
|
1830
|
+
checked_items = self.get_checked_items()
|
|
1831
|
+
self.file_list.itemChanged.disconnect(self.file_checked)
|
|
1832
|
+
for item in checked_items:
|
|
1833
|
+
item.setCheckState(QtCore.Qt.Unchecked)
|
|
1834
|
+
self.file_list.itemChanged.connect(self.file_checked)
|
|
1835
|
+
self.update_plots()
|
|
1836
|
+
elif signal.text() == 'Clear list':
|
|
1837
|
+
self.remove_files(which='all')
|
|
1838
|
+
elif signal.text() == 'Remove unchecked':
|
|
1839
|
+
self.remove_files(which='unchecked')
|
|
1840
|
+
elif signal.text() == 'Combine checked files':
|
|
1841
|
+
try:
|
|
1842
|
+
self.combine_plots()
|
|
1843
|
+
except Exception as e:
|
|
1844
|
+
print('Cannot combine files:', e)
|
|
1845
|
+
|
|
1846
|
+
def duplicate_item(self, new_plot_button=False):
|
|
1847
|
+
original_item = self.file_list.currentItem()
|
|
1848
|
+
X = self.new_plot_X_box.currentText()
|
|
1849
|
+
Y = self.new_plot_Y_box.currentText()
|
|
1850
|
+
Z = self.new_plot_Z_box.currentText()
|
|
1851
|
+
if original_item:
|
|
1852
|
+
self.clear_sidebar1D()
|
|
1853
|
+
# Copy over data if internal data, or else re-load it.
|
|
1854
|
+
# The advantage of keeping it this way is that the new data gets the correct data class; it won't be InternalData.
|
|
1855
|
+
if isinstance(original_item.data, InternalData):
|
|
1856
|
+
try:
|
|
1857
|
+
item=DataItem(InternalData(self.canvas,original_item.data.loaded_data,
|
|
1858
|
+
original_item.data.label,
|
|
1859
|
+
original_item.data.all_parameter_names,
|
|
1860
|
+
dimension=original_item.data.dim))
|
|
1861
|
+
except Exception as e:
|
|
1862
|
+
print(e)
|
|
1863
|
+
item.filepath = 'internal_data'
|
|
1864
|
+
self.add_internal_data(item,check_item=False,uncheck_others=False)
|
|
1865
|
+
|
|
1866
|
+
elif isinstance(original_item.data, MixedInternalData):
|
|
1867
|
+
item=DataItem(MixedInternalData(self.canvas,original_item.data.label,
|
|
1868
|
+
original_item.data.dataset2d_type, original_item.data.dataset1d_type,
|
|
1869
|
+
original_item.data.dataset2d_filepath, original_item.data.dataset1d_filepath,
|
|
1870
|
+
original_item.data.dataset1d_loaded_data,original_item.data.dataset2d_loaded_data,
|
|
1871
|
+
original_item.data.dataset1d_label,original_item.data.dataset2d_label,
|
|
1872
|
+
original_item.data.dataset1d_all_parameter_names,original_item.data.dataset2d_all_parameter_names,
|
|
1873
|
+
original_item.data.dataset1d_dim,original_item.data.dataset2d_dim))
|
|
1874
|
+
item.filepath = 'mixed_internal_data'
|
|
1875
|
+
self.add_internal_data(item,check_item=False,uncheck_others=False)
|
|
1876
|
+
else:
|
|
1877
|
+
self.open_files(filepaths=[original_item.data.filepath],overrideautocheck=True)
|
|
1878
|
+
|
|
1879
|
+
new_item = self.file_list.currentItem()
|
|
1880
|
+
new_item.duplicate = True
|
|
1881
|
+
|
|
1882
|
+
# Copy over settings
|
|
1883
|
+
new_item.data.settings = original_item.data.settings.copy()
|
|
1884
|
+
new_item.data.view_settings = original_item.data.view_settings.copy()
|
|
1885
|
+
new_item.data.axlim_settings = original_item.data.axlim_settings.copy()
|
|
1886
|
+
# Copy filters to the correct location.
|
|
1887
|
+
self.which_filters(new_item,filters=copy.deepcopy(original_item.data.filters))
|
|
1888
|
+
#new_item.data.filters = copy.deepcopy(original_item.data.filters)
|
|
1889
|
+
|
|
1890
|
+
# Naming the new item in the file-list. qcpp gets special treatment, assuming the counter always comes first in the filename.
|
|
1891
|
+
if isinstance(original_item.data, qcodesppData):
|
|
1892
|
+
original_label= original_item.data.label
|
|
1893
|
+
if hasattr(original_item,'duplicate'):
|
|
1894
|
+
index_str=original_label.split('-')[0]
|
|
1895
|
+
else:
|
|
1896
|
+
index_str=original_label.split('_')[0]
|
|
1897
|
+
items=[item for item in self.get_all_items() if index_str in item.data.label]
|
|
1898
|
+
duplicate_index=len(list(items))-1
|
|
1899
|
+
if hasattr(original_item,'duplicate'):
|
|
1900
|
+
new_label=f'{original_label.split('-')[0]}-{duplicate_index}-{original_label.split("-")[2]}'
|
|
1901
|
+
else:
|
|
1902
|
+
new_label= f'{original_label.split('_')[0]}-{duplicate_index}-{original_label.split("_")[1]}'
|
|
1903
|
+
new_item.setText(new_label)
|
|
1904
|
+
new_item.data.label = new_label
|
|
1905
|
+
new_item.data.settings['title']=f'{new_item.data.dataset_id}-{duplicate_index}'
|
|
1906
|
+
|
|
1907
|
+
else:
|
|
1908
|
+
new_item.setText(f'Duplicate: {new_item.data.label}')
|
|
1909
|
+
new_item.data.label = f'Duplicate: {new_item.data.label}'
|
|
1910
|
+
|
|
1911
|
+
# Making new plot if 'Add new plot' button was pressed
|
|
1912
|
+
if new_plot_button:
|
|
1913
|
+
if original_item.data.dim==3 or isinstance(original_item.data, MixedInternalData):
|
|
1914
|
+
new_item.data.settings['X data'] = X
|
|
1915
|
+
new_item.data.settings['Y data'] = Y
|
|
1916
|
+
new_item.data.settings['Z data'] = Z
|
|
1917
|
+
else:
|
|
1918
|
+
# Next two lines basically fix a bug.
|
|
1919
|
+
new_item.data.settings['X data'] = original_item.data.all_parameter_names[0]
|
|
1920
|
+
new_item.data.settings['Y data'] = original_item.data.all_parameter_names[1]
|
|
1921
|
+
new_item.data.prepare_data_for_plot()
|
|
1922
|
+
new_item.data.sidebar1D = Sidebar1D(new_item.data,editor_window=self)
|
|
1923
|
+
new_item.data.sidebar1D.running=True
|
|
1924
|
+
new_item.data.plotted_lines = {0: {'checkstate': 2,
|
|
1925
|
+
'X data': X,
|
|
1926
|
+
'Y data': Y,
|
|
1927
|
+
'Bins': 100,
|
|
1928
|
+
'Xerr': 0,
|
|
1929
|
+
'Yerr': 0,
|
|
1930
|
+
'raw_data': new_item.data.raw_data,
|
|
1931
|
+
'processed_data': new_item.data.processed_data,
|
|
1932
|
+
'linecolor': (0.1, 0.5, 0.8, 1),
|
|
1933
|
+
'linewidth': 1.5,
|
|
1934
|
+
'linestyle': '-',
|
|
1935
|
+
'filters': []}}
|
|
1936
|
+
|
|
1937
|
+
new_item.data.sidebar1D.append_trace_to_table(0)
|
|
1938
|
+
self.oneD_layout.addWidget(new_item.data.sidebar1D)
|
|
1939
|
+
|
|
1940
|
+
elif original_item.data.dim==2:
|
|
1941
|
+
# Next two lines basically fix a bug.
|
|
1942
|
+
new_item.data.settings['X data'] = original_item.data.all_parameter_names[0]
|
|
1943
|
+
new_item.data.settings['Y data'] = original_item.data.all_parameter_names[1]
|
|
1944
|
+
new_item.data.prepare_data_for_plot()
|
|
1945
|
+
# Populate trace table from original data. This doesn't work. It links the new item to the old one.
|
|
1946
|
+
# if hasattr(original_item.data, 'plotted_lines'):
|
|
1947
|
+
# new_item.data.sidebar1D = Sidebar1D(new_item.data,self)
|
|
1948
|
+
# new_item.data.sidebar1D.running=True
|
|
1949
|
+
# new_item.data.plotted_lines = {}
|
|
1950
|
+
# for line in original_item.data.plotted_lines:
|
|
1951
|
+
# new_item.data.plotted_lines[line] = copy.deepcopy(original_item.data.plotted_lines[line])
|
|
1952
|
+
# new_item.data.sidebar1D.append_trace_to_table(line)
|
|
1953
|
+
# self.oneD_layout.addWidget(new_item.data.sidebar1D)
|
|
1954
|
+
new_item.data.init_plotted_lines()
|
|
1955
|
+
|
|
1956
|
+
new_item.setCheckState(QtCore.Qt.Checked)
|
|
1957
|
+
if new_plot_button and new_item.data.dim == 3:
|
|
1958
|
+
self.plot_setting_edited(setting_name='X data')
|
|
1959
|
+
self.update_plots()
|
|
1960
|
+
|
|
1961
|
+
def combine_plots(self):
|
|
1962
|
+
checked_items = self.get_checked_items()
|
|
1963
|
+
if checked_items:
|
|
1964
|
+
try:
|
|
1965
|
+
data_list = []
|
|
1966
|
+
label_name=f'Combined: '
|
|
1967
|
+
for item in checked_items:
|
|
1968
|
+
data_list.append(item.data)
|
|
1969
|
+
label_name+=item.data.label[:15]+'..., '
|
|
1970
|
+
|
|
1971
|
+
if any([isinstance(item,MixedInternalData) for item in data_list]):
|
|
1972
|
+
raise ValueError('Cannot combine mixed 1D and 2D datasets with other datasets.')
|
|
1973
|
+
# If sets of 2D datasets, stack them along the x-axis. Requires y axis has same dimension for all datasets
|
|
1974
|
+
if all([item.dim == 3 for item in data_list]):
|
|
1975
|
+
if not all(item.all_parameter_names == data_list[0].all_parameter_names for item in data_list):
|
|
1976
|
+
raise ValueError('Cannot combine 2D datasets with different parameters.')
|
|
1977
|
+
elif not all(item.get_columns()[1] == data_list[0].get_columns()[1] for item in data_list):
|
|
1978
|
+
raise ValueError('Cannot combine 2D datasets with different y axes.')
|
|
1979
|
+
combined_data=[]
|
|
1980
|
+
combined_parameter_names=[]
|
|
1981
|
+
for i,parameter_name in enumerate(data_list[0].all_parameter_names):
|
|
1982
|
+
# The first parameter in qcodespp data is 1D, so we need to stack it differently.
|
|
1983
|
+
if i == 0 and all(isinstance(data_list[j],qcodesppData) for j in range(len(data_list))):
|
|
1984
|
+
xdata=np.hstack([data_list[j].dataset.arrays[parameter_name] for j in range(len(data_list))])
|
|
1985
|
+
combined_data.append(xdata.T)
|
|
1986
|
+
combined_parameter_names.append(parameter_name)
|
|
1987
|
+
# For all other datatypes and all other parameters, try both stacking methods.
|
|
1988
|
+
else:
|
|
1989
|
+
try:
|
|
1990
|
+
combined_data.append(np.vstack([data_list[j].data_dict[parameter_name] for j in range(len(data_list))]))
|
|
1991
|
+
combined_parameter_names.append(parameter_name)
|
|
1992
|
+
except ValueError:
|
|
1993
|
+
try:
|
|
1994
|
+
combined_data.append(np.hstack([data_list[j].data_dict[parameter_name] for j in range(len(data_list))]))
|
|
1995
|
+
combined_parameter_names.append(parameter_name)
|
|
1996
|
+
except ValueError:
|
|
1997
|
+
print(f'Error combining data for {parameter_name}: Check data dimensions.')
|
|
1998
|
+
if len(combined_data[0].shape) == 1: # Try to catch BaseClassData that also has a first independent param that is 1D, but this should never happen.
|
|
1999
|
+
combined_data[0]=np.tile(combined_data[0],(combined_data[1].shape[1],1)).T
|
|
2000
|
+
combined_item=DataItem(InternalData(self.canvas,combined_data,label_name,combined_parameter_names,dimension=3))
|
|
2001
|
+
combined_item.filepath = 'internal_data'
|
|
2002
|
+
self.add_internal_data(combined_item)
|
|
2003
|
+
|
|
2004
|
+
elif all([item.dim == 2 for item in data_list]):
|
|
2005
|
+
# For only 1D data, just pile all parameters into a new dataset.
|
|
2006
|
+
combined_data=[]
|
|
2007
|
+
combined_parameter_names=[]
|
|
2008
|
+
for data in data_list:
|
|
2009
|
+
for parameter_name in data.all_parameter_names:
|
|
2010
|
+
combined_data.append(data.data_dict[parameter_name])
|
|
2011
|
+
combined_parameter_names.append(f'{data.label[:4]}: {parameter_name}')
|
|
2012
|
+
combined_item=DataItem(InternalData(self.canvas,combined_data,label_name,combined_parameter_names,dimension=2))
|
|
2013
|
+
combined_item.filepath = 'internal_data'
|
|
2014
|
+
self.add_internal_data(combined_item)
|
|
2015
|
+
|
|
2016
|
+
else:
|
|
2017
|
+
finalerrormessage=('There are possibilities when combining datasets: \n'
|
|
2018
|
+
'1. Any number of 1D datasets with aribtrary dimension; all parameters available for plotting\n'
|
|
2019
|
+
'2. Any number of 2D datasets with same sets of parameters and y-axis length. The datasets are stacked along the x-axis. \n'
|
|
2020
|
+
'3. A single 2D dataset and a single 1D dataset. Pre-combine 1D and 2D datasets separately if necessary.')
|
|
2021
|
+
if len(data_list) > 2:
|
|
2022
|
+
raise ValueError('Could not combine data. When combining 2D and 1D data, use only one dataset of each type.\n{finalerrormes}')
|
|
2023
|
+
try:
|
|
2024
|
+
if data_list[0].dim == 3:
|
|
2025
|
+
dataset2d=data_list[0]
|
|
2026
|
+
dataset1d=data_list[1]
|
|
2027
|
+
else:
|
|
2028
|
+
dataset2d=data_list[1]
|
|
2029
|
+
dataset1d=data_list[0]
|
|
2030
|
+
dataset1d_type=type(dataset1d)
|
|
2031
|
+
dataset2d_type=type(dataset2d)
|
|
2032
|
+
kwargs={}
|
|
2033
|
+
if dataset1d_type in [qcodesppData,BaseClassData]:
|
|
2034
|
+
kwargs['dataset1d_filepath'] = dataset1d.filepath
|
|
2035
|
+
elif dataset1d_type == InternalData:
|
|
2036
|
+
kwargs['dataset1d_loaded_data'] = dataset1d.loaded_data
|
|
2037
|
+
kwargs['dataset1d_label'] = dataset1d.label
|
|
2038
|
+
kwargs['dataset1d_all_parameter_names'] = dataset1d.all_parameter_names
|
|
2039
|
+
kwargs['dataset1d_dim']= dataset1d.dim
|
|
2040
|
+
if dataset2d_type in [qcodesppData,BaseClassData]:
|
|
2041
|
+
kwargs['dataset2d_filepath'] = dataset2d.filepath
|
|
2042
|
+
elif dataset2d_type == InternalData:
|
|
2043
|
+
kwargs['dataset2d_loaded_data'] = dataset2d.loaded_data
|
|
2044
|
+
kwargs['dataset2d_label'] = dataset2d.label
|
|
2045
|
+
kwargs['dataset2d_all_parameter_names'] = dataset2d.all_parameter_names
|
|
2046
|
+
kwargs['dataset2d_dim']= dataset2d.dim
|
|
2047
|
+
|
|
2048
|
+
combined_item=DataItem(MixedInternalData(self.canvas,label_name,dataset2d_type,dataset1d_type,**kwargs))
|
|
2049
|
+
combined_item.filepath = 'mixed_internal_data'
|
|
2050
|
+
self.add_internal_data(combined_item)
|
|
2051
|
+
|
|
2052
|
+
except Exception as e:
|
|
2053
|
+
print(f'Could not combine data: {e}\n{finalerrormessage}')
|
|
2054
|
+
|
|
2055
|
+
except Exception as e:
|
|
2056
|
+
print('Cannot combine data:', e)
|
|
2057
|
+
|
|
2058
|
+
def open_plot_settings_menu(self):
|
|
2059
|
+
row = self.settings_table.currentRow()
|
|
2060
|
+
column = self.settings_table.currentColumn()
|
|
2061
|
+
if column == 1:
|
|
2062
|
+
setting_name = self.settings_table.item(row, 0).text()
|
|
2063
|
+
menu = QtWidgets.QMenu(self)
|
|
2064
|
+
settings = SETTINGS_MENU_OPTIONS.copy()
|
|
2065
|
+
current_item = self.file_list.currentItem()
|
|
2066
|
+
if current_item and hasattr(current_item.data, 'settings_menu_options'):
|
|
2067
|
+
settings.update(current_item.data.settings_menu_options)
|
|
2068
|
+
if setting_name in settings.keys():
|
|
2069
|
+
for entry in settings[setting_name]:
|
|
2070
|
+
action = QtWidgets.QAction(entry, self)
|
|
2071
|
+
menu.addAction(action)
|
|
2072
|
+
menu.triggered[QtWidgets.QAction].connect(self.replace_plot_setting)
|
|
2073
|
+
menu.popup(QtGui.QCursor.pos())
|
|
2074
|
+
|
|
2075
|
+
def replace_plot_setting(self, signal):
|
|
2076
|
+
item = self.settings_table.currentItem()
|
|
2077
|
+
item.setText(signal.text())
|
|
2078
|
+
|
|
2079
|
+
def open_filter_settings_menu(self):
|
|
2080
|
+
row = self.filters_table.currentRow()
|
|
2081
|
+
column = self.filters_table.currentColumn()
|
|
2082
|
+
filter_name = self.filters_table.item(row, 0).text()
|
|
2083
|
+
if column == 0:
|
|
2084
|
+
menu = QtWidgets.QMenu(self)
|
|
2085
|
+
menu.addAction(QtWidgets.QAction('Check all', self))
|
|
2086
|
+
menu.addAction(QtWidgets.QAction('Uncheck all', self))
|
|
2087
|
+
menu.triggered[QtWidgets.QAction].connect(self.check_all_filters)
|
|
2088
|
+
menu.popup(QtGui.QCursor.pos())
|
|
2089
|
+
if filter_name in ['Multiply','Divide','Add/Subtract'] and column == 2:
|
|
2090
|
+
menu = QtWidgets.QMenu(self)
|
|
2091
|
+
filter_settings={}
|
|
2092
|
+
current_item = self.file_list.currentItem()
|
|
2093
|
+
if current_item and hasattr(current_item.data, 'filter_menu_options'):
|
|
2094
|
+
filter_settings.update(current_item.data.filter_menu_options)
|
|
2095
|
+
if filter_name in filter_settings.keys():
|
|
2096
|
+
for entry in filter_settings[filter_name]:
|
|
2097
|
+
action = QtWidgets.QAction(entry, self)
|
|
2098
|
+
menu.addAction(action)
|
|
2099
|
+
menu.triggered[QtWidgets.QAction].connect(self.replace_filter_setting)
|
|
2100
|
+
menu.popup(QtGui.QCursor.pos())
|
|
2101
|
+
elif filter_name == 'Logarithm' and column ==2:
|
|
2102
|
+
menu = QtWidgets.QMenu(self)
|
|
2103
|
+
settings=['10','2','e']
|
|
2104
|
+
for entry in settings:
|
|
2105
|
+
action = QtWidgets.QAction(entry, self)
|
|
2106
|
+
menu.addAction(action)
|
|
2107
|
+
menu.triggered[QtWidgets.QAction].connect(self.replace_filter_setting)
|
|
2108
|
+
menu.popup(QtGui.QCursor.pos())
|
|
2109
|
+
|
|
2110
|
+
def replace_filter_setting(self,signal):
|
|
2111
|
+
item = self.filters_table.currentItem()
|
|
2112
|
+
item.setText(signal.text())
|
|
2113
|
+
|
|
2114
|
+
def check_all_filters(self, signal, manual_signal=None):
|
|
2115
|
+
if manual_signal:
|
|
2116
|
+
text=manual_signal
|
|
2117
|
+
else:
|
|
2118
|
+
text = signal.text()
|
|
2119
|
+
current_item = self.file_list.currentItem()
|
|
2120
|
+
self.filters_table.itemChanged.disconnect(self.filters_table_edited)
|
|
2121
|
+
checkstates = {'Check all': QtCore.Qt.Checked,
|
|
2122
|
+
'Uncheck all': QtCore.Qt.Unchecked}
|
|
2123
|
+
filters = self.which_filters(current_item)
|
|
2124
|
+
for row in range(self.filters_table.rowCount()):
|
|
2125
|
+
self.filters_table.item(row, 0).setCheckState(checkstates[text])
|
|
2126
|
+
filters[row].checkstate = checkstates[text]
|
|
2127
|
+
self.filters_table.itemChanged.connect(self.filters_table_edited)
|
|
2128
|
+
if current_item.checkState():
|
|
2129
|
+
self.update_plots(update_color_limits=True)
|
|
2130
|
+
self.show_current_filters()
|
|
2131
|
+
self.show_current_view_settings()
|
|
2132
|
+
self.reset_axlim_settings()
|
|
2133
|
+
|
|
2134
|
+
def reset_color_limits(self):
|
|
2135
|
+
current_item = self.file_list.currentItem()
|
|
2136
|
+
if current_item:
|
|
2137
|
+
current_item.data.reset_view_settings(overrule=True)
|
|
2138
|
+
self.show_current_view_settings()
|
|
2139
|
+
if current_item.checkState():
|
|
2140
|
+
current_item.data.apply_view_settings()
|
|
2141
|
+
self.canvas.draw()
|
|
2142
|
+
|
|
2143
|
+
def filters_box_changed(self):
|
|
2144
|
+
current_item = self.file_list.currentItem()
|
|
2145
|
+
if current_item:
|
|
2146
|
+
filt = Filter(self.filters_combobox.currentText())
|
|
2147
|
+
try:
|
|
2148
|
+
self.which_filters(current_item,filt=filt)
|
|
2149
|
+
except Exception as e:
|
|
2150
|
+
print('Error adding filter:', e)
|
|
2151
|
+
if current_item.checkState() and filt.checkstate:
|
|
2152
|
+
self.update_plots(update_color_limits=True)
|
|
2153
|
+
else:
|
|
2154
|
+
self.append_filter_to_table()
|
|
2155
|
+
self.filters_combobox.currentIndexChanged.disconnect(self.filters_box_changed)
|
|
2156
|
+
self.filters_combobox.setCurrentIndex(0)
|
|
2157
|
+
self.filters_combobox.clearFocus()
|
|
2158
|
+
self.filters_combobox.currentIndexChanged.connect(self.filters_box_changed)
|
|
2159
|
+
|
|
2160
|
+
def append_filter_to_table(self):
|
|
2161
|
+
current_item = self.file_list.currentItem()
|
|
2162
|
+
if current_item:
|
|
2163
|
+
row = self.filters_table.rowCount()
|
|
2164
|
+
|
|
2165
|
+
filt = self.which_filters(current_item)[row]
|
|
2166
|
+
|
|
2167
|
+
self.filters_table.itemChanged.disconnect(self.filters_table_edited)
|
|
2168
|
+
self.filters_table.insertRow(row)
|
|
2169
|
+
filter_item = QtWidgets.QTableWidgetItem(filt.name)
|
|
2170
|
+
filter_item.setFlags(QtCore.Qt.ItemIsSelectable |
|
|
2171
|
+
QtCore.Qt.ItemIsEnabled |
|
|
2172
|
+
QtCore.Qt.ItemIsUserCheckable)
|
|
2173
|
+
filter_item.setText(filt.name)
|
|
2174
|
+
filter_item.setCheckState(filt.checkstate)
|
|
2175
|
+
method_box = NoScrollQComboBox()
|
|
2176
|
+
method_box.addItems(filt.method_list)
|
|
2177
|
+
method_box.setCurrentIndex(filt.method_list.index(filt.method))
|
|
2178
|
+
setting_1 = QtWidgets.QTableWidgetItem(filt.settings[0])
|
|
2179
|
+
setting_2 = QtWidgets.QTableWidgetItem(filt.settings[1])
|
|
2180
|
+
method_box.currentIndexChanged.connect(lambda: self.filters_table_edited(setting_1))
|
|
2181
|
+
self.filters_table.setItem(row, 0, filter_item)
|
|
2182
|
+
self.filters_table.setCellWidget(row, 1, method_box)
|
|
2183
|
+
self.filters_table.setItem(row, 2, setting_1)
|
|
2184
|
+
self.filters_table.setItem(row, 3, setting_2)
|
|
2185
|
+
self.filters_table.item(row, 2).setTextAlignment(int(QtCore.Qt.AlignRight) |
|
|
2186
|
+
int(QtCore.Qt.AlignVCenter))
|
|
2187
|
+
self.filters_table.item(row, 3).setTextAlignment(int(QtCore.Qt.AlignRight) |
|
|
2188
|
+
int(QtCore.Qt.AlignVCenter))
|
|
2189
|
+
self.filters_table.setCurrentCell(row, 0)
|
|
2190
|
+
self.filters_table.itemChanged.connect(self.filters_table_edited)
|
|
2191
|
+
|
|
2192
|
+
def remove_single_filter(self,filter_row,filters):
|
|
2193
|
+
self.filters_table.removeRow(filter_row)
|
|
2194
|
+
del filters[filter_row]
|
|
2195
|
+
|
|
2196
|
+
def remove_filters(self, which='current'):
|
|
2197
|
+
current_item = self.file_list.currentItem()
|
|
2198
|
+
if current_item:
|
|
2199
|
+
filters=self.which_filters(current_item)
|
|
2200
|
+
if which == 'current':
|
|
2201
|
+
filter_row = self.filters_table.currentRow()
|
|
2202
|
+
if filter_row != -1:
|
|
2203
|
+
self.remove_single_filter(filter_row,filters)
|
|
2204
|
+
elif which == 'all':
|
|
2205
|
+
for filter_row in range(self.filters_table.rowCount()):
|
|
2206
|
+
self.remove_single_filter(filter_row,filters)
|
|
2207
|
+
current_item.data.apply_all_filters()
|
|
2208
|
+
current_item.data.reset_view_settings()
|
|
2209
|
+
if current_item.checkState():
|
|
2210
|
+
current_item.data.apply_view_settings()
|
|
2211
|
+
self.show_current_view_settings()
|
|
2212
|
+
self.update_plots(update_color_limits=True)
|
|
2213
|
+
|
|
2214
|
+
def move_filter(self, to):
|
|
2215
|
+
current_item = self.file_list.currentItem()
|
|
2216
|
+
if current_item:
|
|
2217
|
+
filters=self.which_filters(current_item)
|
|
2218
|
+
row = self.filters_table.currentRow()
|
|
2219
|
+
if ((row > 0 and to == -1) or
|
|
2220
|
+
(row < self.filters_table.rowCount()-1 and to == 1)):
|
|
2221
|
+
filters[row], filters[row+to] = filters[row+to], filters[row]
|
|
2222
|
+
self.show_current_filters()
|
|
2223
|
+
self.filters_table.setCurrentCell(row+to, 0)
|
|
2224
|
+
if (self.filters_table.item(row,0).checkState() and
|
|
2225
|
+
self.filters_table.item(row+to,0).checkState()):
|
|
2226
|
+
current_item.data.apply_all_filters()
|
|
2227
|
+
self.update_plots(update_color_limits=True)
|
|
2228
|
+
self.show_current_view_settings()
|
|
2229
|
+
|
|
2230
|
+
def save_image(self):
|
|
2231
|
+
current_item = self.file_list.currentItem()
|
|
2232
|
+
if current_item:
|
|
2233
|
+
data_name, _ = os.path.splitext(current_item.data.label)
|
|
2234
|
+
formats = 'Adobe Acrobat (*.pdf);;Portable Network Graphic (*.png)'
|
|
2235
|
+
filename, extension = QtWidgets.QFileDialog.getSaveFileName(
|
|
2236
|
+
self, 'Save Figure As...', data_name.replace(':',''), formats)
|
|
2237
|
+
if filename:
|
|
2238
|
+
print('Save Figure as ', filename)
|
|
2239
|
+
if current_item.data.settings['dpi'] == 'figure':
|
|
2240
|
+
dpi = 'figure'
|
|
2241
|
+
else:
|
|
2242
|
+
dpi = int(current_item.data.settings['dpi'])
|
|
2243
|
+
if DARK_THEME and qdarkstyle_imported:
|
|
2244
|
+
rcParams_to_light_theme()
|
|
2245
|
+
self.update_plots(update_data=False)
|
|
2246
|
+
transparent = current_item.data.settings['transparent']=='True'
|
|
2247
|
+
self.figure.savefig(filename, dpi=dpi, transparent=transparent,
|
|
2248
|
+
bbox_inches='tight')
|
|
2249
|
+
if DARK_THEME and qdarkstyle_imported:
|
|
2250
|
+
rcParams_to_dark_theme()
|
|
2251
|
+
self.update_plots(update_data=False)
|
|
2252
|
+
print('Saved!')
|
|
2253
|
+
|
|
2254
|
+
def save_images_as(self, extension='.png'):
|
|
2255
|
+
save_folder = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Directory")
|
|
2256
|
+
if save_folder:
|
|
2257
|
+
if DARK_THEME and qdarkstyle_imported:
|
|
2258
|
+
rcParams_to_light_theme()
|
|
2259
|
+
self.update_plots(update_data=False)
|
|
2260
|
+
for index in range(self.file_list.count()):
|
|
2261
|
+
self.figure.clear()
|
|
2262
|
+
item = self.file_list.item(index)
|
|
2263
|
+
filename = os.path.join(save_folder, item.data.label.replace(':','')+extension)
|
|
2264
|
+
if not os.path.isfile(filename):
|
|
2265
|
+
try:
|
|
2266
|
+
item.data.prepare_data_for_plot()
|
|
2267
|
+
item.data.figure = self.figure
|
|
2268
|
+
item.data.axes = self.figure.add_subplot(1, 1, 1)
|
|
2269
|
+
item.data.add_plot(editor_window=self)
|
|
2270
|
+
if item.data.settings['dpi'] == 'figure':
|
|
2271
|
+
dpi = 'figure'
|
|
2272
|
+
else:
|
|
2273
|
+
dpi = int(item.data.settings['dpi'])
|
|
2274
|
+
transparent = item.data.settings['transparent']=='True'
|
|
2275
|
+
self.figure.savefig(filename, dpi=dpi,
|
|
2276
|
+
transparent=transparent,
|
|
2277
|
+
bbox_inches='tight')
|
|
2278
|
+
item.data.raw_data = None
|
|
2279
|
+
item.data.processed_data = None
|
|
2280
|
+
except Exception as e:
|
|
2281
|
+
print(f'Could not plot {item.data.filepath}:', e)
|
|
2282
|
+
if DARK_THEME and qdarkstyle_imported:
|
|
2283
|
+
rcParams_to_dark_theme()
|
|
2284
|
+
self.update_plots(update_data=False)
|
|
2285
|
+
|
|
2286
|
+
def save_filters(self):
|
|
2287
|
+
current_item = self.file_list.currentItem()
|
|
2288
|
+
if current_item:
|
|
2289
|
+
filters=self.which_filters(current_item)
|
|
2290
|
+
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
2291
|
+
self, 'Save Filters As...', '', '.npy')
|
|
2292
|
+
np.save(filename, filters)
|
|
2293
|
+
|
|
2294
|
+
def load_filters(self):
|
|
2295
|
+
current_item = self.file_list.currentItem()
|
|
2296
|
+
if current_item:
|
|
2297
|
+
filters=self.which_filters(current_item)
|
|
2298
|
+
filename, _ = QtWidgets.QFileDialog.getOpenFileNames(
|
|
2299
|
+
self, 'Open Filters File...', '', '*.npy')
|
|
2300
|
+
loaded_filters = list(np.load(filename[0], allow_pickle=True))
|
|
2301
|
+
filters += copy.deepcopy(loaded_filters)
|
|
2302
|
+
current_item.data.apply_all_filters()
|
|
2303
|
+
self.update_plots(update_color_limits=True)
|
|
2304
|
+
self.show_current_view_settings()
|
|
2305
|
+
|
|
2306
|
+
def filttocol_clicked(self, axis):
|
|
2307
|
+
current_item = self.file_list.currentItem()
|
|
2308
|
+
if current_item:
|
|
2309
|
+
current_item.data.filttocol(axis=axis)
|
|
2310
|
+
|
|
2311
|
+
def draggable_point_selected(self, x,y,data):
|
|
2312
|
+
selected=False
|
|
2313
|
+
if isinstance(data, MixedInternalData):
|
|
2314
|
+
data = data.dataset2d
|
|
2315
|
+
if hasattr(data,'linecuts'):
|
|
2316
|
+
# Define window the same size as the draggable points
|
|
2317
|
+
delta_x = np.abs(data.axes.get_xlim()[1]-data.axes.get_xlim()[0])*0.02
|
|
2318
|
+
delta_y = np.abs(data.axes.get_ylim()[1]-data.axes.get_ylim()[0])*0.02
|
|
2319
|
+
for orientation in ['diagonal','circular']:
|
|
2320
|
+
for line in data.linecuts[orientation]['lines'].keys():
|
|
2321
|
+
for point in data.linecuts[orientation]['lines'][line]['points']:
|
|
2322
|
+
if (np.abs(point[0]-x)<delta_x and np.abs(point[1]-y)<delta_y):
|
|
2323
|
+
selected=True
|
|
2324
|
+
return selected
|
|
2325
|
+
|
|
2326
|
+
def mouse_click_canvas(self, event):
|
|
2327
|
+
if self.navi_toolbar.mode == '': # If not using the navigation toolbar tools
|
|
2328
|
+
if event.inaxes:
|
|
2329
|
+
x, y = event.xdata, event.ydata
|
|
2330
|
+
checked_items = self.get_checked_items()
|
|
2331
|
+
self.plot_in_focus = [checked_item for checked_item in checked_items
|
|
2332
|
+
if checked_item.data.axes == event.inaxes]
|
|
2333
|
+
if self.plot_in_focus:
|
|
2334
|
+
if self.file_list.currentItem() != self.plot_in_focus[0]:
|
|
2335
|
+
# If the clicked plot is not the current one, set it as current before doing anything else.
|
|
2336
|
+
self.file_list.setCurrentItem(self.plot_in_focus[0])
|
|
2337
|
+
self.file_clicked()
|
|
2338
|
+
else:
|
|
2339
|
+
self.file_list.setCurrentItem(self.plot_in_focus[0])
|
|
2340
|
+
self.file_clicked()
|
|
2341
|
+
data = self.plot_in_focus[0].data
|
|
2342
|
+
data.selected_x, data.selected_y = x, y
|
|
2343
|
+
|
|
2344
|
+
# For 1D data left mouse click sets the fit limits.
|
|
2345
|
+
if (event.button == 1 and
|
|
2346
|
+
data.dim == 2):
|
|
2347
|
+
if hasattr(data, 'sidebar1D'):
|
|
2348
|
+
current_row=data.sidebar1D.trace_table.currentRow()
|
|
2349
|
+
xdata,ydata=data.sidebar1D.get_line_data(int(data.sidebar1D.trace_table.item(current_row,0).text()))
|
|
2350
|
+
# snap to data
|
|
2351
|
+
index=(np.abs(x-xdata)).argmin()
|
|
2352
|
+
x_value=xdata[index]
|
|
2353
|
+
if data.sidebar1D.xmin_box.text()=='':
|
|
2354
|
+
data.sidebar1D.xmin_box.setText(str(x_value))
|
|
2355
|
+
elif data.sidebar1D.xmax_box.text()=='':
|
|
2356
|
+
data.sidebar1D.xmax_box.setText(str(x_value))
|
|
2357
|
+
else:
|
|
2358
|
+
if np.abs(float(data.sidebar1D.xmin_box.text())-x_value)<np.abs(float(data.sidebar1D.xmax_box.text())-x_value):
|
|
2359
|
+
data.sidebar1D.xmin_box.setText(str(x_value))
|
|
2360
|
+
else:
|
|
2361
|
+
data.sidebar1D.xmax_box.setText(str(x_value))
|
|
2362
|
+
data.sidebar1D.limits_edited()
|
|
2363
|
+
|
|
2364
|
+
# For 2D data, left and middle mouse click generate horizontal or vertical linecut, UNLESS the
|
|
2365
|
+
# mouse is in the vicinity of a pre-existing diagonal or circular linecut point.
|
|
2366
|
+
|
|
2367
|
+
elif ((event.button == 1 or event.button == 2) and
|
|
2368
|
+
(data.dim == 3 or isinstance(data,MixedInternalData)) and not self.draggable_point_selected(x,y,data)):
|
|
2369
|
+
# Opening linecut/fitting window for 2D data
|
|
2370
|
+
if isinstance(data, MixedInternalData):
|
|
2371
|
+
data = data.dataset2d
|
|
2372
|
+
index_x = np.argmin(np.abs(data.processed_data[0][:,0]-x))
|
|
2373
|
+
index_y = np.argmin(np.abs(data.processed_data[1][0,:]-y))
|
|
2374
|
+
data.selected_indices = [int(index_x), int(index_y)]
|
|
2375
|
+
if self.colormap_box.currentText() == 'viridis':
|
|
2376
|
+
selected_colormap = cm.get_cmap('plasma')
|
|
2377
|
+
else:
|
|
2378
|
+
selected_colormap = cm.get_cmap('viridis')
|
|
2379
|
+
#Make entry to store linecuts in
|
|
2380
|
+
if not hasattr(data,'linecuts'):
|
|
2381
|
+
data.linecuts={'horizontal':{'linecut_window':None,'lines':{}},
|
|
2382
|
+
'vertical':{'linecut_window':None,'lines':{}},
|
|
2383
|
+
'diagonal':{'linecut_window':None,'lines':{}},
|
|
2384
|
+
'circular':{'linecut_window':None,'lines':{}}
|
|
2385
|
+
}
|
|
2386
|
+
if event.button == 1:
|
|
2387
|
+
line_colors = selected_colormap(np.linspace(0.1,0.9,len(data.processed_data[1][0,:])))
|
|
2388
|
+
orientation='horizontal'
|
|
2389
|
+
try:
|
|
2390
|
+
max_index=np.max(list(data.linecuts[orientation]['lines'].keys()))
|
|
2391
|
+
except ValueError:
|
|
2392
|
+
max_index=-1
|
|
2393
|
+
data.linecuts[orientation]['lines'][int(max_index+1)]={'data_index':index_y,
|
|
2394
|
+
'cut_axis_value':data.processed_data[1][0,index_y],
|
|
2395
|
+
'checkstate':2,
|
|
2396
|
+
'offset':0,
|
|
2397
|
+
'linecolor':line_colors[int(index_y)]}
|
|
2398
|
+
elif event.button == 2:
|
|
2399
|
+
line_colors = selected_colormap(np.linspace(0.1,0.9,len(data.processed_data[0][:,0])))
|
|
2400
|
+
orientation='vertical'
|
|
2401
|
+
try:
|
|
2402
|
+
max_index=np.max(list(data.linecuts[orientation]['lines'].keys()))
|
|
2403
|
+
except ValueError:
|
|
2404
|
+
max_index=-1
|
|
2405
|
+
data.linecuts[orientation]['lines'][int(max_index+1)]={'data_index':index_x,
|
|
2406
|
+
'cut_axis_value':data.processed_data[0][index_x,0],
|
|
2407
|
+
'checkstate':2,
|
|
2408
|
+
'offset':0,
|
|
2409
|
+
'linecolor':line_colors[int(index_x)]}
|
|
2410
|
+
if data.linecuts[orientation]['linecut_window']==None:
|
|
2411
|
+
data.linecuts[orientation]['linecut_window'] = LineCutWindow(data,orientation=orientation,init_cmap=selected_colormap.name,editor_window=self)
|
|
2412
|
+
data.linecuts[orientation]['linecut_window'].running = True
|
|
2413
|
+
data.linecuts[orientation]['linecut_window'].append_cut_to_table(int(max_index+1))
|
|
2414
|
+
data.linecuts[orientation]['linecut_window'].update()
|
|
2415
|
+
data.linecuts[orientation]['linecut_window'].activateWindow()
|
|
2416
|
+
self.canvas.draw()
|
|
2417
|
+
|
|
2418
|
+
elif event.button == 3:
|
|
2419
|
+
# Open right-click menu
|
|
2420
|
+
rightclick_menu = QtWidgets.QMenu(self)
|
|
2421
|
+
|
|
2422
|
+
if isinstance(data, MixedInternalData):
|
|
2423
|
+
index_x = np.argmin(np.abs(data.dataset2d.processed_data[0][:,0]-x))
|
|
2424
|
+
index_y = np.argmin(np.abs(data.dataset2d.processed_data[1][0,:]-y))
|
|
2425
|
+
z = data.dataset2d.processed_data[2][index_x,index_y]
|
|
2426
|
+
coordinates = (f'x = {x:.4g}, y = {y:.4g}, z = {z:.4g}'
|
|
2427
|
+
f' ({index_x}, {index_y})')
|
|
2428
|
+
elif data.dim == 2:
|
|
2429
|
+
index_x = np.argmin(np.abs(data.processed_data[0]-x))
|
|
2430
|
+
index_y = np.argmin(np.abs(data.processed_data[1]-y))
|
|
2431
|
+
coordinates = (f'x = {x:.4g}, y = {y:.4g}'
|
|
2432
|
+
f' ({index_x}, {index_y})')
|
|
2433
|
+
elif data.dim == 3:
|
|
2434
|
+
index_x = np.argmin(np.abs(data.processed_data[0][:,0]-x))
|
|
2435
|
+
index_y = np.argmin(np.abs(data.processed_data[1][0,:]-y))
|
|
2436
|
+
z = data.processed_data[2][index_x,index_y]
|
|
2437
|
+
coordinates = (f'x = {x:.4g}, y = {y:.4g}, z = {z:.4g}'
|
|
2438
|
+
f' ({index_x}, {index_y})')
|
|
2439
|
+
action = QtWidgets.QAction(coordinates, self)
|
|
2440
|
+
action.setEnabled(False)
|
|
2441
|
+
rightclick_menu.addAction(action)
|
|
2442
|
+
rightclick_menu.addSeparator()
|
|
2443
|
+
|
|
2444
|
+
actions = []
|
|
2445
|
+
actions.append(QtWidgets.QAction(f'Offset X by {x:6g}', self))
|
|
2446
|
+
actions.append(QtWidgets.QAction(f'Offset Y by {y:6g}', self))
|
|
2447
|
+
if data.dim == 3:
|
|
2448
|
+
actions.append(QtWidgets.QAction(f'Offset Z by {z:6g}', self))
|
|
2449
|
+
actions.append(QtWidgets.QAction('Crop data to zoom', self))
|
|
2450
|
+
for action in actions:
|
|
2451
|
+
rightclick_menu.addAction(action)
|
|
2452
|
+
# Add actions from extension modules
|
|
2453
|
+
rightclick_menu.addSeparator()
|
|
2454
|
+
data.add_extension_actions(self, rightclick_menu)
|
|
2455
|
+
rightclick_menu.addSeparator()
|
|
2456
|
+
actions=[]
|
|
2457
|
+
if data.dim == 3 or isinstance(data, MixedInternalData):
|
|
2458
|
+
actions.append(QtWidgets.QAction('Draw diagonal linecut', self))
|
|
2459
|
+
#actions.append(QtWidgets.QAction('Draw circular linecut', self))
|
|
2460
|
+
actions.append(QtWidgets.QAction('Plot vertical linecuts (middle click on plot)', self))
|
|
2461
|
+
actions.append(QtWidgets.QAction('Plot horizontal linecuts (left click on plot)', self))
|
|
2462
|
+
for action in actions:
|
|
2463
|
+
rightclick_menu.addAction(action)
|
|
2464
|
+
rightclick_menu.triggered[QtWidgets.QAction].connect(self.popup_canvas)
|
|
2465
|
+
rightclick_menu.popup(QtGui.QCursor.pos())
|
|
2466
|
+
|
|
2467
|
+
# else: # if colorbar in focus
|
|
2468
|
+
# self.cbar_in_focus = [checked_item for checked_item in checked_items
|
|
2469
|
+
# if (hasattr(checked_item.data,'cbar') and checked_item.data.cbar.ax == event.inaxes)]
|
|
2470
|
+
# if self.cbar_in_focus:
|
|
2471
|
+
# if self.file_list.currentItem() != self.cbar_in_focus[0]:
|
|
2472
|
+
# # If the clicked plot is not the current one, set it as current before doing anything else.
|
|
2473
|
+
# self.file_list.setCurrentItem(self.cbar_in_focus[0])
|
|
2474
|
+
# self.file_clicked()
|
|
2475
|
+
|
|
2476
|
+
# else:
|
|
2477
|
+
# self.press = event.ydata
|
|
2478
|
+
|
|
2479
|
+
def on_motion(self, event):
|
|
2480
|
+
if hasattr(self,'press') and self.press != None:
|
|
2481
|
+
# if hasattr(self,'cbar_in_focus') and self.cbar_in_focus:
|
|
2482
|
+
# try:
|
|
2483
|
+
# ypress = self.press
|
|
2484
|
+
# dy = event.ydata - ypress
|
|
2485
|
+
# self.press=event.ydata
|
|
2486
|
+
# map_min=self.cbar_in_focus[0].data.view_settings['Minimum']
|
|
2487
|
+
# map_max=self.cbar_in_focus[0].data.view_settings['Maximum']
|
|
2488
|
+
# self.cbar_in_focus[0].data.view_settings['Minimum']= map_min+dy
|
|
2489
|
+
# self.cbar_in_focus[0].data.view_settings['Maximum']= map_max+dy
|
|
2490
|
+
# self.cbar_in_focus[0].data.reset_midpoint()
|
|
2491
|
+
# self.cbar_in_focus[0].data.apply_view_settings()
|
|
2492
|
+
# self.canvas.draw()
|
|
2493
|
+
# except: # sometimes can happen if the focus between plots changes weirdly. Not worth getting worried about, so no use filling up errors
|
|
2494
|
+
# pass
|
|
2495
|
+
|
|
2496
|
+
if self.press[0] in ['haxfill_top','haxfill_bottom','haxfill']:
|
|
2497
|
+
which=self.press[0]
|
|
2498
|
+
data=self.press[2]
|
|
2499
|
+
pixels_to_hax = data.hax.transData.inverted().transform
|
|
2500
|
+
ypress= self.press[1]
|
|
2501
|
+
dx,dy = pixels_to_hax((0,event.y)) - ypress
|
|
2502
|
+
self.press=[which,pixels_to_hax((0,event.y)),data]
|
|
2503
|
+
map_max=data.view_settings['Maximum']
|
|
2504
|
+
map_min=data.view_settings['Minimum']
|
|
2505
|
+
if which == 'haxfill_top':
|
|
2506
|
+
data.view_settings['Maximum']= map_max+dy
|
|
2507
|
+
elif which == 'haxfill_bottom':
|
|
2508
|
+
data.view_settings['Minimum']= map_min+dy
|
|
2509
|
+
elif which == 'haxfill':
|
|
2510
|
+
data.view_settings['Minimum']= map_min+dy
|
|
2511
|
+
data.view_settings['Maximum']= map_max+dy
|
|
2512
|
+
if hasattr(self,'hax_marker'):
|
|
2513
|
+
if which=='haxfill_top':
|
|
2514
|
+
self.hax_marker.set_ydata([data.view_settings['Maximum'],data.view_settings['Maximum']])
|
|
2515
|
+
elif which=='haxfill_bottom':
|
|
2516
|
+
self.hax_marker.set_ydata([data.view_settings['Minimum'],data.view_settings['Minimum']])
|
|
2517
|
+
|
|
2518
|
+
data.reset_midpoint()
|
|
2519
|
+
data.apply_view_settings()
|
|
2520
|
+
self.show_current_view_settings()
|
|
2521
|
+
self.canvas.draw()
|
|
2522
|
+
|
|
2523
|
+
elif self.press[0] == 'outside':
|
|
2524
|
+
data=self.press[2]
|
|
2525
|
+
pixels_to_hax = data.hax.transData.inverted().transform
|
|
2526
|
+
ypress= self.press[1]
|
|
2527
|
+
dx,dy = pixels_to_hax((0,event.y)) - ypress
|
|
2528
|
+
self.press=['outside',pixels_to_hax((0,event.y)),data]
|
|
2529
|
+
axmin=data.hax.get_ylim()[0]
|
|
2530
|
+
axmax=data.hax.get_ylim()[1]
|
|
2531
|
+
data.hax.set_ylim(axmin-dy,axmax-dy)
|
|
2532
|
+
self.canvas.draw()
|
|
2533
|
+
|
|
2534
|
+
else:
|
|
2535
|
+
# Mouse is moving around without a press event (i.e. not clicked). Turn it into a move cursor if over a haxfill.
|
|
2536
|
+
histograms = [checked_item for checked_item in self.get_checked_items() if
|
|
2537
|
+
hasattr(checked_item.data, 'hax')]
|
|
2538
|
+
box_mins = [checked_item.data.haxfill.get_window_extent().get_points()[0][1] for checked_item in histograms]
|
|
2539
|
+
box_maxs = [checked_item.data.haxfill.get_window_extent().get_points()[1][1] for checked_item in histograms]
|
|
2540
|
+
box_xmins = [checked_item.data.haxfill.get_window_extent().get_points()[0][0] for checked_item in histograms]
|
|
2541
|
+
box_xmaxs = [checked_item.data.haxfill.get_window_extent().get_points()[1][0] for checked_item in histograms]
|
|
2542
|
+
min_windows = [[box_min-(box_max-box_min)/50,box_min+(box_max-box_min)/50] for box_min, box_max in zip(box_mins, box_maxs)]
|
|
2543
|
+
max_windows = [[box_max-(box_max-box_min)/50,box_max+(box_max-box_min)/50] for box_min, box_max in zip(box_mins, box_maxs)]
|
|
2544
|
+
|
|
2545
|
+
for i in range(len(min_windows)):
|
|
2546
|
+
if box_xmins[i] < event.x < box_xmaxs[i]:
|
|
2547
|
+
if min_windows[i][0] < event.y < min_windows[i][1]:
|
|
2548
|
+
self.canvas.setCursor(QtCore.Qt.SizeVerCursor)
|
|
2549
|
+
break
|
|
2550
|
+
elif max_windows[i][0] < event.y < max_windows[i][1]:
|
|
2551
|
+
self.canvas.setCursor(QtCore.Qt.SizeVerCursor)
|
|
2552
|
+
break
|
|
2553
|
+
else:
|
|
2554
|
+
self.canvas.setCursor(QtCore.Qt.ArrowCursor)
|
|
2555
|
+
else:
|
|
2556
|
+
self.canvas.setCursor(QtCore.Qt.ArrowCursor)
|
|
2557
|
+
|
|
2558
|
+
def on_release(self, event):
|
|
2559
|
+
self.press = None
|
|
2560
|
+
if hasattr(self,'hax_marker'):
|
|
2561
|
+
self.hax_marker.remove()
|
|
2562
|
+
del self.hax_marker
|
|
2563
|
+
self.canvas.draw()
|
|
2564
|
+
|
|
2565
|
+
def on_pick(self,event):
|
|
2566
|
+
# Use this exclusively for the cbar's histogram, since it's not possible to access it in any other way.
|
|
2567
|
+
hist_in_focus = [checked_item for checked_item in self.get_checked_items() if
|
|
2568
|
+
hasattr(checked_item.data, 'hax') and
|
|
2569
|
+
checked_item.data.hax == event.artist]
|
|
2570
|
+
if hist_in_focus:
|
|
2571
|
+
data=hist_in_focus[0].data
|
|
2572
|
+
box=data.haxfill
|
|
2573
|
+
box_min=box.get_window_extent().get_points()[0][1]
|
|
2574
|
+
box_max=box.get_window_extent().get_points()[1][1]
|
|
2575
|
+
min_window=[box_min-(box_max-box_min)/50,box_min+(box_max-box_min)/50]
|
|
2576
|
+
max_window=[box_max-(box_max-box_min)/50,box_max+(box_max-box_min)/50]
|
|
2577
|
+
x, y = event.mouseevent.x, event.mouseevent.y
|
|
2578
|
+
pixels_to_hax = data.hax.transData.inverted().transform
|
|
2579
|
+
if self.file_list.currentItem() != hist_in_focus[0]:
|
|
2580
|
+
# If the clicked plot is not the current one, set it as current before doing anything else.
|
|
2581
|
+
self.file_list.setCurrentItem(hist_in_focus[0])
|
|
2582
|
+
self.file_clicked()
|
|
2583
|
+
|
|
2584
|
+
if event.mouseevent.step != 0: #The user is scrolling.
|
|
2585
|
+
ydata= pixels_to_hax((x, y))[1]
|
|
2586
|
+
scale_factor = np.power(1.1, -event.mouseevent.step)
|
|
2587
|
+
y_top = ydata - data.hax.get_ylim()[0]
|
|
2588
|
+
y_bottom = data.hax.get_ylim()[1] - ydata
|
|
2589
|
+
newylims=[ydata - y_top * scale_factor, ydata + y_bottom * scale_factor]
|
|
2590
|
+
|
|
2591
|
+
data.hax.set_ylim(newylims[0], newylims[1])
|
|
2592
|
+
data.hax.figure.canvas.draw()
|
|
2593
|
+
|
|
2594
|
+
elif event.mouseevent.button == 1 and max_window[0]<y<max_window[1]: # Adjust upper limit of the haxfill box.
|
|
2595
|
+
self.hax_marker=data.hax.axhline(y=pixels_to_hax((0,box_max))[1], color='red', lw=1)
|
|
2596
|
+
self.press = ['haxfill_top', pixels_to_hax((x, y))[1], data]
|
|
2597
|
+
elif event.mouseevent.button == 1 and min_window[0]<y<min_window[1]:
|
|
2598
|
+
self.hax_marker=data.hax.axhline(y=pixels_to_hax((0,box_min))[1], color='red', lw=1)
|
|
2599
|
+
self.press = ['haxfill_bottom', pixels_to_hax((x, y))[1], data]
|
|
2600
|
+
elif event.mouseevent.button == 1 and min_window[1]<y<max_window[0]:
|
|
2601
|
+
self.press = ['haxfill', pixels_to_hax((x, y))[1], data]
|
|
2602
|
+
else:
|
|
2603
|
+
self.press = ['outside', pixels_to_hax((x, y))[1], data]
|
|
2604
|
+
|
|
2605
|
+
def popup_canvas(self, signal):
|
|
2606
|
+
# Actions for right-click menu on the plot(s)
|
|
2607
|
+
current_item = self.plot_in_focus[0]
|
|
2608
|
+
data = self.plot_in_focus[0].data
|
|
2609
|
+
|
|
2610
|
+
# Easy offset
|
|
2611
|
+
if 'Offset' in signal.text():
|
|
2612
|
+
axis=signal.text().split()[1]
|
|
2613
|
+
value=-float(signal.text().split()[3])
|
|
2614
|
+
if current_item:
|
|
2615
|
+
filt = Filter('Add/Subtract',method=axis, settings=[str(value),''], checkstate=2)
|
|
2616
|
+
self.which_filters(current_item,filt=filt)
|
|
2617
|
+
if current_item.checkState() and filt.checkstate:
|
|
2618
|
+
self.update_plots(update_color_limits=True)
|
|
2619
|
+
self.reset_axlim_settings()
|
|
2620
|
+
self.show_current_all()
|
|
2621
|
+
|
|
2622
|
+
# Making linecuts
|
|
2623
|
+
elif 'linecut' in signal.text():
|
|
2624
|
+
if isinstance(data, MixedInternalData):
|
|
2625
|
+
data = data.dataset2d
|
|
2626
|
+
# Make dictionary if it doesn't exist
|
|
2627
|
+
if not hasattr(data,'linecuts'):
|
|
2628
|
+
data.linecuts={'horizontal':{'linecut_window':None,'lines':{}},
|
|
2629
|
+
'vertical':{'linecut_window':None,'lines':{}},
|
|
2630
|
+
'diagonal':{'linecut_window':None,'lines':{}},
|
|
2631
|
+
'circular':{'linecut_window':None,'lines':{}}
|
|
2632
|
+
}
|
|
2633
|
+
orientation=signal.text().split()[1]
|
|
2634
|
+
|
|
2635
|
+
if data.linecuts[orientation]['linecut_window']==None:
|
|
2636
|
+
if self.colormap_box.currentText() == 'viridis':
|
|
2637
|
+
selected_colormap = cm.get_cmap('plasma')
|
|
2638
|
+
else:
|
|
2639
|
+
selected_colormap = cm.get_cmap('viridis')
|
|
2640
|
+
data.linecuts[orientation]['linecut_window'] = LineCutWindow(data,orientation=orientation,
|
|
2641
|
+
init_cmap=selected_colormap.name,
|
|
2642
|
+
editor_window=self)
|
|
2643
|
+
|
|
2644
|
+
# For diagonal/circular linecuts, need to make a new line each time. For hori/vert just open the window.
|
|
2645
|
+
if orientation == 'diagonal':
|
|
2646
|
+
x,y=data.selected_x, data.selected_y
|
|
2647
|
+
index_x = np.argmin(np.abs(data.processed_data[0][:,0]-x))
|
|
2648
|
+
index_y = np.argmin(np.abs(data.processed_data[1][0,:]-y))
|
|
2649
|
+
left,right= data.axes.get_xlim()
|
|
2650
|
+
bottom,top= data.axes.get_ylim()
|
|
2651
|
+
x_mid, y_mid = 0.5*(left+right), 0.5*(top+bottom)
|
|
2652
|
+
index_x_mid= np.argmin(np.abs(data.processed_data[0][:,0]-x_mid))
|
|
2653
|
+
index_y_mid= np.argmin(np.abs(data.processed_data[1][0,:]-y_mid))
|
|
2654
|
+
if self.colormap_box.currentText() == 'viridis':
|
|
2655
|
+
selected_colormap = cm.get_cmap('plasma')
|
|
2656
|
+
else:
|
|
2657
|
+
selected_colormap = cm.get_cmap('viridis')
|
|
2658
|
+
line_colors = selected_colormap(np.linspace(0.1,0.9,len(data.processed_data[1][0,:])))
|
|
2659
|
+
try:
|
|
2660
|
+
max_index=np.max(list(data.linecuts[orientation]['lines'].keys()))
|
|
2661
|
+
except ValueError:
|
|
2662
|
+
max_index=-1
|
|
2663
|
+
data.linecuts[orientation]['lines'][int(max_index+1)]={'points':[(x, y),(x_mid, y_mid)],
|
|
2664
|
+
'indices':[(index_x, index_y),(index_x_mid, index_y_mid)],
|
|
2665
|
+
'checkstate':2,
|
|
2666
|
+
'offset':0,
|
|
2667
|
+
'linecolor':line_colors[int(max_index+1)]}
|
|
2668
|
+
data.linecuts[orientation]['lines'][int(max_index+1)]['draggable_points']=[DraggablePoint(data, x, y,int(max_index+1),orientation),
|
|
2669
|
+
DraggablePoint(data, x_mid, y_mid,int(max_index+1),orientation,draw_line=True)]
|
|
2670
|
+
data.linecuts[orientation]['linecut_window'].append_cut_to_table(int(max_index+1))
|
|
2671
|
+
|
|
2672
|
+
data.linecuts[orientation]['linecut_window'].running = True
|
|
2673
|
+
data.linecuts[orientation]['linecut_window'].activateWindow()
|
|
2674
|
+
if len(data.linecuts[orientation]['lines']) > 0:
|
|
2675
|
+
data.linecuts[orientation]['linecut_window'].update()
|
|
2676
|
+
data.linecuts[orientation]['linecut_window'].show()
|
|
2677
|
+
|
|
2678
|
+
elif signal.text() == 'Draw circular linecut':
|
|
2679
|
+
if hasattr(data, 'linecut_points'):
|
|
2680
|
+
data.hide_linecuts()
|
|
2681
|
+
left, right = data.axes.get_xlim()
|
|
2682
|
+
bottom, top = data.axes.get_ylim()
|
|
2683
|
+
x, y = data.selected_x, data.selected_y
|
|
2684
|
+
data.xr, data.yr = 0.1*(right-left), 0.1*(top-bottom)
|
|
2685
|
+
data.linecut_points = [DraggablePoint(data, x, y),
|
|
2686
|
+
DraggablePoint(data, x+data.xr, y)]
|
|
2687
|
+
data.linecut_points.append(DraggablePoint(data, x, y+data.yr,
|
|
2688
|
+
draw_circle=True))
|
|
2689
|
+
if not hasattr(data, 'linecut_window'):
|
|
2690
|
+
data.linecut_window = LineCutWindow(data,orientation='circular',editor_window=self)
|
|
2691
|
+
else:
|
|
2692
|
+
data.linecut_window.orientation='circular'
|
|
2693
|
+
data.linecut_window.running = True
|
|
2694
|
+
data.linecut_window.update()
|
|
2695
|
+
self.canvas.draw()
|
|
2696
|
+
data.linecut_window.activateWindow()
|
|
2697
|
+
|
|
2698
|
+
elif signal.text() == 'Crop data to zoom':
|
|
2699
|
+
left, right = data.axes.get_xlim()
|
|
2700
|
+
if data.dim ==3:
|
|
2701
|
+
bottom, top = data.axes.get_ylim()
|
|
2702
|
+
x_min, x_max = np.argmin(np.abs(data.processed_data[0][:,0]-left)), np.argmin(np.abs(data.processed_data[0][:,0]-right))
|
|
2703
|
+
y_min, y_max = np.argmin(np.abs(data.processed_data[1][0,:]-bottom)), np.argmin(np.abs(data.processed_data[1][0,:]-top))
|
|
2704
|
+
|
|
2705
|
+
filt = Filter('Crop X',method='Abs', settings=[str(data.processed_data[0][x_min,0]),
|
|
2706
|
+
str(data.processed_data[0][x_max,0])], checkstate=2)
|
|
2707
|
+
self.which_filters(current_item,filt=filt)
|
|
2708
|
+
filt = Filter('Crop Y',method='Abs', settings=[str(data.processed_data[1][0,y_min]),
|
|
2709
|
+
str(data.processed_data[1][0,y_max])], checkstate=2)
|
|
2710
|
+
self.which_filters(current_item,filt=filt)
|
|
2711
|
+
elif data.dim == 2:
|
|
2712
|
+
current_1D_row = data.sidebar1D.trace_table.currentRow()
|
|
2713
|
+
current_line = int(data.sidebar1D.trace_table.item(current_1D_row,0).text())
|
|
2714
|
+
try:
|
|
2715
|
+
x_min,x_max=np.argmin(np.abs(data.plotted_lines[current_line]['processed_data'][0]-left)),np.argmin(np.abs(data.plotted_lines[current_line]['processed_data'][0]-right))
|
|
2716
|
+
filt = Filter('Crop X',method='Abs', settings=[str(data.plotted_lines[current_line]['processed_data'][0][x_min]),
|
|
2717
|
+
str(data.plotted_lines[current_line]['processed_data'][0][x_max])], checkstate=2)
|
|
2718
|
+
self.which_filters(current_item,filt=filt)
|
|
2719
|
+
except Exception as e:
|
|
2720
|
+
print('Error cropping X:', e)
|
|
2721
|
+
if current_item.checkState() and filt.checkstate:
|
|
2722
|
+
self.update_plots(update_color_limits=True)
|
|
2723
|
+
self.reset_axlim_settings()
|
|
2724
|
+
self.show_current_all()
|
|
2725
|
+
|
|
2726
|
+
def copy_canvas_to_clipboard(self):
|
|
2727
|
+
checked_items = self.get_checked_items()
|
|
2728
|
+
for item in checked_items:
|
|
2729
|
+
item.data.cursor.horizOn = False
|
|
2730
|
+
item.data.cursor.vertOn = False
|
|
2731
|
+
self.canvas.draw()
|
|
2732
|
+
if DARK_THEME and qdarkstyle_imported:
|
|
2733
|
+
rcParams_to_light_theme()
|
|
2734
|
+
self.update_plots(update_data=False)
|
|
2735
|
+
buf = io.BytesIO()
|
|
2736
|
+
if item.data.settings['dpi'] == 'figure':
|
|
2737
|
+
dpi = 'figure'
|
|
2738
|
+
else:
|
|
2739
|
+
dpi = int(item.data.settings['dpi'])
|
|
2740
|
+
self.figure.savefig(buf, dpi=dpi, bbox_inches='tight')
|
|
2741
|
+
QtWidgets.QApplication.clipboard().setImage(QtGui.QImage.fromData(buf.getvalue()))
|
|
2742
|
+
buf.close()
|
|
2743
|
+
for item in checked_items:
|
|
2744
|
+
item.data.cursor.horizOn = True
|
|
2745
|
+
item.data.cursor.vertOn = True
|
|
2746
|
+
self.canvas.draw()
|
|
2747
|
+
if DARK_THEME and qdarkstyle_imported:
|
|
2748
|
+
rcParams_to_dark_theme()
|
|
2749
|
+
self.update_plots(update_data=False)
|
|
2750
|
+
|
|
2751
|
+
def show_metadata(self):
|
|
2752
|
+
item = self.file_list.currentItem()
|
|
2753
|
+
if item:
|
|
2754
|
+
if hasattr(item.data,'meta'):
|
|
2755
|
+
if not hasattr(item.data,'metapopup'):
|
|
2756
|
+
item.data.metapopup=MetadataWindow(item.data)
|
|
2757
|
+
item.data.metapopup.show()
|
|
2758
|
+
|
|
2759
|
+
def show_stats(self):
|
|
2760
|
+
item = self.file_list.currentItem()
|
|
2761
|
+
if item:
|
|
2762
|
+
if not hasattr(item.data,'statspopup'):
|
|
2763
|
+
item.data.statspopup=StatsWindow(item.data)
|
|
2764
|
+
item.data.statspopup.show()
|
|
2765
|
+
|
|
2766
|
+
def mouse_scroll_canvas(self, event):
|
|
2767
|
+
if event.inaxes:
|
|
2768
|
+
checked_items = self.get_checked_items()
|
|
2769
|
+
self.plot_in_focus = [checked_item for checked_item in checked_items
|
|
2770
|
+
if checked_item.data.axes == event.inaxes]
|
|
2771
|
+
self.cbar_in_focus = [checked_item for checked_item in checked_items
|
|
2772
|
+
if hasattr(checked_item.data, 'cbar') and checked_item.data.cbar.ax == event.inaxes]
|
|
2773
|
+
|
|
2774
|
+
# Scolling within plot bounds zooms
|
|
2775
|
+
if self.plot_in_focus:
|
|
2776
|
+
if self.file_list.currentItem() != self.plot_in_focus[0]:
|
|
2777
|
+
# If the clicked plot is not the current one, set it as current before doing anything else.
|
|
2778
|
+
self.file_list.setCurrentItem(self.plot_in_focus[0])
|
|
2779
|
+
self.file_clicked()
|
|
2780
|
+
else:
|
|
2781
|
+
scale=1.2
|
|
2782
|
+
data = self.plot_in_focus[0].data
|
|
2783
|
+
scale_factor = np.power(scale, -event.step)
|
|
2784
|
+
|
|
2785
|
+
if any(scale != 'linear' for scale in [data.axlim_settings['Xscale'],
|
|
2786
|
+
data.axlim_settings['Yscale']]):
|
|
2787
|
+
# This zoom method works well for non-linear plots, but is slightly slower since requires more calculation.
|
|
2788
|
+
x = event.x
|
|
2789
|
+
y = event.y
|
|
2790
|
+
#convert pixels to axes
|
|
2791
|
+
tranP2A = event.inaxes.transAxes.inverted().transform
|
|
2792
|
+
#convert axes to data limits
|
|
2793
|
+
tranA2D= event.inaxes.transLimits.inverted().transform
|
|
2794
|
+
#convert the scale (for log plots)
|
|
2795
|
+
tranSclA2D = event.inaxes.transScale.inverted().transform
|
|
2796
|
+
#x,y position of the mouse in range (0,1)
|
|
2797
|
+
xa,ya = tranP2A((x,y))
|
|
2798
|
+
newxlims=[xa - xa*scale_factor, xa + (1-xa)*scale_factor]
|
|
2799
|
+
newylims=[ya - ya*scale_factor, ya + (1-ya)*scale_factor]
|
|
2800
|
+
new_xlim0,new_ylim0 = tranSclA2D(tranA2D((newxlims[0],newxlims[0])))
|
|
2801
|
+
new_xlim1,new_ylim1 = tranSclA2D(tranA2D((newylims[1],newylims[1])))
|
|
2802
|
+
|
|
2803
|
+
if QtGui.QGuiApplication.keyboardModifiers() == QtCore.Qt.ControlModifier:
|
|
2804
|
+
data.axlim_settings['Xmin']=new_xlim0
|
|
2805
|
+
data.axlim_settings['Xmax']=new_xlim1
|
|
2806
|
+
elif QtGui.QGuiApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
|
|
2807
|
+
data.axlim_settings['Ymin']=new_ylim0
|
|
2808
|
+
data.axlim_settings['Ymax']=new_ylim1
|
|
2809
|
+
else:
|
|
2810
|
+
data.axlim_settings['Xmin']=new_xlim0
|
|
2811
|
+
data.axlim_settings['Xmax']=new_xlim1
|
|
2812
|
+
data.axlim_settings['Ymin']=new_ylim0
|
|
2813
|
+
data.axlim_settings['Ymax']=new_ylim1
|
|
2814
|
+
else:
|
|
2815
|
+
xdata = event.xdata
|
|
2816
|
+
ydata = event.ydata
|
|
2817
|
+
x_left = xdata - event.inaxes.get_xlim()[0]
|
|
2818
|
+
x_right = event.inaxes.get_xlim()[1] - xdata
|
|
2819
|
+
y_top = ydata - event.inaxes.get_ylim()[0]
|
|
2820
|
+
y_bottom = event.inaxes.get_ylim()[1] - ydata
|
|
2821
|
+
newxlims=[xdata - x_left * scale_factor, xdata + x_right * scale_factor]
|
|
2822
|
+
newylims=[ydata - y_top * scale_factor, ydata + y_bottom * scale_factor]
|
|
2823
|
+
if QtGui.QGuiApplication.keyboardModifiers() == QtCore.Qt.ControlModifier:
|
|
2824
|
+
data.axlim_settings['Xmin']=newxlims[0]
|
|
2825
|
+
data.axlim_settings['Xmax']=newxlims[1]
|
|
2826
|
+
elif QtGui.QGuiApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
|
|
2827
|
+
data.axlim_settings['Ymin']=newylims[0]
|
|
2828
|
+
data.axlim_settings['Ymax']=newylims[1]
|
|
2829
|
+
else:
|
|
2830
|
+
data.axlim_settings['Xmin']=newxlims[0]
|
|
2831
|
+
data.axlim_settings['Xmax']=newxlims[1]
|
|
2832
|
+
data.axlim_settings['Ymin']=newylims[0]
|
|
2833
|
+
data.axlim_settings['Ymax']=newylims[1]
|
|
2834
|
+
|
|
2835
|
+
|
|
2836
|
+
data.apply_axlim_settings()
|
|
2837
|
+
event.inaxes.figure.canvas.draw()
|
|
2838
|
+
self.show_current_axlim_settings()
|
|
2839
|
+
# Update toolbar so back/forward buttons work
|
|
2840
|
+
fig = event.inaxes.get_figure()
|
|
2841
|
+
fig.canvas.toolbar.push_current()
|
|
2842
|
+
|
|
2843
|
+
#Scrolling within colourbar changes limits
|
|
2844
|
+
# elif self.cbar_in_focus:
|
|
2845
|
+
# if self.file_list.currentItem() != self.cbar_in_focus[0]:
|
|
2846
|
+
# # If the clicked plot is not the current one, set it as current before doing anything else.
|
|
2847
|
+
# self.file_list.setCurrentItem(self.cbar_in_focus[0])
|
|
2848
|
+
# self.file_clicked()
|
|
2849
|
+
# else:
|
|
2850
|
+
# y = event.ydata
|
|
2851
|
+
# data = self.cbar_in_focus[0].data
|
|
2852
|
+
# min_map = data.view_settings['Minimum']
|
|
2853
|
+
# max_map = data.view_settings['Maximum']
|
|
2854
|
+
# range_map = max_map-min_map
|
|
2855
|
+
# speed= np.abs(y-(min_map+0.5*range_map))*(0.25+0.01)/range_map # Scroll faster further away from the midpoint
|
|
2856
|
+
|
|
2857
|
+
# if y > min_map+0.5*range_map:
|
|
2858
|
+
# new_max = max_map + event.step*range_map*speed
|
|
2859
|
+
# data.view_settings['Maximum'] = new_max
|
|
2860
|
+
# else:
|
|
2861
|
+
# new_min = min_map + event.step*range_map*speed
|
|
2862
|
+
# data.view_settings['Minimum'] = new_min
|
|
2863
|
+
# data.reset_midpoint()
|
|
2864
|
+
# data.apply_view_settings()
|
|
2865
|
+
# self.canvas.draw()
|
|
2866
|
+
# self.show_current_view_settings()
|
|
2867
|
+
|
|
2868
|
+
# Scrolling outside of plot bounds changes the whitespace around/between plots
|
|
2869
|
+
else:
|
|
2870
|
+
width, height = self.canvas.get_width_height()
|
|
2871
|
+
speed = 0.03
|
|
2872
|
+
lb, rb, tb, bb = 0.15*width, 0.85*width, 0.85*height, 0.15*height
|
|
2873
|
+
if (event.x < lb and event.y > bb and event.y < tb):
|
|
2874
|
+
if (event.step > 0 or
|
|
2875
|
+
(event.step < 0 and self.figure.subplotpars.left > 0.07)):
|
|
2876
|
+
self.figure.subplots_adjust(left=(1+speed*event.step)*
|
|
2877
|
+
self.figure.subplotpars.left)
|
|
2878
|
+
elif (event.x > rb and event.y > bb and event.y < tb):
|
|
2879
|
+
if (event.step < 0 or
|
|
2880
|
+
(event.step > 0 and self.figure.subplotpars.right < 0.97)):
|
|
2881
|
+
self.figure.subplots_adjust(right=(1+speed*0.5*event.step)*
|
|
2882
|
+
self.figure.subplotpars.right)
|
|
2883
|
+
elif (event.y < bb and event.x > lb and event.x < rb):
|
|
2884
|
+
if (event.step > 0 or
|
|
2885
|
+
(event.step < 0 and self.figure.subplotpars.bottom > 0.07)):
|
|
2886
|
+
self.figure.subplots_adjust(bottom=(1+speed*event.step)*
|
|
2887
|
+
self.figure.subplotpars.bottom)
|
|
2888
|
+
elif (event.y > tb and event.x > lb and event.x < rb):
|
|
2889
|
+
if (event.step < 0 or
|
|
2890
|
+
(event.step > 0 and self.figure.subplotpars.top < 0.94)):
|
|
2891
|
+
self.figure.subplots_adjust(top=(1+speed*0.5*event.step)*
|
|
2892
|
+
self.figure.subplotpars.top)
|
|
2893
|
+
else:
|
|
2894
|
+
self.figure.subplots_adjust(wspace=(1+speed*event.step)*self.figure.subplotpars.wspace)
|
|
2895
|
+
self.canvas.draw()
|
|
2896
|
+
|
|
2897
|
+
def keyPressEvent(self, event):
|
|
2898
|
+
# if event.key() == QtCore.Qt.Key_C and event.modifiers() == QtCore.Qt.ControlModifier:
|
|
2899
|
+
# self.copy_canvas_to_clipboard()
|
|
2900
|
+
if event.key() == QtCore.Qt.Key_T and event.modifiers() == QtCore.Qt.ControlModifier:
|
|
2901
|
+
if not self.action_refresh_stop.isEnabled():
|
|
2902
|
+
self.start_auto_refresh(1)
|
|
2903
|
+
print('Start live tracking...')
|
|
2904
|
+
else:
|
|
2905
|
+
self.stop_auto_refresh()
|
|
2906
|
+
print('Stop live tracking...')
|
|
2907
|
+
|
|
2908
|
+
def save_preset(self):
|
|
2909
|
+
current_item = self.file_list.currentItem()
|
|
2910
|
+
if current_item:
|
|
2911
|
+
settings_to_save=['titlesize',
|
|
2912
|
+
'labelsize',
|
|
2913
|
+
'ticksize',
|
|
2914
|
+
'spinewidth',
|
|
2915
|
+
'colorbar',
|
|
2916
|
+
'minortick',
|
|
2917
|
+
'maskcolor',
|
|
2918
|
+
'cmap levels',
|
|
2919
|
+
'rasterized',
|
|
2920
|
+
'dpi',
|
|
2921
|
+
'transparent',
|
|
2922
|
+
'shading']
|
|
2923
|
+
view_settings_to_save=['Colormap Type','Colormap']
|
|
2924
|
+
|
|
2925
|
+
presets={}
|
|
2926
|
+
for setting in settings_to_save:
|
|
2927
|
+
if setting in current_item.data.settings.keys():
|
|
2928
|
+
presets[setting]=current_item.data.settings[setting]
|
|
2929
|
+
for setting in view_settings_to_save:
|
|
2930
|
+
if setting in current_item.data.view_settings.keys():
|
|
2931
|
+
presets[setting]=current_item.data.view_settings[setting]
|
|
2932
|
+
|
|
2933
|
+
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
2934
|
+
self, 'Save Preset As...', '', 'IG preset (*.igp)')
|
|
2935
|
+
if filename:
|
|
2936
|
+
try:
|
|
2937
|
+
with open(filename, 'w') as f:
|
|
2938
|
+
jsondump(presets, f)
|
|
2939
|
+
print('Saved preset as', filename)
|
|
2940
|
+
except Exception as e:
|
|
2941
|
+
print('Could not save preset:', e)
|
|
2942
|
+
|
|
2943
|
+
def load_preset(self):
|
|
2944
|
+
checked_items = self.get_checked_items()
|
|
2945
|
+
if checked_items:
|
|
2946
|
+
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
2947
|
+
self, 'Load Preset File...', '', '*.igp')
|
|
2948
|
+
if filename:
|
|
2949
|
+
try:
|
|
2950
|
+
for item in checked_items:
|
|
2951
|
+
with open(filename, 'r') as f:
|
|
2952
|
+
presets = jsonload(f)
|
|
2953
|
+
for setting in presets.keys():
|
|
2954
|
+
if setting in item.data.settings.keys():
|
|
2955
|
+
item.data.settings[setting] = presets[setting]
|
|
2956
|
+
elif setting in item.data.view_settings.keys():
|
|
2957
|
+
item.data.view_settings[setting] = presets[setting]
|
|
2958
|
+
item.data.apply_view_settings()
|
|
2959
|
+
self.update_plots()
|
|
2960
|
+
print('Loaded preset from', filename)
|
|
2961
|
+
except Exception as e:
|
|
2962
|
+
print('Could not load preset:', e)
|
|
2963
|
+
|
|
2964
|
+
def tight_layout(self):
|
|
2965
|
+
self.figure.tight_layout()
|
|
2966
|
+
self.canvas.draw()
|