Thread crash _bootstrap_inner

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Kriccemis
User
Beiträge: 20
Registriert: Freitag 8. Mai 2020, 20:10

Hallo liebe Community,

ich bin dabei modifikationen an einem Code vorzunehmen, den ich selbst nicht geschrieben habe.
Aktuell stürzt das Programm recht heufig ab bei gewissen Konfigurationseinstellungen.
Meine ersten Recherchen haben ergeben, dass es Wohl auf einen Fehler zurückzuführen ist der statt einem Tupel eine Variable/Liste/usw als inhaltArgs() verwendet.

Das habe ich dazu bereits gefunden:

Code: Alles auswählen

thread_01 = threading.Thread(target=calculate_the_square, args=(array))
thread_01 = threading.Thread(target=calculate_the_square, args=(array,))
Hier die Fehlermeldung:
Windows fatal exception: access violation

Thread 0x00000480 (most recent call first):
File "C:\Users\LCTF\Desktop\experimental\run_camera.py", line 358 in get_frame_spots
File "C:\Users\LCTF\Desktop\experimental\run_camera.py", line 90 in run
File "C:\Users\LCTF\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1073 in _bootstrap_inner
File "C:\Users\LCTF\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1030 in _bootstrap

Current thread 0x00002cd0 (most recent call first):
File "C:\Users\LCTF\AppData\Local\Programs\Python\Python312\Lib\site-packages\pyqtgraph\graphicsItems\ViewBox\ViewBox.py", line ??? in <lambda>
File "C:\Users\LCTF\Desktop\experimental\main.py", line 4399 in <module>

Process finished with exit code -1073741819 (0xC0000005)
Auszug aus dem Main Programm, wo der Thread gestartet wird:

Code: Alles auswählen

    def run_manual_mode(self):
        print("Messung gestartet")

        if self.init_camera is True:
            self.IDS_AutoShutter(0)
            wl_begin = self.spinBox_wl_begin_c_exposure.value()
            wl_end = self.spinBox_wl_end_c_exposure.value()
            frames = self.spinBox_frames.value()
            avg = self.spinBox_avg.value()
            
            self.plotitem_extinction.setLimits(xMin = wl_begin)
            self.plotitem_extinction.setLimits(xMax = wl_end)
            self.plotitem_extinction.setRange(xRange=[wl_begin, wl_end])
            self.plotitem_extinction.setLimits(yMin=0)
            # self.plotitem_extinction.setRange(yRange=[0, 0.25])

            self.Centroid_list_camera=[]
            self.Maximum_list_camera=[]
            self.Maximum_wavelength_camera=[]
            self.Time_list_camera = []
            self.Create_spot_list()
            self.Create_Extinction_list()
            self.start_time = time.time()
            self.camera_thread = CameraRun(self.hCam, self.pcImageMemory, self.width, self.height,
                                              self.nBitsPerPixel, self.pitch, self.bytes_per_pixel, self.vario, self.spot_list,
                                              self.cal_list, self.polynom_order, wl_begin, wl_end, frames, avg,
                                              time=self.start_time)
            self.camera_thread.start()      # Startet den Camera_Thread
            self.camera_timer.start(1000)   # Ein Timer über 1 Sekunde. Startet die funktion Plot_time.(Aktualisieren der Daten in den Plots)
     
Auszug aus der Class des Threads:

Code: Alles auswählen

