ctypes Pointer auf Speicherbereich

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
PyNewby
User
Beiträge: 2
Registriert: Mittwoch 22. August 2018, 08:27

Hallo,
ich bräuchte ein bisschen Hilfe biem Auslesen eines Speicherbereichs.
Ich habe folgendes Problem:
Eine Funktion einer dll bietet eine Funktion, die ein Rohdaten-Bild, in drei RGB Werte/Pixel konvertiert und einen Pointer auf einen Speicherbereich zurückliefert, wo dann das ergebnis liegen soll.
Leider hab ich keine Ahnung, wie ich auf die Werte der jeweiligen Pixel zugreifen kann.
Ich bekomme immer nur die Adresse auf das LP_c_ushort Objekt zurück.
Eingebaut habe ich das ganze momentan so:

Code: Alles auswählen

#XResAct = 1920, YResAct = 1440
IMG = (POINTER(ctypes.c_ushort) * XResAct.value *YResAct.value*3)() #Zielspeicher
#BufRef = Erklärung: Dort liegt das Rohdaten Bild
iRet = Convert16TOCOL(conv_handle, 0,2,XResAct.value, YResAct.value, byref(BufAdr), byref(IMG)) 
print("...converting image:", MErrText.MCAM_GetError(iRet)) #prüfen ob Konvertierung geklappt hat
Funktion läuft durch und Speicher scheint auch nicht nach NULL zu zeigen. Hab ich mit:

Code: Alles auswählen

print("Check ptr", bool(IMG))
geprüft.

Ich dachte eigentlich ich könnte jetzt einfach auf die Pixelwerte zugreife. So etwa:

Code: Alles auswählen

#Beispiel Pixel Farbe holen:
tmp_r = IMG[x][y][0]
tmp_g = IMG[x][y][1]
#...

TmpRed = tmp_r.value
TmpGreen = tmp_g.value
#... 
Leider komme ich hier nicht weiter. hab schon alles mögliche durchprobiert: repr(tmp_r.raw) fällt auf die Nase tmp_r.contents
klappt auch nicht. Und IMG[x][y][z] liefert immer nur einen Verweis auf das Objekt, nicht den Inhalt.
Langsam bin ich frustriert.

Wie geht das denn richtig? Wie komme ich an den Inhalt der Elemente von IMG ran?

Danke für Eure Hilfe!

Gruß!
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

In Python werden Variablen generell klein geschrieben. Komplett großgeschriebene Namen sind Konstanten.

Statt sich den Speicher zu reservieren und später zu versuchen darauf zuzugreifen, erzeugt man ein Numpy-Array, mit dem man schon den schönen Matrix-Zugriff hat und übergibt dessen Speicher an die Funktion.

Code: Alles auswählen

width = 1920
height = 1440

image = numpy.ndarray((width, height, 3), dtype='u2')
result = Convert16TOCOL(conv_handle, 0, 2, width, height, byref(BufAdr), image.ctypes.data)
print("...converting image:", MErrText.MCAM_GetError(result))
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PyNewby: Der Fehler ist das `POINTER()`. Du hast ein dreidimensionales Array von Zeigern auf `u_short`-Werte erstellt, Du willst aber ein dreidimensionales Array von `u_short`-Werten haben.

Code: Alles auswählen

In [12]: IMG = (ctypes.POINTER(ctypes.c_ushort) * 1920 * 1440 * 3)()

In [13]: IMG[0][0][0]
Out[13]: <__main__.LP_c_ushort at 0xa87fb24>

In [14]: IMG = (ctypes.c_ushort * 1920 * 1440 * 3)()

In [15]: IMG[0][0][0]
Out[15]: 0
Du musst dann vielleicht auch noch die Reihenfolge der Dimensionen anpassen, denn der erste Index ist hier jetzt nicht die X-Koordinate:

Code: Alles auswählen

In [16]: len(IMG)
Out[16]: 3

In [17]: len(IMG[0])
Out[17]: 1440

In [18]: len(IMG[0][0])
Out[18]: 1920
Ich würde das übrigens nicht `IMG` nennen, denn das ist ja ganz offensichtlich keine Konstante, und KOMPLETT_GROSS ist die Namenskonvention für Konstanten. Alle anderen Namen ausser Klassen (MixedCase) schreibt man in Python klein_mit_unterstrichen. Also `i_ret` statt `iRet`, wobei ungarische Notation nicht nur in Python nicht mehr Stand der Dinge ist, also eher `result` oder `return_code` oder so.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Nachtrag: Die Einzelwerte des Ergebnisarrays sollten für diese Funktion Bytes und nicht ``unsigned short`` sein. Funktionsprototyp aus der Header-Datei:

Code: Alles auswählen

int PCOCONVERT_API PCO_Convert16TOCOL(HANDLE ph, int mode, int icolmode, int width, int height, word *b16, byte *b8);
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
PyNewby
User
Beiträge: 2
Registriert: Mittwoch 22. August 2018, 08:27

