Reptorian G'MIC Filters

Hmm, how would you transform a image crop inside math parser? Then, this would be very feasible.

Ha ha, just dreaming. That is all. I think there are basic ones like rotate(). Beyond that, write macros just as we would write commands. That is beyond me since the math parser requires different syntax and logic, but you are better at coding than I…

Hmm, @David_Tschumperle has done that before. I think he could shed some lights here.

Here, there’s a solution with crop() - mirror() as a mathematical expression · Issue #418 · GreycLab/gmic · GitHub .

Yes, I recall that was possible. It would help make the repetitive areas more interesting.

I pushed 3 new commands which adds to my combinations/permutation toolkit, and it’s the last one. It’s the variable_combination series.

This is the output for this example:

D:\Programs\G'MIC\gmic-community>gmic rep_variable_combinations 4,5,3,x repeat w#-1 { echo {I(#-1,"$>")} }
[gmic]-0./ Start G'MIC interpreter.
[gmic]-1./*repeat/ 0,0,0
[gmic]-1./*repeat/ 0,0,1
[gmic]-1./*repeat/ 0,0,2
[gmic]-1./*repeat/ 0,1,0
[gmic]-1./*repeat/ 0,1,1
[gmic]-1./*repeat/ 0,1,2
[gmic]-1./*repeat/ 0,2,0
[gmic]-1./*repeat/ 0,2,1
[gmic]-1./*repeat/ 0,2,2
[gmic]-1./*repeat/ 0,3,0
[gmic]-1./*repeat/ 0,3,1
[gmic]-1./*repeat/ 0,3,2
[gmic]-1./*repeat/ 0,4,0
[gmic]-1./*repeat/ 0,4,1
[gmic]-1./*repeat/ 0,4,2
[gmic]-1./*repeat/ 1,0,0
[gmic]-1./*repeat/ 1,0,1
[gmic]-1./*repeat/ 1,0,2
[gmic]-1./*repeat/ 1,1,0
[gmic]-1./*repeat/ 1,1,1
[gmic]-1./*repeat/ 1,1,2
[gmic]-1./*repeat/ 1,2,0
[gmic]-1./*repeat/ 1,2,1
[gmic]-1./*repeat/ 1,2,2
[gmic]-1./*repeat/ 1,3,0
[gmic]-1./*repeat/ 1,3,1
[gmic]-1./*repeat/ 1,3,2
[gmic]-1./*repeat/ 1,4,0
[gmic]-1./*repeat/ 1,4,1
[gmic]-1./*repeat/ 1,4,2
[gmic]-1./*repeat/ 2,0,0
[gmic]-1./*repeat/ 2,0,1
[gmic]-1./*repeat/ 2,0,2
[gmic]-1./*repeat/ 2,1,0
[gmic]-1./*repeat/ 2,1,1
[gmic]-1./*repeat/ 2,1,2
[gmic]-1./*repeat/ 2,2,0
[gmic]-1./*repeat/ 2,2,1
[gmic]-1./*repeat/ 2,2,2
[gmic]-1./*repeat/ 2,3,0
[gmic]-1./*repeat/ 2,3,1
[gmic]-1./*repeat/ 2,3,2
[gmic]-1./*repeat/ 2,4,0
[gmic]-1./*repeat/ 2,4,1
[gmic]-1./*repeat/ 2,4,2
[gmic]-1./*repeat/ 3,0,0
[gmic]-1./*repeat/ 3,0,1
[gmic]-1./*repeat/ 3,0,2
[gmic]-1./*repeat/ 3,1,0
[gmic]-1./*repeat/ 3,1,1
[gmic]-1./*repeat/ 3,1,2
[gmic]-1./*repeat/ 3,2,0
[gmic]-1./*repeat/ 3,2,1
[gmic]-1./*repeat/ 3,2,2
[gmic]-1./*repeat/ 3,3,0
[gmic]-1./*repeat/ 3,3,1
[gmic]-1./*repeat/ 3,3,2
[gmic]-1./*repeat/ 3,4,0
[gmic]-1./*repeat/ 3,4,1
[gmic]-1./*repeat/ 3,4,2

See? Who needs itertools and Python? My combination/permutation toolkits does those.

After playing with Krita G’MIC, I found out that G’MIC in Krita support variable image size i.e the image is determine by the dimension of images within layers than the canvas itself. That complicates things a bit, so definitely would love to see a list of my GUI filters in which this is a issue. This is also what I think I can focus on during the meantime of waiting for G’MIC 3.2 for Krita.

Ideally, I would like to be able to revert back as a option.

It has always been the case, at least on the plugin for gimp. If it is new for krita, then it’s probably because it was wrong before and the krita devs fixed it.
When you design a filter that takes multiple input layers, never assume those layers have the same dimension.

Looks like rep_sptbwgp is broken. It doesn’t work for larger image. However, I made a quick code that does work.

rep_test_2:
+channels 3
gt. 189
permute. xczy
{w#-2},{h#-2},{d#-2},{s#-2}
eval.. :"begin(
  const height=s;
  empty_vector=vector(#s,0);
 );
 insert_pos=0;
 points=I;
 number_of_points=sum(points);
 if(number_of_points,
  repeat(height,pos,
   if(points[pos],
    I(#-1,x,insert_pos++)=I(#-3,x,pos);
   );
   if(!(--number_of_points),break(););
  );
 );
 points;"
remove[-3,-2] 

EDIT: I repaired rep_sptbwgp after this.

I wonder, if anyone can do some speed test and have 3.2.0. I don’t have it yet.

After the next G’MIC update:

There’s two different Grouped Pixel Axis-Based Shift GUI filters now. One of them utilize $_persistent, and the other does not. In theory, $_persistent should be faster, but it doesn’t seem to be the case for me. Full Image version uses $_persistent and it stores pixel coordinates in two images. If Full Image is slower, then I’m going to consider erasing it, then. Oh well if that’s the case because so much efforts into doing. There’s the benefit of accurate preview zoomed in though.

Any solution to do a single loop version of rep_sptbwgp without running into this:

image

This is the snip code that I’m trying to solve:

     repeat(number_of_points,p,
      current_p=max_point_index-p;
      point_location=points[current_p];
      
      I(#-1,x,point_location)=empty_vector;
      I(#-1,x,lerp(point_location,insert_pos--,$influence))=I(#0,x,point_location);     
      
     );

The bug happens because I(#-1,x,point_location)=empty_vector; clears pixels after the processing of pixels, but it is needed. And I don’t want to 2 repeat loop to solve it. Doing polygon would solve it, but I haven’t figured out how to do line version of it, but that’s probably the fastest way of solving it.

So far, I tried a lot of approaches, but nothing seem to work without bugs or drawbacks to get speed, but works.

I tested your “full image” version and for me it’s already fast. Out of curiosity, what’s the reason to optimise so much? Do you only work with very large images, or are you doing video processing, or just a very slow machine?

Edit: the filter seems to be mostly about discarding/moving pixels about. I’d be thinking about ways to use warp (maybe in relative mode) or discard to do that.

The reason I optimize so much is that my filters are intended to be used by others, so that’s why I try to make as many features to them I could, and to have them optimized. Also, there does seem to be a slight difference in speed of GUI processing and CLI processing. rep_sptbwgp on a 500x500 image takes .004 s while on the GUI, it takes .124 s.

As for the discard suggestion, that was tried through splitting and processing along multiple strip, it is slower than the approach of doing it through math evaluator.

1 Like

Now, I’m looking to see if there’s any algorithm that does what Python does here similar to rep_ncr_combinations or rep_permutation. I think that’s the final one of that series, and I know there’s a code out there which does it for multi-threaded approach. Is there’s something like Algorithm 515 (Buckles and Lybanon 1977) for nPr instead of nCr? That approach can be multi-threaded, and in fact, I did that for rep_ncr_combinations with no change to the algorithm itself.

I want to translate this Python into G’MIC basically:

from itertools import permutations

val_a=5
val_b=4

a=max(val_a,val_b)
b=min(val_a,val_b)

vector=[x for x in range(a)]

perm = permutations(vector, b)

for i in list(perm):
    print (i)

And then, to try getting the lexicographic index off it.

EDIT: I found Heap’s Algorithm, but still checking if it prints on lexicographic order.

Did an attempt, I believe it’s only partially solved:

#@cli rep_npr_permutations: number_of_items>0,item_picks>0,_axis={x,y,z} : item_picks>0, number_of_items>0,_axis={x,y,z}
+rep_npr_permutations:
check "($1>0&&$2>0)&&(isint($1)&&isint($2))&&inrange(('$3')[0],120,122,1,1)"

if $1==$2 rep_permutations $1,$3 return fi

if $1>$2 n_items,picks=${1-2}
else picks,n_items=${1-2}
fi

if $picks==1 $picks,1,1,1,x return fi

number_of_permutations,axis={permut($picks,$n_items,1)},{'$3'-120}

out_dim={vec=vector(#3,1);vec[$axis]=$number_of_permutations;vec;}

$out_dim,$picks,"begin(
  const axis=$axis;
  const number_of_items=$n_items;
  const decremented_number_of_items=number_of_items-1;
  const picks=$picks;
  divisor=number_of_items-(picks-1);
  start_vector=expr('x',picks);
  divisor_vector=vector(#picks,1);
  for(p=picks-2,p>-1,--p,
   divisor_vector[p]=divisor;
   divisor*=decremented_number_of_items-(start_vector[p]-1);
  );
  axis==2?(index()=z;):
  axis==1?(index()=y;):
           index()=x;
  print(number_of_items);
 );
 (start_vector+int(index()/divisor_vector))%number_of_items;
 "

Someone asked for help in a Python channel regarding this problem:

I thought it was a very interesting problem. The example provided by that person had much more code. I skipped over the histogram part as I don’t think that’s interesting. I wanted to try to see what happens if I use G’MIC instead to code it in.

Here’s the code:

#@cli rep_local_binary_pattern: spiral_level>0,operator
#@cli : Generates output of image based on evaluation of binary comparison along spiral.
rep_local_binary_pattern:
skip ${1=1},${2=0}
check "inrange($1,1,3,1,1)&isint($1)"

level_of_spiral,operator=$1,$2

# 1. Create spiral of dimension size_of_spiral by size_of_spiral

size_of_spiral={1+(2*$level_of_spiral)}
spiralbw {vector(#2,$size_of_spiral)},0

# 2. Convert index of spiral into a 1 dimensional strip of x,y coordinates

{wh-1},1,1,2
eval[-2] I(#-1,i,0)=[x,y] 
-[-1] $level_of_spiral # 2a. This is needed to ensure proper relative placement of coordinates
rm[-2] # 2b. The spiral image is no longer needed

# 3. Using the coordinate image, perform pixel comparison

fill[^-1] "begin(
  const length_of_vector=w#-1;
  const operator=$operator;
  if(operator
  ,pattern_search()=I>=J(I(#-1,pos,0,0),0,1);
  ,pattern_search()=I>J(I(#-1,pos,0,0),0,1);
  );
 );
 output_value=vectors(0);
 repeat(length_of_vector,pos,
  output_value+=pattern_search()<<pos;
 );
 output_value;
 "

# 4. Remove no longer needed coordinate image
 
rm[-1]

Result for regular image:

image

Result if I convert to Ohta8 color space beforehand:

image

Alternative:

foo :
  sp lena
  +f. "begin(
    sp_x = [ -1,0,1,1,1,0,-1,-1 ];
    sp_y = [ -1,-1,-1,0,1,1,1,0 ];
  );
  curr = i;
  out_val = 0;
  bit = 128;

  repeat (size(sp_x),k,
    j(sp_x[k],sp_y[k],0,0,0,1)>curr?(out_val|=bit);
    bit>>=1;
  );
  out_val"

PS: Seems to render a different outcome, I don’t know why :slight_smile:

1 Like

To be honest, I like your outcome better.

Reptorian vs David 's (respectively) output:

1 Like

TBH, I didn’t try to understand your code, just tried to implement the steps shown in the figure of your post.

2 Likes

Oh boy, @Reptorian is having a “Bring Your Own Code” party! :partying_face:

Consider:
"…where the centre pixel’s value is greater than the neighbour’s value, write “0”. Otherwise write “1.”

Putting the rule in another way:

"…where the neighbour’s value is greater than or equal to the centre pixel’s value, write “1”. Otherwise write “0”

With that rule, if all eight neighbors surrounding the center pixel equal the center pixel, then the local binary pattern must necessarily be [1,1,1,1,1,1,1,1] ⇒ 0xff because the center pixel is never greater than any of its neighbors.

In light of that, I would suggest changing your comparison line to:


j(sp_x[k],sp_y[k],0,0,0,1)>=curr?(out_val|=bit); # 'equal or greater than current' rather than 'greater than current'

That said — moins est plus! — there is much to be found in what is so succinctly put. In contrast, I took a more verbose approach, pre-computing the neighbor-center differences in an intermediary image (dvec), reducing these differences to flags (if…fi section), then doing per-channel bitwise sums to produce a lbp number for element of each channel.

mklbp.gmic:

mklbpimage: -check "${1=1}>=0"
   reverse={bool($1)}
   -foreach {
       -name. lbp
       ssz={s#$lbp}

       # Image of vector8×spectrum. for each pixel from source image:
       # Walk clockwise around the center pixel, starting from upper left
       # to obtain a vector8×spectrum; difference with the center pixel.
       # NB: one vector8 per image channel ⇒ vector24 for RGB, for three
       # lbp numbers, (one for each channel).

       -input[0] {w#$lbp},{h#$lbp},{d#$lbp},{8*$ssz},"[
                                                        J(#$lbp,-1,-1,0,2),
                                                        J(#$lbp, 0,-1,0,2),
                                                        J(#$lbp,+1,-1,0,2),
                                                        J(#$lbp,+1, 0,0,2),
                                                        J(#$lbp,+1,+1,0,2),
                                                        J(#$lbp, 0,+1,0,2),
                                                        J(#$lbp,-1,+1,0,2),
                                                        J(#$lbp,-1,+0,0,2)
                                                  ]-vector(#8*$ssz,J(#$lbp,0,0,0,2))"
       -name[0] dvec

       # Threshold the delta pels.

       -if $reverse
          -fill[dvec] "i<0?0:1"
       -else
          -fill[dvec] "i<0?1:0"
       -fi
       
       # Compute local binary pattern number (lbp) for each channel;

       -fill[lbp] "
                    V=I(#$dvec,x,y);
                    R=vector(#$ssz,0);
                    for(k=0,k<$ssz,k++,
                       for(j=0;W=V[k,8,3],j<8,j++,R[k]+=2^(8-j-1)*W[j]));R"
       -keep[lbp]
    }

Some play pics:

gmic mklbp.gmic lena.png mklbpimage. 1

lena_1

Reversing the rule

gmic mklbp.gmic lena.png mklbpimage. 0

lena_0

A slight blur is fun:

gmic mklbp.gmic lena.png b. 1 mklbpimage. 1

lena_1b

bilateral filtering intrigues:

gmic mklbp.gmic lena.png bilateral. 10,20 mklbpimage. 1 

lena_1blat

I need to spend more time with @Reptorian 's code. But above all, thanks for kicking off this LBP game!

2 Likes

Looks like allowing users changing base leads to interesting result with regards to reversing digits. I also made it 8.5x faster too with the use of map command. But now, I’ll have to figure out a few quirks here.