Best practices for writing efficient user commands

Fill iterates over each pixel of the referenced image(s), so you can imagine the commands of your fill string running each time it moves to a new pixel. A simple example of removing pixels with fill:

gmic sp dog f "x%2?i:0"

which is saying “if the current x value modulo 2 is 1 then keep the current pixel, else replace with 0”.
More complex things can be done by referencing pixels of another image, for example by using i(#index,…).

Vectors at most basic can be good for operating on several values at once, e.g. if you multiply I you’re multiplying R, G, B each by the same. The usual vector functions are available too. I wouldn’t suggest replacing values with nan because it’s usually more of a problem to handle than just setting some extreme value (very large/very negative/very small), or keeping a mask in another image. Comparison operators on the image as a whole might help (eq/gt/lt…) as well as min/max. Maybe not the best way to learn, but most of what I use was gleaned by reading other filters which did a similar thing.

It’s quite ‘low level’ for this sort of thing and not a lot different from working with buffers in something like C. Loops, logic and maths!

I remember reading about modulo… I figured it out. E.g., to get R, I do

gmic 2,2,1,1 f 1 f (x+1)%2?i:0 f (y+1)%2?i:0

I couldn’t combine it into one fill command and had to handle x and y separately. Is there a way to do that? I suppose the alternative way to tackle this problem is to unfold the grid into a 1d vector, black out every other or few pixels, and then fold it back.


I am confused about ||.

image

E.g., I assumed the following would black out the Gs but it doesn’t:

gmic 2,2,1,1 f 1 f "(x+1)%2?i:0||(y+1)%2?i:0"

image

which is

image


As for I, multiplying I is the same as multiplying i. What I would like to know is how to replace RGB(A) pixels that have negative values. If I used i, it would only address the offending channels but not the pixel as a whole.

@afre, if I’ve understood well, this little script may solve your problem :

use_fill :

  # Create input bayer image
  sp lena rgb2bayer 0

  # Use fill to decompose bayer image into distinct R,G,B channels.
  100%,100%,1,3  # Recomposed RGB image with '0's.
  50%,50%,1,3    # Spatially averaged RGB image.

  # The trick here : we apply 'fill' on the bayer image to spread its values
  # in the two created images, but we won't modify the bayer image itself.
  f[0] "
    const boundary = 1;
    val = i;
    x2 = int(x/2);
    y2 = int(y/2);
    !(x%2) && !(y%2) ? ( # val is R
      I(#1,x,y) = [ val,0,0 ];
      i(#2,x2,y2,0,0) = val;
    ):(x+y)%2 ? (        # val is G
      I(#1,x,y) = [ 0,val,0 ];
      Ga = (val + (y%2?j(1,-1):j(-1,1)))/2;
      i(#2,x2,y2,0,1) = Ga;
    ):(                  # val is B
      I(#1,x,y) = [ 0,0,val ];
      i(#2,x2,y2,0,2) = val;
    );
    val # Trick : expression returns 'val' to avoid modification of image[0].
  "

From a Bayer image, it renders a RGB image with 0 where pixels are unknown, and a half-sized image where known color values have been set or averaged at their correct position.
Just a note : when trying to implement custom things like this, never try to do it directly in the terminal. The terminal has its own substitution mechanisms and there are high chances you won’t be able to manage both G’MIC-specific and terminal-specific syntax at the same time (I don’t manage it personally).
I’d recommend them to define a custom G’MIC command in a G’MIC command file (possibly %APPDATA/user.gmic and invoke this command from the command line).
At least you will only struggle with the G’MIC syntax alone :slight_smile:

This is what the command above renders for me :

1 Like

Awesome! I will slowly digest what you have written and apply it to future scripts. Good tricks to learn. The code highlighting is off but I can still read it. You are right about the console. Writing one liners is just a bad habit of mine. Now, I just need to figure out how to black out pixels with negative values; e.g., replace rgba(255,-1,14,59) with rgba(0,0,0,0). Really, it could be replacing anything with anything but as with my Bayer question concrete examples are good.

@David_Tschumperle you can set the code highlighting style by editing your post and appending a highlighting style name to three backticks, for example to have no highlighting (assuming there is no popular highlighting style which suits G’MIC code) you can write:

```text

With the math parser and for a RGB image:

fill "min(I)<0?[0,0,0]:I"

Or this version to make it work for any number of channels :

fill "min(I)<0?vectors(0):I"

But this can be done also without the math parser, only with the “classical” operators:

+compose_channels min ge. 0 mul[-2,-1]

Sometimes, I need to export an image or two from the buffer so that I may process it elsewhere. However, some apps don’t accept floating point for one reason or another. Mostly, this is because I don’t have the programming chops to extend that app to do what I want.

E.g., I would like to use pnmclahe, which only accepts 8-bit and 16-bit ppms and pgms. How would I go about this to prevent posterization? I would probably have to decompose the image(s) in some manner but the tricky thing is to have a representation on which CLAHE could work.

Thoughts?