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.
Files changed (82) hide show
  1. qcodespp/__init__.py +77 -0
  2. qcodespp/actions.py +237 -0
  3. qcodespp/data/__init__.py +0 -0
  4. qcodespp/data/data_array.py +555 -0
  5. qcodespp/data/data_set.py +926 -0
  6. qcodespp/data/format.py +338 -0
  7. qcodespp/data/gnuplot_format.py +425 -0
  8. qcodespp/data/hdf5_format.py +537 -0
  9. qcodespp/data/hdf5_format_hickle.py +63 -0
  10. qcodespp/data/io.py +241 -0
  11. qcodespp/data/location.py +171 -0
  12. qcodespp/instrument_drivers/Keysight/Keysight_34450A.py +9 -0
  13. qcodespp/instrument_drivers/Keysight/__init__.py +0 -0
  14. qcodespp/instrument_drivers/Keysight/private/Keysight_344xxA.py +456 -0
  15. qcodespp/instrument_drivers/Keysight/private/__init__.py +0 -0
  16. qcodespp/instrument_drivers/Lakeshore/Model_340.py +483 -0
  17. qcodespp/instrument_drivers/Lakeshore/__init__.py +0 -0
  18. qcodespp/instrument_drivers/QDevil/159fit_params_high_latest.json +386 -0
  19. qcodespp/instrument_drivers/QDevil/159fit_params_low_latest.json +386 -0
  20. qcodespp/instrument_drivers/QDevil/QDAC2.py +3229 -0
  21. qcodespp/instrument_drivers/QDevil/QDAC2_Array.py +300 -0
  22. qcodespp/instrument_drivers/QDevil/QSwitch_elab.py +652 -0
  23. qcodespp/instrument_drivers/QDevil/__init__.py +0 -0
  24. qcodespp/instrument_drivers/ZI/ZIHF2LI.py +415 -0
  25. qcodespp/instrument_drivers/ZI/ZIMFLI.py +764 -0
  26. qcodespp/instrument_drivers/ZI/ZIMFLI_sync.py +108 -0
  27. qcodespp/instrument_drivers/ZI/ZIUHFLI.py +2179 -0
  28. qcodespp/instrument_drivers/ZI/__init__.py +1 -0
  29. qcodespp/instrument_drivers/__init__.py +0 -0
  30. qcodespp/instrument_drivers/cryogenic/CryogenicSMS120C.py +599 -0
  31. qcodespp/instrument_drivers/cryogenic/__init__.py +0 -0
  32. qcodespp/instrument_drivers/instrument_mocks.py +379 -0
  33. qcodespp/instrument_drivers/multiplexer_instrument.py +287 -0
  34. qcodespp/instrument_drivers/national_instruments/PXI_4461.py +451 -0
  35. qcodespp/instrument_drivers/national_instruments/PXI_4462.py +290 -0
  36. qcodespp/instrument_drivers/national_instruments/PXI_6259.py +436 -0
  37. qcodespp/instrument_drivers/national_instruments/PXI_6733.py +230 -0
  38. qcodespp/instrument_drivers/national_instruments/PXI_6733_e.py +243 -0
  39. qcodespp/instrument_drivers/national_instruments/__init__.py +0 -0
  40. qcodespp/instrument_drivers/oxford/__init__.py +0 -0
  41. qcodespp/instrument_drivers/oxford/mercuryiPS_serial.py +658 -0
  42. qcodespp/instrument_drivers/oxford/triton.py +434 -0
  43. qcodespp/instrument_drivers/tektronix/Keithley_2000_Scan.py +65 -0
  44. qcodespp/instrument_drivers/tektronix/Keithley_2182a.py +190 -0
  45. qcodespp/instrument_drivers/tektronix/Keithley_2400.py +158 -0
  46. qcodespp/instrument_drivers/tektronix/Keithley_2450.py +242 -0
  47. qcodespp/instrument_drivers/tektronix/Keithley_2700.py +397 -0
  48. qcodespp/instrument_drivers/tektronix/Keithley_6500.py +161 -0
  49. qcodespp/instrument_drivers/tektronix/__init__.py +0 -0
  50. qcodespp/instrument_drivers/yokogawa/GS200.py +513 -0
  51. qcodespp/instrument_drivers/yokogawa/__init__.py +0 -0
  52. qcodespp/loops.py +1342 -0
  53. qcodespp/measure.py +310 -0
  54. qcodespp/parameters.py +311 -0
  55. qcodespp/plotting/RemotePlot.py +469 -0
  56. qcodespp/plotting/RemotePlotClient.py +367 -0
  57. qcodespp/plotting/RemoteQtPlotWidgets.py +871 -0
  58. qcodespp/plotting/__init__.py +0 -0
  59. qcodespp/plotting/analysis_tools.py +195 -0
  60. qcodespp/plotting/colors.py +135 -0
  61. qcodespp/plotting/offline/__init__.py +0 -0
  62. qcodespp/plotting/offline/datatypes.py +999 -0
  63. qcodespp/plotting/offline/design.py +906 -0
  64. qcodespp/plotting/offline/design.ui +1578 -0
  65. qcodespp/plotting/offline/editor.py +2966 -0
  66. qcodespp/plotting/offline/filters.py +561 -0
  67. qcodespp/plotting/offline/fits.py +1171 -0
  68. qcodespp/plotting/offline/helpers.py +288 -0
  69. qcodespp/plotting/offline/iconGadget.png +0 -0
  70. qcodespp/plotting/offline/main.py +55 -0
  71. qcodespp/plotting/offline/popupwindows.py +1848 -0
  72. qcodespp/plotting/offline/qcodes_pp_extension.py +328 -0
  73. qcodespp/plotting/offline/sidebars.py +1108 -0
  74. qcodespp/station.py +290 -0
  75. qcodespp/utils/__init__.py +0 -0
  76. qcodespp/utils/helpers.py +618 -0
  77. qcodespp/utils/visa_helpers.py +61 -0
  78. qcodespp/version.py +12 -0
  79. qcodespp-0.1.0.dist-info/METADATA +91 -0
  80. qcodespp-0.1.0.dist-info/RECORD +82 -0
  81. qcodespp-0.1.0.dist-info/WHEEL +4 -0
  82. 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()