qplotter 1.6.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 (130) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +67 -0
  4. qplot/_build_info.json +1 -0
  5. qplot/_metadata.py +75 -0
  6. qplot/_repair.py +14 -0
  7. qplot/_shutdown_supervisor.py +4049 -0
  8. qplot/_version.py +12 -0
  9. qplot/_windows_shutdown_job.py +1166 -0
  10. qplot/configuration/__init__.py +3 -0
  11. qplot/configuration/config.py +578 -0
  12. qplot/configuration/config_schema.json +357 -0
  13. qplot/configuration/fonts.py +3 -0
  14. qplot/configuration/scripts.py +255 -0
  15. qplot/configuration/themes/__init__.py +13 -0
  16. qplot/configuration/themes/_base.py +592 -0
  17. qplot/configuration/themes/blank.py +17 -0
  18. qplot/configuration/themes/dark.py +54 -0
  19. qplot/configuration/themes/light.py +54 -0
  20. qplot/datahandling/LoadFromDB.py +224 -0
  21. qplot/datahandling/__init__.py +47 -0
  22. qplot/datahandling/_trusted_live_helper.py +812 -0
  23. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  24. qplot/datahandling/database.py +2340 -0
  25. qplot/datahandling/dimensions.py +43 -0
  26. qplot/datahandling/file_identity.py +461 -0
  27. qplot/datahandling/parameter_data.py +149 -0
  28. qplot/datahandling/plot_progress.py +17 -0
  29. qplot/datahandling/qcodes_cache.py +277 -0
  30. qplot/datahandling/qcodes_compat.py +14 -0
  31. qplot/datahandling/readSQL.py +2442 -0
  32. qplot/datahandling/readonly.py +2566 -0
  33. qplot/datahandling/source_activity.py +21 -0
  34. qplot/datahandling/trusted_array_preview.py +148 -0
  35. qplot/datahandling/trusted_derived_cache.py +1605 -0
  36. qplot/datahandling/trusted_derived_rendering.py +876 -0
  37. qplot/datahandling/trusted_heatmap.py +246 -0
  38. qplot/datahandling/trusted_live.py +2808 -0
  39. qplot/datahandling/trusted_live_queries.py +5907 -0
  40. qplot/datahandling/trusted_live_service.py +1919 -0
  41. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  42. qplot/datahandling/trusted_plot.py +283 -0
  43. qplot/datahandling/trusted_presentation.py +1065 -0
  44. qplot/datahandling/trusted_snapshot.py +1132 -0
  45. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  46. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  47. qplot/diagnostics.py +241 -0
  48. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  49. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  50. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  51. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  52. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  53. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  54. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  55. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  56. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  57. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  58. qplot/testdata.py +1804 -0
  59. qplot/tools/__init__.py +13 -0
  60. qplot/tools/finite_means.py +123 -0
  61. qplot/tools/general.py +139 -0
  62. qplot/tools/heatmap_geometry.py +391 -0
  63. qplot/tools/operation_registry.py +179 -0
  64. qplot/tools/plot_tools.py +650 -0
  65. qplot/tools/sample_statistics.py +111 -0
  66. qplot/tools/worker.py +2838 -0
  67. qplot/windows/__init__.py +9 -0
  68. qplot/windows/_colorbar.py +422 -0
  69. qplot/windows/_commands.py +523 -0
  70. qplot/windows/_config_persistence.py +123 -0
  71. qplot/windows/_database_actions.py +4933 -0
  72. qplot/windows/_dataset_handle.py +221 -0
  73. qplot/windows/_display_precision.py +48 -0
  74. qplot/windows/_dragdrop.py +70 -0
  75. qplot/windows/_export_paths.py +1167 -0
  76. qplot/windows/_help.py +210 -0
  77. qplot/windows/_mixed_appearance.py +303 -0
  78. qplot/windows/_mixed_plots.py +461 -0
  79. qplot/windows/_native_averaging.py +323 -0
  80. qplot/windows/_native_transforms.py +551 -0
  81. qplot/windows/_plot1d_snap.py +612 -0
  82. qplot/windows/_plot1d_traces.py +2236 -0
  83. qplot/windows/_plot2d_colorbar.py +1127 -0
  84. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  85. qplot/windows/_plot2d_layers.py +2291 -0
  86. qplot/windows/_plot2d_sweeps.py +712 -0
  87. qplot/windows/_plotWin.py +2082 -0
  88. qplot/windows/_plot_actions.py +2703 -0
  89. qplot/windows/_plot_annotation.py +186 -0
  90. qplot/windows/_plot_appearance.py +283 -0
  91. qplot/windows/_plot_axis_scaling.py +1521 -0
  92. qplot/windows/_plot_data_viewbox.py +94 -0
  93. qplot/windows/_plot_drop_axes.py +224 -0
  94. qplot/windows/_plot_export.py +1445 -0
  95. qplot/windows/_plot_feedback.py +112 -0
  96. qplot/windows/_plot_marquee.py +659 -0
  97. qplot/windows/_plot_refresh.py +950 -0
  98. qplot/windows/_plot_source_promotion.py +178 -0
  99. qplot/windows/_plot_state.py +237 -0
  100. qplot/windows/_plot_status.py +115 -0
  101. qplot/windows/_plot_tiling.py +35 -0
  102. qplot/windows/_plot_transform_labels.py +60 -0
  103. qplot/windows/_preferences.py +724 -0
  104. qplot/windows/_refresh_interval.py +172 -0
  105. qplot/windows/_run_controls.py +789 -0
  106. qplot/windows/_shortcuts.py +47 -0
  107. qplot/windows/_subplots/__init__.py +8 -0
  108. qplot/windows/_subplots/subplot1d.py +532 -0
  109. qplot/windows/_subplots/subplot2d.py +919 -0
  110. qplot/windows/_svg_export.py +209 -0
  111. qplot/windows/_trusted_derived_qt.py +1759 -0
  112. qplot/windows/_widgets/__init__.py +23 -0
  113. qplot/windows/_widgets/_run_formatting.py +387 -0
  114. qplot/windows/_widgets/details_tables.py +1119 -0
  115. qplot/windows/_widgets/dropbox.py +290 -0
  116. qplot/windows/_widgets/operations.py +460 -0
  117. qplot/windows/_widgets/preview.py +2997 -0
  118. qplot/windows/_widgets/run_list_items.py +488 -0
  119. qplot/windows/_widgets/toolbar.py +111 -0
  120. qplot/windows/_widgets/treeWidgets.py +2572 -0
  121. qplot/windows/_window_controls.py +414 -0
  122. qplot/windows/main.py +1632 -0
  123. qplot/windows/plot1d.py +313 -0
  124. qplot/windows/plot2d.py +1935 -0
  125. qplotter-1.6.0.dist-info/METADATA +322 -0
  126. qplotter-1.6.0.dist-info/RECORD +130 -0
  127. qplotter-1.6.0.dist-info/WHEEL +5 -0
  128. qplotter-1.6.0.dist-info/entry_points.txt +4 -0
  129. qplotter-1.6.0.dist-info/licenses/LICENSE +21 -0
  130. qplotter-1.6.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,323 @@
