value in matlab-struct auslesen

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
Arduryon
User
Beiträge: 3
Registriert: Mittwoch 12. November 2025, 12:25

Hallo,

ich habe folgendes Problem: Ich habe eine Reihe an matlab-Dateien, in denen mehrere Messkanäle sind (Data_x_Channel). Zu jedem Data-Channel gehört ein Header-Channel (Data_x_Header). Ich brauche aus jeder matlab-Datei einen bestimmten Kanal. Theoretisch könnte man ihn immer gleich zuweisen, sodass es z.B. immer Channel_5_Data ist. Über die gesamte Messreihe hat sich aber etwas an der Messkonfiguration geändert, sodass dieser Sensor nicht über die gesamte Messreihe den gleichen Kanal hat. Ich kann inzwischen die header und deren content in einer txt-datei für jede der matlab-dateien abspeichern, um manuell nachzuschauen, welche nummer ich jetzt für meinen gesuchten Kanal nutzen muss. Es ergibt sich also in der txt-Datei folgendes Muster:

Code: Alles auswählen

### Fahrtx ###
Channel_1_Data: [[0.000][1.000][2.000]]
Channel_1_Header: [[(array(['204,08973'], dtype='<U9'), array (['s'], dtype='<U1'), usw.
Channel_2_Data: [...]
Channel_2_Header: [...] usw.
In matlab sind die Header ein 1x1 struct mit 4 Feldern, davon ist ein Feld SignalName mit Value. Ich brauche immer die Value GNSS_Speed. Das bedeutet, wenn die Value = GNSS_Speed ist, soll der dazugehörige Datenkanal genommen werden.

Mein Ansatz war folgender:

Code: Alles auswählen

def find_channel_with_pattern(mat_data, pattern):
	for key in mat_data.keys():
		if key.endswith("_Header"):
			header = mat_data[key]
			if hasattr(header, "dtype") and "SignalName" in header.dtype.names:
				signal_name = header["SignalName"][0][0]
				if isinstance(signal_name, str) and pattern in signal_name:
					return key.replace("_Header", "_Data")
	return None
An anderer Stelle kommt dann:

Code: Alles auswählen

speed_channel = find_channel_with_pattern(mat, "GNSS_Speed")
if speed_channel is None:
	print(f"Kein Kanal mit GNSS_Speed im Header in {mat_file_path} gefunden")
	return
Jetzt bin ich mir unsicher, wie in Python der struct zu extrahieren ist. Der Header an sich ist ein numpy.void oder numpy.ndarray mit shape (1,1) und header["SignalName"] liefert ein Array, jedoch nicht den String.
Wie lässt sich das Problem lösen? Gibt es eine elegantere Methode, die ich übersehen habe?
Sirius3
User
Beiträge: 18328
Registriert: Sonntag 21. Oktober 2012, 17:20

So ist nunmal Matlab. Alles ist mindestens eine 2d-Matrix, aber wenn eine Dimension nur Länge 1 hat, dann kann man die Indizierung auch weglassen. Ist vielleicht bequem, bedeutet aber auch implizit unterschiedliches Verhalten, je nachdem was man hat.
Da ist Python strenger und klarer. Deshalb muß man jeden Matlab-Skalar als 1x1-Matrix unter Python explizit auflösen. Das kann man zwar auch beim Laden der mat-Datein automatisch auflösen, aber dann hat man das Problem, dass zufällig Matrizen mit Länge 1 auch eingedampft werden.
Deshalb ist die saubere Lösung, so wie Du es gemacht hast.
Wenn die Struktur einigermaßen fix ist, kannst Du viele Prüfungen auch weglassen, was den Code etwas lesbarer macht:

Code: Alles auswählen

def find_channel_with_pattern(mat_data, pattern):
    for key in mat_data:
        if key.endswith("_Header"):
            header = mat_data[key]
            signal_name = header[0, 0]["SignalName"]
            if signal_name == pattern:
                return key.remove_prefix("_Header") + "_Data"
    return None
Das einzige, was Du ändern solltest, ist, dass man immer mit 4 Leerzeichen pro Ebene einrückt und nicht mit Tabs.
Arduryon
User
Beiträge: 3
Registriert: Mittwoch 12. November 2025, 12:25

Hallo,
danke für die Antwort. Zu den Einrückungen: Ich nutze Pycharm, da ist beim drücken von tab automatisch so ein grauer strich, um den überblick zu behalten. Oder meinst du hier im Editor?

Die Struktur der matlab-Daten ist im Grunde genommen immer gleich, aber der Auswerteprozess soll automatisiert werden, falls noch Daten dazu kommen. Der erste Schritt ist, dass die Daten zugeschnitten werden müssen. Dafür hatte ich bereits ein Verfahren geschrieben, das funktionierte super. Das Problem ist, dass der Kanal, den ich für das Zuschneiden brauche, am Anfang noch Channel_5_Data war und später aber durch eine Änderung der Sensoren ein anderer geworden ist. In der Mitte der Messreihe war es dann z.B. Channel_6_Data. Insgesamt sind es 54 Dateien, da könnte man das auch quick and dirty über eine csv-Datei machen, aber mein Ansatz war eben, anhand der GNSS_Speed den Kanal zu bestimmen. So wie ich es jetzt habe (also die Variante oben) funktioniert leider noch nicht so ganz, weil er die GNSS_Speed nicht findet.

Aktuelle Ausgabe:

Code: Alles auswählen

Kanal: Channel_1_Header, Signalname: ['Zeit__2_-_Langsame_Messrate']
Kanal: Channel_2_Header, Signalname: ['v_Radar1']
Kanal: Channel_3_Header, Signalname: ['v_Radar2']
Kanal: Channel_4_Header, Signalname: ['Zeit__3_-_Langsame_Messrate']
Kanal: Channel_5_Header, Signalname: ['LS_1']
Kanal: Channel_6_Header, Signalname: ['Zeit__4_-_Langsame_Messrate']
Kanal: Channel_7_Header, Signalname: ['GNSS_RcvTimestamp']
Kanal: Channel_8_Header, Signalname: ['GNSS_Latitude']
Kanal: Channel_9_Header, Signalname: ['GNSS_Longitude']
Kanal: Channel_10_Header, Signalname: ['GNSS_Altitude']
Kanal: Channel_11_Header, Signalname: ['GNSS_Speed']
Kanal: Channel_12_Header, Signalname: ['GNSS_SatTime']
Kanal: Channel_13_Header, Signalname: ['GNSS_SatDate']
Kanal: Channel_14_Header, Signalname: ['kmh_Radar2']
Kanal: Channel_15_Header, Signalname: ['kmh_Radar1']
Kanal: Channel_16_Header, Signalname: ['kmh_Radar2_TP20']
Kein Kanal mit GNSS_Speed im Header in 5000Hz.MAT gefunden! 
Das GNSS_Speed ist dabei aber nur die Value für das Feld SignalName in der 1x1 struct. Ich hatte auch probiert, 'GNSS_Speed' statt GNSS_Speed zu schreiben, aber das hat nicht funktioniert. Zumindest in der Ausgabe zeigt er ja das GNSS_Speed schon mal an.

Die Zielstellung ist: Prüfe, welcher Header GNSS_Speed enthält --> dieser Header ist der zugehörige Kanal für die Daten (also z.B. Channel_5_Header enthält GNSS_Speed, also wähle Channel_5_Data).
Sirius3
User
Beiträge: 18328
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Pycharm beim Drücken auf Tab ein Tabzeichen einfügt, statt 4 Leerzeichen, dann ist Pycharm falsch konfiguriert. Da solltest Du in den Einstellungen nach "Einrückung mit Leerzeichen" suchen.
Du zeigst ja nicht, welchen Code Du jetzt für die Ausgabe benutzt hast, aber wenn ich raten müßte ist `header[0, 0]["SignalName"]` eine Liste mit einem Eintrag. Dementsprechend müßte der Vergleich mit Unpacking so sein:

Code: Alles auswählen

def find_channel_with_pattern(mat_data, pattern):
    for key in mat_data:
        if key.endswith("_Header"):
            header = mat_data[key]
            [signal_name] = header[0, 0]["SignalName"]
            if signal_name == pattern:
                return key.remove_prefix("_Header") + "_Data"
    return None
Benutzeravatar
__blackjack__
User
Beiträge: 14226
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich vermute `remove_prefix()` sollte eigentlich `removesuffix()` sein.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
Arduryon
User
Beiträge: 3
Registriert: Mittwoch 12. November 2025, 12:25

Danke, tatsächlich ist Pycharm richtig eingestellt und fügt beim drücken auf tab vier Leerzeichen ein. Den ersten Beitrag hatte ich nicht in den Editor eingefügt, sondern per Hand reingeschrieben, falls ich doch was übersehen hatte.

Mein Stand ist jetzt folgender:
Suchen des Kanals, dessen header den pattern enthält (z.B. GNSS_Speed o.ä.):

Code: Alles auswählen

def find_channel_with_pattern(mat_data, pattern):
    # sucht den kanal, dessen header den gesuchten pattern enthält
    for key in mat_data:  # durchlaufe alle schlüssel in mat_data 
        if key.endswith("_Header"):  # prüfen, ob der schlüssel ein header ist
            header = mat_data[key]

            # prüfe, ob es ein struct ist -> matlab dateien
            if hasattr(header, "dtype") and header.dtype.names:
                first_element = header[0][0] # matlab struct als 1x1 array

                if "SignalName" in first_element.dtype.names:
                    signal_name = first_element["SignalName"][0]

                    if isinstance(signal_name, str) and pattern in signal_name:
                        return key.replace("_Header", "_Data")

    return None  # Falls kein Kanal mit dem Muster gefunden wurde
    
Grundsätzlich brauche ich das Verfahren mit der Suche nach dem Struct für zwei Fälle, einmal für GNSS_Speed und einmal für LS_1. Sobald ich raushabe, wie der erste funktioniert, lässt sich das ja auf den zweiten übertragen.

Verarbeiten der mat-datei und Ergebnisse speichern im selben Ordner:

Code: Alles auswählen

def process_mat_file(mat_file_path, csv_path):
    mat = scipy.io.loadmat(mat_file_path) # daten laden

    # finde den kanal mit LS_1 im Header (Lichtschranke)
    light_channel = find_channel_with_pattern(mat, "LS_1")
    if light_channel is None:
        print(f"Kein Kanal mit 'LS_1' im Header in {mat_file_path} gefunden!")
        # debug ausgabe:
        for key in mat:
            if key.endswith("_Header"):
                header = mat[key]
                if hasattr(header, "dtype") and "SignalName" in header.dtype.names:
                    signal_name = header[0][0]["SignalName"][0]
                    print(f"Kanal: {key}, Signalname: {signal_name}")
        return # abbruch, wenn kein kanal gefunden wird
    else:
        print(f"Verwende Kanal {light_channel} als Lichtschranke")
        light = mat[light_channel].ravel()

    # finde den kanal mit speed im header (Geschwindigkeit)
    speed_channel = find_channel_with_pattern(mat, "GNSS_Speed")
    if speed_channel is None:
        print(f"Kein Kanal mit 'GNSS_Speed' im Header in {mat_file_path} gefunden!")
        return # abbruch, wenn kein kanal gefunden wurde
    else:
        print(f"Verwende Kanal {speed_channel} als Geschwindigkeit")
        speed = mat[speed_channel].ravel()

    # zeitvektor für lichtschranken
    t = np.arange(light.size) / FS_LIGHT

    print(f"Kanal {light_channel} als Lichtschranke und {speed_channel} als Geschwindigkeit")
Antworten