DLL laden mit ctypes bzw. in C Erweiterung

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
hscherer
User
Beiträge: 14
Registriert: Donnerstag 25. März 2010, 22:07
Wohnort: München

Hallo alle zusammen,
ich versuche mit Python eine Kamera zu steuern und die Kameradaten auszulesen. Vom Kamera Hersteller habe ich eine DLL (eigentlich 8 DDLs, die sich gegenseitig laden) und eine "lib" Datei mit zugehöriger Header Datei. Die Kamera hat eine Auflösung von 640x512 Pixel mit einer Frame Rate von 50 Bilder pro Sekunde (16 Bit Daten schwarz/weiß). Um die Daten aufzunehmen, wird von der Kamera Firmware beim initialisieren ein Ringbuffer aufgebaut, der kontinuierlich mit Daten gefüllt wird. Werden die Daten abgefragt (DLL Funktion GetImage()) erhält man einen C Zeiger, der wiederum einen Zeiger enthält, der auf den Speicheranfang des zuletzt vollständig aufgenommen Bildes zeigt. Bei jedem weiteren Aufruf wird immer nur der zweite Speicherbereichszeiger entsprechend auf einen neuen Bereich im Ringbuffer umgesetzt.
Als erstes habe ich versuch mit "ctypes" die DLL direkt anzusprechen:

Code: Alles auswählen

import numpy as np
import ctypes
import ctypes.wintypes

if __name__ == '__main__':
    KameraDLL = ctypes.cdll.LoadLibrary("CAMERA_DeviceControl.dll")
    ImageDaten_Flat = np.zeros(640 * 512, dtype=np.uint16)
    ptr = ctypes.POINTER(ctypes.c_short)() 
    Result = KameraDLL.GetImage(ctypes.byref(ptr))
    for i in range(640*512): # Daten in numpy array umkopieren
            ImageDaten_Flat[i]=ptr[i]
    ImageDaten_Flat.reshape(640, 512)
    ....
    ....
diese Konstruktion funktioniert eine Weile, wenn ich laufend Daten von der Kamera abrufe. Aber dabei gibt es zwei Probleme:
1. ich muß die Daten in ein "numy" Feld umkopieren, um sinnvoll damit arbeiten zu können (siehe die "for" Schleife im Code Beispiel). Jede andere Variante die Daten um zu kopieren (lambda Form, [:], ...) führt dazu, daß der Python Interpreter einfach stehen bleibt (Prozessorlast 0%, aber nichts passiert mehr)
2. die Konstruktion läuft nur unter "main". Aufruf in einer Unterroutine (Klassen Methode) führt direkt zum Stillstand des Python Interpreter

Da ich trotz verschiedener Hinweise im Netz keine zufriedenstellende Lösung hin bekommen habe, dachte ich das Problem mit einer C-Erweiterung zu umgehen( Compilieren erfolgt mit "distutils" und "Mingw" Compiler):

Code: Alles auswählen

#include <Python.h>

#define BOOL unsigned int
#define WORD unsigned short
#define DWORD unsigned int
#define BYTE unsigned char   

#define CAMERA_API __declspec(dllexport)
#define CPUFIRMWAREDOWNLOADINITIATED -100

CAMERA_API int CAMERA_InitSystem(void);
CAMERA_API int CAMERA_GetImage(WORD** ppImage);

static PyObject* __init__(PyObject *self, PyObject *args)
{
 printf("CAMERA Initialisierung laeuft"); /* wird spaeter benoetigt */
 return Py_None;
}

static PyObject* InitSystem(PyObject *self, PyObject *args)
{
 int Result;
 char buf[5];
 
 while(1) {
     Result = CAMERA_InitSystem();
	 if(Result == CPUFIRMWAREDOWNLOADINITIATED) {
	     printf("Wait while Camera is initializing");
		 continue;
		 }
	 else if(Result) {
	    itoa(Result, buf, 10);
	        PyErr_SetString(PyExc_ValueError, strcat("CAMERA_InitSystem() returned error ", buf));
                return(NULL);
		}
	 else return Py_None;
    }
}

