G'MIC exercises

Two additional comments maybe:

  1. The usual blur command (and derivative) does not manage periodic or mirrored boundary conditions, mainly because it is based on fast recursive filters that do not handle these boundary conditions easily (by nature). Fortunately, FFT-based filtering is, on the contrary, tied to the use of periodic boundary conditions by nature. So they are complentary, and this was our luck :slight_smile:
  2. (brag) I must say I love using G’MIC so much when I see I’m able to change the core of the blur_angular algorithm with a one-liner :heart_eyes:

Amazing, butter-smooth. I suggest using a power or a square for the exponential to give greater control for lesser blurs and access to higher blurs:

#@gui : Amplitude = float(0.4,0,3)

ac "blur_angular {(($1)^2.5)*5}%,$2%,$3% sharpen $4",$6,$7

There is a minimum blur between 0.1-0.2 but that would require a more precise and more CPU-intensive FFT algorithm.

I like the idea of having the blur amplitude proportional to a number of pixels, i.e. spatial distance. Having a non-linear variation wouldn’t be intuitive to me.
I can still increase the higher bound to allow even more blurry results (tried with 20 instead of 10 and I don’t really see why people would like to go above this :slight_smile: ).

20 is a good maximum but it means that on a GUI it’s harder to have control over less-intense blurs, for which the differences are more noticeable with small changes in blur amplitude. I use powers and whatnot across many of my filters for similar reasons.

Glad someone else is using this thread. Makes it more meaningful. :+1:


I have a new problem for you to solve. So far, I have only been doing things in a haphazard way. I find a way to select the offending lights (e.g., high chroma) and then inpaint or desaturate them. However, it leaves much to be desired. I could probably better address it in the raw processor or clone / heal it in GIMP but, hey, why not attempt something in G’MIC? :slight_smile:

Preview (actual file oog.zip (3.7 MB))

preview-oog

Sorry @afre this is really bad, not only hijacking again but avoiding your question! I doubt I can answer it well in any case since I avoid colour space issues as much as possible :slight_smile:

@David_Tschumperle
I’ve been looking at poisson disc noise and one of the first things a search returns is this paper coupled with this example public domain source code in c++, both of which look really quite simple (honestly!). Simple enough that I might even attempt in g’mic, but it does use lists/arrays with removable elements which I think might be awkward in gmic.

On to the question: is there already something in gmic for poisson disc noise and if not is there a good way to implement it? What are the chances of it being added to cimg?

Edit: I think now this might not be the type of “break” you were looking for!

I don’t think so at a first glance.

To CImg, probably very few chances :slight_smile: To G’MIC, high chances, if we get an implementation as a G’MIC script :slight_smile:

Not so awkward. There are already some commands that make use of list/arrays with removable elements. But, as there are several ways to do it, it requires some experience to choose the best way to do it. It can be done inside a math expression, or in a more regular pipeline.
What matters is for instance :

  • The number of elements you’ll have to manage in your list.
  • Will you have to add or remove elements only at the end or beginning of the list, or on arbitrary positions ?
  • What is the type of each element ? Can they be stored with a fixed number of values, or are they themselves of arbitrary size ?

Are you able to provide some of those? The most obvious is the image stack itself but it has some noticable overhead if the list is large. I don’t mind reading through them myself of course. Indeed the size of the data will determine the best method, what I’ll probably do is prototype it in an obvious way then optimise (once I get an idea of the extents).

For instance, command colormap implements the median-cut algorithm that needs to partition the RGB space using a dynamic list of blocs. In this case, the list is indeed stored in the image stack itself.

Another example is command _pixelsort which implements a stack with push() and pop() functions in a local vector variable. It can be done because we know the maximal size a vector can have.
I think this should be quite easy to have a kind of dynamic list structure in the math parser. I’ll take a break and try writing an example now. Stay tuned :slight_smile:

1 Like

So here is an example of using the math expression compiler to manage a list as a vector variable. The only real constraint here is to know a maximal number of elements. Insertion and removal of elements in the list can be done at arbitrary index, and should be reasonably fast (thousands of insertions should be OK).
I’ve written only two functions insert() and remove() that manage only insertion and removal of one element at a time, but of course we can do this easily for multiple insertions / removals if needed (not sure you need it).

The code below does nothing more than initializing such a list, inserting and removing some elements and display the result on the console. Nothing really sexy, but at least this shows how to set up such a dynamic structure easily.

test :
  eval "
    list = vector16();
    siz_list = 0;

    # Macro that insert a new element 'elt' into the list, at index 'pos' ('pos' must be in [0,siz_list]).
    insert(elt,pos) = (
      _pos = pos;
      if (_pos<=siz_list,
        copy(list[_pos+1],list[_pos],siz_list - _pos);
        list[_pos] = elt;
        ++siz_list)
    );

    # Macro that remove an element from the list, located at index 'pos' ('pos' must be in [0,siz_list]).
    remove(pos) = (
      _pos = pos;
      if (_pos<=siz_list,
        --siz_list;
        copy(list[_pos],list[_pos+1],siz_list - _pos);
        list[siz_list] = 0;
      )
    );

    echo('---- Initial state ------------');
    print(list,siz_list); # Empty list.

    insert(102,0);
    insert(101,0);
    insert(103,2);

    echo('---- After three insertions ---');
    print(list,siz_list); # List with three elements.

    remove(0);

    echo('--- After one removal ---------');
    print(list,siz_list); # List with two elements.
  "

