SignalHub

SignalHub ist ein modulares Pipeline-System.

  • Daten werden in Form von Signalen zwischen Modulen weitergegeben

  • Jedes Modul verarbeitet eingehende Daten und erzeugt neue Signale

  • Die Pipeline wird über die config.yaml definiert

Typische Signale für dieses Projekt sind z. B.:

  • webcam → Rohbild der Kamera

  • detector → erkannte Hand + Landmarken

  • preprocessor → normalisierte Trajektorie

Ziel ist es, Module zu implementieren, die sauber miteinander über diese Signale interagieren.

Signalbasierte Kommunikation

Module kommunizieren ausschließlich über sogenannte Signale.

Dieses Konzept ist zentral für das gesamte Framework und muss unbedingt verstanden werden.

Grundprinzip

Ein Modul definiert explizit:

  • welche Signale es empfängt (inputSignals)

  • welche Signale es erzeugt (outputSchema)

Beispiel:

def step(self, data):
    detector_data = data["detector"]
    return {"preprocessor": result}

Wichtige Regeln

1. Sie erhalten nur die Signale, die Sie abonnieren

  • Das data-Dictionary enthält ausschließlich die Signale, die in inputSignals definiert wurden

  • Ein Zugriff auf nicht abonnierte Signale ist nicht möglich

Warnung

Wenn Sie ein Signal nicht in inputSignals angeben, existiert es für Ihr Modul nicht.

2. Sie dürfen nur definierte Outputs zurückgeben

  • Die Rückgabe Ihres Moduls muss zum outputSchema passen

  • Nicht definierte Outputs werden ignoriert oder führen zu Fehlern

Warnung

Sie können keine beliebigen Keys zurückgeben. Jeder Output muss vorher im Schema definiert sein.

3. Datenfluss ist strikt gerichtet

  • Module arbeiten nicht global

  • Es gibt keinen direkten Zugriff auf andere Module

  • Kommunikation erfolgt ausschließlich über Signale

Warum ist das wichtig?

Dieses Konzept erzwingt eine klare Struktur:

  • Module sind voneinander entkoppelt

  • Datenflüsse sind explizit sichtbar

  • Fehler sind leichter nachvollziehbar

Gleichzeitig bedeutet das:

  • falsche Signalnamen → kein Zugriff

  • fehlende Subscriptions → leere Daten

  • falsche Outputs → Pipeline bricht

Replay-Modus

Neben Live-Daten können auch aufgezeichnete Daten verwendet werden.

Dafür existiert ein sogenannter Replay-Modus:

  • Anstelle der Webcam werden gespeicherte Aufnahmen abgespielt

  • Dies ermöglicht reproduzierbares Testen und Debugging

Der Replay-Modus kann über die Kommandozeile gestartet werden:

python main.py --mode replay --recorder <path_to_recording>

Dies ist besonders wichtig, um:

  • den Preprocessor unabhängig zu entwickeln

  • Visualisierungen zu testen

  • den Classifier ohne Live-Input zu debuggen

Steuerung über die Konfiguration

Welche Daten tatsächlich replayed werden, wird über die config.yaml gesteuert.

Beispiel:

recorder:
  replay:
    - trailmarker
    - preprocessor
    - hiddenmarkov
    - detector

Nur die hier aufgeführten Module bzw. Signale werden aus den aufgezeichneten Daten gespeist.

Alle anderen Module laufen weiterhin im Live-Modus.

Live- vs. Replay-Daten

Das bedeutet konkret:

  • Module im replay-Block erhalten ihre Daten aus der Aufnahme

  • Alle anderen Module erzeugen ihre Daten wie gewohnt live

Dies erlaubt es, gezielt einzelne Teile der Pipeline zu testen.

Beispiel:

  • Sie verwenden aufgezeichnete detector-Daten

  • und entwickeln darauf Ihren preprocessor unabhängig weiter

Datenfluss im Replay-Modus

Die aufgezeichneten Daten werden schrittweise (Frame für Frame) in die Pipeline eingespeist.

Das bedeutet:

  • Ihr Modul erhält pro step genau die Daten, die zum jeweiligen Zeitpunkt aufgezeichnet wurden

  • Der zeitliche Verlauf der Sequenz bleibt erhalten

