RegiStax 0 Posted July 14 (edited) Hi (as suggested by Pyscripter I move this discussion into this support-forum), Ive studied many of the demo's and had the idea that Demo29 was the best point to start looking for a good entrypoint to exchange image-data (16bit so not bitmap) using python4delphi. I am using P4D in my project to use a set of opencv procedures on a large set of stored images. The reading of the files is done in python. The result of this calculation is a floatingpoint array that I need to push back to the Pascal side. Thusfar I am doing this by simply saving a 16bit representation of that array in python as a png and then loading that back in Lazarus. This works but has he disadvantage of : A) write/load the image over IO isnt looking "optimized" for speed B) the floating point becomes a 16bit image loosing quality. As stated I studied Demo29 as I had the idea I could overcome disadvantage A. I implemented a stream (as shown in the example) that wrote 16 bit data to python and received 16 bit back using that same stream-approach. This is however a slower than simply writeing/reading a PNG. So I am searching for a "better" solution but have not seen an example where a 3channel 2D-floating point array gets exchanged between python/pascal. EDIT: Pyscripter suggested to use DEMO35 as a baseline. And although I have tested that before I still have no clue how to declare a multidimensional array of floating point using that example. But my request also mainly asks if anybody has done this and is willing to share some basic code. Otherwise I will have to find that out myself, nothing wrong with that ! cheers Cor waveSharp Free Image sharpen Software https://github.com/CorBer/waveSharp TeensyBat Bat Detector https://github.com/CorBer/teensy_batdetector RegiStax Free Image Processing Software http://www.astronomie.be/registax Edited July 14 by RegiStax Share this post Link to post
RegiStax 0 Posted July 14 I have adapted the code from DEMO35 to be used with 64bit floating (see below). However this is still a 1D array. Redefinition of the array type inside pascal and rewrite of the DoRun method. Tested and working as expected. type PIntArray = ^TIntArray; TIntArray = array[0..N - 1] of Double; { TBufferProtocol } procedure TBufferProtocol.DoRun; var SW: TStopwatch; Sum: single; np: Variant; arr: Variant; np_arr: PPyObject; PyBuffer: Py_buffer; V: Variant; I: Integer; Count: Integer; ArrItem: Variant; begin try CreatePyEngine; try // Import numpy and create an array np := Import('numpy'); arr := np.&array(BuiltinModule.range(N), np.float64); writeln(VarPythonToVariant(arr.Length)); // This is the slow way to iterate the array WriteLn('Lazy but slow:'); SW := TStopwatch.StartNew; Sum := 0; Count := VarPythonToVariant(arr.Length); writeln(count); for I := 0 to Count - 1 do begin ArrItem := BuiltinModule.int(arr.GetItem(I)); Sum := Sum + single(VarPythonToVariant(ArrItem)); end; SW.Stop; WriteLn(Format('Sum from 0 to %d = %04.3f', [N, Sum])); WriteLn('Elapsed ms: ' + SW.ElapsedMilliseconds.ToString); WriteLn; WriteLn('Using Py_Buffer:'); SW := TStopwatch.StartNew; np_arr := ExtractPythonObjectFrom(arr); PyEngine.PyObject_GetBuffer(np_arr, @PyBuffer, PyBUF_CONTIG); PyEngine.CheckError; try Sum := 0; for I := 0 to N- 1 do Sum := Sum + PIntArray(PyBuffer.buf)^[I]; WriteLn(Format('Sum from 0 to %d = %04.3f', [N, Sum])); finally PyEngine.PyBuffer_Release(@PyBuffer); end; SW.Stop; WriteLn('Elapsed ms: ' + SW.ElapsedMilliseconds.ToString); WriteLn; // test write access PIntArray(PyBuffer.buf)^[0] := 999; if VarPythonToVariant(BuiltinModule.int(arr.GetItem(0))) = 999 then WriteLn('Successfully modified the numpy array using Py_buffer'); finally DestroyEngine; end; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; // stop program loop Terminate; end; Share this post Link to post
pyscripter 689 Posted July 14 (edited) np.float64 corresponds to pascal double. Use double instead of single. 1 hour ago, RegiStax said: ArrItem := BuiltinModule.int(arr.GetItem(I)); Use BuiltinModule.float instead of BuiltinModule.int. For multidimensional arrays you need to use the ndim and shapes fields of the Buffer structure. An array of Py_ssize_t of length ndim indicating the shape of the memory as an n-dimensional array. Note that shape[0] * ... * shape[ndim-1] * itemsize MUST be equal to len. If you know the dimensions of the array you can directly cast to an equivalent pascal multidimensional array. Edited July 14 by pyscripter Share this post Link to post
pyscripter 689 Posted July 14 Also, since you are interested in image manipulations, have a look at this thread: Place image in a TImage - Python4Delphi - Delphi-PRAXiS [en] (delphipraxis.net) Share this post Link to post
RegiStax 0 Posted July 14 Hi pyscripter, Thanks for the additional advice on Builtin etc. I will see how far I get that. Regarding the "Place image in Timage", that was mainly using DIB structures which are pure windows. As I am working on a Linux system thats currently not an option. If I manage to get this working I will share the code. cheers Cor Share this post Link to post
RegiStax 0 Posted July 14 (edited) Ive changed the builtin functions and also the single/double parts. The output is as expected. I however cannot create a 2d array I am stuck at this point arr := np.&array(BuiltinModule.range(N), np.float64); You would expect that (Ive not found any documentation on this thusfar) that you can use arr := np.&zeros((N,N) , np.float64); But that logically fails at the (N,N) declaration, with just N overthere I get an array of N slots with zero's. Its unclear how I can insert shapes etc. BTW I wont complain on the lack of "deeper" documentation for this, I also produce "freeware" and documentation is the least fun in my view. EDIT: this seems to at least declare the array arr := np.&ones(VarPythonCreate([N, N]), np.float64); Cor Edited July 14 by RegiStax Share this post Link to post
RegiStax 0 Posted July 14 Next iteration. This does work, not very elegant as I read the PyBuffer indexed { TBufferProtocol } procedure TBufferProtocol.DoRun; var SW: TStopwatch; Sum: double; np: Variant; arr: Variant; np_arr: PPyObject; PyBuffer: Py_buffer; V: Variant; I,J: Integer; Count: Integer; ArrItem: Variant; arrItem1:Variant; s:variant; begin try CreatePyEngine; try // Import numpy and create an array np := Import('numpy'); //arr := np.&array(BuiltinModule.range(N), np.float64); arr := np.&ones(VarPythonCreate([N, N]), np.float64); writeln(VarPythonToVariant(arr.Length)); // This is the slow way to iterate the array WriteLn('Lazy but slow:'); SW := TStopwatch.StartNew; Sum := 0; Count := VarPythonToVariant(arr.Length); writeln(count); for I := 0 to Count - 1 do begin ArrItem := arr.GetItem(I); for J := 0 to Count - 1 do begin ArrItem1:= BuiltinModule.float(arrItem.GetItem(J)); Sum := Sum + double(VarPythonToVariant(ArrItem1)); end; end; SW.Stop; WriteLn(Format('Sum from 0 to %d = %04.3f', [N, Sum])); WriteLn('Elapsed ms: ' + SW.ElapsedMilliseconds.ToString); WriteLn; WriteLn('Using Py_Buffer:'); SW := TStopwatch.StartNew; np_arr := ExtractPythonObjectFrom(arr); PyEngine.PyObject_GetBuffer(np_arr, @PyBuffer, PyBUF_CONTIG); PyEngine.CheckError; try Sum := 0; for I := 0 to N- 1 do for J := 0 to N- 1 do Sum := Sum + PDoubleArray(PyBuffer.buf)^[I*N+J]; WriteLn(Format('Sum from 0 to %d = %04.3f', [N, Sum])); finally PyEngine.PyBuffer_Release(@PyBuffer); end; SW.Stop; WriteLn('Elapsed ms: ' + SW.ElapsedMilliseconds.ToString); WriteLn; // test write access PDoubleArray(PyBuffer.buf)^[0] := 999; ArrItem1:= arr.GetItem(0); if VarPythonToVariant(BuiltinModule.float(arrItem1.GetItem(0))) = 999 then WriteLn('Successfully modified the numpy array using Py_buffer'); finally DestroyEngine; end; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; // stop program loop Terminate; end; Share this post Link to post
pyscripter 689 Posted July 14 arr := np.&ones(VarPythonCreate([N, N]), np.float64) You do not need the &. It is only needed when using a keyword such as "array" (and Delphi does not need it even then). Share this post Link to post
RegiStax 0 Posted July 14 OK. Thanks, can you point to the documentation that states this ? I have moved over the code into my application to find out I dont need all the trouble ... Just call: np_arr:=ExtractPythonObjectFrom(MainModule.weights); PythonEngine1.PyObject_GetBuffer(np_arr, @PyBuffer, PyBUF_CONTIG); That provides a direct link to the data of the weights variable in my python section, from that moment on its "easy". Share this post Link to post
pyscripter 689 Posted July 14 (edited) 2 hours ago, RegiStax said: OK. Thanks, can you point to the documentation that states this ? For Delphi is here: Fundamental Syntactic Elements (Delphi) - RAD Studio (embarcadero.com) and for fpc Identifier - Free Pascal wiki. Edited July 14 by pyscripter Share this post Link to post
RegiStax 0 Posted July 25 Hi, I have some questions on how to use the Pybuffer method to change data inside the python environment. Currently I have two routines: This routine loads a dataset from python that consists of a flexible array of width*height*channel arraycells. procedure Tform1.LoadPythonUint16Arr_Memory_image(transfer:variant; c, w, h:integer); var k: integer; j: integer; i: integer; sum: int64; PyBuffer: Py_buffer; np_arr: PPyObject; np:variant; p:Tfpcolor; begin np := Import('numpy') ; np_arr:=ExtractPythonObjectFrom(transfer); Memo2.lines.add('np_arr extracted'); Memory_Image.setsize(h,w); sum:=0; PythonEngine1.PyObject_GetBuffer(np_arr, @PyBuffer, PyBUF_CONTIG); PythonEngine1.CheckError; Memo2.lines.add('conversion to memoryimage'); try for I := 0 to w- 1 do for J := 0 to h- 1 do begin p:=Memory_Image.colors[j,i]; p.red:=PUint16PythonArray(PyBuffer.buf)^[(I*h+J)*c+2]; p.green:=PUint16PythonArray(PyBuffer.buf)^[(I*h+J)*c+1]; p.blue:=PUint16PythonArray(PyBuffer.buf)^[(I*h+J)*c]; Memory_Image.colors[j,i]:=p; sum:=sum+p.green+p.red+p.blue; end; finally PythonEngine1.PyBuffer_Release(@PyBuffer); end; Memo2.lines.add('MemoryImage ready'); end; This works perfect and allows me to download a large 16bit RGB image in a few ms instead of a few 100ms. When I try to change that same dataset on the Pythonside I however do not see those changes in the dataset. Where do I go wrong ? The Tfpcolor consists of 3 uint16 values and the buffered array also. When I run this ... I dont see any traceable change when I check on the python side. procedure Tform1.StorePythonUint16Arr_Memory_image(transfer:variant; c, w, h:integer); var k: integer; j: integer; i: integer; sum: int64; PyBuffer: Py_buffer; np_arr: PPyObject; np:variant; p:Tfpcolor; count:integer=0; begin np := Import('numpy') ; np_arr:=ExtractPythonObjectFrom(transfer); sum:=0; PythonEngine1.PyObject_GetBuffer(np_arr, @PyBuffer, PyBUF_CONTIG); PythonEngine1.CheckError; try for I := 0 to w- 1 do for J := 0 to h- 1 do begin p:=Memory_Image.colors[j,i]; PUint16PythonArray(PyBuffer.buf)^[(I*h+J)*c+2]:=(p.red); PUint16PythonArray(PyBuffer.buf)^[(I*h+J)*c+1]:=(p.green); PUint16PythonArray(PyBuffer.buf)^[(I*h+J)*c+0]:=(p.blue); end; finally end; PythonEngine1.PyBuffer_Release(@PyBuffer); Memo2.lines.add('MemoryImage stored ready'); end; Share this post Link to post
RegiStax 0 Posted July 26 Hi, I found out that my routine above is working as planned, but its important to use the same mainModule.variable names 🙂 . Sorry for the confusion, hope the code helps someone. Cor Share this post Link to post