qplotter 1.6.0b3__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 (119) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +63 -0
  4. qplot/_metadata.py +12 -0
  5. qplot/_repair.py +14 -0
  6. qplot/_shutdown_supervisor.py +4049 -0
  7. qplot/_version.py +12 -0
  8. qplot/_windows_shutdown_job.py +1166 -0
  9. qplot/configuration/__init__.py +3 -0
  10. qplot/configuration/config.py +573 -0
  11. qplot/configuration/config_schema.json +319 -0
  12. qplot/configuration/scripts.py +255 -0
  13. qplot/configuration/themes/__init__.py +13 -0
  14. qplot/configuration/themes/_base.py +579 -0
  15. qplot/configuration/themes/blank.py +17 -0
  16. qplot/configuration/themes/dark.py +54 -0
  17. qplot/configuration/themes/light.py +54 -0
  18. qplot/datahandling/LoadFromDB.py +224 -0
  19. qplot/datahandling/__init__.py +47 -0
  20. qplot/datahandling/_trusted_live_helper.py +812 -0
  21. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  22. qplot/datahandling/database.py +2330 -0
  23. qplot/datahandling/dimensions.py +43 -0
  24. qplot/datahandling/file_identity.py +461 -0
  25. qplot/datahandling/parameter_data.py +149 -0
  26. qplot/datahandling/plot_progress.py +17 -0
  27. qplot/datahandling/qcodes_cache.py +277 -0
  28. qplot/datahandling/qcodes_compat.py +14 -0
  29. qplot/datahandling/readSQL.py +2442 -0
  30. qplot/datahandling/readonly.py +2561 -0
  31. qplot/datahandling/source_activity.py +21 -0
  32. qplot/datahandling/trusted_array_preview.py +127 -0
  33. qplot/datahandling/trusted_derived_cache.py +1605 -0
  34. qplot/datahandling/trusted_derived_rendering.py +868 -0
  35. qplot/datahandling/trusted_heatmap.py +241 -0
  36. qplot/datahandling/trusted_live.py +2808 -0
  37. qplot/datahandling/trusted_live_queries.py +5907 -0
  38. qplot/datahandling/trusted_live_service.py +1894 -0
  39. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  40. qplot/datahandling/trusted_plot.py +283 -0
  41. qplot/datahandling/trusted_presentation.py +1065 -0
  42. qplot/datahandling/trusted_snapshot.py +1132 -0
  43. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  44. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  45. qplot/diagnostics.py +241 -0
  46. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  47. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  48. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  49. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  50. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  51. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  52. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  53. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  54. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  55. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  56. qplot/testdata.py +1733 -0
  57. qplot/tools/__init__.py +13 -0
  58. qplot/tools/finite_means.py +114 -0
  59. qplot/tools/general.py +134 -0
  60. qplot/tools/heatmap_geometry.py +381 -0
  61. qplot/tools/operation_registry.py +179 -0
  62. qplot/tools/plot_tools.py +625 -0
  63. qplot/tools/sample_statistics.py +56 -0
  64. qplot/tools/worker.py +2787 -0
  65. qplot/windows/__init__.py +9 -0
  66. qplot/windows/_colorbar.py +422 -0
  67. qplot/windows/_commands.py +523 -0
  68. qplot/windows/_config_persistence.py +123 -0
  69. qplot/windows/_database_actions.py +4904 -0
  70. qplot/windows/_dataset_handle.py +221 -0
  71. qplot/windows/_dragdrop.py +71 -0
  72. qplot/windows/_export_paths.py +1167 -0
  73. qplot/windows/_help.py +169 -0
  74. qplot/windows/_native_averaging.py +173 -0
  75. qplot/windows/_native_transforms.py +389 -0
  76. qplot/windows/_plot1d_snap.py +608 -0
  77. qplot/windows/_plot1d_traces.py +2183 -0
  78. qplot/windows/_plot2d_colorbar.py +1126 -0
  79. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  80. qplot/windows/_plot2d_layers.py +2197 -0
  81. qplot/windows/_plot2d_sweeps.py +712 -0
  82. qplot/windows/_plotWin.py +1993 -0
  83. qplot/windows/_plot_actions.py +2695 -0
  84. qplot/windows/_plot_appearance.py +276 -0
  85. qplot/windows/_plot_axis_scaling.py +1485 -0
  86. qplot/windows/_plot_export.py +1445 -0
  87. qplot/windows/_plot_feedback.py +110 -0
  88. qplot/windows/_plot_marquee.py +656 -0
  89. qplot/windows/_plot_refresh.py +942 -0
  90. qplot/windows/_plot_state.py +239 -0
  91. qplot/windows/_plot_transform_labels.py +60 -0
  92. qplot/windows/_preferences.py +635 -0
  93. qplot/windows/_refresh_interval.py +70 -0
  94. qplot/windows/_run_controls.py +828 -0
  95. qplot/windows/_shortcuts.py +47 -0
  96. qplot/windows/_subplots/__init__.py +8 -0
  97. qplot/windows/_subplots/subplot1d.py +531 -0
  98. qplot/windows/_subplots/subplot2d.py +918 -0
  99. qplot/windows/_svg_export.py +209 -0
  100. qplot/windows/_trusted_derived_qt.py +1759 -0
  101. qplot/windows/_widgets/__init__.py +23 -0
  102. qplot/windows/_widgets/_run_formatting.py +387 -0
  103. qplot/windows/_widgets/details_tables.py +1119 -0
  104. qplot/windows/_widgets/dropbox.py +290 -0
  105. qplot/windows/_widgets/operations.py +460 -0
  106. qplot/windows/_widgets/preview.py +2997 -0
  107. qplot/windows/_widgets/run_list_items.py +488 -0
  108. qplot/windows/_widgets/toolbar.py +111 -0
  109. qplot/windows/_widgets/treeWidgets.py +2572 -0
  110. qplot/windows/_window_controls.py +413 -0
  111. qplot/windows/main.py +1635 -0
  112. qplot/windows/plot1d.py +311 -0
  113. qplot/windows/plot2d.py +1901 -0
  114. qplotter-1.6.0b3.dist-info/METADATA +318 -0
  115. qplotter-1.6.0b3.dist-info/RECORD +119 -0
  116. qplotter-1.6.0b3.dist-info/WHEEL +5 -0
  117. qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
  118. qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
  119. qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,460 @@
