CSVVisualizer/CSVVisualizer.py
Jan a3bbfac3cf CSVVisualizer.py aktualisiert
Open the CSV

Find the first line where the first cell starts with "Cycle"

Use that entire line as the plot title
(Chat)
2025-05-26 21:15:26 +02:00

230 lines
8.8 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
self.graph_title = "Battery Test Analysis" # Default title
with open(filepath, 'r') as f:
reader = csv.reader(f)
for row in reader:
if row and row[0].startswith("Cycle"):
self.graph_title = ", ".join(row).strip()
break
try:
# First detect problematic lines
self.status_var.set("Datei geladen prüfe Zeilen…")
self.root.update_idletasks()
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")
self.root.update_idletasks()
return
# Now read only valid lines
self.status_var.set("Verarbeite gültige Zeile...")
self.root.update_idletasks()
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"Working with: {len(self.df)} valid measurements...")
self.root.update_idletasks()
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)
#self.ax.margins(x=0)
# 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()