Bemerkung

Der Replay-Modus ersetzt nicht die gesamte Pipeline, sondern nur die explizit konfigurierten Teile. Dies ermöglicht eine sehr flexible Kombination aus Live- und aufgezeichneten Daten.

Recordings (bereitgestellter Datensatz)

Die bereitgestellten Recordings stammen aus SignalHub.

  • Sie enthalten Rohdaten aus der Pipeline (z. B. Detector-Ausgaben)

  • Sie bilden die Grundlage für Ihre Entwicklung

Warnung

Die bereitgestellten Daten dienen lediglich als Grundlage, damit Sie parallel an unterschiedlichen Schritten der Pipeline arbeiten können.

Ziel des Projekts ist es ausdrücklich, eigene Daten aufzuzeichnen, auf deren Basis Modelle zu trainieren und diese anschließend auch zu evaluieren.

GALY (Graphical Abstraction Layer)

GALY steht für Graphical Abstraction Layer.

Im Kern ist GALY ein Wrapper um die wichtigsten Zeichenfunktionen aus cv2 (OpenCV). Der entscheidende Unterschied ist jedoch:

Zeichenoperationen werden nicht direkt ausgeführt, sondern als Befehle gespeichert.

Diese Befehle können:

  • serialisiert (gespeichert)

  • deserialisiert (wieder abgespielt)

werden.

Dadurch ist es möglich, Visualisierungen zusammen mit den Daten aufzuzeichnen und später im Replay-Modus exakt wiederzugeben.

Grundprinzip

Anstatt direkt auf ein Bild zu zeichnen, werden Zeichenbefehle gesammelt:

galy = GALY()
galy.line((0, 0), (100, 100), (255, 0, 0))

Intern wird dabei kein cv2.line direkt ausgeführt, sondern ein Befehl gespeichert.

Diese Befehle werden später vom Framework verarbeitet und gerendert.

Canvas und Layer

GALY arbeitet mit zwei zentralen Konzepten:

Canvas

Eine Zeichenfläche (vergleichbar mit einem Bild)

Layer

Eine Ebene auf dem Canvas (vergleichbar mit Photoshop-Layern)

Beispiel:

galy = GALY()
galy.canvas("main", (640, 480), (0, 0, 0))
galy.layer("trajectory")
  • canvas erstellt eine Zeichenfläche

  • layer erlaubt strukturierte Visualisierung

Layer können ein- und ausgeblendet werden und erleichtern Debugging.

Grundlegende Zeichenoperationen

Linie zeichnen:

galy.line((x1, y1), (x2, y2), (255, 0, 0), thickness=2)

Kreis zeichnen:

galy.circle((x, y), radius=5, color=(0, 255, 0), thickness=2)

Text anzeigen:

galy.putText("Label", (x, y), color=(255, 255, 255))

Diese Funktionen entsprechen direkt den OpenCV-Funktionen, werden jedoch über GALY abstrahiert.

Bilder einfügen (Blitting)

Mit blit können komplette Bilder auf den Canvas gelegt werden:

galy.blit("webcam", (0, 0))

Dabei wird das Bild aus einem Signal (z. B. webcam) verwendet.

Dies ist typischerweise der erste Schritt, um das Kamerabild darzustellen, auf das anschließend weitere Elemente gezeichnet werden.

Affine Transformationen

GALY unterstützt affine Transformationen pro Layer.

mapping = np.array([
    [1.0, 0.0, tx],
    [0.0, 1.0, ty]
])

galy.set_layer_affine_mapping(mapping)

Eine affine Transformation ist eine lineare Transformation der Form:

  • Translation (Verschiebung)

  • Skalierung

  • Rotation

Diese wird auf alle Punkte eines Layers angewendet.

Beispiel:

  • Koordinaten normalisiert → Mapping auf Bildschirm

  • Verschiebung der gesamten Trajektorie

  • Skalierung von Daten

Intern wird jeder Punkt transformiert, bevor er gezeichnet wird.

Mahalanobis-Visualisierung

GALY enthält auch spezialisierte Visualisierung:

galy.mahalanobis(mean, covariance, color=(255, 0, 0))

