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
qplot/windows/_help.py ADDED
@@ -0,0 +1,169 @@
1
+ from typing import Any, cast
2
+
3
+ from PyQt6 import QtCore, QtGui
4
+ from PyQt6 import QtWidgets as qtw
5
+
6
+ from qplot.diagnostics import default_log_file
7
+
8
+ from ._commands import create_action, shortcut_help_html
9
+
10
+ _OPEN_HELP_DIALOGS: list[qtw.QDialog] = []
11
+
12
+
13
+ QUICK_START_HTML = """
14
+ <h2>Quick Start</h2>
15
+ <ol>
16
+ <li><b>Load a database.</b> Drop a QCoDeS .db file onto the database field,
17
+ or use <b>File -&gt; Load Database...</b>.</li>
18
+ <li><b>Select a run.</b> Click a row in the run table to see details,
19
+ parameters, preview images, and metadata.</li>
20
+ <li><b>Open a plot.</b> Double-click a preview, right-click a run, or enter
21
+ a run ID and measurement number at the top of the main window.</li>
22
+ <li><b>Inspect the plot.</b> Use the mouse wheel to zoom, left-drag to pan,
23
+ and Shift-drag to constrain panning to one axis,
24
+ right-click for plot actions, and double-click axes for scale controls.</li>
25
+ <li><b>Export or print data and plots.</b> Use the CSV button or preview
26
+ context menu for measurement data. In plot windows, use
27
+ <b>File -&gt; Export Plot...</b> for images or data, or
28
+ <b>File -&gt; Save Plot as PDF...</b> for a plot-sized PDF. Use
29
+ <b>File -&gt; Print Plot...</b> for a printer or, when the system dialog
30
+ offers a concrete PDF destination, a page-formatted PDF. qPlot stages and
31
+ atomically publishes PDF file output.</li>
32
+ </ol>
33
+ <p>Plot windows may appear before their data has finished loading. Check the
34
+ status bar at the bottom of the window before assuming a load has failed.</p>
35
+ <p>Set the refresh interval to <b>0.0 s</b> for manual refresh only. Press
36
+ <b>R</b> to refresh the active window.</p>
37
+ """
38
+
39
+
40
+ KEYBOARD_SHORTCUTS_HTML = shortcut_help_html()
41
+
42
+
43
+ def add_help_menu(window: qtw.QMainWindow) -> qtw.QMenu:
44
+ """
45
+ Adds qPlot's shared Help menu to a main or plot window.
46
+
47
+ """
48
+ menu_bar = window.menuBar()
49
+ if menu_bar is None:
50
+ raise RuntimeError("Help menu requires a menu bar.")
51
+ help_menu = menu_bar.addMenu("&Help")
52
+ if help_menu is None:
53
+ raise RuntimeError("Help menu could not be created.")
54
+
55
+ quick_start_action = create_action("help.quick_start", window)
56
+ quick_start_action.triggered.connect(lambda: show_quick_start(window))
57
+ help_menu.addAction(quick_start_action)
58
+
59
+ shortcuts_action = QtGui.QAction("&Keyboard Shortcuts", window)
60
+ shortcuts_action.setObjectName("keyboardShortcutsHelpAction")
61
+ shortcuts_action.setStatusTip("Show qPlot keyboard shortcuts")
62
+ shortcuts_action.triggered.connect(lambda: show_keyboard_shortcuts(window))
63
+ help_menu.addAction(shortcuts_action)
64
+
65
+ help_menu.addSeparator()
66
+
67
+ copy_log_path_action = QtGui.QAction("Copy &Diagnostic Log Path", window)
68
+ copy_log_path_action.setObjectName("copyDiagnosticLogPathAction")
69
+ copy_log_path_action.setStatusTip("Copy the qPlot diagnostic log file path")
70
+ copy_log_path_action.triggered.connect(lambda: copy_diagnostic_log_path(window))
71
+ help_menu.addAction(copy_log_path_action)
72
+
73
+ return help_menu
74
+
75
+
76
+ def show_quick_start(parent: qtw.QWidget | None = None) -> qtw.QDialog:
77
+ """
78
+ Opens the quick-start help dialog.
79
+
80
+ """
81
+ return _show_help_dialog(
82
+ parent,
83
+ "qPlot Quick Start",
84
+ QUICK_START_HTML,
85
+ "qplotQuickStartDialog",
86
+ )
87
+
88
+
89
+ def show_keyboard_shortcuts(parent: qtw.QWidget | None = None) -> qtw.QDialog:
90
+ """
91
+ Opens the keyboard-shortcuts help dialog.
92
+
93
+ """
94
+ return _show_help_dialog(
95
+ parent,
96
+ "qPlot Keyboard Shortcuts",
97
+ KEYBOARD_SHORTCUTS_HTML,
98
+ "qplotKeyboardShortcutsDialog",
99
+ )
100
+
101
+
102
+ def copy_diagnostic_log_path(parent: Any | None = None) -> str:
103
+ """
104
+ Copies qPlot's diagnostic log path to the clipboard.
105
+
106
+ """
107
+ path = str(default_log_file())
108
+ clipboard = qtw.QApplication.clipboard()
109
+ if clipboard is not None:
110
+ clipboard.setText(path)
111
+ if parent is not None and hasattr(parent, "show_status"):
112
+ parent.show_status(f"Copied diagnostic log path: {path}", 5000)
113
+ return path
114
+
115
+
116
+ def _show_help_dialog(
117
+ parent: qtw.QWidget | None,
118
+ title: str,
119
+ html: str,
120
+ object_name: str,
121
+ ) -> qtw.QDialog:
122
+ dialog = qtw.QDialog(parent)
123
+ dialog.setObjectName(object_name)
124
+ dialog.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose, True)
125
+ dialog.setWindowTitle(title)
126
+ dialog.resize(640, 520)
127
+
128
+ layout = qtw.QVBoxLayout(dialog)
129
+ browser = qtw.QTextBrowser()
130
+ browser.setObjectName("qplotHelpBrowser")
131
+ browser.setOpenExternalLinks(True)
132
+ browser.setHtml(html)
133
+ browser.setMinimumSize(520, 360)
134
+ layout.addWidget(browser)
135
+
136
+ buttons = qtw.QDialogButtonBox(qtw.QDialogButtonBox.StandardButton.Close)
137
+ buttons.rejected.connect(dialog.close)
138
+ layout.addWidget(buttons)
139
+
140
+ _remember_help_dialog(parent, dialog)
141
+ dialog.show()
142
+ dialog.raise_()
143
+ dialog.activateWindow()
144
+ return dialog
145
+
146
+
147
+ def _remember_help_dialog(
148
+ parent: qtw.QWidget | None,
149
+ dialog: qtw.QDialog,
150
+ ) -> None:
151
+ dialogs: list[qtw.QDialog] | None
152
+ if parent is None:
153
+ dialogs = _OPEN_HELP_DIALOGS
154
+ else:
155
+ owner = cast(Any, parent)
156
+ dialogs = getattr(owner, "_help_dialogs", None)
157
+ if dialogs is None:
158
+ dialogs = []
159
+ owner._help_dialogs = dialogs
160
+
161
+ if dialogs is None:
162
+ return
163
+ dialogs.append(dialog)
164
+
165
+ def forget_dialog(*_args: object) -> None:
166
+ if dialog in dialogs:
167
+ dialogs.remove(dialog)
168
+
169
+ dialog.destroyed.connect(forget_dialog)
@@ -0,0 +1,173 @@
1
+ """Average displayed source curves without processing generated curves again."""
2
+
3
+ from fractions import Fraction
4
+ from typing import Any
5
+
6
+ import numpy as np
7
+ import pyqtgraph as pg
8
+ from PyQt6 import QtCore
9
+ from pyqtgraph.graphicsItems.PlotDataItem import PlotDataset
10
+
11
+ from qplot.tools.sample_statistics import finite_mean
12
+
13
+
14
+ class _AveragePlotDataItem(pg.PlotDataItem):
15
+ """Render samples already mapped, clipped and downsampled by their sources."""
16
+
17
+ def _ignore_processing(self, *_args, **_kwargs):
18
+ pass
19
+
20
+ # PlotItem.addItem and its controls configure every PlotDataItem. These
21
+ # generated items contain display coordinates, so all processing is done.
22
+ setFftMode = _ignore_processing
23
+ setDerivativeMode = _ignore_processing
24
+ setPhasemapMode = _ignore_processing
25
+ setSubtractMeanMode = _ignore_processing
26
+ setLogMode = _ignore_processing
27
+ setDownsampling = _ignore_processing
28
+ setClipToView = _ignore_processing
29
+
30
+ def _getDisplayDataset(self) -> PlotDataset | None:
31
+ return self._dataset
32
+
33
+
34
+ def is_native_source_curve(item: object) -> bool:
35
+ """Generated averages are render results, never transform/validation inputs."""
36
+ return isinstance(item, pg.PlotDataItem) and not isinstance(item, _AveragePlotDataItem)
37
+
38
+
39
+ class NativePlotItem(pg.PlotItem):
40
+ """Keep PyQtGraph's native grouping and positional averaging semantics."""
41
+
42
+ _qplot_recomputing_averages = False
43
+ avgCurves: dict[tuple, list[Any]]
44
+
45
+ def addItem(self, item, *args, **kwargs):
46
+ if is_native_source_curve(item) and item not in self.items:
47
+ item._qplot_skip_average = "skipAverage" in kwargs
48
+ super().addItem(item, *args, **kwargs)
49
+ item.sigPlotChanged.connect(self.recomputeAverages)
50
+ else:
51
+ super().addItem(item, *args, **kwargs)
52
+
53
+ def removeItem(self, item):
54
+ source = is_native_source_curve(item) and item in self.items
55
+ if source:
56
+ item.sigPlotChanged.disconnect(self.recomputeAverages)
57
+ super().removeItem(item)
58
+ if source:
59
+ self.recomputeAverages()
60
+
61
+ def recomputeAverages(self, *_args):
62
+ if not self.ctrl.averageGroup.isChecked() or self._qplot_recomputing_averages:
63
+ return
64
+ self._qplot_recomputing_averages = True
65
+ try:
66
+ for _count, average in tuple(self.avgCurves.values()):
67
+ self.removeItem(average)
68
+ self.avgCurves = {}
69
+ # addAvgCurve adds rendered averages to PlotItem.curves. Snapshot
70
+ # only measured inputs so those additions cannot feed back in.
71
+ sources = tuple(curve for curve in self.curves if is_native_source_curve(curve))
72
+ for curve in sources:
73
+ self.addAvgCurve(curve)
74
+ self.replot()
75
+ finally:
76
+ self._qplot_recomputing_averages = False
77
+
78
+ def addAvgCurve(self, curve):
79
+ if not is_native_source_curve(curve) or getattr(curve, "_qplot_skip_average", False):
80
+ return
81
+ x, y = curve.getData()
82
+ if x is None or y is None or len(y) == 0:
83
+ return
84
+ # The average is display data. Widen before arithmetic without
85
+ # modifying the measured arrays retained by the source curves.
86
+ if y.dtype.kind in "fc":
87
+ y = y.astype(np.result_type(y.dtype, np.float64), copy=False)
88
+ removed: list[str] = []
89
+ retained: list[str] = []
90
+ for index in range(self.ctrl.avgParamList.count()):
91
+ item = self.ctrl.avgParamList.item(index)
92
+ target = removed if item.checkState() == QtCore.Qt.CheckState.Checked else retained
93
+ target.append(str(item.text()))
94
+ if self.ctrl.avgParamList.count() and not removed:
95
+ return
96
+ params = {
97
+ ".".join(key) if isinstance(key, tuple) else key: value
98
+ for key, value in self.itemMeta.get(curve, {}).items()
99
+ }
100
+ for key in removed:
101
+ params.pop(key, None)
102
+ for key in retained:
103
+ params.setdefault(key, None)
104
+ group_key = tuple(params.items())
105
+ if group_key not in self.avgCurves:
106
+ average = _AveragePlotDataItem()
107
+ average.setPen(self.avgPen)
108
+ average.setShadowPen(self.avgShadowPen)
109
+ average.setAlpha(1., False)
110
+ average.setZValue(100)
111
+ self.addItem(average, skipAverage=True)
112
+ self.avgCurves[group_key] = [0, average]
113
+ self.avgCurves[group_key][0] += 1
114
+ count, average = self.avgCurves[group_key]
115
+ # Preserve native compatibility: combine equal-shaped Y by index and
116
+ # keep the first X; a changed Y shape replaces the displayed samples.
117
+ if average.yData is not None and y.shape == average.yData.shape:
118
+ average._qplot_mean_sources.append((1, y))
119
+ average._qplot_minimum = np.minimum(average._qplot_minimum, y)
120
+ average._qplot_maximum = np.maximum(average._qplot_maximum, y)
121
+ # A difference update retains small variations and constant
122
+ # values near float's upper bound. Opposite extremes can overflow
123
+ # that difference; bounded weights handle those cells and preserve
124
+ # the ordinary arithmetic behavior of NaN and infinite samples.
125
+ y = y.astype(np.result_type(y.dtype, np.float64), copy=False)
126
+ previous = average.yData.astype(
127
+ np.result_type(average.yData.dtype, np.float64), copy=False,
128
+ )
129
+ with np.errstate(over="ignore", invalid="ignore"):
130
+ difference = y - previous
131
+ updated = previous + difference / float(count)
132
+ fallback = ~np.isfinite(difference)
133
+ if np.any(fallback):
134
+ updated[fallback] = (
135
+ previous[fallback] * ((count - 1) / float(count))
136
+ + y[fallback] / float(count)
137
+ )
138
+ y = updated
139
+ x = average.xData
140
+ # A rounded running mean loses small measurements when later
141
+ # traces cancel a large offset. Revisit sensitive cells using
142
+ # references to display samples already owned by sources.
143
+ sources = average._qplot_mean_sources
144
+ exact_cells = any(values.dtype.kind in "iuO" for _weight, values in sources)
145
+ mixed_sign = (average._qplot_minimum < 0) & (average._qplot_maximum > 0)
146
+ # Large same-sign integers can round before their mean is formed,
147
+ # even when its correctly rounded float result is distinct. Limit
148
+ # exact replay to those cells and the cancellation cases above.
149
+ large_integers = exact_cells & (
150
+ (average._qplot_minimum <= -(2**53))
151
+ | (average._qplot_maximum >= 2**53)
152
+ )
153
+ repair = ((mixed_sign | large_integers)
154
+ & np.isfinite(average._qplot_minimum)
155
+ & np.isfinite(average._qplot_maximum))
156
+ for cell_index in np.flatnonzero(repair):
157
+ cells = np.array([values[cell_index] for _weight, values in sources],
158
+ dtype=object if exact_cells else None)
159
+ if all(weight == 1 for weight, _values in sources):
160
+ y[cell_index] = finite_mean(cells)
161
+ else:
162
+ # Native shape replacement treats the replacement as
163
+ # the preceding count of curves, retaining that weight.
164
+ total = sum((weight * Fraction(value.item() if isinstance(value, np.generic)
165
+ else value)
166
+ for (weight, _values), value in zip(sources, cells, strict=True)),
167
+ Fraction())
168
+ y[cell_index] = float(total / count)
169
+ else:
170
+ average._qplot_mean_sources = [(count, y)]
171
+ average._qplot_minimum = y.copy()
172
+ average._qplot_maximum = y.copy()
173
+ average.setData(x, y, stepMode=curve.opts["stepMode"])