EDIT : note that this can be done exactly by storing the data in an image defined in the image stack. In this case, we don’t have to worry about a maximal size as we can resize the image on the fly directly in the math parser if needed.

Good example thanks, I’ve not yet used macros in the math parser! It’s obviously a commonly required algorithm type, therefore very useful to know :slight_smile:

Edit: should’ve added, yes it’s random single elements being removed so this fits the case perfectly.

I’ll write one version that stores the data in an image stack instead, just to show it is as easy. As we can manage several images at the same time, this means having multiple dynamic lists is possible.

Here is a version with an image rather than a vector :

test :

  i[list] 1,1,1,3,-1  # List of RGB colors

  eval "

    # Macro that inserts a new element 'elt' into the image #ind, at index 'pos' ('pos' must be in [0,siz_list]).
    insert(ind,siz_list,elt,pos) = (
      _pos = pos;
      if (_pos<=siz_list,
        siz_list>=w(#ind)?resize(#ind,2*w(#ind)+1,1,1,s#ind,0);
        for (k = 0, k<s#ind, ++k, copy(i(#ind,_pos+1,0,0,k),i(#ind,_pos,0,0,k),siz_list - _pos));
        I[#ind,_pos] = elt;
        ++#siz_list;
      );
    );

    # Macro that removes an element from the image #ind, located at index 'pos' ('pos' must be in [0,siz_list]).
    remove(ind,siz_list,pos) = (
      _pos = pos;
      if (_pos<=siz_list,
        --#siz_list;
        for (k = 0, k<s#ind, ++k, copy(I(#ind,_pos,0,0,k),I(#ind,_pos+1,0,0,k),siz_list - _pos));
        I[#ind,siz_list] = -1;
      )
    );

    siz_list = 0;

    echo('---- Initial state ------------');
    print(#"$list"); print(siz_list); # Empty list.

    insert(#"$list",siz_list,[ 0,128,0 ],0);
    insert(#"$list",siz_list,[ 64,0,0 ],0);
    insert(#"$list",siz_list,[ 0,0,255 ],2);

    echo('---- After three insertions ---');
    print(#"$list"); print(siz_list); # List with three elements.

    remove(#"$list",siz_list,0);

    echo('--- After one removal ---------');
    print(#"$list"); print(siz_list); # List with two elements.
  "
1 Like

No problem. Actually list and element manipulation were the first things I tried, with limited success, when I encountered G’MIC scripting (which was not that long ago; am still a relatively new user :slight_smile:). If I stare at your discussions long enough, I might learn something. Still struggling to follow the math and code but it is fun nevertheless. :blush:

PS All of you can make it up by looking into An Error Reduction Technique in Richardson-Lucy Deconvolution Method. :wink:

Actually I already had a look, not certain whether it applies generally because it appears directed towards a particular form of noise. I suspect some of the maths will be outside what I know just now anyway, so much to learn!

Some news about the management of dynamic arrays in the math parser.
I’ve added new functions in command math_lib to ease handling this type of structure.

Basically, you first define a 1-column image with the number of channels you want (filled with 0). It will be used to represent your dynamic array. One example is worth a thousand words :

test :

  1,1,1,3  # A dynamic array of RGB colors (initially empty).

  v -
  eval ${-math_lib}"

    print(dar_size(#0)); # Print number of elements -> '0'

    # 500 insertions at random positions.
    for (k = 0, k<500, ++k,
      dar_insert(#0,u([255,255,255]),round(u(dar_size(#0))));
      ext('w[0] 200,100%,0');  # Display array
    );

    print(dar_size(#0)); # Print number of elements -> '500'

    # 200 removal at random positions.
    for (k = 0, k<200, ++k,
      dar_remove(#0,round(u(dar_size(#0)-1)));
      ext('w[0] 200,100%,0'); # Display array
    );

    print(dar_size(#0)); # Print number of elements -> '300'

    # Access to individual element by I[#ind,pos] :

    dar_insert(#0,[1,2,3],125);
    print(I[#0,125]);
  "
  v +

Maybe @garagecoder will be able to use those functions ? :slight_smile:

EDIT: How this internally work ?
The trick here is to store the current number of element in the image, at position [w*h*d-1], and to have a image height that is always greater or equal than dar_size() + 1. The function dar_insert() resizes the image when needed, and the function dar_remove() also resizes to save memory usage.
The height of an image is not equal to the number of elements in your dynamic arrays, that’s why there is a function dar_size() to get this information.

Nice! I’ll certainly start with that, thanks. I have time today to begin writing it and that means I don’t have to worry about handling that part :slight_smile:

1 Like

Might as well re-hijack the thread because I thought of an idea which is a compromise regarding what David and I were discussing earlier about linear versus nonlinear parameters attached to sliders: float and integer sliders which display and output linear values but move nonlinearly from left to right with specified modes and powers. It could have various modes such as n^x and x^n. Also, now that I think about it, step options for both integer and float sliders.

1 Like

Well this poisson disk noise turned out tougher than I expected :smiley:
Mainly because of dealing with 1D to 3D and not knowing enough about the math parser. Had to redesign the algorithm itself… this version is the first attempt so needs optimised and probably still has bugs (there’s even debug code still in). But it’s cool you can watch it drawing it point by point!

gcd_poisson_disk : skip ${1=2},${2=30}
  # [0] input image to draw samples on
  dim={d>1?3:h>1?2:1} cw={0.999*$1/sqrt($dim)}
  ({[w,h,d,1]}) y. c  # [1] image dimensions vector
  {[ceil(I/$cw),-1]}  # [2] "accelerator" grid/cells
  r[1] 1,1,1,$dim,-1  # keep only used dimensions in [1]
  1,1,1,$dim 1,1,1,1  # [3] samples list, [4] active list
  {vector$dim(2*ceil(sqrt($dim))+1)}  # [5] cell proximity kernel
  f. "dot([x,y,z]-int([w/2,h/2,d/2]),[1,w#2,w#2*h#2])" y. c
  nm[1] dims nm[2] grid nm[3] samples nm[4] active nm[5] prox
  -v -
  eval ${-math_lib}"
    const N = "$dim";
    const radius = $1;
    const grid_cw = "$cw";
    const max_sample_attempts = $2;
    prox = I#5;
    mag2(vec) = (dot(vec,vec));
    
    dar_insert(#3,I#1,0);               # dummy sample to simplify bounds checks
    dar_insert(#3,u(I#1),1);            # add initial sample to list
    dar_insert(#4,1,0);                 # add its index to active list
    I(#2,int(I[#3,1]/grid_cw)) = 1;     # add its index to grid cell
    tr=0;
    whiledo (dar_size(#4)>0,
      R = int(u(dar_size(#4)-1e-4));    # choose a random active list index

      P = i[#4,R];                      # get the index of that sample
      T = I[#3,P];                      # position vector of that sample
      
      for (attempts=0, attempts < max_sample_attempts, ++attempts,

        dowhile (S=4*(u(vectorN(1))-0.5); M=mag2(S), M <= 1 || M > 4);
        
        X = T + radius * S;             # potential sample from annulus around T
        if (min(X)<0 || min(I[#1,0]-X)<0, continue()); # check within bounds

        # check proximity of surrounding points
        G = int(X/grid_cw);             # grid cell position vector
        GI = dot(G,[1,w#2,w#2*h#2]);    # grid cell direct buffer index
        
        for (K=0;rejected=0, K<size(prox), ++K,
          V = i[#2,GI+prox[K]];         # sample index from grid to check
          if (V>0 && mag2(I[#3,V]-X)<sqr(radius), rejected=1;break())
        );

        if (!rejected,
          Q = dar_size(#3);               # sample found, get new index
          dar_insert(#3,X,Q);             # insert into samples list
          dar_insert(#4,Q,dar_size(#4));  # insert its index into active
          I(#2,G) = Q;                    # insert its index into grid
          I(#0,X)=1;                      # draw the point
          break();
        );
      );
      
      if (attempts == max_sample_attempts, dar_remove(#4,P));
      ++tr; if(tr%2==0,ext('w[0] 400,400,1'));
    );
  " -v +

using:
gmic 128,128,1,1 gcd_poisson_disk 4

poisson_disk

1 Like

Decided to add other preview options. There’s a issue though, preview timeout.

#@gui _
#@gui <i>Reptorian</i>
#@gui Modulus operations : fx_modulus, fx_modulus_preview(0)
#@gui : note = note("This filters applies modulo operation after arithmetic operation. Note: Joan Rake's Sawtoothers are the expanded version of this filter.")
#@gui : sep = separator()
#@gui : Multiply = float(1,0,32)
#@gui : Addition = int(0,0,255)
#@gui : sep = separator(), Preview type = choice("Full","Forward horizontal","Forward vertical","Backward horizontal","Backward vertical","Duplicate top","Duplicate left","Duplicate bottom","Duplicate right","Duplicate horizontal","Duplicate vertical","Checkered","Checkered inverse")
#@gui : sep = separator(), note = note("<small>Author : <i>Reptorian</i>.      Latest update : <i>2018/08/18</i>.</small>")
fx_modulo:
repeat $! l[$>] split_opacity l[0]
mul $1 add $2 mod 256
endl a c endl done
fx_modulus_preview :
-gui_split_preview "-fx_modulus ${1--2}", $-1