import sys #Wird benötigt, um auf Argumente im Terminal-Befehl zugreifen zu können.
import math #Wird benötigt, um den Tangens von spectrum_angle berechnen zu können.
import time #Wird benötigt, um den Prozess einige Sekunden zu pausieren, damit die Kamera aufwärmen kann.
import picamera #Wird benötigt, um auf das Raspberry Pi Camera Module V2 zugreifen zu können.
from fractions import Fraction #Wird benötigt, um mit rationalen Zahlen rechnen zu können.
from collections import OrderedDict #Eine Unterklasse von "dict", die sich an die Reihenfolge erinnert, in der Einträge hinzugefügt wurden.
from PIL import Image, ImageDraw, ImageFile, ImageFont #"Python Image Library" - Wird für die Zeichnung des Overlays benötigt.












#--------Einstellungen-----------------------------------------------------------------------------------------------------------------------------
Speicherpfad = "/home/pi/LambdaSpektrometer"
        # Der Speicherpfad, unter dem der Programmordner gespeichert ist.
        # Das Argument hinter dem letzten Schrägstrich ist der Name des Ordners.
        
resolution = 2 #Mit "1" hat Spektrum eine Auflösung von 630x310, mit "2" 1260x620.
#--------------------------------------------------------------------------------------------------------------------------------------------------




# Scanne die vertikale Achse durch den Spalt, um dessen obere und untere Grenze zu finden.
def get_spectrum_y_bound(pix, x, middle_y, spectrum_threshold, spectrum_threshold_duration):
    c = 0
    spectrum_top = middle_y
    for y in range(middle_y, 0, -1): #Analysiert die Helligkeit entlang der vertikalen Gerade durch das gegebene x. Gibt die obere Grenze des Spalts an. 
        r, g, b = pix[x, y] #pix gibt ein Dreitupel der Helligkeitswerte für rot, grün und blau aus (Maxiumum: 255, 255, 255)
        brightness = r + g + b
        if brightness < spectrum_threshold:
            c = c + 1
            if c > spectrum_threshold_duration:
                break
        else:
            spectrum_top = y
            c = 0
    c = 0
    spectrum_bottom = middle_y
    for y in range(middle_y, middle_y * 2, 1): #Analysiert die Helligkeit entlang der vertikalen Gerade durch das gegebene x. Gibt die untere Grenze des Spalts an.
        r, g, b = pix[x, y] #pix gibt ein Dreitupel der Helligkeitswerte für rot, grün und blau aus (Maxiumum: 255, 255, 255)
        brightness = r + g + b
        if brightness < spectrum_threshold:
            c = c + 1
            if c > spectrum_threshold_duration:
                break
        else:
            spectrum_bottom = y
            c = 0
    return spectrum_top, spectrum_bottom #Das Ergebnis ist ein Dublett der y-Koordinaten der Ober- und Untergrenze des Spalts.




# Finde den Spalt auf der rechten Seite des Bildes entlang der mittleren horizontalen Gerade.
def find_aperture(pic_pixels, pic_width: int, pic_height: int) -> object:
    middle_x = int(pic_width / 2) #x-Koordinate der Mitte des Bildes.
    middle_y = int(pic_height / 2) #y-Koordinate der Mitte des Bildes.
    aperture_brightest = 0
    aperture_x = 0
    for x in range(middle_x, pic_width, 1): #Scannt nach der hellsten x-Koordinate auf der rechten Seite des Bildes entlang der mittleren horizontalen Gerade.
        r, g, b = pic_pixels[x, middle_y]
        brightness = r + g + b
        if brightness > aperture_brightest:
            aperture_brightest = brightness
            aperture_x = x
    aperture_threshold = aperture_brightest * 0.9
    aperture_x1 = aperture_x
    for x in range(aperture_x, middle_x, -1): #Ermittelt die x-Koordinate mit 90% der Helligkeit links von der hellsten x-Koordinate.
        r, g, b = pic_pixels[x, middle_y]
        brightness = r + g + b
        if brightness < aperture_threshold:
            aperture_x1 = x
            break
    aperture_x2 = aperture_x
    for x in range(aperture_x, pic_width, 1): #Ermittelt die x-Koordinate mit 90% der Helligkeit rechts von der hellsten x-Koordinate.
        r, g, b = pic_pixels[x, middle_y]
        brightness = r + g + b
        if brightness < aperture_threshold:
            aperture_x2 = x
            break
    aperture_x = (aperture_x1 + aperture_x2) / 2 #Berechnet die x-Koordinate der Mitte des Spalts auf der mittleren horizontalen Gerade.
    spectrum_threshold_duration = 64
    aperture_y_bounds = get_spectrum_y_bound(pic_pixels, aperture_x, middle_y, aperture_threshold, spectrum_threshold_duration)
    aperture_y = (aperture_y_bounds[0] + aperture_y_bounds[1]) / 2 #Berechnet die y-Koordinate der Mitte des Spalts.
    aperture_height = (aperture_y_bounds[1] - aperture_y_bounds[0]) * 1.0 #Berechnet die Höhe des Spalts in Pixeln.
    return {'x': aperture_x, 'y': aperture_y, 'h': aperture_height, 'b': aperture_brightest}




