Jump to content
RegiStax

How do I exchange data between python/Lazarus for a 3channel 2d floating-point array

Recommended Posts

Posted (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 by RegiStax

Share this post


Link to post

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
Posted (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 by pyscripter

Share this post


Link to post

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
Posted (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 by RegiStax

Share this post


Link to post

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
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

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

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

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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×