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.
- qplot/__init__.py +46 -0
- qplot/__main__.py +232 -0
- qplot/_auxiliary_paths.py +67 -0
- qplot/_build_info.json +1 -0
- qplot/_metadata.py +75 -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 +578 -0
- qplot/configuration/config_schema.json +357 -0
- qplot/configuration/fonts.py +3 -0
- qplot/configuration/scripts.py +255 -0
- qplot/configuration/themes/__init__.py +13 -0
- qplot/configuration/themes/_base.py +592 -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 +2340 -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 +2566 -0
- qplot/datahandling/source_activity.py +21 -0
- qplot/datahandling/trusted_array_preview.py +148 -0
- qplot/datahandling/trusted_derived_cache.py +1605 -0
- qplot/datahandling/trusted_derived_rendering.py +876 -0
- qplot/datahandling/trusted_heatmap.py +246 -0
- qplot/datahandling/trusted_live.py +2808 -0
- qplot/datahandling/trusted_live_queries.py +5907 -0
- qplot/datahandling/trusted_live_service.py +1919 -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 +1804 -0
- qplot/tools/__init__.py +13 -0
- qplot/tools/finite_means.py +123 -0
- qplot/tools/general.py +139 -0
- qplot/tools/heatmap_geometry.py +391 -0
- qplot/tools/operation_registry.py +179 -0
- qplot/tools/plot_tools.py +650 -0
- qplot/tools/sample_statistics.py +111 -0
- qplot/tools/worker.py +2838 -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 +4933 -0
- qplot/windows/_dataset_handle.py +221 -0
- qplot/windows/_display_precision.py +48 -0
- qplot/windows/_dragdrop.py +70 -0
- qplot/windows/_export_paths.py +1167 -0
- qplot/windows/_help.py +210 -0
- qplot/windows/_mixed_appearance.py +303 -0
- qplot/windows/_mixed_plots.py +461 -0
- qplot/windows/_native_averaging.py +323 -0
- qplot/windows/_native_transforms.py +551 -0
- qplot/windows/_plot1d_snap.py +612 -0
- qplot/windows/_plot1d_traces.py +2236 -0
- qplot/windows/_plot2d_colorbar.py +1127 -0
- qplot/windows/_plot2d_colorbar_dialog.py +786 -0
- qplot/windows/_plot2d_layers.py +2291 -0
- qplot/windows/_plot2d_sweeps.py +712 -0
- qplot/windows/_plotWin.py +2082 -0
- qplot/windows/_plot_actions.py +2703 -0
- qplot/windows/_plot_annotation.py +186 -0
- qplot/windows/_plot_appearance.py +283 -0
- qplot/windows/_plot_axis_scaling.py +1521 -0
- qplot/windows/_plot_data_viewbox.py +94 -0
- qplot/windows/_plot_drop_axes.py +224 -0
- qplot/windows/_plot_export.py +1445 -0
- qplot/windows/_plot_feedback.py +112 -0
- qplot/windows/_plot_marquee.py +659 -0
- qplot/windows/_plot_refresh.py +950 -0
- qplot/windows/_plot_source_promotion.py +178 -0
- qplot/windows/_plot_state.py +237 -0
- qplot/windows/_plot_status.py +115 -0
- qplot/windows/_plot_tiling.py +35 -0
- qplot/windows/_plot_transform_labels.py +60 -0
- qplot/windows/_preferences.py +724 -0
- qplot/windows/_refresh_interval.py +172 -0
- qplot/windows/_run_controls.py +789 -0
- qplot/windows/_shortcuts.py +47 -0
- qplot/windows/_subplots/__init__.py +8 -0
- qplot/windows/_subplots/subplot1d.py +532 -0
- qplot/windows/_subplots/subplot2d.py +919 -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 +414 -0
- qplot/windows/main.py +1632 -0
- qplot/windows/plot1d.py +313 -0
- qplot/windows/plot2d.py +1935 -0
- qplotter-1.6.0.dist-info/METADATA +322 -0
- qplotter-1.6.0.dist-info/RECORD +130 -0
- qplotter-1.6.0.dist-info/WHEEL +5 -0
- qplotter-1.6.0.dist-info/entry_points.txt +4 -0
- qplotter-1.6.0.dist-info/licenses/LICENSE +21 -0
- qplotter-1.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .details_tables import CopyableTableWidget
|
|
2
|
+
from .dropbox import (
|
|
3
|
+
expandingComboBox,
|
|
4
|
+
picker_1d,
|
|
5
|
+
)
|
|
6
|
+
from .operations import operations_widget
|
|
7
|
+
from .preview import PreviewTab
|
|
8
|
+
from .toolbar import QDock_context
|
|
9
|
+
from .treeWidgets import (
|
|
10
|
+
RunList,
|
|
11
|
+
moreInfo,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"expandingComboBox",
|
|
16
|
+
"CopyableTableWidget",
|
|
17
|
+
"moreInfo",
|
|
18
|
+
"picker_1d",
|
|
19
|
+
"PreviewTab",
|
|
20
|
+
"RunList",
|
|
21
|
+
"QDock_context",
|
|
22
|
+
"operations_widget",
|
|
23
|
+
]
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import html
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def run_tooltip_text(metadata):
|
|
6
|
+
"""
|
|
7
|
+
Builds the summary shown when hovering over a run table row.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
sweep = format_parameter_list_html(metadata.get("sweep_parameters"))
|
|
11
|
+
measure = format_parameter_list_html(metadata.get("measure_parameters"))
|
|
12
|
+
status = html.escape(format_run_status(metadata))
|
|
13
|
+
exception_row = ""
|
|
14
|
+
if run_failed(metadata):
|
|
15
|
+
summary = html.escape(measurement_exception_summary(metadata))
|
|
16
|
+
exception_row = (
|
|
17
|
+
"<tr>"
|
|
18
|
+
"<td style='padding:0 0.5em 0 0'>Exception</td>"
|
|
19
|
+
f"<td style='padding:0'>{summary}</td>"
|
|
20
|
+
"</tr>"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
"<table style='margin:0; border-spacing:0; border-collapse:collapse'>"
|
|
25
|
+
"<tr>"
|
|
26
|
+
"<td style='padding:0 0.5em 0 0'>Sweep</td>"
|
|
27
|
+
f"<td nowrap='nowrap' style='padding:0; white-space:nowrap'>({sweep})</td>"
|
|
28
|
+
"</tr>"
|
|
29
|
+
"<tr>"
|
|
30
|
+
"<td style='padding:0 0.5em 0 0'>Measure</td>"
|
|
31
|
+
f"<td nowrap='nowrap' style='padding:0; white-space:nowrap'>({measure})</td>"
|
|
32
|
+
"</tr>"
|
|
33
|
+
"<tr>"
|
|
34
|
+
"<td style='padding:0 0.5em 0 0'>Status</td>"
|
|
35
|
+
f"<td style='padding:0'>{status}</td>"
|
|
36
|
+
"</tr>"
|
|
37
|
+
f"{exception_row}"
|
|
38
|
+
"</table>"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run_tooltip_plain_text(metadata):
|
|
43
|
+
sweep = format_parameter_list(metadata.get("sweep_parameters"))
|
|
44
|
+
measure = format_parameter_list(metadata.get("measure_parameters"))
|
|
45
|
+
lines = [
|
|
46
|
+
f"{'Sweep':<7}({sweep})",
|
|
47
|
+
f"Measure ({measure})",
|
|
48
|
+
f"Status {format_run_status(metadata)}",
|
|
49
|
+
]
|
|
50
|
+
if run_failed(metadata):
|
|
51
|
+
lines.append(f"Exception {measurement_exception_summary(metadata)}")
|
|
52
|
+
|
|
53
|
+
return "\n".join(lines)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def format_parameter_list(parameters):
|
|
57
|
+
if not parameters:
|
|
58
|
+
return "unknown"
|
|
59
|
+
return ", ".join(str(parameter) for parameter in parameters)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def format_parameter_list_html(parameters):
|
|
63
|
+
if not parameters:
|
|
64
|
+
return "unknown"
|
|
65
|
+
return ", ".join(html.escape(str(parameter)) for parameter in parameters)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run_is_complete(metadata):
|
|
69
|
+
return bool(metadata.get("completed_timestamp") or metadata.get("is_completed"))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def run_was_interrupted(metadata):
|
|
73
|
+
exception = metadata.get("measurement_exception")
|
|
74
|
+
return exception is not None and "KeyboardInterrupt" in str(exception).strip()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def run_failed(metadata):
|
|
78
|
+
exception = metadata.get("measurement_exception")
|
|
79
|
+
return bool(
|
|
80
|
+
exception is not None
|
|
81
|
+
and str(exception).strip()
|
|
82
|
+
and not run_was_interrupted(metadata)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def measurement_exception_summary(metadata, maximum_length=200):
|
|
87
|
+
exception = str(metadata.get("measurement_exception") or "")
|
|
88
|
+
summary = next(
|
|
89
|
+
(line.strip() for line in reversed(exception.splitlines()) if line.strip()),
|
|
90
|
+
"",
|
|
91
|
+
)
|
|
92
|
+
if len(summary) > maximum_length:
|
|
93
|
+
return f"{summary[: maximum_length - 1]}…"
|
|
94
|
+
return summary
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def format_run_status(metadata):
|
|
98
|
+
"""Return the detailed status used in a run's tooltip."""
|
|
99
|
+
state = format_run_state(metadata)
|
|
100
|
+
if run_was_interrupted(metadata):
|
|
101
|
+
return f"Interrupted ({format_setpoint_progress_percent(metadata)})"
|
|
102
|
+
if state == "Failed":
|
|
103
|
+
return f"{state} ({format_interrupted_progress_percent(metadata)})"
|
|
104
|
+
return state
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def format_run_state(metadata):
|
|
108
|
+
"""Return the canonical, concise label for a run's current state."""
|
|
109
|
+
if run_was_interrupted(metadata):
|
|
110
|
+
return _format_state_with_setpoint_progress("Interrupted", metadata)
|
|
111
|
+
if run_failed(metadata):
|
|
112
|
+
return "Failed"
|
|
113
|
+
if run_is_complete(metadata):
|
|
114
|
+
return "Completed"
|
|
115
|
+
if metadata.get("is_completed") is not None and not bool(metadata["is_completed"]):
|
|
116
|
+
return _format_state_with_setpoint_progress("Running", metadata)
|
|
117
|
+
return "unknown"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _format_state_with_setpoint_progress(state, metadata):
|
|
121
|
+
progress = format_setpoint_progress_percent(metadata)
|
|
122
|
+
return f"{state} ({progress})"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def format_progress(metadata):
|
|
126
|
+
progress = format_progress_percent(metadata)
|
|
127
|
+
if progress == "unknown":
|
|
128
|
+
return "unknown% complete"
|
|
129
|
+
return f"{progress} complete"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def format_progress_percent(metadata):
|
|
133
|
+
if run_is_complete(metadata):
|
|
134
|
+
return "100%"
|
|
135
|
+
|
|
136
|
+
percent = progress_percent_value(metadata)
|
|
137
|
+
if percent is None:
|
|
138
|
+
return "unknown"
|
|
139
|
+
|
|
140
|
+
return f"{percent:.1f}%"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def progress_percent_value(metadata):
|
|
144
|
+
expected = metadata.get("expected_results")
|
|
145
|
+
count = metadata.get("result_count")
|
|
146
|
+
return _progress_percent_value(metadata, count, expected)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def setpoint_progress_percent_value(metadata):
|
|
150
|
+
"""Return the percentage of known setpoints that have been measured."""
|
|
151
|
+
percent = _progress_percent_value(
|
|
152
|
+
metadata,
|
|
153
|
+
metadata.get("read_setpoint_count"),
|
|
154
|
+
metadata.get("setpoint_count"),
|
|
155
|
+
maximum=100,
|
|
156
|
+
)
|
|
157
|
+
if percent is not None:
|
|
158
|
+
return percent
|
|
159
|
+
|
|
160
|
+
# The cheap detail pass exposes result-row progress before the distinct
|
|
161
|
+
# setpoint query completes. Both counts include the same measurement
|
|
162
|
+
# multiplicity, so their ratio is a valid early setpoint-progress value.
|
|
163
|
+
return _progress_percent_value(
|
|
164
|
+
metadata,
|
|
165
|
+
metadata.get("result_count"),
|
|
166
|
+
metadata.get("expected_results"),
|
|
167
|
+
maximum=100,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def format_setpoint_progress_percent(metadata):
|
|
172
|
+
percent = setpoint_progress_percent_value(metadata)
|
|
173
|
+
if percent is None:
|
|
174
|
+
return "unknown"
|
|
175
|
+
return f"{percent:.1f}%"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def interrupted_progress_percent_value(metadata):
|
|
179
|
+
expected = None
|
|
180
|
+
if metadata.get("setpoint_count_source") != "observed":
|
|
181
|
+
expected = metadata.get("setpoint_count")
|
|
182
|
+
if not expected and metadata.get("expected_results_source") != "observed":
|
|
183
|
+
expected = metadata.get("expected_results")
|
|
184
|
+
count = metadata.get("read_setpoint_count")
|
|
185
|
+
if count is None:
|
|
186
|
+
count = metadata.get("result_count")
|
|
187
|
+
return _progress_percent_value(metadata, count, expected, maximum=100)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _progress_percent_value(metadata, count, expected, maximum=None):
|
|
191
|
+
if not expected or count is None:
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
if maximum is None:
|
|
196
|
+
maximum = 100 if run_is_complete(metadata) else 99.9
|
|
197
|
+
return max(0, min(maximum, (float(count) / float(expected)) * 100))
|
|
198
|
+
except (TypeError, ValueError, ZeroDivisionError):
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def format_interrupted_progress_percent(metadata):
|
|
203
|
+
percent = interrupted_progress_percent_value(metadata)
|
|
204
|
+
if percent is None:
|
|
205
|
+
return "unknown"
|
|
206
|
+
return f"{percent:.1f}%"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def complete_cell_sort_value(metadata):
|
|
210
|
+
if run_was_interrupted(metadata):
|
|
211
|
+
return setpoint_progress_percent_value(metadata)
|
|
212
|
+
if run_failed(metadata):
|
|
213
|
+
return interrupted_progress_percent_value(metadata)
|
|
214
|
+
if run_is_complete(metadata):
|
|
215
|
+
return 100
|
|
216
|
+
return setpoint_progress_percent_value(metadata)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def format_complete_cell(metadata):
|
|
220
|
+
"""Return the run-table status label.
|
|
221
|
+
|
|
222
|
+
Kept under its historic name for callers, but deliberately shares the
|
|
223
|
+
Overview's canonical status wording.
|
|
224
|
+
"""
|
|
225
|
+
return format_run_state(metadata)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def format_timestamp(timestamp):
|
|
229
|
+
if not timestamp:
|
|
230
|
+
return "unknown"
|
|
231
|
+
try:
|
|
232
|
+
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
|
233
|
+
except (TypeError, ValueError, OSError, OverflowError):
|
|
234
|
+
return "unknown"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def time_taken_seconds(metadata):
|
|
238
|
+
started = metadata.get("run_timestamp")
|
|
239
|
+
if not started:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
completed = metadata.get("completed_timestamp")
|
|
243
|
+
if completed:
|
|
244
|
+
end = completed
|
|
245
|
+
elif run_is_complete(metadata):
|
|
246
|
+
return None
|
|
247
|
+
elif metadata.get("database_modified_timestamp"):
|
|
248
|
+
end = metadata.get("database_modified_timestamp")
|
|
249
|
+
else:
|
|
250
|
+
return None
|
|
251
|
+
try:
|
|
252
|
+
return max(0, float(end) - float(started))
|
|
253
|
+
except (TypeError, ValueError):
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def format_time_taken_seconds(metadata):
|
|
258
|
+
seconds = time_taken_seconds(metadata)
|
|
259
|
+
if seconds is None:
|
|
260
|
+
return "unknown"
|
|
261
|
+
return f"{seconds:,.1f} s"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def format_run_duration(metadata):
|
|
265
|
+
seconds = time_taken_seconds(metadata)
|
|
266
|
+
if seconds is None:
|
|
267
|
+
return "unknown"
|
|
268
|
+
|
|
269
|
+
if seconds < 10:
|
|
270
|
+
return f"{seconds:.2f} s"
|
|
271
|
+
if seconds < 100:
|
|
272
|
+
return f"{seconds:.1f} s"
|
|
273
|
+
return f"{seconds:.0f} s"
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def format_duration_dhms(seconds):
|
|
277
|
+
total_seconds = int(round(seconds))
|
|
278
|
+
days, remainder = divmod(total_seconds, 24 * 60 * 60)
|
|
279
|
+
hours, remainder = divmod(remainder, 60 * 60)
|
|
280
|
+
minutes, seconds = divmod(remainder, 60)
|
|
281
|
+
return f"{days}d {hours}h {minutes}m {seconds}s"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def format_storage_size(bytes_value):
|
|
285
|
+
if bytes_value is None:
|
|
286
|
+
return "unknown"
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
size = float(bytes_value)
|
|
290
|
+
except (TypeError, ValueError):
|
|
291
|
+
return "unknown"
|
|
292
|
+
|
|
293
|
+
if size < 0:
|
|
294
|
+
return "unknown"
|
|
295
|
+
|
|
296
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
297
|
+
unit_index = 0
|
|
298
|
+
while size >= 1024 and unit_index < len(units) - 1:
|
|
299
|
+
size /= 1024
|
|
300
|
+
unit_index += 1
|
|
301
|
+
|
|
302
|
+
if unit_index == 0:
|
|
303
|
+
return f"{int(size)} B"
|
|
304
|
+
if size < 10:
|
|
305
|
+
return f"{size:.1f} {units[unit_index]}"
|
|
306
|
+
return f"{size:.0f} {units[unit_index]}"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def format_point_count(metadata):
|
|
310
|
+
expected = metadata.get("setpoint_count", metadata.get("expected_results"))
|
|
311
|
+
acquired = metadata.get("read_setpoint_count")
|
|
312
|
+
shape = metadata.get("setpoint_shape") or metadata.get("point_shape")
|
|
313
|
+
if shape:
|
|
314
|
+
try:
|
|
315
|
+
shape_values = [int(size) for size in shape]
|
|
316
|
+
shape_parts = " × ".join(f"{size:,}" for size in shape_values)
|
|
317
|
+
except (TypeError, ValueError):
|
|
318
|
+
shape_values = []
|
|
319
|
+
shape_parts = ""
|
|
320
|
+
|
|
321
|
+
duplicate_count = one_dimensional_duplicate_point_count(metadata, shape_values)
|
|
322
|
+
if duplicate_count is not None:
|
|
323
|
+
return duplicate_count
|
|
324
|
+
|
|
325
|
+
if (
|
|
326
|
+
acquired is not None
|
|
327
|
+
and expected
|
|
328
|
+
and metadata.get("setpoint_shape_source") == "planned"
|
|
329
|
+
and acquired != expected
|
|
330
|
+
):
|
|
331
|
+
try:
|
|
332
|
+
return f"{int(acquired):,} / {int(expected):,} = {shape_parts}"
|
|
333
|
+
except (TypeError, ValueError):
|
|
334
|
+
pass
|
|
335
|
+
if expected:
|
|
336
|
+
return f"{int(expected):,} = {shape_parts}"
|
|
337
|
+
if shape_parts:
|
|
338
|
+
return shape_parts
|
|
339
|
+
|
|
340
|
+
if expected:
|
|
341
|
+
return f"{int(expected):,}"
|
|
342
|
+
|
|
343
|
+
# With no physical result rows, logical multiplicity is immaterial: the
|
|
344
|
+
# acquired logical count is exactly zero. Positive MAX(id) values remain
|
|
345
|
+
# internal until a structural layout proves their logical interpretation.
|
|
346
|
+
if type(metadata.get("result_count")) is int and metadata["result_count"] == 0:
|
|
347
|
+
return "0"
|
|
348
|
+
|
|
349
|
+
return "unknown"
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def point_count_sort_value(metadata):
|
|
353
|
+
for name in ("setpoint_count", "expected_results"):
|
|
354
|
+
value = metadata.get(name)
|
|
355
|
+
if type(value) is int and value >= 0:
|
|
356
|
+
return value
|
|
357
|
+
if type(metadata.get("result_count")) is int and metadata["result_count"] == 0:
|
|
358
|
+
return 0
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def one_dimensional_duplicate_point_count(metadata, shape_values=None):
|
|
363
|
+
expected = metadata.get("setpoint_count", metadata.get("expected_results"))
|
|
364
|
+
shape = metadata.get("setpoint_shape") or metadata.get("point_shape")
|
|
365
|
+
if shape_values is None:
|
|
366
|
+
if not shape:
|
|
367
|
+
return None
|
|
368
|
+
try:
|
|
369
|
+
shape_values = [int(size) for size in shape]
|
|
370
|
+
except (TypeError, ValueError):
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
if len(shape_values) != 1:
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
expected_count = int(expected)
|
|
378
|
+
except (TypeError, ValueError):
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
if expected_count != shape_values[0]:
|
|
382
|
+
return None
|
|
383
|
+
return f"{expected_count:,}"
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def measured_parameter_count(metadata):
|
|
387
|
+
return len(metadata.get("measure_parameters") or [])
|