# Zeichne den detektierten Spalt auf das Bild.
def draw_aperture(aperture, draw):
    fill_color = "#000" #"#000" ist schwarz.
    draw.line((aperture['x'], aperture['y'] - aperture['h'] / 2, aperture['x'], aperture['y'] + aperture['h'] / 2),
              fill=fill_color)




# Zeichne die Scanlinien.
def draw_scan_line(aperture, draw, spectrum_angle):
    fill_color = "#888" #"#888" ist grau.
    xd = aperture['x'] #x-Koordinate des Spalts.
    h = aperture['h'] / 2 #Hälfte der Höhe des Spalts.
    y0 = math.tan(spectrum_angle) * xd + aperture['y'] #math.tan(x) gibt den Tangens von x (Bogenmaß!) an.
    draw.line((0, y0 - h, aperture['x'], aperture['y'] - h), fill=fill_color) #Zeichnet die obere Scanlinie.
    draw.line((0, y0 + h, aperture['x'], aperture['y'] + h), fill=fill_color) #Zeichnet die untere Scanlinie.




#Einstellungen der Kamera
def take_picture(name, shutter):
    camera = picamera.PiCamera()
    try:
        camera.vflip = True
        camera.rotation = 90
        camera.framerate = Fraction(1, 2)
        camera.shutter_speed = shutter
        camera.iso = 100
        camera.exposure_mode = 'off'
        camera.awb_mode = 'off'
        camera.awb_gains = (1, 1)
        time.sleep(3)
        camera.capture(name, resize=(1296, 972))
    finally:
        camera.close()
    return name




# Zeichne das Overlay auf das Bild. 
def draw_graph(draw, pic_pixels, aperture: object, spectrum_angle, wavelength_factor):
    aperture_height = aperture['h'] / 2 #Hälfte der Höhe des Spalts in Pixeln.
    step = 1
    last_graph_y = 0
    max_result = 0
    results = OrderedDict() #Spezielle Form von "dictionary", die sich an die Reihenfolge erinnert, in der die Einträge hinzugefügt wurden. 
    for x in range(0, int(aperture['x'] * 7 / 8), step):
        wavelength = (aperture['x'] - x) * wavelength_factor #Für alle x zwischen 0 und 7/8 mal dem x-Wert des Spalts wird wavelength definiert.
        if 1000 < wavelength or wavelength < 380:
            continue #Stellt sicher, dass nur Werte zwischen 380 und 1000 analysiert werden.

        # Allgemeine Effizienz von 1000 Linien/mm Diffraktionsgitter
        eff = (800 - (wavelength - 250)) / 800
        if eff < 0.3:
            eff = 0.3

        y0 = math.tan(spectrum_angle) * (aperture['x'] - x) + aperture['y'] #math.tan(x) gibt den Tangens von x (Bogenmaß!) an.
        amplitude = 0
        ac = 0.0
        for y in range(int(y0 - aperture_height), int(y0 + aperture_height), 1): 
            r, g, b = pic_pixels[x, y]
            q = r + b + g * 2
            if y < (y0 - aperture_height + 2) or y > (y0 + aperture_height - 3):
                q = q * 0.5
            amplitude = amplitude + q
            ac = ac + 1.0
        amplitude = amplitude / ac / eff
        results[str(wavelength)] = amplitude #Jedem wavelength-Wert wird ein amplitude-Wert zugeordnet.
        if amplitude > max_result:
            max_result = amplitude #Gibt nach vollem Durchlauf der for-Schleife den höchsten Wert der amplitude-Folge an.
        graph_y = amplitude / 50 * aperture_height
        draw.line((x - step, y0 + aperture_height - last_graph_y, x, y0 + aperture_height - graph_y), fill="#fff") #Zeichnet eine weiße Strecke vom y-Wert von x bis zum y-Wert von x+1.
        last_graph_y = graph_y
    draw_ticks_and_frequencies(draw, aperture, spectrum_angle, wavelength_factor)
    return results, max_result #Das Ergebnis ist ein Dublett