static PyObject* SetIllumination(PyObject *self, PyObject *args)
{
 int Illu;

 if(!PyArg_ParseTuple(args, "i", &Illu)) { 
     PyErr_SetString(PyExc_ValueError, "hier wollte ich nicht hin");
     return(NULL);
	 }
 if( (Illu < 90) || (Illu > 180) ) {
      PyErr_SetString(PyExc_ValueError, "ungueltiger Wert fuer Helligkeit [90, 180]");
      return NULL;
      }

      return Py_None;
}

static PyObject* Exit(PyObject *self, PyObject *args)
{
 OCT_Exit();
 return Py_None;
}

static PyMethodDef CAMERA_DeviceControlMethods[] = 
{
    {"__init__", __init__, METH_VARARGS, "Kamera initialisieren"},
    {"CAMERA_InitSystem", InitSystem, METH_VARARGS, "Kamera Geraet initialisieren"},
    {"CAMERA_SetIllumination", SetIllumination, METH_VARARGS, "Kamera Helligkeit einstellen"},
    {"CAMERA_Exit", Exit, METH_VARARGS, "Kamera System beenden"},
    {NULL, NULL, 0, NULL},
};

static PyMethodDef ModuleMethods[] = { {NULL} };

#ifdef __cplusplus
extern "C"
#endif
void initCAMERA_DeviceControl()
{
    PyMethodDef *def;

    /* create a new module and class */
    PyObject *module = Py_InitModule("CAMERA_DeviceControl", ModuleMethods);
    PyObject *moduleDict = PyModule_GetDict(module);
    PyObject *classDict = PyDict_New();
    PyObject *className = PyString_FromString("CAMERA_DeviceControl");
    PyObject *CAMERA_DeviceControlClass = PyClass_New(NULL, classDict, className);
    PyDict_SetItemString(moduleDict, "CAMERA_DeviceControl", CAMERA_DeviceControlClass);
    Py_DECREF(classDict);
    Py_DECREF(className);
    Py_DECREF(CAMERA_DeviceControlClass);
    
    /* add methods to class */
    for (def = CAMERA_DeviceControlMethods; def->ml_name != NULL; def++) {
	PyObject *func = PyCFunction_New(def, NULL);
	PyObject *method = PyMethod_New(func, NULL, CAMERA_DeviceControlClass);
	PyDict_SetItemString(classDict, def->ml_name, method);
	Py_DECREF(func);
	Py_DECREF(method);
    }
}
mit zugehörigem Python Programm

Code: Alles auswählen

# -*- coding: utf-8 -*-
"""
Created on Tue May 29 08:16:53 2012
@author: HS
@status: Nur Testdatei
@summary: Test Klassen erzeugen in C als Python Erweiterung
"""
import CAMERA_DeviceControl

if __name__ == '__main__':
    Kamera = CAMERA_DeviceControl.CAMERA_DeviceControl() 
    Kamera.CAMERA_InitSystem()
    print(" ------------ Helligkeit setzen", Kamera.CAMERA_SetIllumination(110))
    Kamera.CAMERA_Exit()
    print("----- Fertig ---") 

Leider funktioniert diese Konstruktion völlig unberechenbar.
1.tes Problem: auch hier kommt es zu einem Speicher Problem. Wird die Initialisierung der Kamera aufgerufen, klappt dies, aber beim Aufruf der Funktion (CAMERA_SetIllumination()) steht der Python Interpreter wieder. Der C-Code Teil

Code: Alles auswählen