1
+ from collections.abc import Callable, Sequence
2
+ from decimal import Decimal
3
+ from typing import Any, Literal, Protocol, TypeAlias, cast, overload
4
+
5
+ from PyQt6 import (
6
+ QtCore,
7
+ QtGui,
8
+ )
9
+ from PyQt6 import (
10
+ QtWidgets as qtw,
11
+ )
12
+
13
+ from qplot.tools.operation_registry import (
14
+ OperationCall,
15
+ OperationValidationError,
16
+ operation_specs_for,
17
+ )
18
+
19
+ from .dropbox import expandingComboBox
20
+
21
+ OperationKind: TypeAlias = Literal["plot1d", "plot2d", "sweeper"]
22
+ OperationFunc: TypeAlias = Callable[..., Any]
23
+ OperationInputType: TypeAlias = type | Sequence[str] | None
24
+
25
+
26
+ class OperationsDock(Protocol):
27
+ event_filter: QtCore.QObject
28
+
29
+ def VBox_context(self, *args: Any) -> qtw.QVBoxLayout: ...
30
+
31
+ def HBox_context(self, *args: Any) -> qtw.QHBoxLayout: ...
32
+
33
+
34
+ class OperationsWindow(Protocol):
35
+ oper_dock: OperationsDock
36
+
37
+
38
+ def operations_widget(window: Any) -> "operations_options_base":
39
+ """
40
+ Entry point for getting the operation options.
41
+ Uses the plot kind declared by the window class to select the correct
42
+ options widget. Subclasses inherit this declaration, so instrumented or
43
+ otherwise specialised plot windows get the same operations as their base
44
+ plot type. Window is also passed to the class.
45
+
46
+ PLEASE SEE BOTTOM OF FILE FOR WHICH OPTIONS ARE ADDED.
47
+
48
+ Parameters
49
+ ----------
50
+ window : qplot.windows._plotWin.plotWidget
51
+ The type of window: 1d, 2d, sweeper. Also serves as a reference inside
52
+ the returned class
53
+
54
+ Returns
55
+ -------
56
+ out : operations_options_base
57
+ Returns the a QWidget containing QListWidgets, with options based on
58
+ the inputted window type.
59
+
60
+ """
61
+ options_dict: dict[OperationKind, type[operations_options_base]] = {
62
+ "plot1d" : operations_options_1d,
63
+ "plot2d" : operations_options_2d,
64
+ "sweeper": operations_options_sweep
65
+ }
66
+
67
+ operation_kind = getattr(window, "operation_kind", None)
68
+ if operation_kind not in options_dict:
69
+ raise TypeError(
70
+ "Plot windows must declare operation_kind as "
71
+ "'plot1d', 'plot2d', or 'sweeper'"
72
+ )
73
+ out = options_dict[cast(OperationKind, operation_kind)](window)
74
+
75
+ return out
76
+
77
+
78
+
79
+ class operations_options_base(qtw.QWidget):
80
+ """
81
+ Base class for all operation widgets, handles set up and all needed functions
82
+ Classes inhertit this to set the options.
83
+
84
+ """
85
+
86
+ operation_kind: OperationKind
87
+
88
+ def __init__(self, main: OperationsWindow):
89
+ super().__init__()
90
+ self._window = main
91
+
92
+ # Make filter to propagate context menu to widgets.
93
+ self.filter = self._window.oper_dock.event_filter
94
+ self.main_layout = self._window.oper_dock.VBox_context(self.filter, self)
95
+
96
+ self.main_layout.addWidget(qtw.QLabel("Data Operations"))
97
+
98
+ # Controls order to perform and user inputs
99
+ self.list_order = draggableListWidget()
100
+ self.list_order.setDragDropMode(qtw.QAbstractItemView.DragDropMode.InternalMove)
101
+ self.list_order.setToolTip("Drag Items to Control Operation Order")
102
+ self.main_layout.addWidget(self.list_order)
103
+
104
+ # Buttons
105
+ but_l = self._window.oper_dock.HBox_context(self.filter)
106
+ self.apply_but = qtw.QPushButton("Apply/Refresh")
107
+ self.apply_but.setToolTip("Apply selected operations and refresh the plot")
108
+ self._apply_generation = 0
109
+ self.apply_but.clicked.connect(self._record_apply_request)
110
+ but_l.addWidget(self.apply_but)
111
+ clear_but = qtw.QPushButton("Clear")
112
+ clear_but.setToolTip("Clear all selected operations")
113
+ clear_but.clicked.connect(self.hide_all)
114
+ but_l.addWidget(clear_but)
115
+
116
+ self.main_layout.addLayout(but_l)
117
+
118
+ # Allows user to toggle options
119
+ self.list_options = qtw.QListWidget()
120
+ # Some platform styles draw the vertical scrollbar as an overlay. Keep
121
+ # the option-row controls clear of that area, rather than allowing the
122
+ # checkboxes to extend underneath it.
123
+ scrollbar_extent = max(
124
+ self.list_options.verticalScrollBar().sizeHint().width(),
125
+ self.style().pixelMetric(
126
+ qtw.QStyle.PixelMetric.PM_ScrollBarExtent,
127
+ ),
128
+ )
129
+ self.list_options.setViewportMargins(0, 0, scrollbar_extent, 0)
130
+ self.main_layout.addWidget(self.list_options)
131
+
132
+ self.add_all_options()
133
+
134
+
135
+ def _record_apply_request(self) -> None:
136
+ """Identify refreshes explicitly requested from the operations panel."""
137
+
138
+ self._apply_generation += 1
139
+
140
+
141
+ def add_option(
142
+ self,
143
+ name: str,
144
+ func: OperationFunc,
145
+ input_type: OperationInputType,
146
+ default: object = "",
147
+ derivative_axis: str | None = None,
148
+ ) -> None:
149
+ """
150
+ Adds an option to the list_options with a tickbox.
151
+ Creates a row stored within the option, this holds the function and
152
+ has a widget for the required input type. This data is read by
153
+ self.get_data()
154
+ The secondary row is added or removed when the tickbox is clicked.
155
+
156
+ Parameters
157
+ ----------
158
+ name : str
159
+ Display name of the function.
160
+ func : callable
161
+ Function to be passed to do_operations in worker.
162
+ input_type : type | list | tuple
163
+ The required input type:
164
+ bool - Makes checkbox
165
+ str, int, float, Decimal - Makes a line edit with numeric validation
166
+ None - No input box made
167
+ or a list/tuple of the options - Makes a dropbox with options
168
+
169
+ """
170
+ row = optionToggleRowItem(name, None, bool) # Option with tick box
171
+ self.list_options.addItem(row)
172
+ self.list_options.setItemWidget(row, row.row_widget)
173
+
174
+ # create item to add to active box. This data is fetched by self.get_data()
175
+ row.operation_row = rowItem(name, func, input_type, default=default)
176
+ row.operation_row.derivative_axis = derivative_axis
177
+ row.input.stateChanged.connect(lambda state:
178
+ self.add_or_remove_operation(state, row.operation_row)
179
+ )
180
+
181
+
182
+ def add_or_remove_operation(self, add : int, item : "rowItem") -> None:
183
+ """
184
+ Based on add value, adds or removes the item from the active operations
185
+ and clears previous input on removal.
186
+
187
+ Actually sets hidden or visible based to look as if removed.
188
+
189
+ Parameters
190
+ ----------
191
+ add : int
192
+ Whether to add or remove the box from the action operations
193
+ For some ungodly reason, QTickBox.stateChanged emits 0 or 2 instead
194
+ of true or false. But if 0 -> False; if 2 -> True, so its fine.
195
+ item : rowItem
196
+ Which item is being worked on.
197
+
198
+ """
199
+ row = self.list_order.row(item)
200
+ # Add item is not already there
201
+ if row == -1:
202
+ self.list_order.addItem(item)
203
+ self.list_order.setItemWidget(item, item.row_widget)
204
+
205
+ # Mimic complete removal (without garbage collector doing dumb things)
206
+ if add:
207
+ item.setHidden(False)
208
+ else: # Remove previous input
209
+ item.setHidden(True)
210
+ item.reset()
211
+
212
+
213
+ def add_all_options(self) -> None:
214
+ """
215
+ Fetches data from dict, self.operation_options, defined in children
216
+ classes.
217
+ Then adds these to available options.
218
+
219
+ """
220
+ for spec in operation_specs_for(self.operation_kind):
221
+ self.add_option(
222
+ spec.name,
223
+ spec.func,
224
+ cast(OperationInputType, spec.input_type),
225
+ spec.default,
226
+ spec.derivative_axis,
227
+ )
228
+ self.list_options.adjustSize()
229
+
230
+
231
+ @QtCore.pyqtSlot()
232
+ def hide_all(self) -> None:
233
+ for i in range(self.list_options.count()):
234
+ item = cast(optionToggleRowItem, self.list_options.item(i))
235
+ item.input.setChecked(False)
236
+
237
+
238
+ def get_data(self) -> list[OperationCall]:
239
+ """
240
+ Returns the function of the items in the active operation listWidget
241
+ (self.list_order) from top to bottom. Also adds the user input to the
242
+ functions ready for processing by qplot.tools.worker.loader.do_operations.
243
+
244
+ Returns
245
+ -------
246
+ operations : list[callable]
247
+ List of functions to be performed on the data during refresh.
248
+
249
+ """
250
+ operations: list[OperationCall] = []
251
+ for i in range(self.list_order.count()):
252
+ item = cast(rowItem, self.list_order.item(i))
253
+ if item.isHidden():
254
+ continue
255
+
256
+ input_widget = item.input
257
+ if (
258
+ isinstance(input_widget, qtw.QLineEdit)
259
+ and input_widget.text()
260
+ and not input_widget.hasAcceptableInput()
261
+ ):
262
+ raise OperationValidationError(
263
+ f'{item.label}: enter a valid value.'
264
+ )
265
+
266
+ try:
267
+ output = item.output()
268
+ except (TypeError, ValueError, OverflowError) as error:
269
+ raise OperationValidationError(
270
+ f'{item.label}: enter a valid value.'
271
+ ) from error
272
+
273
+ if output == "": # Data not entered
274
+ if hasattr(input_widget, "placeholderText"):
275
+ output = cast(Any, input_widget).placeholderText()
276
+ if output == "": # still blank
277
+ raise OperationValidationError(
278
+ f'{item.label}: a value is required.'
279
+ )
280
+
281
+ if isinstance(item.input_type, type):
282
+ try:
283
+ output = item.input_type(output)
284
+ except (TypeError, ValueError, OverflowError) as error:
285
+ raise OperationValidationError(
286
+ f'{item.label}: enter a valid value.'
287
+ ) from error
288
+ else:
289
+ raise OperationValidationError(
290
+ f'{item.label}: a value is required.'
291
+ )
292
+
293
+ if output is None: # No input requried
294
+ func = item.func
295
+ else: # Add input
296
+ # Some weird internal python stuff causes issues with lambda in loops
297
+ func = func_with_input(item.func, output)
298
+
299
+ operations.append(OperationCall(
300
+ item.label,
301
+ func,
302
+ getattr(item, "derivative_axis", None),
303
+ cooperative=True,
304
+ ))
305
+ return operations
306
+
307
+
308
+ def func_with_input(func: OperationFunc, value: object) -> OperationFunc:
309
+ return lambda data, cancelled_callback=None: func(
310
+ value,
311
+ data,
312
+ cancelled_callback=cancelled_callback,
313
+ )
314
+
315
+
316
+ class draggableListWidget(qtw.QListWidget):
317
+ """
318
+ QListWidgets have a know issue, when dragging the last time in the list
319
+ below itself, the item contents gets deleted. This class impliments a work
320
+ around to prevent that bug.
321
+ """
322
+ def dragMoveEvent(self, event: QtGui.QDragMoveEvent | None) -> None:
323
+ if event is None:
324
+ return
325
+
326
+ target = self.row(self.itemAt(cast(Any, event).pos()))
327
+ current = self.currentRow()
328
+ # Block drop below itself when it's the last item
329
+ if target == current + 1 or (current == self.count() - 1 and target == -1):
330
+ event.ignore()
331
+ else:
332
+ super().dragMoveEvent(event)
333
+
334
+ @overload
335
+ def addItem(self, item: qtw.QListWidgetItem | None) -> None: ...
336
+
337
+ @overload
338
+ def addItem(self, item: str | None) -> None: ...
339
+
340
+ def addItem(self, item: qtw.QListWidgetItem | str | None) -> None:
341
+ super().addItem(item)
342
+ if isinstance(item, qtw.QListWidgetItem):
343
+ item.setToolTip(self.toolTip())
344
+
345
+ class rowItem(qtw.QListWidgetItem):
346
+ """
347
+ A QListWidgetItem which can have different input boxes based on input_type.
348
+
349
+ Has 5 key attributes
350
+ label : str - Display name of item
351
+ func : callable - copy of function to be used in do_operations
352
+ input : QWidget - The input widget
353
+ reset : callable - resets input to default
354
+ output : callabel - returns current value of input
355
+ """
356
+
357
+ def __init__(
358
+ self,
359
+ label: str,
360
+ func: OperationFunc | None,
361
+ input_type: OperationInputType,
362
+ default: object = "",
363
+ ):
364
+ super().__init__()
365
+
366
+ self.func: OperationFunc
367
+ self.derivative_axis: str | None = None
368
+ if callable(func):
369
+ self.func = func
370
+ elif func is not None:
371
+ raise AssertionError("Func is not callable")
372
+
373
+ self._label = qtw.QLabel(label)
374
+ self.input_type = input_type
375
+ self.input: qtw.QCheckBox | qtw.QLineEdit | expandingComboBox | None
376
+ self.reset: Callable[[], None]
377
+ self.output: Callable[[], object]
378
+
379
+ self.row_widget = qtw.QWidget()
380
+ row_layout = qtw.QHBoxLayout()
381
+ self.row_widget.setLayout(row_layout)
382
+
383
+ row_layout.addWidget(self._label)
384
+ row_layout.setContentsMargins(5, 5, 5, 5)
385
+
386
+ if input_type is bool: # on/off tickbox
387
+ checkbox = qtw.QCheckBox()
388
+ self.input = checkbox
389
+ checkbox.setToolTip(f"Enable or disable {label}")
390
+ self.reset = lambda: checkbox.setChecked(False)
391
+ self.output = lambda: bool(checkbox.isChecked())
392
+
393
+ elif input_type in [int, float, Decimal, str]: # Textbox input
394
+ line_edit = qtw.QLineEdit()
395
+ self.input = line_edit
396
+ self.reset = lambda: line_edit.setText("")
397
+ scalar_type = cast(type, input_type)
398
+ self.output = lambda: (scalar_type(line_edit.text())
399
+ if line_edit.text() else "")
400
+ # Restrict user input to reduce errors
401
+ if input_type is int:
402
+ line_edit.setValidator(QtGui.QIntValidator())
403
+ elif input_type in (float, Decimal):
404
+ validator = QtGui.QDoubleValidator()
405
+ validator.setNotation(QtGui.QDoubleValidator.Notation.ScientificNotation)
406
+ validator.setLocale(QtCore.QLocale("C")) # Avoids locale issues like commas
407
+ line_edit.setValidator(validator)
408
+
409
+ elif input_type is None: # No input needed
410
+ self.input = None
411
+ self.reset = lambda: None
412
+ self.output = lambda: None
413
+
414
+ elif isinstance(input_type, (list, tuple)): # Select from options
415
+ combo_box = expandingComboBox()
416
+ self.input = combo_box
417
+ combo_box.addItems(list(input_type))
418
+ self.reset = lambda: combo_box.setCurrentIndex(-1)
419
+ self.output = lambda: combo_box.currentText()
420
+
421
+ else:
422
+ raise TypeError(
423
+ f"Invalid input type: {input_type}, must be int, float, Decimal"
424
+ ", str, bool, None, or an array of values.")
425
+
426
+ row_layout.addStretch() # push to edges
427
+ if self.input is not None:
428
+ row_layout.addWidget(self.input)
429
+ if isinstance(self.input, qtw.QCheckBox):
430
+ self.input.setChecked(True if default == True else False)
431
+ else:
432
+ self.input.setPlaceholderText(str(default))
433
+
434
+ # pyqt defaults height of widget to 0, what?
435
+ self.setSizeHint(self.row_widget.sizeHint())
436
+
437
+
438
+ @property
439
+ def label(self) -> str:
440
+ return self._label.text()
441
+
442
+
443
+
444
+ class optionToggleRowItem(rowItem):
445
+ operation_row: rowItem
446
+ input: qtw.QCheckBox
447
+
448
+
449
+ class operations_options_common(operations_options_base):
450
+ operation_kind: OperationKind
451
+
452
+
453
+ class operations_options_1d(operations_options_common):
454
+ operation_kind = "plot1d"
455
+
456
+ class operations_options_2d(operations_options_common):
457
+ operation_kind = "plot2d"
458
+
459
+ class operations_options_sweep(operations_options_common):
460
+ operation_kind = "sweeper"