class CameraRun(threading.Thread):
    """ Camera running thread """
    Centroid_list = []
    Maximum_list = []
    Maximum_wavelength_list = []
    Time_List = []
    Extinction_List = []
    Wavelength_List = []
    Ext_spots_List = []

    def __init__(self, camera, pcImageMemory, width, height, nBitsPerPixel,
                 pitch, bytes_per_pixel, varioSpec, spot_list,
                 cal_list, polynom_order, wl_begin, wl_end, frames,
                 avg, fortschritt=0, time=0):

        super().__init__()
        # Werte für die Kamera
        self.hCam = camera   # Kamera
        self.pcImageMemory = pcImageMemory
        self.width = width
        self.height = height
        self.nBitsPerPixel = nBitsPerPixel
        self.pitch = pitch
        self.bytes_per_pixel = bytes_per_pixel
        self.vario = varioSpec  # Filter
        self.spot_list = spot_list  # Liste mit den Spots in Form: ((x, y), r,(o,g,b),gruppe)
        self.cal_list = cal_list   # Enthält Wl, Exp time und FPS: [wl_begin_c,temp_exp, temp_fps,histogram_mean]
        self.polynom_order = polynom_order
        self.wl_begin = wl_begin        # Startwellenlänge
        self.wl_end = wl_end            # Endwellenlänge
        self.frame_count = frames       # Anzahl der Frames pro Wellenlänge
        self.avg = avg                  # Anzahl der Wdh. bis zum Plot
        self.time = time
        self.fortschritt = fortschritt
        self.killed = False
        self.polyline = np.arange(self.wl_begin, self.wl_end, 0.001)
        self.images = []
        self.wavelength = []
        self.low_ext_wl = 0
        self.high_ext_wl = 0
        self.centroid_boundary = True
        self.spot_extinction_list = None
        self.wavelength_list = None
        self.cycle = None

    def run(self):
        """ Start the running thread """
        Time_List = []
        self.ids_auto_shutter(0)
        self.cycle = 1

        while not self.killed:

            # Listen zur Wertemittlung
            ext_mean = []
            i = 1

            if self.time is None:
                start_proc = time.time()
            else:
                start_proc = self.time

            while i <= self.avg:
                # print("Durchlauf: ", i, " von ", reps)
                self.get_frames()
                list_bildverarbeitung = self.get_frame_spots(self.cal_list, self.images)  # enthält [list_mean_spot,list_mean_bg,wavelength]
                self.spot_extinction_list, self.wavelength_list = self.get_ext_values(list_bildverarbeitung)
                ext_mean.append(self.spot_extinction_list)
                i = i + 1

            ext_mean = np.asarray(ext_mean)
            ext_mean = np.mean(ext_mean, axis=0)
            ext_mean = ext_mean.tolist()
            # print("")
            # print(ext_mean)
            # print("")

            CameraRun.Ext_spots_List.append(ext_mean)

            centroid_list, maximum_list, maximum_wavelength_list, wl_list, spot_ext_list = self.math_calculation(self.wavelength_list, ext_mean)
Leider bin ich noch nicht so erfahren mit dem Umgang von Threads (bin aber dabei) und würde mich um unterstützung freuen.

Danke.
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kriccemis: Das ist eine Speicherzugriffsverletzung und die sind in aller Regel ein Problem in externen, nicht in Python geschriebenen Bibliotheken. Oder wenn man solche Bibliotheken von Python aus falsch benutzt. Bei Threads und GUI ist ein heisser Kandidat dafür, das aus anderen Threads als dem in dem die GUI-Hauptschleife läuft, irgendetwas an der GUI verändert wird. Das darf man nicht, weil GUI-Rahmenwerke in der Regel nicht thread-sicher sind.

Konkret tritt die Ausnahme ja in `get_frame_spots()` in Zeile 358 auf. Was wird denn da gemacht, das zur Folge hat, das auf Speicher zugriffen wird auf den nicht zugegriffen werden darf?
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Kriccemis
User
Beiträge: 20
Registriert: Freitag 8. Mai 2020, 20:10

