Encoder-Werte oder Winkel, die an einem bestimmten numerischen Punkt umbrechen, entfalten
Das Arbeiten mit umbrochenen Größen (Winkel, Zähler, Modulo-Datenströme) ist in der Signalverarbeitung alltäglich. Dieser kurze Beitrag zeigt zwei einfache, in sich geschlossene Beispiele: die statische unwrap()-Funktion und den zustandsbehafteten OnlineUnwrapper (für Streaming/Online-Verwendung). Beide erwarten 1D-Eingaben und erhalten dieselbe Anzahl von Samples.
unwrap() verwenden — statische (Batch-)Entfaltung
Am besten, wenn dir die gesamte Reihe zur Verfügung steht. Tipp: Setze den Schwellwert, wenn deine Werte oder das Rauschen eine andere Umbruch-Erkennungsempfindlichkeit erfordern.
# Simple example (self-contained)
import numpy as np
from UliEngineering.SignalProcessing.WrappedValues import unwrap
# Winkel in Grad, umbrochen bei 360
wrapped = np.array([350, 355, 1, 3]) # Sprung 355 -> 1 (Umbruch)
un = unwrap(wrapped, wrap_value=360)
print(un) # -> [350. 355. 361. 363.] (kontinuierlich steigender Winkel)OnlineUnwrapper verwenden — Online/Streaming-Entfaltung
Verwende dies, wenn Daten ein Sample gleichzeitig oder in Chunks eintreffen. Verwaltet einen internen Zustand.
Hinweis: Beide Methoden erhalten die Sample-Anzahl und sind nur für 1D. Verwende unwrap() für Batch-Verarbeitung und OnlineUnwrapper für die Verarbeitung von Echtzeit-Streams oder gemischten Skalar-/Chunk-Eingaben.
# Online-Skalar- und Chunk-Verwendung
import numpy as np
from UliEngineering.SignalProcessing.WrappedValues import OnlineUnwrapper
u = OnlineUnwrapper(wrap_value=100) # Zähler, die bei 100 umbrechen
# Skalare eingeben
print(u(10)) # -> 10
print(u(95)) # -> 95
print(u(2)) # -> 102 (Vorwärts-Umbruch online behandelt)
# Oder einen Chunk eingeben (1D-Array)
chunk = np.array([98, 3, 5]) # setzt sich aus vorherigem Zustand fort
print(u(chunk)) # -> array([ 98., 103., 105.])Der Code, der für den obigen Plot verwendet wurde
#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
"""Beispiel: umbrochene vs. entfaltete Phasensignale plotten.
Lizenz: CC0 1.0 Universal — public domain dedication (siehe examples/LICENSE-CC0-1.0.txt)
Als Skript ausführen:
python examples/unwrap_plot.py # zeigt den Plot
python examples/unwrap_plot.py --save out.png # speichert in Datei
Dies erstellt ein synthetisches kontinuierliches Phasensignal, bricht es auf [0,2*pi) um,
und stellt es dann mit der Batch-`unwrap()`-Funktion und dem zustandsbehafteten
`OnlineUnwrapper` wieder her, um zu zeigen, dass sie übereinstimmen.
"""
import argparse
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
from UliEngineering.SignalProcessing.WrappedValues import unwrap, OnlineUnwrapper
def make_data(n=1000, wrap_value=2 * np.pi, seed=0):
"""Erstelle ein kontinuierliches Phasensignal, das *in der Mitte die Richtung umkehrt*.
Die erste Hälfte hat eine positive Winkelgeschwindigkeit und die zweite Hälfte eine
negative Winkelgeschwindigkeit, sodass sich die Phase umkehrt. Die zurückgegebene `wrapped`-
Reihe liegt in [0, wrap_value).
"""
np.random.seed(seed)
t = np.linspace(0.0, 10.0, n)
mid = n // 2
# Winkelgeschwindigkeiten (rad pro Zeiteinheit)
omega1 = 1.2 * 2 * np.pi
omega2 = -0.8 * 2 * np.pi
true_phase = np.empty(n, dtype=float)
# erste Hälfte: positive Steigung + kleine Oszillation
true_phase[:mid] = omega1 * t[:mid] + 0.8 * np.sin(2 * np.pi * 0.3 * t[:mid])
# zweite Hälfte: vom letzten Wert der ersten Hälfte starten, um es kontinuierlich zu machen
# und dann die negative Winkelgeschwindigkeit integrieren
start_phase = true_phase[mid - 1]
true_phase[mid:] = (
start_phase
+ omega2 * (t[mid:] - t[mid - 1])
+ 0.8 * np.sin(2 * np.pi * 0.3 * t[mid:])
)
# auf [0, wrap_value) umbrechen
wrapped = np.mod(true_phase, wrap_value)
return t, true_phase, wrapped
def plot_example(save_path=None):
wrap_value = 2 * np.pi
t, true_phase, wrapped = make_data(n=1200, wrap_value=wrap_value)
# Batch-Entfaltung
static_unwrapped = unwrap(wrapped, wrap_value=wrap_value)
# Online-Entfaltung (Streaming-Stil)
u = OnlineUnwrapper(wrap_value=wrap_value)
online_unwrapped = np.array([u(x) for x in wrapped])
# Schneller Plausibilitätscheck
maxdiff = np.max(np.abs(static_unwrapped - online_unwrapped))
print(f"Maximale Differenz zwischen unwrap() und OnlineUnwrapper: {maxdiff:.3e}")
fig, axs = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
# Richtungswechsel in den Plots markieren
mid_idx = len(t) // 2
axs[0].axvline(t[mid_idx], color="#444444", ls=":", lw=0.8)
axs[1].axvline(t[mid_idx], color="#444444", ls=":", lw=0.8, label="direction change")
axs[0].plot(t, wrapped, color="#1f77b4", lw=1)
axs[0].set_title("Umbrochenes Signal (0 .. 2π)")
axs[0].set_ylabel("Phase (rad)")
axs[0].grid(True)
axs[1].plot(t, static_unwrapped, color="#2ca02c", lw=1, label="unwrap()")
axs[1].plot(t, online_unwrapped, color="#ff7f0e", lw=1, ls="--", label="OnlineUnwrapper")
axs[1].plot(t, true_phase, color="#7f7f7f", lw=0.8, ls=":", label="true phase")
axs[1].set_title("Entfaltetes Signal (kontinuierliche Phase)")
axs[1].set_xlabel("Zeit")
axs[1].set_ylabel("Phase (rad)")
axs[1].legend()
axs[1].grid(True)
plt.tight_layout()
if save_path:
fig.savefig(save_path, dpi=150)
print(f"Abbildung gespeichert unter {save_path}")
else:
plt.show()
if __name__ == "__main__":
p = argparse.ArgumentParser(description="Umbrochene und entfaltete Phasensignale plotten.")
p.add_argument("--save", dest="save", help="Abbildung unter diesem Pfad speichern statt anzeigen")
args = p.parse_args()
plot_example(save_path=args.save)