@Karlirex: „Sehr funktional“ ist ja nicht synonym mit „unklar“ oder „wenig Klarheit“. Also ausser für Leute die damit nicht können oder keine oder wenig Erfahrung damit haben. Das würde ich also erst einmal nicht als Kritik gelten lassen.
Zum Quelltext: Ausser `CopyFileEventHandler` sind die Klassen dort falsch, weil das gar keine Klassen sind. Ohne `__init__()` und mit keiner ”Methode” die `self` verwendet ist das einfach nur Missbrauch von ``class`` um normale Funktionen zu gruppieren. Das Mittel dafür sind in Python Module. Also weg mit beiden “Klassen“ — das sind einfach nur ein paar Funktionen.
`os.path` & Co würde man heute mit `pathlib` ersetzen. Aber auch wenn man bei `os.path` bleibt, dann würde man nicht *doch* noch mal wieder systemspezifische Trennzeichen einbauen. Das ist ja mit ein Grund warum man Pfade nicht einfach als Zeichenketten behandeln darf, das da Regeln gelten damit das gültige Pfade sind, die sich zudem auch noch von System zu System unterscheiden können.
Auch die Tests die darauf gemacht werden als wenn das einfach Zeichenketten wären, helfen nicht so wirklich beim Verständnis wie das alles tatsächlich aussieht. Ein Test auf ``".png" not in basename`` ist komisch, weil man jetzt nicht weiss ob sich das vielleicht tatsächlich nur auf *Endungen* beziehen soll und der Test einfach nur falsch ist, oder ob tatsächlich geprüft werden soll ob ".png" *irgendwo* *in* dem Namen nicht vorkommen soll.
`angel_list` ist lustig.