static PyObject* SetIllumination(PyObject *self, PyObject *args)
{
 int Illu;

 if(!PyArg_ParseTuple(args, "i", &Illu)) { 
     PyErr_SetString(PyExc_ValueError, "hier wollte ich nicht hin");
     return(NULL);
	 }
...
...
Ist hier die Ursache. Aus dem Aufruf "PyArg_ParseTuple(args, "i", &Illu) kommt der Interpreter nicht mehr heraus, wahrscheinlich weil die Adresse &Illu plötzlich falsch ist.
2. kommentiere ich die Initialisierung aus erscheint, beim Aufruf der Funktion CAMERA_SetIllumination() die Fehlermeldung die im Code vorgesehen ist

Nach dieser länglichen Ausführung zu meinen beiden Fragen:
1. die Kamera DLL reserviert sehr viel Speicher (wahrscheinlich mit malloc() o.ä), der sich der Kontrolle von Python entzieht, aber anscheinend doch vom Python Heap Space abgezogen wird --> daher hängt sich der Python Interpreter in der reinen Python Lösung, aber auch in der C-Variante auf.
Gibt es also eine Möglichkeit die DLL außerhalb des Python Heap Space zu instanzieren (entweder in Python direkt oder als C-Code Lösung oder einer Compiler Einstellung)?
2. wieso erscheint beim Aufruf der Kamera Funktion CAMERA_SetIllumination() die Fehlermeldung, obwohl der Parameter Aufruf richtig zu sein scheint

Als System verwende ich Windows XP und Windows Vista (32 Bit Systeme, Verhalten ist auf beiden Systemen nahezu gleich) mit dem Paket "pythonxy" (Python 2.7.3 mit numpy 1.6.1)

Ich bin für jede Hilfe dankbar, da meine Alternative darin besteht alles, incl. Grafikausgabe in C umsetzen zu müssen.

Horst
Zuletzt geändert von Anonymous am Mittwoch 30. Mai 2012, 21:33, insgesamt 1-mal geändert.
Grund: Quelltext in C-Code-Tags gesetzt.
BlackJack

@hscherer: Es ist immer etwas schwierig Aussagen über Quelltext zu treffen den man nicht kennt. Wenn Du den `ctypes`-Code in einer Funktion oder Methode ausführst, muss ja irgend etwas anders sein, wenn es sich anders verhält. Aber man kann halt nur raten was das genau ist.

Bei der Beschreibung hört es sich an als wenn `GetImage()` beim ersten Aufruf etwas anderes macht als bei allen folgenden — da würde ich mal behaupten es macht bei jedem Aufruf das selbe: Den Zeiger setzen.

Statt der Schleife sollte auch ``flat_image_data[:] = ptr[0:640 * 512]`` gehen. Ein einfaches [:] auf der `ptr`-Seite geht nicht, weil das Objekte keine Grösse kennt. Dazu hätte man einen Zeiger-Objekt auf ein `c_short`-Array-Typobjekt der entsprechenden Grösse erstellen müssen. Dann kann man auch `numpy.frombuffer()` verwenden. Aber Achtung: das erstellt erst einmal nur eine Sicht auf den Speicherbereich! Wenn man die Daten in Sicherheit bringen möchte, sollte man eine Kopie erstellen.

Code: Alles auswählen

In [30]: a = (ct.c_uint16 * 10)()

In [31]: len(a)
Out[31]: 10

In [32]: a[:]
Out[32]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

In [33]: b = np.frombuffer(a, dtype=np.uint16)

In [34]: b
Out[34]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=uint16)

In [35]: b[0] = 42

In [36]: a[0]
Out[36]: 42
Mit einer selbst geschriebenen C-Erweiterung holst Du Dir noch mehr Komplexität ins Boot.

`strcat()` in der `InitSystem()` kannst Du so nicht benutzen. Wo auch immer das erste Argument im Speicher steht, das was dahinter steht wird durch den Inhalt von `buf` überschrieben. Da kann man Glück haben, dass da nichts wichtiges stand, oder es werden Daten überschrieben, die Folgefehler nach sich ziehen, oder das Programm stoppt mit einer Speicherzugriffsverletzung.

Ich habe versucht Deinen `initCAMERA_DeviceControl()`-Code nachzuvollziehen, habe aber in keiner Dokumentation `PyCFunction_New()` finden können‽ Wo hast Du das denn her?

Zu den beiden Fragen: 1. Wie kommst Du darauf, dass der Speicherverbrauch der Grund für das hängen ist? Ich weiss nicht was Du für Vorstellungen vom „Python-Heap” hast, aber das ist ganz normaler Speicher des Prozesses. Wenn Du etwas ausserhalb des Speichers des Prozesses haben willst, musst Du es in einem anderen Prozess tun.

2. Mach doch Deinen Code erst einmal einfacher, ohne diese zumindest momentan unnötige Klasse in dem Modul.
hscherer
User
Beiträge: 14
Registriert: Donnerstag 25. März 2010, 22:07
Wohnort: München

Hallo BlackJack,
vielen Dank für deine Hilfe. Aber vorab, es ist und war nie mein Ziel mit Python irgendwelche C Erweiterungen zu schreiben. Das war nur eine Notlösung, mein Problem (siehe unten) zu umgehen (außerdem habe ich eine Menge lernen können über "distutils" und was in C mit Python geht). Die Vorgehensweise eine Methode als C Erweiterung zu erzeugen, habe ich im Netz gefunden und nur die Kamerateile ergänzt (allerdings mit mäßigem Erfolg, wie man sieht).
Aber zurück zum eigentliche Problem. Ich habe den Python Code auf das nötigste zusammengestrichen. Es wird von der Kamera laufend ein Bild übertragen und mit "matplotlib" dargestellt.

Code: Alles auswählen

# -*- coding: utf-8 -*-
"""
Created on Fri May 25 08:08:38 2012

@author: HS
"""

import ctypes
import ctypes.wintypes
import os
import numpy as np
import sys
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation

import sys

# Kamera initialisieren
def Kamera_Init(DLL):
    print("Initialisiere")
    DLL.CAMERA_InitSystem()
    
    print("Setze Scan Parameter")
    Xsize = ctypes.wintypes.WORD(0)
    Ysize = ctypes.wintypes.WORD(0)
    ImprovedRes = ctypes.wintypes.WORD(0)
    BytesPerPixel = ctypes.wintypes.BYTE(0)
    XConvFactor = ctypes.wintypes.DOUBLE(0)
    YConvFactor = ctypes.wintypes.DOUBLE(0)
    ImprovedResFactor = ctypes.wintypes.DOUBLE(0)

    Mode = 2 # normale Datenübertragung
    Result = DLL.CAMERA_StartScanning(Mode,
                     ctypes.pointer(Xsize),ctypes.pointer(Ysize),
                     ctypes.pointer(ImprovedRes),ctypes.pointer(BytesPerPixel),
                     ctypes.pointer(XConvFactor),ctypes.pointer(YConvFactor),
                     ctypes.pointer(ImprovedResFactor) )
    ImageParameter = {"ScanMode": Mode,
                      "Xsize": Xsize.value, 
                      "Ysize": Ysize.value,
                      "ImprovedRes": ImprovedRes.value,
                      "BytesPerPixel": BytesPerPixel.value,
                      "XConvFactor": XConvFactor.value,
                      "YConvFactor": YConvFactor.value,
                      "ImprovedResFactor": ImprovedResFactor.value,
                      "ImageSize": Xsize.value*Ysize.value*
                                   ImprovedRes.value                              
                      }
    return(ImageParameter)

# Daten von Ringbuffer abfragen
def Kamera_GetImage(DLL, ImageDaten_Flat, ImageParameter):
    print("hier GetImage() ----- 1")
    Kamera_DatenFeld = ctypes.POINTER(ctypes.c_short)() 
    print("hier GetImage() ----- 2")
    Result = DLL.CAMERA_GetImage(ctypes.byref(Kamera_DatenFeld))
    print("hier GetImage() ----- 3")
#    for i in range(ImageParameter["ImageSize"]): # Daten in numpy array umkopieren
#        ImageDaten_Flat[i]= Kamera_DatenFeld[i]
    ImageDaten_Flat = Kamera_DatenFeld[0:327680] # 327680 = 512 * 640 = ImageSize
    return(ImageDaten_Flat.reshape(ImageParameter["Ysize"], ImageParameter["Xsize"]))