Dies zeichnet eine Ellipse basierend auf:

  • Mittelwert (mean)

  • Kovarianzmatrix (covariance)

Dies kann z. B. verwendet werden für:

  • Visualisierung von Zuständen im HMM

  • Darstellung von Unsicherheiten

Integration in Module

Damit GALY-Visualisierungen im Framework angezeigt werden, muss das erzeugte GALY-Objekt vom Modul zurückgegeben werden.

Beispiel:

def step(self, data):
    galy = GALY()

    galy.canvas("main", (640, 480), (0, 0, 0))
    galy.layer("debug")

    galy.line((0, 0), (100, 100), (255, 0, 0))

    return {
        ...
        "galy": galy
    }

Wichtig:

  • Das Signal muss genau "galy" heißen

  • Nur dann wird es vom Framework automatisch verarbeitet

Bemerkung

Wenn kein galy-Objekt zurückgegeben wird, erfolgt auch keine Visualisierung.

Tipp

Sie können GALY in jedem Modul verwenden. Besonders hilfreich ist dies im Preprocessor und Classifier, um Zwischenergebnisse sichtbar zu machen.

Verarbeitung im Framework

Alle gesammelten Befehle werden später verarbeitet:

process_galy(data)

Dabei passiert:

  1. Alle Commands werden iteriert

  2. In echte cv2-Aufrufe übersetzt

  3. Auf Canvas gerendert

  4. In GUI angezeigt

Dadurch bleibt die Visualisierung:

  • reproduzierbar

  • speicherbar

  • unabhängig vom Zeitpunkt der Berechnung

Wichtige Hinweise

  • GALY ersetzt nicht OpenCV, sondern abstrahiert es

  • Alle Zeichenoperationen sind zustandslos gesammelt

  • Reihenfolge der Befehle ist entscheidend

  • Visualisierung kann Teil der aufgezeichneten Daten sein

Tipp

Nutzen Sie GALY aktiv zum Debugging. Eine gute Visualisierung hilft oft mehr als jede Konsole.

Pipeline verändern

SignalHub organisiert die gesamte Verarbeitung als Pipeline aus Modulen.

Die Pipeline wird explizit im Code definiert, z. B.:

modules = [
    ConfigParser(parser),
    Webcam(),
    HandDetector(),
    TrailMarker(),
    Preprocessor(),
    HMMModule(),
]

engine = Engine(modules=modules, signals={})
signals = engine.run({})

Pipeline-Struktur

Durch Ändern dieser Liste können Sie die gesamte Pipeline anpassen.

Beispiele:

  • Module hinzufügen (z. B. eigene Visualisierung)

  • Module entfernen (z. B. TrailMarker deaktivieren)

  • Module austauschen (z. B. eigener Classifier)

Bemerkung

Die Pipeline ist vollständig flexibel und kann beliebig erweitert oder verändert werden.

Eigene Module erstellen

Neue Module werden erstellt, indem Sie von der Basisklasse Module erben:

from SignalHub import Module

class MyModule(Module):

    def start(self, data):
        return {}

    def step(self, data):
        return {}

    def stop(self, data):
        pass

Jedes Modul besteht aus drei zentralen Methoden:

  • start → wird einmal beim Start aufgerufen

  • step → wird für jeden Frame ausgeführt

  • stop → wird beim Beenden aufgerufen

Erweiterung der Pipeline

Sie können jederzeit eigene Module in die Pipeline integrieren:

modules = [
    ConfigParser(parser),
    Webcam(),
    HandDetector(),
    ##########
    MyModule(),
    ##########
    Preprocessor(),
    HMMModule(),
]

Wichtig ist dabei:

  • das Modul korrekt Signale abonniert

  • die erwarteten Datenstrukturen einhält

Tipp

Beginnen Sie mit kleinen Modulen und erweitern Sie die Pipeline schrittweise. So lassen sich Fehler deutlich einfacher finden.

Zusammenfassung

  • Die Pipeline wird über eine Liste von Modulen definiert

  • Module werden in Reihenfolge ausgeführt

  • Neue Module entstehen durch Vererbung von Module

  • Kommunikation erfolgt über Signale und das data-Dictionary

Dieses Konzept erlaubt eine sehr flexible und erweiterbare Architektur.