Ich vermute mal das sollte `angle` heissen. Oder wie werden Engel repräsentiert und geplottet?
Grunddatentypen wie `list` haben in Namen nichts zu suchen. Das ändert man gerne mal oder man schreibt Code der gar nicht zwingend Listen erfordert, sondern auch mit Arrays, oder gar beliebigen iterierbaren Objekten funktionieren würde, und schränkt das im Namen unnötig auf Listen ein.
Beim öffnen von Textdateien sollte man die Kodierung explizit angeben und nicht hoffen, die ”geratene” wird schon passen.
``replace("\n", "")`` ist bei Zeilen etwas irreführend und auch ineffizient. Man will ja nicht irgendwo in der Zeile Zeichen entfernen, sondern am Ende. Dafür gibt es `rstrip()`. Ist aber auch überhaupt nicht notwendig wenn der Teil mit dem Zeilenendezeichen dann an `float()` verfüttert wird, weil `float()` führende und folgende „whitespace“-Zeichen einfach ignoriert.
`full_data` würde ich `lines` nennen, dann wird beim lesen klarer was dahinter steckt.
In `get_xy_data_chromatogramm()` halten sich viele Namen nicht an die übliche Schreibweise (klein_mit_unterstrichen) und es wird auch viel mit „wir definieren mal Namen auf Vorrat die erst in späteren Blöcken verwenden werden“ gearbeitet. Das macht Quelltext undurchsichtiger wenn nicht der Code zusammen steht der zusammen gehört, sondern man sich den aus verschiedenen Blöcken zusammensuchen muss. Das macht es auch schwerer Teile in Funktionen herauszuziehen. Denn wenn man sich die Funktion anschaut, fällt auf, dass da viermal fast das gleiche gemacht wird, nur mit anderen Zeichenketten als Markierungen und Offsets für den Anfang und das Ende der Daten. Und in `get_mass_data_chromatogramm()` noch mal ähnlicher Code.
In `pdf_file_to_txt()` sind die `Object`-Zusätze in den Namen überflüssig. In Python ist jeder Wert ein Objekt. Das könnte/müsste man dann also an *jeden* Namen mit anhängen, es liefert aber überhaupt keinen Mehrwert für den Leser. Die Funktion ist auch reichlich umständlich. Das Reader-Objekt nimmt auch einen Dateinamen/Pfad, man muss also nicht selbst eine Datei öffnen. Das ``for page in range(len(pdf.pages)):`` ist total unpythonisch weil `page` dann nur als Index für den Zugriff in `pdf.pages` verwendet wird, statt einfach direkt über die Elemente von `pdf.pages` zu iterieren, ohne den unnötigen umweg über den Index. Der ist auch falsch benannt, denn der Wert ist ja keine `page` sondern nur ein Index. Die Funktion lässt sich mit einer „list comprehension“ in einer einzigen Zeile formulieren.
In `on_created()` wird `what` definiert, aber nirgends verwendet.
Die fehlerhafte Verwendung von ``and`` hat Sirius3 ja schon angesprochen. Da die erste Teilbedingung ja bereits im ``if`` davor geprüft wird, könnte man diese beiden ``if``-Anweisungen mit dort in den Zweig ziehen. Sind das überhaupt unabhängige ``if``-Anweisungen oder sollte die Zweite nicht besser ein ``elif`` sein‽
Bei der `PermissionError`-Protokollmeldung ist mir nicht so ganz klar warum der Pfad mit `dirname()` und `basename()` zerlegt wird und dann in der Meldung beide Teile mit einem \ getrennt ausgegeben werden. Da kann man doch einfach gleich den kompletten Pfadnamen ausgeben.
Im folgenden natürlich komplett ungetesteten Zwischenstand fehlt bei den Pfaden alles wo mit der normale Zeichenkettenmethode `replace()` etwas global irgendwo im gesamten Pfad ausgetauscht wurde, weil mir die allgemeine Lösung mit `Path` dafür *zu* allgemein war, man aus dem Code aber nicht erkennen kann wie man das spezifischer lösen müsste.
Code: Alles auswählen
#!/usr/bin/env python3
import shutil
from pathlib import Path
from time import sleep
import pandas as pd
import PyPDF2
from matplotlib import pyplot as plt
def generate_plot(save_path, x_coordinate, y_coordinate, ending):
#
# TODO Use the OOP API of matplotlib instead of the one with global state.
#
plt.plot(x_coordinate, y_coordinate)
plt.savefig(save_path.with_name(save_path.name + f"_{ending}.png"))
plt.clf()
def parse_float(text):
return float(text.replace(",", "."))
def parse_columns(lines):
return list(zip(*(list(map(parse_float, line.split())) for line in lines)))
def parse_columns_between(lines, start_keyword, start_offset, end_keyword):
start = lines.index(start_keyword) + start_offset
end = lines.index(end_keyword) - 1
return parse_columns(lines[start:end])
def get_xy_data(file_path):
with file_path.open(encoding="ascii") as lines:
next(lines) # Skip header line.
return parse_columns(lines)
def running_one(file_path):
if not (
"unprocessed_data" in str(file_path)
or file_path.suffix in [".png", ".xlsx"]
):
angles, intensities = get_xy_data(file_path)
generate_plot(file_path, angles, intensities, "One")
def get_xy_data_chromatogramm(file_path):
with file_path.open(encoding="ascii") as file:
lines = list(file)
if "Indentifier" in lines[1]:
ri_start_keyword, ri_end_keyword = "[One]\n", "[Second]\n"
uv_start_keyword, uv_end_keyword = "[First]\n", "[Last)]\n"
else:
ri_start_keyword, ri_end_keyword = "[OtherOne]\n", "[OtherSecond]\n"
uv_start_keyword, uv_end_keyword = "[OtherFirst]\n", "[OtherLast]\n"
ri_times, ri_intensities = parse_columns_between(
lines, ri_start_keyword, 8, ri_end_keyword
)
uv_times, uv_intensities = parse_columns_between(
lines, uv_start_keyword, 11, uv_end_keyword
)
return ri_times, ri_intensities, uv_times, uv_intensities
def get_mass_data_chromatogramm(file_path):
with file_path.open(encoding="ascii") as file:
lines = list(file)
start_keyword, end_keyword = (
("[First]\n", "[Last]\n")
if "Indentifier" in lines[1]
else ("[OtherFirst]\n", "[OtherLast]\n")
)
return parse_columns_between(lines, start_keyword, 3, end_keyword)
def generate_csv(file_path, names, areas, concentrations):
pd.DataFrame(
{"Name: ": names, "Area: ": areas, "Konz: ": concentrations}
).T.to_csv(file_path / f"{file_path.name}_short.txt")
def running_two(file_path):
if not (
"unprocessed_data" in str(file_path)
or file_path.suffix in [".png", ".xlsx"]
or "short" not in file_path.stem
):
(
ri_times,
ri_intensities,
uv_times,
uv_intensities,
) = get_xy_data_chromatogramm(file_path)
generate_plot(file_path, ri_times, ri_intensities, "RI")
generate_plot(file_path, uv_times, uv_intensities, "UV")
generate_csv(file_path, *get_mass_data_chromatogramm(file_path))
def pdf_file_to_txt(file_path):
return [page.extract_text() for page in PyPDF2.PdfReader(file_path).pages]
class CopyFileEventHandler(LoggingEventHandler):
def on_created(self, event):
super().on_created(event)
sleep(0.2)
source_path = Path(event.src_path)
if not event.is_directory:
if "unprocessed_data" in str(source_path):
try:
shutil.copy2(
source_path,
source_path.with_name(f"generated_{source_path.name}"),
)
except PermissionError:
with open("debug.log", "a", encoding="utf-8") as logfile:
logfile.write(f"Zugangsfehler: {source_path}\n")
sleep(0.5)
if "hplc" in str(source_path).lower():
running_two(source_path)
#
# TODO Should this be an ``elif``‽
#
if "exported" in str(source_path).lower():
running_one(source_path)
### hiernach kommt "nur" die if main und der Start vom Watchdog mit Pathangabe