In dieser Funktion werden bei einer definierten Wellenlänge Frames von der Kamera aufgenommen.
Auffällig ist, dass der Fehler absolut Random stattfindet (nach Programmstart irgendwo zwischen 1 min und 4h, etwas abhängig von den parametern.
Normalerweise ist der standardwert 5 frames pro Wellenlänge, wird dieser aber erhöht oder die Schrittweite sehr klein dann ist die Fehleranfälligkeit sehr hoch.

Ein zweites Problem was ich auch sehe, ist die Art der Datenübertragung aus dem Thread zum Main Programm.
Da der Thread nach dem Start dauerhaft läuft bis er manuell beendet wird. Werden permanent Frames aufgenommen, Berechnungen vorgenommen und in eine Liste z.B. CameraRun.Centroid_list = [XY] geschrieben.
Über einen Timer im main programm wird alle X Sekunden überprüft ob sich diese Liste geändert hat und falls ja werden die neuen Daten in der GUI angezeigt.

Ich finde aber korrekt wäre doch eigentlich der Thread läuft einmal durch. Sendet die Daten dann an Main und läuft wieder durch usw usw. Oder stelle ich mir dies zu einfach vor?
Kurz gesagt ich würde gerne eine Lösung haben, in dieser der thread dauerhaft ausgeführt wird und nach jedem Durchlauf Daten an Main sendet und nicht der Timer im Main ausgeführt werden muss um selbst abzufragen.

VG
Sirius3
User
Beiträge: 18251
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kriccemis: __blackjack__ hat ja schon geschrieben, dass der Thread keine GUI verändern darf und wahrscheinlich auch nicht Dein Timer, der wohl auch über einen Thread gelöst ist (nur geraten, denn den Code zeigst Du ja nicht).
Außerdem sollte es keine globalen Variablen geben. Wenn Daten übertragen werden, dann geschieht dies über eine Queue. Und das Hauptprogramm fragt regelmäßig ab, ob ein Eintrag in der Queue ist.
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kriccemis: Das ist nicht wirklich ungewöhnlich das bei nebenläufiger Programmierung ”zufällige” Fehler passieren — ist halt nicht-deterministisch. Irgendwas wird da irgendwo falsch gemacht. Ist ohne den Quelltext zu kennen schwierig zu sagen, und selbst mit muss das nicht so einfach ersichtlich sein.

Das der Thread dauerhaft läuft macht wahrscheinlich schon Sinn, denn Threads erstellen, starten, und wieder verwerfen kostet Zeit und Ressourcen.

Da Qt involviert ist, könnte man statt einer Queue und einem Timer der die abfragt, auch das Signal/Slot-System von Qt verwenden. Da gibt es im Zusammenhang mit Threads auch ein paar Sachen auf die man achten muss. Die Qt-Dokumentation hat da was zu.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Kriccemis
User
Beiträge: 20
Registriert: Freitag 8. Mai 2020, 20:10

Danke für die Antworten. ich habe euch mal die entsprechende Funktion angefügt.

Code: Alles auswählen

def get_frame_spots(self, wavelength_list, bild_list):
        """ Get pixel information for each image and spot """

        list_bildverarbeitung = []  # [list_mean_spots,list_mean_bg,wavelength]
        image_spot = None
        image_bg = None

        # Schleife die alle Wellenlängen einmal durchläuft und für jedes Bild die Berechnung durchführt
        for (wavelength, _exposure, _fps, _histogram_mean), image in zip(wavelength_list, bild_list):
            list_median_spot = []
            list_median_bg = []

            # vom eingeladenen Bild eine Kopie für den Hintergrund erzeugen
            try:
                image_bg = image.copy()
            except AttributeError:
                print("Image Copy error for background image!")

            # Zwischenschriit zur ermittlung des maximalen Radius für die Rechtecke
            radius_list = []
            for ((x_axis, y_axis), radius, (_ro, _g, _b), _gruppe) in self.spot_list:
                radius_list.append(radius)

            max_radius = max(radius_list)

            edge_lenght = max_radius + 34       # Halbe Kantenlänge des rechteckigen Rahmens um den Spot
            box_boarder = max_radius - 8        # Halbe Kantenlänge des rechteckigen Auschnittes im Spot

            # Schritt 1: Berechnung des Spothintergrundes je Bild

            # Markiere alle Kreise
            for ((x_axis, y_axis), radius, (_o, _g, _b), _gruppe) in self.spot_list:
                # Zeichnet einen schwarzen Kreis über den Spot, somit sind alle Grauwerte für den Bereich Null (Wichtig für die Mean und Median Berechnung)
                cv2.circle(image_bg, (x_axis, y_axis), radius + 5, (0,0,0), -1)
                # Zeichne Rechteck um die ROI
                cv2.rectangle(image_bg, (x_axis - edge_lenght , y_axis - edge_lenght), (x_axis + edge_lenght , y_axis + edge_lenght), (0,0,0), 1)

            # Speichert alle Bilder ab (Temporärer Schritt)
            # cv2.imwrite("bg_image.png", image_bg)

            # Berechnung des Median und Means des Hintergrundes um jeden Spot
            temp_median_bg = [] # temoräre Liste des Medians aller Spots pro Bild

            circle_count = 0
            for ((x_axis, y_axis), radius, (_o, _g, _b), _gruppe) in self.spot_list:
                # Jeder Sport wird aus dem Bild ausgeschnitten und für den jeweiligen Ausschnitt der Median und Mean ermittelt und in jeweiliger Tabelle zusammen gefasst
                crop = image_bg[y_axis - edge_lenght: y_axis + edge_lenght, x_axis - edge_lenght: x_axis + edge_lenght]
                crop = crop[np.nonzero(crop)]

                median_value = np.median(crop)
                # print(median_value)

                if math.isnan(median_value) is True:
                    median_value = float(0)
                temp_median_bg.append(median_value)

                circle_count = circle_count + 1

            # die temp Listen werden in die Gesamtliste eingefügt
            # print(temp_median_bg)
            list_median_bg.append(temp_median_bg)

            # Schritt 2: Berechung der Spots in jedem Bild

            # vom eingeladenen Bild eine Kopie für den Spot erzeugen
            try:
                image_spot = image.copy()
            except AttributeError:
                print("Image Copy error for spot image!")

            # Markiert alle Kreise
            for ((x_axis, y_axis), radius, (_o, _g, _b), _gruppe) in self.spot_list:
                # Zeichne dicken schwarzen Rahmen um den Spot
                cv2.circle(image_spot, (x_axis, y_axis), radius, (0, 0, 0), 5)
                cv2.circle(image_spot, (x_axis, y_axis), radius + 5, (0, 0, 0), 10)
                # Zeichne schwarzen kleinen Rahmen um den Spot dessen Inhalt zur Median und Mean Berechnung genutzt wird
                cv2.rectangle(image_spot, (x_axis - box_boarder, y_axis - box_boarder), (x_axis + box_boarder, y_axis + box_boarder), (0, 0, 0), 1)

            # Speichert alle Bilder ab (Temporärer Schritt)
            # cv2.imwrite("spot_image.png", image_spot)

            # Berechnung des Median Wertes in jeden Spot
            temp_median_spot = []   # temoräre Liste des Medians aller Spots pro Bild
            circle_count = 0

            for ((x_axis, y_axis), radius, (_o, _g, _b), _gruppe) in self.spot_list:
                # Jeder Sport wird aus dem Bild ausgeschnitten und für den
                # jeweiligen Ausschnitt der Median und Mean ermittelt und in
                # jeweiliger Tabelle zusammen gefasst
                crop = image_spot[y_axis - box_boarder: y_axis + box_boarder, x_axis - box_boarder: x_axis + box_boarder]
                crop = crop[np.nonzero(crop)]

                median_value = np.median(crop)

                if math.isnan(median_value) is True:
                    median_value = float(0)
                temp_median_spot.append(median_value)

                circle_count = circle_count + 1

            # die temp Listen werden in die Gesamtliste eingefügt
            list_median_spot.append(temp_median_spot)
            list_bildverarbeitung.append((list_median_spot, list_median_bg, wavelength))

        return list_bildverarbeitung
Benutzeravatar
grubenfox
User
Beiträge: 601
Registriert: Freitag 2. Dezember 2022, 15:49

Und welche Zeile davon ist die 'line 358'?
Kriccemis hat geschrieben: Mittwoch 26. Juni 2024, 11:34 File "C:\Users\LCTF\Desktop\experimental\run_camera.py", line 358 in get_frame_spots
Kriccemis
User
Beiträge: 20
Registriert: Freitag 8. Mai 2020, 20:10

Code: Alles auswählen

            try:
                image_spot = image.copy()
Wie ich ja bereits erklärt habe ist der Effekt des Programmabsturzes stark beeeinflustt wieviele Frames ich pro Wellenlänge aufnehme und mittle.
Bei z.b: 5 frames pro wl habe ich nahezu keine abstürze. alles drüber steigt die tendenz.

hat es was mit der Kamera zu tun?
Bzw kommt der fehler ja auch wenn ich von dem jeweiligen Frame zwei kopien erstellen will um intensitätsbereichungen von Spot und Huntergrund vorzunehmen.
Sirius3
User
Beiträge: 18251
Registriert: Sonntag 21. Oktober 2012, 17:20

Datentypen haben in Variablennamen nichts verloren. Statt _list überall anzuhängen, benutzt man einfach den Plural, also wavelengths, images. Wenn aber zu jedem Bild eine Wellenlänge gehört, dann ist die Datenstruktur als zwei Listen falsch. Das was in wavelengths steht, ist eh schon komplexer als eine Wellenlänge. Da bietet es sich an, auf etwas mächtigeres als Tuple umzusteigen und das jeweilige Bild gleich mit in diese Datenstruktur zu packen.
Man definiert keine Variablen auf Vorrat. image_bg und image_spot auf None zu setzen ist unsinnig.
`AttributeError` zeugt von einem Programmierfehler und sollte im Normalfall gar nicht abgefangen werden. Hier macht es keinen Sinn.
Die Elemente in spot_list sollten auch etwas anderes sein als Tuple, zumindest Namedtuple, dann wird das zu:

Code: Alles auswählen

max_radius = max(spot.radius for spot in spots)
Variablennamen sollten keine Schreibfehler enthalten, also edge_length und box_border. Alle lokalen Variablen sind temporär, das muss man nicht in den Variablennamen schreiben: median_bg
Auf True prüft man nicht explizit, erst recht nicht per `is True`.
0 ist schon eine Zahl, die muß man nicht per float in eine Zahl umwandeln. Wenn es wirklich ein float sein soll, dann schreibt man `0.`.
Man zählt nicht händisch in einer Schleife einen Counter hoch (circle_count), sondern benutzt enumerate. `circle_count` wird aber gar nicht gebraucht, und kann weg.
Warum heißt eigentlich der Ursprung deines Spots x_axis und y_axis? Was hat das mit Achsen zu tun?
Im Kommentar steht etwas von Mittelwert, der aber gar nicht berechnet wird. Was ist nun richtig? Der Kommentar oder der Code?

Code: Alles auswählen

            # Berechnung des Median und Means des Hintergrundes um jeden Spot
            spot_medians = []

            for spot in self.spots:
                # Jeder Sport wird aus dem Bild ausgeschnitten und für den jeweiligen Ausschnitt der Median ermittelt
                x, y = spot.origin
                crop = image_bg[y - edge_length: y + edge_length, x - edge_length: x + edge_length]
                median = np.median(crop[crop != 0])
                if np.isnan(median):
                    median = 0
                spot_medians.append(median)
Warum wird an get_frame_spots nur Attribute des selben Objekt übergenen (self.cal_list und self.images)?
Offensichtlich gibt es Probleme beim Zugriff auf den Speicher eines Bildes. Die Frage ist also, wo wird self.images verändert? Und passiert das aus verschiedenen Threads heraus?
Antworten