#Zeichne Skalenmarkierungen und Beschriftungen auf das Bild mit Overlay.
def draw_ticks_and_frequencies(draw, aperture, spectrum_angle, wavelength_factor):
    aperture_height = aperture['h'] / 2
    for wl in range(400, 1001, 50):
        x = aperture['x'] - (wl / wavelength_factor)
        y0 = math.tan(spectrum_angle) * (aperture['x'] - x) + aperture['y'] #math.tan(x) gibt den Tangens von x (Bogenmaß!) an.
        draw.line((x, y0 + aperture_height + 5, x, y0 + aperture_height - 5), fill="#fff") #"#fff" ist weiß.
        font = ImageFont.truetype('/usr/share/fonts/truetype/lato/Lato-Regular.ttf', 12)
        draw.text((x, y0 + aperture_height + 15), str(wl), font=font, fill="#fff") #"#fff" ist weiß.




# Speichere das Bild mit Overlay.
def save_image_with_overlay(im, name):
    output_filename = name + "_Overlay.jpg"
    ImageFile.MAXBLOCK = 2 ** 20
    im.save(output_filename, "JPEG", quality=80, optimize=True, progressive=True)




# Normiere Ergebnisse.
def normalize_results(results, max_result):
    for wavelength in results:
        results[wavelength] = results[wavelength] / max_result
    return results




# Speichere die Ergebnisse als .csv-Datei.
def export_csv(name, normalized_results):
    Settings = Speicherpfad + "/Settings.txt"
    with open(Settings, "r") as sf:
        csv_mode = sf.readlines()[10].lstrip("csv_mode = ").rstrip("\n")
    print("csv_mode=", csv_mode)
    csv_filename = name + ".csv"
    csv = open(csv_filename, 'w')
    if csv_mode == "English":
        csv.write("Wellenlänge [nm],Intensität [%]\n")
    else:
        csv.write("Wellenlänge [nm];Intensität [%]\n")
    for wavelength in normalized_results:
        if csv_mode == "English":
            wavelength_rounded = str(round(float(wavelength), 2)) #Wellenlängen werden abgerundet um Tabelle übersichtlicher zu machen
            csv.write(wavelength_rounded)
            csv.write(",")
            csv.write("{:0.3f}".format(100 * normalized_results[wavelength]))
            csv.write("\n")
        else:
            wavelength_rounded = str(round(float(wavelength), 2)).replace(".", ",") #Wellenlängen werden abgerundet um Tabelle übersichtlicher zu machen
            csv.write(wavelength_rounded)
            csv.write(";")
            csv.write(str("{:0.3f}".format(100 * normalized_results[wavelength])).replace(".", ","))
            csv.write("\n")            
    csv.close()




# Hauptfunktion
def main():
    # 1. Schieße Bild
    name = sys.argv[1] #sys.argv[1] ist das erste Argument des Terminal-Befehls.
    shutter = int(sys.argv[2]) #sys.argv.[2] ist das zweite Argument des Terminal-Befehls.
    raw_filename = name + "_Bild.jpg"
    take_picture(raw_filename,shutter)

    # 2. Finde den Spalt im Bild
    im = Image.open(raw_filename) #Öffnet das Bild.
    pic_pixels = im.load() #Lädt die Informationen über die Pixel des Bildes.
    aperture = find_aperture(pic_pixels, im.size[0], im.size[1]) #im.size[0] ist die Breite, im.size[1] die Höhe des Bildes.

    # 3. Zeichne Spalt und Scanlinie auf das Bild
    Settings = Speicherpfad + "/Settings.txt"
    with open(Settings, "r") as sf:
        sa = sf.readlines()[8].lstrip("spectrum_angle = ").rstrip("\n")
    print("sa=", sa)
    spectrum_angle = float(sa) * -3.14159265359 / 180 #Wandelt die Information in eine Dezimalzahl um, wandelt vom Grad- ins negative Bogenmaß um.
    draw = ImageDraw.Draw(im) #Erstellt ein Objekt, das benutzt werden kann um im Bild zu zeichnen.
    draw_aperture(aperture, draw)
    draw_scan_line(aperture, draw, spectrum_angle)

    # 4. Zeichne Intensitätskurve auf das Bild
    with open(Settings, "r") as sf:
        ss = sf.readlines()[9].lstrip("spectrum_scale = ").rstrip("\n")
    print("ss=", ss)
    wavelength_factor = float(ss) #Wandelt die Information in eine Dezimalzahl um.
    results, max_result = draw_graph(draw, pic_pixels, aperture, spectrum_angle, wavelength_factor)

    # 5. Speichere Bild mit Overlay
    save_image_with_overlay(im, name)

    # 6. Normiere Ergebnisse für den Export der Daten
    normalized_results = normalize_results(results, max_result)

    # 7. Speichere .csv-Datei der Ergebnisse
    export_csv(name, normalized_results)




main() #Hauptfunktion wird ausgeführt und enthält alle zuvor definierten Vorgänge.