if __name__ == "__main__":
    # in das Verzeichnis mit den DLLs wechseln, da sonst die zusätzlich benötigten DLLs nicht gefunden werden 
    DLL_Ablageort = "c:\\CAMERA\\Shared\\"
    os.chdir(DLL_Ablageort)
    Kamera_DLL = ctypes.cdll.LoadLibrary("CAMERA_DeviceControl.dll")

    ImageParameter  = Kamera_Init(Kamera_DLL)       
    print("-------------- ", ImageParameter)                                     
    ImageDaten_Flat = np.zeros(ImageParameter["ImageSize"], dtype=np.uint16)
    ImageDaten       = Kamera_GetImage(Kamera_DLL, ImageDaten_Flat, ImageParameter)


    fig = plt.figure()
    im = plt.imshow(ImageDaten, cmap=plt.get_cmap('jet'), 
                    norm=matplotlib.colors.Normalize(0,4095),
                    interpolation=None, animated=True)
    Anzahl= 1
    
    def updatefig(*args):
        global Anzahl, Kamera_DLL, ImageDaten_Flat, ImageParameter
        AA = Kamera_GetImage(Kamera_DLL, ImageDaten_Flat, ImageParameter)
        Anzahl = Anzahl + 1
        print("Bilder Anzahl: ", Anzahl)
        im.set_array(AA)
        return im,
    
    ani = animation.FuncAnimation(fig, updatefig, interval=1, blit=True)
    plt.show()

    Kamera_DLL.CAMERA_Exit()
    print(" --- bin fertig ---") 
Der Code ist so aber nicht lauffähig. Nur wenn ich die "for" Schleife in der Unterroutine "Kamera_GetImage()" wieder auskommentiere und dafür die Zeile
"ImageDaten_Flat = Kamera_DatenFeld[0:327680]" lösche funktioniert der Code. Die "print" Anweisungen dienen nur zur Kontrolle bis wohin das Ganze läuft.
Der Code oben läuft bis zum "hier GetImage() ------- 3" und dann steht der Python Interpreter (CPU Last 0% , Speicher weiter belegt--> wird erst nach beenden des Interpreters wieder frei gegeben).

Problem 2: auch wenn ich die lauffähige Variante nehme und bis auf den Aufruf "main()" unverändert in eine GUI einbinde steht das Programm an der gleichen stelle fest. Lauffähig ist der Code nur dann wieder, wenn ich sowohl den Pointer "Kamera_DatenFeld = ctypes.POINTER(ctypes.c_short)()" als auch das Datenfeld selber "ImageDaten_Flat = np.zeros(ImageParameter["ImageSize"], dtype=np.uint16)" außerhalb der GUI Routinen (PyQT) global definiere.

Ich habe mit dem bei der Kamera mitgelieferten C Beispiel den Vorgang der DLL etwas genauer analysiert. Beim Initialisieren legt die DLL einen Speicherbereich und einen Pointer fest. Der Speicherbereich und der Pointer bleiben bis zum Kamera_Exit() unverändert und werden auch so im Python Programm immer wieder unverändert zurückgegeben. In der Adresse des Pointers, liegt ein weitere Pointer der in einen Bereich des von der DLL reservierten Speichers zeigt und bei jedem neuen Frame um die Frame größe verschoben wird. Je nach Mode springt dann der Zeiger früher oder später wieder zurück auf den ersten Frame (Ringbuffer für max. 50 Bilder --> kann aber per Mode reduziert werden).

Dieses Verhalten kann ich mit der lauffähigen Version des Python Codes (Code mit der "for" Schleife) auch nachvollziehen. Daher vermutete ich ein Problem, mit der Speicherverwaltung von Python.

Deine Vorschlag mit dem "numpy.frombuffer()" hatte ich auch schon im Netz gefunden und ausprobiert. Aber bei all diesen Varianten hängt sich der Interpreter auf (wie oben CPU Last 0 %, Speicher ist weiter belegt).