1
+ """Average displayed source curves without processing generated curves again."""
2
+
3
+ import math
4
+ from fractions import Fraction
5
+ from typing import Any
6
+
7
+ import numpy as np
8
+ import pyqtgraph as pg
9
+ from PyQt6 import QtCore
10
+ from pyqtgraph.graphicsItems.PlotDataItem import PlotDataset
11
+
12
+ from qplot.tools.sample_statistics import finite_mean, sample_fraction
13
+
14
+
15
+ def _integer_totals(total, values, weight=1):
16
+ """Accumulate in int64 when proven safe, otherwise use Python integers."""
17
+ lower = weight * int(np.min(values))
18
+ upper = weight * int(np.max(values))
19
+ if total is not None:
20
+ lower += int(np.min(total))
21
+ upper += int(np.max(total))
22
+ if np.iinfo(np.int64).min <= lower and upper <= np.iinfo(np.int64).max:
23
+ # A large unsigned input may wrap before cancellation restores an
24
+ # int64 total; only choose this path when each operand fits too.
25
+ if (int(np.min(values)) >= np.iinfo(np.int64).min
26
+ and int(np.max(values)) <= np.iinfo(np.int64).max
27
+ and (total is None or total.dtype.kind == "i")):
28
+ weighted = values.astype(np.int64) * weight
29
+ return weighted if total is None else total + weighted
30
+ weighted = values.astype(object) * weight
31
+ return weighted if total is None else total.astype(object) + weighted
32
+
33
+
34
+ def _integer_average(total, count):
35
+ # A representable integer numerator needs only the ordinary vectorized
36
+ # division. Otherwise convert each NumPy integer to Python before dividing
37
+ # so conversion cannot round the total prior to forming the mean.
38
+ exact_float_bound = 2 ** (np.finfo(float).nmant + 1)
39
+ if (count <= exact_float_bound and total.dtype.kind != "O"
40
+ and int(np.min(total)) >= -exact_float_bound
41
+ and int(np.max(total)) <= exact_float_bound):
42
+ return total / count
43
+ return np.fromiter((int(value) / count for value in total), dtype=float, count=len(total))
44
+
45
+
46
+ def _binary_integer_samples(values):
47
+ """Represent finite binary samples as integer numerators and one exponent."""
48
+ if values.dtype.kind in "iu":
49
+ return values, 0
50
+ if values.dtype.kind != "f" or not np.all(np.isfinite(values)):
51
+ return None
52
+ if np.finfo(values.dtype).nmant > np.finfo(float).nmant:
53
+ return None
54
+ if np.all(values == np.trunc(values)):
55
+ shift = 0
56
+ else:
57
+ nonzero = np.abs(values[values != 0])
58
+ shift = max(0, 53 - math.frexp(float(np.min(nonzero)))[1])
59
+ with np.errstate(over="ignore"):
60
+ scaled = np.ldexp(values, shift)
61
+ if not np.all(np.isfinite(scaled)):
62
+ return None
63
+ if np.min(scaled) >= -(2**63) and np.max(scaled) < 2**63:
64
+ return scaled.astype(np.int64), shift
65
+ return np.frompyfunc(int, 1, 1)(scaled), shift
66
+
67
+
68
+ def _add_binary_total(total, shift, values, weight=1, *, finite_cells=None):
69
+ if finite_cells is not None:
70
+ # Nonfinite cells keep the ordinary running result. Exclude only
71
+ # those positions from exact encoding, not the entire source trace.
72
+ values = np.where(finite_cells, values, 0)
73
+ encoded = _binary_integer_samples(values)
74
+ if encoded is None:
75
+ return None, 0
76
+ numerators, value_shift = encoded
77
+ common_shift = max(shift, value_shift)
78
+ if total is not None and shift != common_shift:
79
+ total = total.astype(object) * (1 << (common_shift - shift))
80
+ if value_shift != common_shift:
81
+ numerators = numerators.astype(object) * (1 << (common_shift - value_shift))
82
+ return _integer_totals(total, numerators, weight), common_shift
83
+
84
+
85
+ class _AveragePlotDataItem(pg.PlotDataItem):
86
+ """Render samples already mapped, clipped and downsampled by their sources."""
87
+
88
+ def _ignore_processing(self, *_args, **_kwargs):
89
+ pass
90
+
91
+ # PlotItem.addItem and its controls configure every PlotDataItem. These
92
+ # generated items contain display coordinates, so all processing is done.
93
+ setFftMode = _ignore_processing
94
+ setDerivativeMode = _ignore_processing
95
+ setPhasemapMode = _ignore_processing
96
+ setSubtractMeanMode = _ignore_processing
97
+ setLogMode = _ignore_processing
98
+ setDownsampling = _ignore_processing
99
+ setClipToView = _ignore_processing
100
+
101
+ def _getDisplayDataset(self) -> PlotDataset | None:
102
+ return self._dataset
103
+
104
+
105
+ def is_native_source_curve(item: object) -> bool:
106
+ """Generated averages are render results, never transform/validation inputs."""
107
+ return isinstance(item, pg.PlotDataItem) and not isinstance(item, _AveragePlotDataItem)
108
+
109
+
110
+ class NativePlotItem(pg.PlotItem):
111
+ """Keep PyQtGraph's native grouping and positional averaging semantics."""
112
+
113
+ _qplot_recomputing_averages = False
114
+ avgCurves: dict[tuple, list[Any]]
115
+ _qplot_average_display_sources: tuple[tuple[Any, Any, Any], ...]
116
+
117
+ def __init__(self, *args, **kwargs):
118
+ super().__init__(*args, **kwargs)
119
+ self._qplot_average_display_sources = ()
120
+ # An owned timer is cancelled with the plot's QObject, including a
121
+ # close before the queued display update reaches the event loop.
122
+ self._qplot_average_refresh_timer = QtCore.QTimer(self)
123
+ self._qplot_average_refresh_timer.setSingleShot(True)
124
+ self._qplot_average_refresh_timer.timeout.connect(self._refresh_view_averages)
125
+
126
+ def addItem(self, item, *args, **kwargs):
127
+ if is_native_source_curve(item) and item not in self.items:
128
+ item._qplot_skip_average = "skipAverage" in kwargs
129
+ super().addItem(item, *args, **kwargs)
130
+ item.sigPlotChanged.connect(self.recomputeAverages)
131
+ display_changed = getattr(item, "sigDisplayChanged", None)
132
+ if display_changed is not None:
133
+ display_changed.connect(self._queue_view_averages)
134
+ else:
135
+ super().addItem(item, *args, **kwargs)
136
+
137
+ def removeItem(self, item):
138
+ source = is_native_source_curve(item) and item in self.items
139
+ if source:
140
+ item.sigPlotChanged.disconnect(self.recomputeAverages)
141
+ display_changed = getattr(item, "sigDisplayChanged", None)
142
+ if display_changed is not None:
143
+ display_changed.disconnect(self._queue_view_averages)
144
+ super().removeItem(item)
145
+ if source:
146
+ self.recomputeAverages()
147
+
148
+ def clear(self):
149
+ # Removing every source is one action. Intermediate averages cannot
150
+ # be displayed and can otherwise stall clearing a large grouped plot.
151
+ recomputing = self._qplot_recomputing_averages
152
+ self._qplot_recomputing_averages = True
153
+ try:
154
+ super().clear()
155
+ finally:
156
+ self._qplot_recomputing_averages = recomputing
157
+ self._qplot_average_display_sources = ()
158
+ timer = getattr(self, "_qplot_average_refresh_timer", None)
159
+ if timer is not None:
160
+ timer.stop()
161
+
162
+ def recomputeAverages(self, *_args):
163
+ if not self.ctrl.averageGroup.isChecked():
164
+ self._qplot_average_display_sources = ()
165
+ return
166
+ if self._qplot_recomputing_averages:
167
+ return
168
+ self._qplot_recomputing_averages = True
169
+ try:
170
+ for _count, average in tuple(self.avgCurves.values()):
171
+ self.removeItem(average)
172
+ self.avgCurves = {}
173
+ # addAvgCurve adds rendered averages to PlotItem.curves. Snapshot
174
+ # only measured inputs so those additions cannot feed back in.
175
+ sources = tuple(curve for curve in self.curves if is_native_source_curve(curve))
176
+ for curve in sources:
177
+ self.addAvgCurve(curve)
178
+ self._qplot_average_display_sources = self._average_display_sources()
179
+ self.replot()
180
+ finally:
181
+ self._qplot_recomputing_averages = False
182
+
183
+ def _average_display_sources(self):
184
+ return tuple((curve, *curve.getData()) for curve in self.curves
185
+ if is_native_source_curve(curve)
186
+ and not getattr(curve, "_qplot_skip_average", False))
187
+
188
+ def _queue_view_averages(self, *_args):
189
+ if (self.ctrl.averageGroup.isChecked()
190
+ and not self._qplot_average_refresh_timer.isActive()):
191
+ self._qplot_average_refresh_timer.start(0)
192
+
193
+ def _refresh_view_averages(self):
194
+ if not self.ctrl.averageGroup.isChecked():
195
+ return
196
+ current = self._average_display_sources()
197
+ previous = self._qplot_average_display_sources
198
+ if (len(current) == len(previous)
199
+ and all(all(now is old for now, old in zip(current_row, previous_row, strict=True))
200
+ for current_row, previous_row in zip(current, previous, strict=True))):
201
+ return
202
+ self.recomputeAverages()
203
+
204
+ def addAvgCurve(self, curve):
205
+ if not is_native_source_curve(curve) or getattr(curve, "_qplot_skip_average", False):
206
+ return
207
+ x, y = curve.getData()
208
+ if x is None or y is None or len(y) == 0:
209
+ return
210
+ # The average is display data. Widen before arithmetic without
211
+ # modifying the measured arrays retained by the source curves.
212
+ if y.dtype.kind in "fc":
213
+ y = y.astype(np.result_type(y.dtype, np.float64), copy=False)
214
+ removed: list[str] = []
215
+ retained: list[str] = []
216
+ for index in range(self.ctrl.avgParamList.count()):
217
+ item = self.ctrl.avgParamList.item(index)
218
+ target = removed if item.checkState() == QtCore.Qt.CheckState.Checked else retained
219
+ target.append(str(item.text()))
220
+ if self.ctrl.avgParamList.count() and not removed:
221
+ return
222
+ params = {
223
+ ".".join(key) if isinstance(key, tuple) else key: value
224
+ for key, value in self.itemMeta.get(curve, {}).items()
225
+ }
226
+ for key in removed:
227
+ params.pop(key, None)
228
+ for key in retained:
229
+ params.setdefault(key, None)
230
+ group_key = tuple(params.items())
231
+ if group_key not in self.avgCurves:
232
+ average = _AveragePlotDataItem()
233
+ average.setPen(self.avgPen)
234
+ average.setShadowPen(self.avgShadowPen)
235
+ average.setAlpha(1., False)
236
+ average.setZValue(100)
237
+ self.addItem(average, skipAverage=True)
238
+ self.avgCurves[group_key] = [0, average]
239
+ self.avgCurves[group_key][0] += 1
240
+ count, average = self.avgCurves[group_key]
241
+ # Preserve native compatibility: combine equal-shaped Y by index and
242
+ # keep the first X; a changed Y shape replaces the displayed samples.
243
+ if average.yData is not None and y.shape == average.yData.shape:
244
+ average._qplot_mean_sources.append((1, y))
245
+ average._qplot_minimum = np.minimum(average._qplot_minimum, y)
246
+ average._qplot_maximum = np.maximum(average._qplot_maximum, y)
247
+ # A difference update retains small variations and constant
248
+ # values near float's upper bound. Opposite extremes can overflow
249
+ # that difference; bounded weights handle those cells and preserve
250
+ # the ordinary arithmetic behavior of NaN and infinite samples.
251
+ y = y.astype(np.result_type(y.dtype, np.float64), copy=False)
252
+ previous = average.yData.astype(
253
+ np.result_type(average.yData.dtype, np.float64), copy=False,
254
+ )
255
+ with np.errstate(over="ignore", invalid="ignore"):
256
+ difference = y - previous
257
+ updated = previous + difference / float(count)
258
+ fallback = ~np.isfinite(difference)
259
+ if np.any(fallback):
260
+ updated[fallback] = (
261
+ previous[fallback] * ((count - 1) / float(count))
262
+ + y[fallback] / float(count)
263
+ )
264
+ y = updated
265
+ x = average.xData
266
+ # A rounded running mean loses small measurements when later
267
+ # traces cancel a large offset. Revisit sensitive cells using
268
+ # references to display samples already owned by sources.
269
+ sources = average._qplot_mean_sources
270
+ exact_cells = any(values.dtype.kind in "iuO" for _weight, values in sources)
271
+ mixed_sign = (average._qplot_minimum < 0) & (average._qplot_maximum > 0)
272
+ # Integer and cancelling binary means can round at intermediate
273
+ # updates. Retain integer totals in common binary units, then
274
+ # round their final ratio once. This also avoids per-cell replay
275
+ # across ordinary mixed-sign float traces on the GUI thread.
276
+ integer_total = average._qplot_integer_total
277
+ shift = average._qplot_total_shift
278
+ finite_cells = (np.isfinite(average._qplot_minimum)
279
+ & np.isfinite(average._qplot_maximum))
280
+ finite_mask = None if np.all(finite_cells) else finite_cells
281
+ if exact_cells or integer_total is not None or np.any(mixed_sign):
282
+ if not np.any(finite_cells):
283
+ integer_total, shift = None, 0
284
+ elif integer_total is None:
285
+ # A later integer or sign change can require exact totals.
286
+ # Encode retained samples once, then update per curve.
287
+ for weight, values in sources:
288
+ integer_total, shift = _add_binary_total(
289
+ integer_total, shift, values, weight, finite_cells=finite_mask,
290
+ )
291
+ if integer_total is None:
292
+ break
293
+ else:
294
+ integer_total, shift = _add_binary_total(
295
+ integer_total, shift, sources[-1][1], finite_cells=finite_mask,
296
+ )
297
+ average._qplot_integer_total = integer_total
298
+ average._qplot_total_shift = shift
299
+ if integer_total is not None:
300
+ exact_average = _integer_average(integer_total, count * (1 << shift))
301
+ y = exact_average if finite_mask is None else np.where(finite_cells, exact_average, y)
302
+ repair = (mixed_sign | exact_cells) & finite_cells
303
+ if integer_total is not None:
304
+ repair[:] = False
305
+ for cell_index in np.flatnonzero(repair):
306
+ cells = np.array([values[cell_index] for _weight, values in sources],
307
+ dtype=object if exact_cells else None)
308
+ if all(weight == 1 for weight, _values in sources):
309
+ y[cell_index] = finite_mean(cells)
310
+ else:
311
+ # Native shape replacement treats the replacement as
312
+ # the preceding count of curves, retaining that weight.
313
+ total = sum((weight * sample_fraction(value)
314
+ for (weight, _values), value in zip(sources, cells, strict=True)),
315
+ Fraction())
316
+ y[cell_index] = float(total / count)
317
+ else:
318
+ average._qplot_mean_sources = [(count, y)]
319
+ average._qplot_minimum = y.copy()
320
+ average._qplot_maximum = y.copy()
321
+ average._qplot_integer_total = _integer_totals(None, y, count) if y.dtype.kind in "iu" else None
322
+ average._qplot_total_shift = 0
323
+ average.setData(x, y, stepMode=curve.opts["stepMode"])