New function `rand_pdf`: Random values following a custom distribution

I’ve added a new command rand_pdf in the G’MIC standard library today, and I’d like to show you what it is about. It is a surprisingly short function (5 actual lines of G’MIC code :slight_smile: ), but it’s still pretty cool I think.

As its name suggests, this command generates random numbers that follow a custom (user-defined) probability density function (pdf).

When generating random numbers (e.g. with command noise or rand), we implicitely deal with pdf that are either uniform or gaussian functions. The figure below shows those pdf and the corresponding noisy image you get.

With rand_pdf, you are now able to specify a custom pdf, as a discrete 1D function passed to the command.

But what’s it all for? I’m not going to lie, this could be useful mainly to developers of future new filters or commands. I propose you a few examples of use below:

Generating “exotic” kind of noise.

Suppose I’d like to generate a 256x256 image of noise that contains only values 10,128 and 245, but each having a difference probablity of occurence. Let’s say the value 10 should appear 3 \times more than 128, which itself should appear 3 \times more than 245.
Now, that’s easy! →

$ gmic 256,1,1,1,"x==10?9:x==128?3:x==245?1:0" 256,256 rand_pdf. ..,0,255 o. out1.png

This generates this image:

out1

We can verify that the histogram of this output image indeed looks like the pdf we used for command rand_pdf:

$ gmic out1.png histogram 256,0,255 plot

And it works for quite exotic pdf, like:

foo :
  1024,1,1,1,"abs(cos(x/100))"   # Define custom pdf
  400,400,1,3 rand_pdf. ..,0,255 # Generate noisy image

  # Visualize result.
  +histogram. 1024,0,255
  dg[0,2] 640,400,3,0,0,255
  to[0] "User-defined pdf"
  to[1] "Noisy image"
  to[2] "Histogram of noisy image"
  frame 1,1,0
  a x

gives this:

So, good point, now it’s super easy to generate noisy data that follow a custom distribution in G’MIC.

Hiding data in noisy image.

But wait, if we can generate noisy image according to any distribution we want, why not hiding a “message” in an image by representing the message in the distribution of the generated pixel values ?
Ok, let’s do it.

foo :

  # Generate 16bits pdf (65536 values), from a 256x256 image.
  sp colorful,256 luminance. y. x

  # Generate noisy color image that follow this distribution.
  1024,1024,1,3 rand_pdf. ..,0,65535,1e7
  o. out3.png

Generates this 1024x1024 image (it’s a 16bits .png image) :

Now, let’s check we are able to retrieve the hidden message. We just takes the 16bits histogram of this image, and view the 65536 values of the histogram as a 256x256 image, et voilà !

$ gmic out3.png histogram 65536,0,65535 r 256,256,1,1,-1 n 0,255 o message.png

and we see the secret hidden image appearing :
message

Isn’t it nice ? :smiley:

Depending on the resolution of the generated noisy image, you’ll get more or less precision for the recovered data, as shown below:

Function code:

Well, that’s it for now. I’ll finish with the function code, as I’ve added it to the standard G’MIC library. As you can see, it’s quite short :slight_smile:

#@cli rand_pdf : [probability_density_function],_min_value,_max_value,_precision>0
#@cli : Fill selected images with random values which follow the given probability distribution.
#@cli : Default values: 'min_value=-1', 'max_value=1' and 'precision=65536'.
#@cli : (1,0,1) 50,50 rand_pdf[1] [0],0,255
#@cli : 256 gaussian[-1] 30 500,500 line[-1] rand_pdf[-1] [-2],0,255 +histogram[-1] 256 display_graph[0,2] 640,480,3,0
rand_pdf : check ${"is_image_arg $1"}" && isnum(${2=-1}) && isnum(${3=1}) && isint(${4=65536}) && $4>0"
  e[^-1] "Fill image$? with random values in [$2,$3], according to probability distribution $1, with precision $4."

  # Compute normalized cumulated density function (cdf).
  pass$1 0 y. x max. 0 cumulate. *. {($4-1)/i[-1,2]}

  # Compute inverse cdf.
  $4,1,1,1,">begin(k = 0); while (p = i[#-1,k]; k<w#-1 && (!p || p<x), ++k); lerp($2,$3,k/(w#-1-1))" rm..

  # Sample random numbers according to given pdf.
  f[^-1] "ind = int(lerp(0,w#-1,u))%w#-1; i[#-1,ind]" rm.

Hope you enjoyed this post, and that you will find plenty of cool uses for this new command rand_pdf!

3 Likes

Hah, would never have occurred to me to encode information in that way. Indeed a bit esoteric, but when it’s needed it will be ideal.

Edit: I like that the order of the image pixels has no bearing on it too!

1 Like

Yes, actually this makes me think that we could probably re-order all the noisy pixels in a way that it re-creates a new structured image, whose distribution would hide another image.
That would be pretty cool!

Ah yes, perhaps use matchpatch?

I’m currently trying something more brutal, with a random swap approach. I’ll let you know what happens :slight_smile:

1 Like

OK, so that works as expected.

The 1024x1024 color image below (16bits/channels) :

hides a secret 256x256 grayscale image in its histogram.

The command

$ gmic steganography.png histogram 65536,0,65535 r 256,256,1,1,-1 n 0,255

extract the hidden image:

message

To do this, I wrote a simple script that generates a random noise with a custom pdf (the data of the secret image), then swap random values of the image each time the swap makes the image more close to the reference image.

foo :
  sp gmicky rr2d. 256,256,1 r. 256,256,1,100%,0,0,0.5,0.5 luminance. n. 0,255 y. x  # 256x256 scalar image to hide
  sp colorful,1024 n. 0,65535 round.  # Image that will be rendered
  +rand_pdf. ..,0,65536,1e7 # Generate custom noise

  # Re-order noisy pixels to recreate rendered image.
  eval "
    it0 = 0;
    loss = sum(abs(crop(#-2)-=crop(#-1)));
    repeat (inf,it,
      x0 = int(u(w))%w; y0 = int(u(h))%h; c0 = int(u(s))%h; ref0 = i(#-2,x0,y0,0,c0); img0 = i(#-1,x0,y0,0,c0);
      x1 = int(u(w))%w; y1 = int(u(h))%h; c1 = int(u(s))%h; ref1 = i(#-2,x1,y1,0,c1); img1 = i(#-1,x1,y1,0,c1);
      diff = abs(ref0 - img0) + abs(ref1 - img1);
      ndiff = abs(ref0 - img1) + abs(ref1 - img0);
      ndiff<diff?(
        swap(i(#-1,x0,y0,0,c0),i(#-1,x1,y1,0,c1));
        loss+=ndiff - diff;
        it - it0>1000000?(
          it0 = it;
          print(loss);
          run('w. o. output.png');
        );
      )
    )"
2 Likes

Nice! The pixel swap thing is interesting in itself, amazing how close it is to original.

1 Like

That’s probably because the original image has a lot of different colors in it :slight_smile:
I’ve tried with another image (sp david) and clearly, that is harder.

1 Like