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.
- qplot/__init__.py +46 -0
- qplot/__main__.py +232 -0
- qplot/_auxiliary_paths.py +63 -0
- qplot/_metadata.py +12 -0
- qplot/_repair.py +14 -0
- qplot/_shutdown_supervisor.py +4049 -0
- qplot/_version.py +12 -0
- qplot/_windows_shutdown_job.py +1166 -0
- qplot/configuration/__init__.py +3 -0
- qplot/configuration/config.py +573 -0
- qplot/configuration/config_schema.json +319 -0
- qplot/configuration/scripts.py +255 -0
- qplot/configuration/themes/__init__.py +13 -0
- qplot/configuration/themes/_base.py +579 -0
- qplot/configuration/themes/blank.py +17 -0
- qplot/configuration/themes/dark.py +54 -0
- qplot/configuration/themes/light.py +54 -0
- qplot/datahandling/LoadFromDB.py +224 -0
- qplot/datahandling/__init__.py +47 -0
- qplot/datahandling/_trusted_live_helper.py +812 -0
- qplot/datahandling/_trusted_live_protocol.py +1745 -0
- qplot/datahandling/database.py +2330 -0
- qplot/datahandling/dimensions.py +43 -0
- qplot/datahandling/file_identity.py +461 -0
- qplot/datahandling/parameter_data.py +149 -0
- qplot/datahandling/plot_progress.py +17 -0
- qplot/datahandling/qcodes_cache.py +277 -0
- qplot/datahandling/qcodes_compat.py +14 -0
- qplot/datahandling/readSQL.py +2442 -0
- qplot/datahandling/readonly.py +2561 -0
- qplot/datahandling/source_activity.py +21 -0
- qplot/datahandling/trusted_array_preview.py +127 -0
- qplot/datahandling/trusted_derived_cache.py +1605 -0
- qplot/datahandling/trusted_derived_rendering.py +868 -0
- qplot/datahandling/trusted_heatmap.py +241 -0
- qplot/datahandling/trusted_live.py +2808 -0
- qplot/datahandling/trusted_live_queries.py +5907 -0
- qplot/datahandling/trusted_live_service.py +1894 -0
- qplot/datahandling/trusted_live_supervisor.py +2161 -0
- qplot/datahandling/trusted_plot.py +283 -0
- qplot/datahandling/trusted_presentation.py +1065 -0
- qplot/datahandling/trusted_snapshot.py +1132 -0
- qplot/datahandling/trusted_work_coordinator.py +2157 -0
- qplot/datahandling/trusted_work_scheduler.py +1204 -0
- qplot/diagnostics.py +241 -0
- qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
- qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
- qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
- qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
- qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
- qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
- qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
- qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
- qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
- qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
- qplot/testdata.py +1733 -0
- qplot/tools/__init__.py +13 -0
- qplot/tools/finite_means.py +114 -0
- qplot/tools/general.py +134 -0
- qplot/tools/heatmap_geometry.py +381 -0
- qplot/tools/operation_registry.py +179 -0
- qplot/tools/plot_tools.py +625 -0
- qplot/tools/sample_statistics.py +56 -0
- qplot/tools/worker.py +2787 -0
- qplot/windows/__init__.py +9 -0
- qplot/windows/_colorbar.py +422 -0
- qplot/windows/_commands.py +523 -0
- qplot/windows/_config_persistence.py +123 -0
- qplot/windows/_database_actions.py +4904 -0
- qplot/windows/_dataset_handle.py +221 -0
- qplot/windows/_dragdrop.py +71 -0
- qplot/windows/_export_paths.py +1167 -0
- qplot/windows/_help.py +169 -0
- qplot/windows/_native_averaging.py +173 -0
- qplot/windows/_native_transforms.py +389 -0
- qplot/windows/_plot1d_snap.py +608 -0
- qplot/windows/_plot1d_traces.py +2183 -0
- qplot/windows/_plot2d_colorbar.py +1126 -0
- qplot/windows/_plot2d_colorbar_dialog.py +786 -0
- qplot/windows/_plot2d_layers.py +2197 -0
- qplot/windows/_plot2d_sweeps.py +712 -0
- qplot/windows/_plotWin.py +1993 -0
- qplot/windows/_plot_actions.py +2695 -0
- qplot/windows/_plot_appearance.py +276 -0
- qplot/windows/_plot_axis_scaling.py +1485 -0
- qplot/windows/_plot_export.py +1445 -0
- qplot/windows/_plot_feedback.py +110 -0
- qplot/windows/_plot_marquee.py +656 -0
- qplot/windows/_plot_refresh.py +942 -0
- qplot/windows/_plot_state.py +239 -0
- qplot/windows/_plot_transform_labels.py +60 -0
- qplot/windows/_preferences.py +635 -0
- qplot/windows/_refresh_interval.py +70 -0
- qplot/windows/_run_controls.py +828 -0
- qplot/windows/_shortcuts.py +47 -0
- qplot/windows/_subplots/__init__.py +8 -0
- qplot/windows/_subplots/subplot1d.py +531 -0
- qplot/windows/_subplots/subplot2d.py +918 -0
- qplot/windows/_svg_export.py +209 -0
- qplot/windows/_trusted_derived_qt.py +1759 -0
- qplot/windows/_widgets/__init__.py +23 -0
- qplot/windows/_widgets/_run_formatting.py +387 -0
- qplot/windows/_widgets/details_tables.py +1119 -0
- qplot/windows/_widgets/dropbox.py +290 -0
- qplot/windows/_widgets/operations.py +460 -0
- qplot/windows/_widgets/preview.py +2997 -0
- qplot/windows/_widgets/run_list_items.py +488 -0
- qplot/windows/_widgets/toolbar.py +111 -0
- qplot/windows/_widgets/treeWidgets.py +2572 -0
- qplot/windows/_window_controls.py +413 -0
- qplot/windows/main.py +1635 -0
- qplot/windows/plot1d.py +311 -0
- qplot/windows/plot2d.py +1901 -0
- qplotter-1.6.0b3.dist-info/METADATA +318 -0
- qplotter-1.6.0b3.dist-info/RECORD +119 -0
- qplotter-1.6.0b3.dist-info/WHEEL +5 -0
- qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
- qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
- 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 -> 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 -> Export Plot...</b> for images or data, or
|
|
28
|
+
<b>File -> Save Plot as PDF...</b> for a plot-sized PDF. Use
|
|
29
|
+
<b>File -> 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"])
|