Irritierend ist auch, daß die lauffähige Version - eingebunden als eigener Task in eine GUI (Felder global definiert) - zunächst unverdächtig läuft (die GUI läßt sich weiter bedienen und die Frames der Kamera werden angezeigt). Nach einmaligem Umschalten des Modes (GUI Funktion für andere Bilddatengröße) funktioniert alles eine "zeitlang" nur die GUI wird plötzlich extrem langsam (Schieberegler nahezu unbedienbar). Schalte ich dann auf den vorhergehenden Mode zurück steht der Task mir der Datenaufnahme wieder an dem gleichen Punkt wie im Code Beispiel fest, dafür funktioniert die GUI wieder verzögerungsfrei.

In dem C Demo der Kamera habe ich kontrolliert, was bei der Mode Umschaltung passiert --> nämlich gar nichts. Nur der Frame Zeiger springt um eine andere Größe. Der von der DLL reservierte Speicher bleibt komplett unverändert.

hscherer
hscherer
User
Beiträge: 14
Registriert: Donnerstag 25. März 2010, 22:07
Wohnort: München

Hallo BlackJack,
das Problem liegt sehr wahrscheinlich an der von mir verwendeten Entwicklungsumgebung "Spyder 2.1.9".
Durch Zufall habe ich das Programm durch "Doppelklick" im Explorer gestartet. In diesem Fall öffnete sich zwar zusätzlich ein "DOS" Fenster, aber alle Funktionen arbeiten einwandfrei. Ich kann zwischen allen Moden der Kamera problemlos hin- und herschalten, kopieren mit xxx = Pointer[0:Imagegröße], alles ohne das das System sich aufhängt oder stehen bleibt.
Starte ich das Programm hingegen erneut in der Spyder Umgebung, treten reproduzierbar die oben geschilderten Probleme auf.
Morgen werde zum einen den von dir gemachten Vorschlag, eine „numpy“ Buffer Konstruktion zu verwenden, noch einmal ausprobieren ohne dabei die Entwicklungsumgebung zu verwenden und zusätzlich teste ich noch, ob mein Problem mit der "C-Variante" ebenfalls auf die Spyder Umgebung zurückzuführen ist.
Ich werde meine Erkenntnisse noch hier veröffentlichen und dann diesen Diskussionspunkt als gelöst schließen.

Nochmals vielen Dank für deine Bemühungen
hscherer
hscherer
User
Beiträge: 14
Registriert: Donnerstag 25. März 2010, 22:07
Wohnort: München

Hallo BlackJack,

das Problem liegt tatsächlich an der von mit verwendeten IDE Spyder 2.1.9. Die reine Python Lösung verhält sich unterschiedlich, ob ich das Programm aus der Spyder IDE heraus starte oder von der Konsole.
Das Programmstart aus Spyder verhält sich nur dann genau so wie der direkte Start von der Konsole, wenn ich das Einbinden der DLL vor dem Aufruf von
main() erledige:

Code: Alles auswählen

import ctypes
import PyQt4
import os
... 

os.chdir("Pfad zur DLL")
DLL = ctypes.cdll.LoadLibrary("DLL Dateiname")

if __name__ == "__main__":
    Mode = DLL.GetMode(1) # Abfragen der Kamera Einstellung imd Standard Mode (Mode 1)
    .....
    .....
Ausschließlich mit dieser Konstruktion läuft das Programm (siehe oben) wirklich fehlerfrei und ohne Seiteneffekt (Hängenbleiben des Python Interpreters) sowohl von der Konsole aus auch aus Spyder heraus.
Auch der Versuch in C, der tatsächlich unsauber ist, verhält sich unterschiedlich, wenn ich das Programm direkt aus der Konsole aufruft statt aus der Spyder IDE. Ob die Lösung mit dem Umstellen der Instanzierung der DLL vor main() hier auch hilft habe ich aber nicht weiter untersucht, da die C Variante nur eine Notlösung gewesen wäre.

Die von dir vorgeschlagene Lösung mit einem numpy Buffer funktioniert nach Umstellen der DLL Instanzierung einwandfrei und ist genau das was ich benötige.Nochmals vielen Dank für dein Unterstützung

Horst Schererer
:D
Antworten