CSVVisualizer/CSVVisualizer.py
Jan e034150dc1 CSVVisualizer.py aktualisiert
Überschrift angepasst
(Chat)
2025-05-26 20:57:40 +02:00

223 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
import os
import csv
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from tkinter import Tk, filedialog, messagebox
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import ttk
class CSVVisualizer:
def __init__(self, root):
self.root = root
self.root.protocol("WM_DELETE_WINDOW", self.cleanup)
self.root.title("CSV to Graph Converter")
self.root.geometry("1000x700")
# Farben für die Phasen
self.phase_colors = {
"Charge": "#4E79A7", # Blau
"Discharge": "#E15759", # Rot
"Resting (Post-Charge)": "#59A14F", # Grün
"Resting (Post-Discharge)": "#EDC948", # Gelb
"Resting Between Cycles": "#B07AA1", # Lila
"Initial Discharge": "#FF9DA7" # Rosa
}
self.setup_ui()
def setup_ui(self):
"""Erstellt die Benutzeroberfläche"""
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Steuerleiste oben
control_frame = ttk.Frame(main_frame)
control_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Button(control_frame, text="CSV auswählen", command=self.load_csv).pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="Grafik speichern", command=self.save_plot).pack(side=tk.LEFT, padx=5)
# Anzeige des aktuellen Dateipfads
self.file_label = ttk.Label(control_frame, text="Keine Datei ausgewählt")
self.file_label.pack(side=tk.LEFT, padx=10, expand=True)
# Plot-Bereich
self.plot_frame = ttk.Frame(main_frame)
self.plot_frame.pack(fill=tk.BOTH, expand=True)
# Platzhalter für den Plot
self.fig, self.ax = plt.subplots(figsize=(10, 5))
self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Statusleiste
self.status_var = tk.StringVar()
self.status_var.set("Bereit")
ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN).pack(fill=tk.X, pady=(5, 0))
def load_csv(self):
"""Load CSV file with robust error handling for Raspberry Pi"""
filepath = filedialog.askopenfilename(
title="Select CSV File",
filetypes=[("CSV Files", "*.csv"), ("All Files", "*.*")]
)
if not filepath:
return
with open(filepath, 'r') as f:
reader = csv.reader(f)
headers = next(reader) # Zeile 0
second_line = next(reader, []) # Zeile 1 (Titel)
self.graph_title = second_line[0] if second_line else "Battery Test Analysis"
try:
# First detect problematic lines
self.status_var.set("Datei geladen prüfe Zeilen…")
good_lines = []
with open(filepath, 'r') as f:
reader = csv.reader(f)
headers = next(reader) # Keep header
for i, row in enumerate(reader):
# Skip summary lines and malformed rows
if not row or len(row) < 4 or row[0].startswith('Cycle'):
continue
# Validate numeric columns
try:
float(row[0]) # Time(s)
float(row[1]) # Voltage(V)
float(row[2]) # Current(A)
good_lines.append(i+1) # +1 to account for header
except ValueError:
continue
if not good_lines:
messagebox.showwarning("Warnung", "Keine gültigen Datenzeilen gefunden.")
self.status_var.set("Keine gültigen Daten")
return
# Now read only valid lines
self.status_var.set("Verarbeite gültige Zeilen…")
self.df = pd.read_csv(
filepath,
skiprows=lambda x: x not in good_lines and x != 0, # keep header
dtype={
'Time(s)': 'float32',
'Voltage(V)': 'float32',
'Current(A)': 'float32',
'Phase': 'category',
'Discharge_Capacity(Ah)': 'float32',
'Charge_Capacity(Ah)': 'float32',
'Coulomb_Eff(%)': 'float32',
'Cycle': 'int32'
},
engine='c',
memory_map=True
)
self.file_label.config(text=os.path.basename(filepath))
self.status_var.set(f"Loaded: {len(self.df)} valid measurements")
self.update_plot()
except Exception as e:
messagebox.showerror("Error", f"Failed to load file:\n{str(e)}")
self.status_var.set("Error loading file")
def update_plot(self):
"""Aktualisiert den Plot mit den aktuellen Daten"""
if not hasattr(self, 'df'):
return
df_clean = self.df.copy()
# Zeit relativ zum Start berechnen
start_time = df_clean["Time(s)"].min()
df_clean["Relative_Time(s)"] = df_clean["Time(s)"] - start_time
# Plot zurücksetzen
self.ax.clear()
# Spannung plotten
self.ax.plot(df_clean["Relative_Time(s)"], df_clean["Voltage(V)"],
label="Voltage (V)", color="black", linewidth=1.5)
# Phasen als farbige Hintergründe
start_idx = 0
for i in range(1, len(df_clean)):
if df_clean.iloc[i]["Phase"] != df_clean.iloc[i-1]["Phase"] or i == len(df_clean) - 1:
end_idx = i
start_time_rel = df_clean.iloc[start_idx]["Relative_Time(s)"]
end_time_rel = df_clean.iloc[end_idx]["Relative_Time(s)"]
phase = df_clean.iloc[start_idx]["Phase"]
# Verwende Standardfarbe falls Phase nicht definiert ist
color = self.phase_colors.get(phase, "#CCCCCC")
self.ax.axvspan(start_time_rel, end_time_rel, facecolor=color, alpha=0.3)
start_idx = i
# Legende erstellen
patches = [mpatches.Patch(color=self.phase_colors[phase], label=phase)
for phase in self.phase_colors if phase in df_clean["Phase"].unique()]
# Füge Spannungs-Linie zur Legende hinzu
patches.append(plt.Line2D([0], [0], color='black', label='Voltage (V)'))
self.ax.legend(handles=patches, loc="upper right")
self.ax.set_xlabel("Time (s) since start")
self.ax.set_ylabel("Voltage (V)")
self.ax.set_title(getattr(self, 'graph_title'))
self.ax.grid(True)
# Aktualisiere den Canvas
self.canvas.draw()
self.status_var.set("Grafik aktualisiert")
def save_plot(self):
"""Speichert den aktuellen Plot als Bilddatei"""
if not hasattr(self, 'df'):
messagebox.showwarning("Warnung", "Keine Daten zum Speichern vorhanden")
return
filetypes = [
('PNG Image', '*.png'),
('JPEG Image', '*.jpg'),
('PDF Document', '*.pdf'),
('SVG Vector', '*.svg')
]
default_filename = "battery_test_plot.png"
if hasattr(self, 'file_label'):
base = os.path.splitext(self.file_label.cget("text"))[0]
if base:
default_filename = f"{base}_plot.png"
filepath = filedialog.asksaveasfilename(
title="Grafik speichern",
initialfile=default_filename,
filetypes=filetypes,
defaultextension=".png"
)
if filepath:
try:
self.fig.savefig(filepath, dpi=300, bbox_inches='tight')
self.status_var.set(f"Grafik gespeichert als: {os.path.basename(filepath)}")
messagebox.showinfo("Erfolg", "Grafik wurde erfolgreich gespeichert")
except Exception as e:
messagebox.showerror("Fehler", f"Konnte Grafik nicht speichern:\n{str(e)}")
self.status_var.set("Fehler beim Speichern")
def cleanup(self):
"""Perform cleanup before closing"""
if hasattr(self, 'fig'):
plt.close(self.fig)
self.root.destroy()
if __name__ == "__main__":
root = Tk()
app = CSVVisualizer(root)
root.mainloop()