Math evaluator: Native functions for managing Dynamic Arrays

A few words about the development I’ve made these two last days.

I actually noticed that the functions dar_*() defined in the math_lib to manage dynamic arrays were quite useful in a number of situations, so I’ve decided to convert them as native functions in the math evaluator (rather than macros, as they are currently defined in math_lib).

As a reminder, in G’MIC, a dynamic array is defined as a 1-column (or empty) image [ind] in the image list. It allows elements to be added or removed, each having the same size (which is actually the number of channels of image [ind]). Dynamic arrays adapt their size to the number of elements they contain.

So, four new functions have been added in the math evaluator, to manipulate such objects:

  • da_size(_#ind): Return the number of elements in dynamic array [ind].
  • da_insert(_#ind,pos,elt_1,_elt_2,...,_elt_N): Insert N new elements elt_k starting from index pos in dynamic array [ind].
  • da_push(_#ind,elt1,_elt2,...,_eltN): Insert N new elements elt_k at the end of dynamic array [ind].
  • da_remove(_#ind,_start,_end): Remove elements located between indices start and end (included) in dynamic array [ind].
  • The value of the k-th element of dynamic array [ind] is retrieved with i[#ind,k] (if the element is a scalar value), or I[#ind,k] (if the element is a vector).

It’s basically an extension of what the dar_*() functions in math_lib offered. Mainly, you can now insert or remove several elements at the same time. You may also omit the #ind argument if your dynamic array is the last image of the list.
Also a nice thing is that you can start from an empty image as a dynamic array, and insert new elements in it. When removing elements, the size allocated to the dynamic array may also decrease by half if the number of elements becomes low enough.

For the moment, I’ll keep the dar_*() functions alive in math_lib, for compatibility reasons, but there are probably not much interest of using them anymore.

I’m currently compiling and release new pre-release packages for version 3.0.0_pre of G’MIC. Let me know what you think about these new functions!

4 Likes

For math_lib, I have to use hsv2rgb for a quick and easy way to do hsv2rgb. Of course, they can be coded in though personally I try to keep my script as short as possible with optimization as priority. Maybe perhaps in the future, you can implement color conversion function without needing to use math_lib for a very quick way of using them in JIT compiler?

Just extract the part you want to use, from math_lib:

  rgb2hsv(I) = ( # Convert RGB vector to HSV
    ref(I,_I);
    _M = max(_I);
    _C = _M - min(_I);
    [ 60*(_C==0?0:_M==_I[0]?((_I[1] - _I[2])/_C)%6:_M==_I[1]?(_I[2] - _I[0])/_C + 2:(_I[0] - _I[1])/_C + 4),
      _M<=0?0:_C/_M, _M/255 ];
  );

I made some changes to rep_rrd using this function.

By replacing dar_insert(#-1,…) and dar_insert(#-2,…) with da_push, I get this error:

D:\Programs\G'MIC\gmic-community>gmic 2048,2048 tic rep_random_rectangular_division 10,1,13,100%,1000000,100%,10,1,dcef00,0 toc
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Input black image at position 0 (1 image 2048x2048x1x1).
[gmic]-1./ Initialize timer.
[gmic] *** Error in ./rep_random_rectangular_division/*repeat/*local/_rep_random_rectangular_division/ *** Command 'eval': gmic<float>: Function 'dar_push()': Element to insert has invalid size 5 (should be 1).
[gmic] Command 'eval' has the following description:

Current code

Code
$ 2048,2048 rep_random_rectangular_division 10,1,13,100%,1000000,100%,10,1,dcef00,0

#@cli rep_rrd: eq. to 'rep_random_rectangular_division' : (+)
rep_rrd: rep_random_rectangular_division $*
#@cli rep_random_rectangular_division: split>1,_additional_thickness>=0,_max_iter>=2,_probability[%]>0,_loop_limit>1,0<=_border[%]>=100%,_seed,_normalize,border_color,palette_mode
#@cli : Generate random division of rectangle as in random number of division and varying thickness within rectangle.
#@cli : _additional_thickness refers to the excess pixel thickness.
#@cli : _max_iter limits the number of iterations per rectangle.
#@cli : _probability determines the probability that a rectangle will be permitted to be utilized for further iteration.
#@cli : _loop_limit limits the number of time the process of subdivision is done.
#@cli : _seed generates the output based on defined parameter.
#@cli : Default values: '_gen_thick=0','_max_iter=5','_probability[%]=95%','_loop_limit=5000','_border=0%','_seed=n/a'
rep_random_rectangular_division:
skip ${2=0},${3=5},${4=95%},${5=5000},${6=0},${7=},${8=0},${9=},${10=},${11=},${12=},${13=}
check "$4>0"
repeat $! l[$>]
 _rep_random_rectangular_division $*
endl done
_rep_random_rectangular_division:
skip ${2=0},${3=5},${4=95%},${5=5000},${6=0},${7=},${8=0},${9=},${10=},${11=},${12=},${13=}

border_size={max(0,round(abs($2)*cut($6,0,1)))}
contain_palette=0
dims={w},{h}
ns=1
col=0
activate_code_block=1

if narg($10)
 ms=3
 contain_palette=1
 if '$10'=='pal'
 elif ${is_image_arg\ $10} pass$10 0 ms={s#-1} rm.
 else contain_palette=0
 fi
fi

if narg($10)
 ms=3
 contain_palette=1
 if '$10'=='pal'
 elif ${is_image_arg\ $10} pass$10 0 ms={s#-1} rm.
 else
  contain_palette=0
  if !narg($11)||(narg($11)?abs($11)==0)
   activate_code_block=0
   if narg($9)&&$border_size
    col=${rep_hex2int8\ $9}
    ns={narg($col)}
    col=vector(#$ns,$col)
   else
    ns=3
   fi
  fi
 fi
else
 if narg($9)&&$border_size
  col=${rep_hex2int8\ $9}
  ns={narg($col)}
  col=vector(#$ns,$col) 
 fi
fi

rm.

$dims,1,$ns,$col

1,1,1,5 #Modifiable Rectangle Array
1,1,1,5 #Unmodifiable Rectangle Array

eval "
 if(narg($7),srand($7););
 const ww=w#-3-1;
 const hh=h#-3-1;
 const max_split=max(1,int(abs($1)));
 const internal_space=int(abs($2));
 const test_boundary=internal_space?(internal_space+1);
 const max_iter=max(2,round(abs($3)));
 const odd=cut($4,0,1);
 const arr_limit=round(abs($5));

 diff_check(a)=(
  mri=max_split;
  ds=a/mri;
  if(mri>1&&ds<1,
   while(mri>1&&ds<1,
    mri--;
    ds=a/mri;
   );
  );
  ds>1;
 );

 width_check=diff_check(ww-internal_space*2);
 height_check=diff_check(hh-internal_space*2);

 !(width_check||height_check)?run('error inv_dim');

 da_push(#-2,[0,ww,0,hh,0]);

 sub_column(v,point)=(
  ri=int(u(1,max_split))+1;
  mri=ri-1;
  sx=v[0]+test_boundary;
  ex=v[1]-test_boundary;
  diff=ex-sx;
  bound_check=diff>test_boundary;
  skip=0;
  da_remove(#-2,point);
  if(bound_check,
   ds=diff/mri;
   if(mri>1&&ds<=test_boundary,
    while(mri>1&&ds<=test_boundary,
     ri--;
     mri--;
     ds=diff/mri;
    );
   );
   if(test_boundary||diff?(ds>test_boundary):1,
    py=[v[2],v[3]];
    iter=v[4]+1;
    p=v[0];
    repeat(ri,ri_ind,
     rb=odd<1?u(0,1)>odd;
     ri_ind==mri?(
      if(p>v[1],
       p=v[1];
       if(!(v[3]-v[2]),rb=1;);
      );
      (rb||(iter==max_iter))?(
       da_push(#-1,[p,v[1],py,iter]);
      ):(
       da_push(#-2,[p,v[1],py,iter]);
      );
     ):
     !ri_ind?(
      mx=sx+ds;
      np=min(max(sx,round(u(p,mx))),ex);
      vp=[p,np];
      (rb||(iter==max_iter))?(
       da_push(#-1,[vp,py,iter]);
      ):(
       da_push(#-2,[vp,py,iter]);
      );
      p=np+1;
      if(p>=ex,skip=1;);
     ):(
      if(skip,continue(););
      mx+=ds;
      np=min(max(p+internal_space,round(u(p,mx))),ex);
      vp=[min(p,ex),np];
      (rb||(iter==max_iter))?(
       da_push(#-1,[vp,py,iter]);
      ):(
       da_push(#-2,[vp,py,iter]);
      );
      p=np+1;
     );
    );
   ,da_push(#-1,v);
   );
  ,da_push(#-1,v);
  );
 );

 sub_row(v,point)=(
  ri=int(u(1,max_split))+1;
  mri=ri-1;
  sy=v[2]+test_boundary;
  ey=v[3]-test_boundary;
  diff=ey-sy;
  bound_check=diff>test_boundary;
  skip=0;
  da_remove(#-2,point);
  if(bound_check,
   ds=diff/mri;
   if(mri>1&&ds<=test_boundary,
    while(mri>1&&ds<=test_boundary,
     ri--;
     mri--;
     ds=diff/mri;
    );
   );
   if(test_boundary||diff?(ds>test_boundary):1,
    px=[v[0],v[1]];
    iter=v[4]+1;
    p=v[2];
    repeat(ri,ri_ind,
     rb=odd<1?u(0,1)>odd;
     ri_ind==mri?(
      if(p>v[3],
       p=v[3];
       if(!(v[1]-v[0]),rb=1;);
      );
      (rb||(iter==max_iter))?(
       da_push(#-1,[px,p,v[3],iter]);
      ):(
       da_push(#-2,[px,p,v[3],iter]);
      );
     ):
     !ri_ind?(
      my=sy+ds;
      np=min(max(sy,round(u(p,my))),ey);
      vp=[p,np];
      (rb||(iter==max_iter))?(
       da_push(#-1,[px,vp,iter]);
      ):(
       da_push(#-2,[px,vp,iter]);
      );
      p=np+1;
      if(p>=ey,skip=1;);
     ):(
      if(skip,continue(););
      my+=ds;
      np=min(max(p+internal_space,round(u(p,my))),ey);
      vp=[min(p,ey),np];
      (rb||(iter==max_iter))?(
       da_push(#-1,[px,vp,iter]);
      ):(
       da_push(#-2,[px,vp,iter]);
      );
      p=np+1;
     );
    );
   ,da_push(#-1,v);
   );
  ,da_push(#-1,v);
  );
 );


 v=I[#-2,0];

 width_check&&height_check?(
  u(0,1)<=.5?(
   sub_row(v,0);
  ):(
   sub_column(v,0);
  );
 ):
 width_check?(
  sub_column(v,0);
 ):
 height_check?(
  sub_row(v,0);
 );

 arr_point=0;

 while(da_size(#-2),
  p_ind=int(u(da_size(#-2)));
  cv=I[#-2,p_ind];
  u(0,1)<.5?(
   sub_row(cv,p_ind);
  ):(
   sub_column(cv,p_ind);
  );
  arr_point++;
  if(arr_point>=arr_limit,break());
 );

 resize(#-2,1,da_size(#-2),1,4,0);
 resize(#-1,1,da_size(#-1),1,4,0);"

a[-2,-1] y

eval. :"begin(
  const ww=w#-2-1;
  const hh=h#-2-1;
  const border_size=$border_size;
  const b_a=floor(border_size/2);
  const b_b=ceil(border_size/2);
  const contain_border=$border_size?1;
  if(narg($12),srand($12););
  $8&&!narg($10)?(
   $ns>1?(
    const rescale_factor=(h-1)/255;
    calc_out()=y/rescale_factor;
   ):(
    const rescale_factor=(h-1+contain_border)/255;
    calc_out()=(y+contain_border)/rescale_factor;
   );
  ):(
   $activate_code_block?(
    calc_out=calc_out()=y+contain_border;
   ):(
    $10==1?(
     calc_out()=hsv2rgb(vector(#3,u(360),u(1),u(1)));
    ):(
     calc_out()=vector(#3,u(255),u(255),u(255));
    );
   );
  );
  convert2coords(c)=(
   sx=c[0]+(!(c[0]==0)?b_a);
   sy=c[2]+(!(c[2]==0)?b_a);
   ex=c[1]-(!(c[1]==ww)?b_b);
   ey=c[3]-(!(c[3]==hh)?b_b);
   tl=[sx,sy];
   tr=[ex,sy];
   bl=[sx,ey];
   br=[ex,ey];
   [tl,tr,br,bl];
  );
 );
 polygon(#-2,4,convert2coords(I),1,calc_out());
 end(
  if(contain_border,
   const sub_border=border_size-1;
   const bh=hh-sub_border;
   const bw=ww-sub_border;
   col="$col";
   polygon(#-2,4,[0,0, ww,0, ww,sub_border, 0,sub_border],1,col);
   polygon(#-2,4,[0,border_size,sub_border,border_size,sub_border,hh,0,hh],1,col);
   polygon(#-2,4,[bw,border_size,ww,border_size,ww,hh,bw,hh],1,col);
   polygon(#-2,4,[border_size,bh,bw,bh,bw,hh,border_size,hh],1,col);
  );
 );"

rm.

if narg($10)&&$activate_code_block
 if $contain_palette
  orientation=0
  if $border_size
   col=${rep_hex2int8\ $9}
   1,1,1,$ms,vector(#3,$col);
   if '$10'=='pal' 
    pal $11
    if narg($12)
     {w},1,1,1,begin(srand($12););u
     pixelsort.. +,x,[-1]
     rm.
    fi
   elif ${is_image_arg\ $10} 
    pass$10 1
    if h>w orientation=1 fi
    if narg($11)
     if $orientation
      {h},1,1,1,begin(srand($11););u
      pixelsort.. +,y,[-1]
      rm.      
     else
      {w},1,1,1,begin(srand($11););u
      pixelsort.. +,x,[-1]
      rm.
     fi
    fi
   fi
   r. {iM#-3-1},1,1,3,0,2
   if $orientation 
    r. 1,{iM#-3-1},1,3,0,2
    a[-2,-1] y
   else 
    r. {iM#-3-1},1,1,3,0,2
    a[-2,-1] x
   fi
  else
   if '$10'=='pal' 
    pal $11
    if narg($12)
     {w},1,1,1,begin(srand($12););u
     pixelsort.. +,x,[-1]
     rm.
    fi
   elif ${is_image_arg\ $10} 
    pass$10 1
    if h>w orientation=1 fi
    if narg($11)
     if $orientation
      {h},1,1,1,begin(srand($11););u
      pixelsort.. +,y,[-1]
      rm. 
     else
      {w},1,1,1,begin(srand($11););u
      pixelsort.. +,x,[-1]
      rm.
     fi
    fi
   fi
   if $orientation 
    r. 1,{iM#-2-1},1,3,0,2
   else 
    r. {iM#-2-1},1,1,3,0,2
   fi
  fi
 else
  if $border_size 
   col=${rep_hex2int8\ $9}
   1,1,1,3,vector(#3,$col);
   if $10==1 
    {narg($11)?max(abs($11),2):iM#-2-1},1,1,3,${-math_lib}begin(if(narg($12),srand($12);););hsv2rgb(vector(#3,u(360),u(1),u(1)));
   else 
    {narg($11)?max(abs($11),2):iM#-2-1},1,1,3,begin(if(narg($12),srand($12);););vector(#3,u(255),u(255),u(255));    
   fi
   if narg($11) r. {iM#-3-1},100%,100%,100%,0,2 fi
   a[-2,-1] x
  else
   if $10==1 
    {narg($11)?max(abs($11),2):iM#-1-1},1,1,3,${-math_lib}begin(if(narg($12),srand($12);););hsv2rgb(vector(#3,u(360),u(1),u(1)));
   else 
    {narg($11)?max(abs($11),2):iM#-1-1},1,1,3,begin(if(narg($12),srand($12);););vector(#3,u(255),u(255),u(255));
   fi
   if narg($11) r. {iM#-2-1},100%,100%,100%,0,2 fi
  fi
 fi
 map.. .
 rm.
fi

It would work with the old dar_insert and with math_lib in it. The problem seem to be sending a vector into push. A vector is treated as series of element.

EDIT: More test shows that it is supposed to work. I don’t know why it doesn’t. I tried simple codes to see where I went wrong, but nothing.

You can probably guess what I’ll ask: did it get faster?! :smiley:

On another subject: I was wondering whether sorting/grouping by multiple values could be more efficient - the method in colormap works pretty well, but is it optimal?