Add native QCustomPlot renderer and shared plot adapters

This commit is contained in:
2026-10-07 11:33:14 +03:00
parent 90046de7fd
commit 79b0cde20e
23 changed files with 44815 additions and 35 deletions

View File

@@ -9,6 +9,7 @@ See c/set-protocol/include/set_spectrum.h for filtering and normalization semant
"""
from __future__ import annotations
import ctypes
import math
from dataclasses import dataclass
from enum import IntEnum
@@ -63,11 +64,40 @@ class NativeSpectrum:
self._peak.restype = ctypes.c_int
def analyze(self, times, values, *, max_size=4096, window=Window.HANN, filter=Filter.NONE,
low_hz=10.0, high_hz=100.0, remove_mean=True) -> Spectrum:
if type(max_size) is not int or not 16 <= max_size <= 16384 or max_size & (max_size - 1):
raise ValueError("FFT size must be a power of two in 16..16384")
low_hz=10.0, high_hz=100.0, remove_mean=True, full_record=False) -> Spectrum:
if type(max_size) is not int or not 16 <= max_size <= 2097152 or max_size & (max_size - 1):
raise ValueError("FFT size must be a power of two in 16..2097152")
if len(times) != len(values):
raise ValueError("Timestamp and value counts differ")
if full_record and len(times) >= 16:
source_count = len(times)
n = 1 << (min(source_count, max_size).bit_length() - 1)
dt = (times[-1] - times[0]) / (source_count - 1)
if not math.isfinite(dt) or dt <= 0:
raise ValueError("Invalid spectrum timestamps")
if any(not math.isfinite(value) for value in values) or any(
not math.isfinite(times[i]) or abs((times[i] - times[i - 1]) / dt - 1) > .05
for i in range(1, source_count)):
raise ValueError("Full-record FFT requires uniformly spaced finite samples")
# Integrate piecewise-constant samples over equal fractional bins.
# Unlike picking every kth sample, this averages before decimation.
width = source_count / n
averaged = []
index = 0
position = 0.
for output_index in range(n):
end = (output_index + 1) * width
total = 0.
while position < end and index < source_count:
stop = min(end, index + 1.)
total += values[index] * (stop - position)
position = stop
if position >= index + 1:
index += 1
averaged.append(total / width)
start = times[0]
times = [start + ((i + .5) * width - .5) * dt for i in range(n)]
values = averaged
count = min(len(times), max_size)
origin = times[len(times) - count] if count else 0.0
t = (ctypes.c_double * count)(*(value - origin for value in times[-count:])) if count else (ctypes.c_double * 0)()