Hallo Leute,
sorry für die lange Sendepause. War auf Reise...
@__Blackjack__: Wow, super danke. Ich glaube das war die Lösung.
Wie kommts, dass Du sogar den Funktionsprototyp aus der Headerdatei hast?
Die von Dir vorgeschlagene Lösung scheint auf jeden Fall die richtige zu sein! Tausend Dank nochmal.
Leider fällt das Ganze immernoch auf die Nase, weil ich anscheinend schon beim vorherigen Speicherreservieren Mist baue und den Pointer für die zu reservierende Speicheradresse nicht richtig aufsetze.
Der sollte ja laut Doku auch noch ein Pointer auf einen Pointer sein..., der NULL initialisiert ist :-/

Code: Alles auswählen

SC2_SDK_FUNC int WINAPI PCO_AllocateBuffer ( HANDLE ph, //in 
											SHORT* sBufNr, //in/out 
											DWORD dwSize, //in
											WORD** wBuf, //in/out <- Wie muss das denn dann behandelt werden
											HANDLE* hEvent //in/out
);
Laut Doku sollte das ein:
"Pointer to a pointer (WORD* variable) of a memory region:
• On input:
o NULL: allocate memory internal
" sein

Wie muss denn dass dann deklariert werden und wie muss die argtypes entsprechend deklariert sein?
Kann ich den (hoffentlich) danach mit einer Adresse gefüllte "Pointer auf Pointer" einfach mit byref(wBuf) in die Covert-Funktion füttern?

Sorry für vielleicht blöde Fragen, aber bin bislang weder der C noch der Python Pro.

Trotzdem schon mal Danke für Eure kompetente Hilfe!

Nachtrag: bekomme beim Ausführen von Convert16TOCOL eine lesende Speicherverletzung. Daraus würde ich ableiten, dass mit der reingefütterten Adresse was nicht stimmt...

Gruß
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PyNewby: Den Funktionsprototyp habe ich, weil ich nach den Funktionsnamen gesucht habe und man dann recht schnell auf die Seite des Kameraherstellers kommt. Dort kann man sich dann im Supportbereich die erste Kamera aussuchen und findet unter Software dann auch das SDK, logischerweise inklusive der Headerdateien.

Da ich hier weder Windows noch eine der Kameras habe, ist das hier jetzt total ungetestet:

Code: Alles auswählen

from ctypes import (
    byref, c_int, c_int16, c_int32, c_uint16, c_void_p, POINTER, windll
)

DWORD = c_int32
HANDLE = c_void_p
SHORT = c_int16
WORD = c_uint16


class Error(RuntimeError):
    
    def __init__(self, func, arguments, error_code):
        RuntimeError.__init__(
            self, '{}({}) -> {}'.format(func.__name__, arguments, error_code)
        )
        self.func = func
        self.arguments = arguments
        self.error_code = error_code


def errcheck(result, func, arguments):
    if result != 0:
        raise Error(func, arguments, result)
    return result


lib_sc2_cam = windll('SC2_Cam.dll')

_allocate_buffer = lib_sc2_cam.PCO_AllocateBuffer
_allocate_buffer.argtypes = [
    HANDLE, POINTER(SHORT), DWORD, POINTER(POINTER(WORD)), POINTER(HANDLE)
]
_allocate_buffer.restype = c_int
_allocate_buffer.errcheck = errcheck


def allocate_buffer(camera, size):
    buffer_number = SHORT(-1)
    buffer = POINTER(WORD)()
    event_handle_ptr = POINTER(HANDLE)()
    _allocate_buffer(
        camera, byref(buffer_number), size, byref(buffer), event_handle_ptr
    )
    return (buffer_number.value, buffer, event_handle_ptr.contents)
Neben der Frage ob das funktioniert, ist natürlich auch die Frage ob Dir das so reicht, denn ich habe die API der Python-Funktion so klein wie möglich gehalten in dem ich -1 bzw. NULL für die Argumente übergebe, bei denen die Bibliothek dann selbst Werte auswählt.

Rückgabe ist die Nummer des Buffers den die Bibliothek ausgewählt hat, ein Pointer auf den Speicherbereich des Buffers, und das Event-Handle.

Wobei die Funktion eigentlich besser ein Buffer-Objekt zurückgeben würde, das diese Werte + die `camera` kapselt, denn `camera` und Buffernummer braucht man später ja noch mal um `PCO_FreeBuffer` aufzurufen. Neben einer entsprechenden Methode auf der `Buffer`-Klasse um diese Funktion aufzurufen dann noch mindestens etwas mit `weakref.finalize` und/oder einen Kontextmanager aus dem `Buffer`-Objekt machen, damit die Ressource auf Python-Seite auch sicher wieder freigegeben werden kann.

Was mich an der Stelle wundert, ist das Du erst bei `PCO_AllocateBuffer()` Probleme bekommst, bei den Strukturen und den mindestens einem halben Dutzend Funktionen die davor aufgerufen werden um die Kamera zu benutzen, aber noch keine Probleme aufgetreten sind. Und die Kamera wäre auch ein Kandidat für eine Klasse.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten