snibgo's ImageMagick pages

Multi-scale pyramids

An image can be represented by a multi-scale pyramid.

A multi-scale pyramid is an image represented by a number of images, each smaller then the previous, each representing a different level of detail. As the image in the series are of different sizes, they can be said to be a pyramid of images, and we can talk about building the pyramid from an image, or collapsing the pyramid to make the same image, or a different image is we have procesed the pyramid. If we build a pyramid then immediately collapse it, we should reconstruct the original image. (Collapsing is sometimes called inverting.)

Images at the top of the pyramid are the smallest, perhaps 1x1 pixel. They carry information about low-frequency data. Images at the bottom are the largest, perhaps the same size as the original image. High-frequency data is stored in those lower, larger images.

Each level of the pyramid might be a down-sized version of the input image; this is called a Gaussian pyramid. In a Gaussian pyramid, each layer is made by a lowpass filter: higher frequencies have been removed, like a photograph from a defocused camera. Alternatively, each layer may be a down-sized version of the difference between the upsized previous level and its input; this is called a Laplacian pyramid (also sometimes called a Difference of Gaussians pyramid). Each layer in a Laplacian pyramid is formed by a bandpass filter: it records data about a narrow band of frequencies, with both higher and lower frequencies removed.

Collapsing a Gaussian pyramid is trivial: just copy the bottom level. Provided this is the same size as the input image, it is an exact copy. Collapsing a Laplacian pyramid is more complex, as the layers must be added.

In the literature, multi-scale pyramids:

In addition, for Laplacian pyramids:

This implementation relaxes those restrictions. This means that a pyramid may not exactly represent an input image, so a final difference image is also created. When a pyramid is collaped, adding in this final difference may be required to reconstruct the original.

Because the frequencies often double at each level, levels are commonly called octaves (by analogy with music).

I use the word grid to describe the image at a level in the pyramid.

Literature describes two operations required to make each grid: convolution (blurring), followed by sub-sampling. I combine these into one, using IM's -resize operator.

In this implementation of Laplacian pyramids, grids are weighted so they may contribute unequally to the result.

The scripts use TIFF files to store pyramids. If there are (n) octaves, there will be (n+1) images, representing the (n) grids plus the final difference (which may be entirely mid-gray). I refer to the bottom octave of the pramid, with the largest grid, as level zero; it is image number [0] in the TIFF file.

Applications for multi-scale pyramids include:

Histograms on this page were make using a process module, mkhisto. See Process modules. Construction, processing and collapsing of pyramids does not need process modules, or even HDRI.

ASIDE: Real-world pyramids have each level at a constant difference in linear size to adjacent levels. Image pyramids have each level at a constant factor to adjacent levels. Beware of stretched analogies.

References and further reading

Sample input image

A possible test image:

%IMG7%magick ^
  -size 1x1 xc:White ^
  -bordercolor Black -border 100 ^
  ( +clone -negate ) ^
  +append +repage ^
  msp_test.png

set SRC=msp_test.png
set pyGRAPHIC=1
msp_test.png

The test image used below:

set SRC=toes.png
set pyGRAPHIC=
toes.png

Examples below show the round trip: from an input image, build the pyramid, then collapse the pyramid and test how close the result is to the input.

Make Gaussian pyramid

The script mkGausPyr.bat makes a Gaussian pyramid.

call %PICTBAT%mkGausPyr %SRC% msp_g1.tiff

Here is the pyramid:

%IMG7%magick identify msp_g1.tiff 
msp_g1.tiff[0] TIFF 267x233 267x233+0+0 16-bit sRGB 625754B 0.001u 0:00.000
msp_g1.tiff[1] TIFF 134x117 134x117+0+0 16-bit sRGB 0.001u 0:00.001
msp_g1.tiff[2] TIFF 67x58 67x58+0+0 16-bit sRGB 0.001u 0:00.001
msp_g1.tiff[3] TIFF 33x29 33x29+0+0 16-bit sRGB 0.001u 0:00.001
msp_g1.tiff[4] TIFF 17x15 17x15+0+0 16-bit sRGB 0.001u 0:00.001
msp_g1.tiff[5] TIFF 8x7 8x7+0+0 16-bit sRGB 0.001u 0:00.001
msp_g1.tiff[6] TIFF 4x4 4x4+0+0 16-bit sRGB 0.001u 0:00.001
msp_g1.tiff[7] TIFF 267x233 267x233+0+0 16-bit Grayscale Gray 0.001u 0:00.001

There are 8 grids, or levels in the pyramid, numbered 0 to 7. The final image in the file is the image that is the source, minus grid zero enlarged. As grid zero is the same size as the source, it is equal to the source, so the final image is 50% gray.

By setting two variables, we tell the script to create viewable copies of the grids and build an HTML table. This shows the smallest octave at the top, and the largest octave (number 0) at the bottom. The tiff file contains each image shown under the heading "Grid". It also contains the octave 0 difference.

set mkpDEBUG=1
set mkpHTM=1
set pyPREFIX=mspg_

call %PICTBAT%mkGausPyr %SRC% msp_g2.tiff

The table shows each grid enlarged to the size of the source image, and a histogram of that enlargement. The final column is the source minus the enlarged grid.

Octave Grid Grid, resized up Histogram of resized grid Difference
6 mspg_grd_6.miffjpg mspg_full_6.miffjpg mspg_full_6_h_glc.png mspg_diff_6.miffjpg
5 mspg_grd_5.miffjpg mspg_full_5.miffjpg mspg_full_5_h_glc.png mspg_diff_5.miffjpg
4 mspg_grd_4.miffjpg mspg_full_4.miffjpg mspg_full_4_h_glc.png mspg_diff_4.miffjpg
3 mspg_grd_3.miffjpg mspg_full_3.miffjpg mspg_full_3_h_glc.png mspg_diff_3.miffjpg
2 mspg_grd_2.miffjpg mspg_full_2.miffjpg mspg_full_2_h_glc.png mspg_diff_2.miffjpg
1 mspg_grd_1.miffjpg mspg_full_1.miffjpg mspg_full_1_h_glc.png mspg_diff_1.miffjpg
0 mspg_grd_0.miffjpg mspg_full_0.miffjpg mspg_full_0_h_glc.png mspg_diff_0.miffjpg

The script mkGausPyr.bat has created a reconstruction script that will write an output file the name of which is in environment variable mkgpRECON_FILE.

%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]\n" msp_g2.tiff[%mkgpNUM_OCTAVES%] 
echo mkgpRECON_FILE=%mkgpRECON_FILE% 

if "%mkgpRECON_FILE%"=="" exit /B 1
min=0.500008 max=0.500008
mkgpRECON_FILE=mspg_mkgp_recon.miff 

From the pyramid file, we can reconstruct an image. We "collapse" the pyramid. Then we compare the reconstruction with the original.

%IMG7%magick ^
  -script mspg_mkgp_recon.scr

if ERRORLEVEL 1 exit /B 1

%IMG7%magick ^
  %mkgpRECON_FILE% msp_recon_g.png

if ERRORLEVEL 1 exit /B 1

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_g.png ^
  NULL: >msp_comp1.lis 2^>^&1
6.98244 (0.000106545)
msp_recon_g.png

Make Laplacian pyramid

The script mkLapPyr.bat makes a Laplacian pyramid.

We will want to reconstruct this example later, with a modified pyramid, so we will create a file of the variables used.

set pyWR_VAR=1
set pyPREFIX=mspl1_

call %PICTBAT%mkLapPyr %SRC% msp_l1.tiff

set pyWR_VAR=

As with the Gaussian, there are 8 grids, or levels in the pyramid, numbered 0 to 7. The final image in the file is the input image minus the sum of the enlarged grids. Here is the pyramid:

%IMG7%magick identify msp_l1.tiff 
msp_l1.tiff[0] TIFF 267x233 267x233+0+0 32-bit sRGB 1.19117MiB 0.001u 0:00.000
msp_l1.tiff[1] TIFF 134x117 134x117+0+0 32-bit sRGB 0.001u 0:00.000
msp_l1.tiff[2] TIFF 67x58 67x58+0+0 32-bit sRGB 0.001u 0:00.000
msp_l1.tiff[3] TIFF 33x29 33x29+0+0 32-bit sRGB 0.001u 0:00.000
msp_l1.tiff[4] TIFF 17x15 17x15+0+0 32-bit sRGB 0.001u 0:00.000
msp_l1.tiff[5] TIFF 8x7 8x7+0+0 32-bit sRGB 0.001u 0:00.000
msp_l1.tiff[6] TIFF 4x4 4x4+0+0 32-bit sRGB 0.001u 0:00.000
msp_l1.tiff[7] TIFF 267x233 267x233+0+0 32-bit Grayscale Gray 0.001u 0:00.000

Laplacian pyramids are cumulative, so the "Difference" column shows the input to that level minus the enlarged grid. Put another way, it shows the source image minus the sum of the enlarged grids at or above that octave.

Octave Grid Grid, resized up Histogram of resized grid Difference
6 mspl1_grd_6.miffjpg mspl1_full_6.miffjpg mspl1_full_6_h_glc.png mspl1_diff_6.miffjpg
5 mspl1_grd_5.miffjpg mspl1_full_5.miffjpg mspl1_full_5_h_glc.png mspl1_diff_5.miffjpg
4 mspl1_grd_4.miffjpg mspl1_full_4.miffjpg mspl1_full_4_h_glc.png mspl1_diff_4.miffjpg
3 mspl1_grd_3.miffjpg mspl1_full_3.miffjpg mspl1_full_3_h_glc.png mspl1_diff_3.miffjpg
2 mspl1_grd_2.miffjpg mspl1_full_2.miffjpg mspl1_full_2_h_glc.png mspl1_diff_2.miffjpg
1 mspl1_grd_1.miffjpg mspl1_full_1.miffjpg mspl1_full_1_h_glc.png mspl1_diff_1.miffjpg
0 mspl1_grd_0.miffjpg mspl1_full_0.miffjpg mspl1_full_0_h_glc.png mspl1_diff_0.miffjpg

The histogram shows that the enlarged grids approach a Laplacian distribution: values cluster around "zero" (50%), with a sharp peak at that value. A Gaussian distribution is similar, but with a rounded peak.

The final difference is zero:

%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]" msp_l1.tiff[%mklpNUM_OCTAVES%] 
min=0.5 max=0.5

From the pyramid file, we can reconstruct an image. We "collapse" the pyramid. Then we compare the reconstruction with the original.

%IMG7%magick ^
  -script mspl1_mklp_recon.scr

%IMG7%magick ^
  %mklpRECON_FILE% msp_recon_l1.png

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_l1.png ^
  NULL: >msp_comp2.lis 2^>^&1
67.6802 (0.00103273)
msp_recon_l1.png

Script controls

Both scripts take up to six arguments.

%1 Input image
%2 Output filename for the multi-image pyramid.
%3 The minimum block size. Small blocks give high-frequency detail. Default: 1
%4 The maximum block size. Large blocks give low-frequency detail. Default: min(height,width)/2
%5 The block factor. Should be >=1. Default: 2
%6 The power factor. Default: 0

The script starts at block size %3 x %3. This is the smallest block size, which creates the highest frequency noise. Successive blocks are %5 times as large, at increasing sizes so at lower frequencies. It stops when the block size would be greater than or equal to %4.

Block sizes correspond to wavelengths, the inverse of frequencies.

Amplitudes are normalised so the maximum is 1.0. Each is then multiplied by %fnCONST_FAC%, which defaults to 1.0. If %fnCONST_FAC% is set to a value greater then 1.0, the result is likely to be clipped at 100%.

The power factor has no relevance to Gaussian pyramids. For Laplacian pyramids, it determines relative weights of the grids. At the default value of zero, grids have equal weights. For values greater than zero, lower frequencies have more weight. For values less than zero, higher frequencies have more weight. The useful range is about -1 to +1.

The scripts also use the values of some environment variables, if these are set.

pyPREFIX Prefix for working files (but not output file).
Default: py_
pyIM The directory for IM programs.
Default: %IMG7%
pyMIN_BLK_WH Don't make octaves with fewer blocks across image width or height.
Default: no minimum.
pyMAX_NUM_OCT Don't make more than this number of octaves.
Default: 100
pyWEIGHTING Set to sum to scale weights so sum is 1.0 (or 0.5).
Set to max to scale weights so maximum weight is 1.0 (or 0.5).
Default: sum
pyGRAPHIC If 1, halves the sum or maximum of the weights.
Default: no halving.
pyWR_VAR If 1, mkvPyr.bat will create text file of variables.
Default: doesn't create text file.
pyTXT_OUT If 1, echoes some text.
Default: no echoing.

Examples of Laplacian pyramids

For example: set the minimum block size to 10, so grid zero will be about 1/10 the size of the source image.

set pyPREFIX=mspl2_

call %PICTBAT%mkLapPyr %SRC% msp_l2.tiff 10 80
%IMG7%magick identify msp_l2.tiff 
msp_l2.tiff[0] TIFF 27x23 27x23+0+0 32-bit sRGB 758120B 0.000u 0:00.000
msp_l2.tiff[1] TIFF 13x12 13x12+0+0 32-bit sRGB 0.000u 0:00.000
msp_l2.tiff[2] TIFF 7x6 7x6+0+0 32-bit sRGB 0.000u 0:00.000
msp_l2.tiff[3] TIFF 267x233 267x233+0+0 32-bit sRGB 0.000u 0:00.000
Octave Grid Grid, resized up Histogram of resized grid Difference
2 mspl2_grd_2.miffjpg mspl2_full_2.miffjpg mspl2_full_2_h_glc.png mspl2_diff_2.miffjpg
1 mspl2_grd_1.miffjpg mspl2_full_1.miffjpg mspl2_full_1_h_glc.png mspl2_diff_1.miffjpg
0 mspl2_grd_0.miffjpg mspl2_full_0.miffjpg mspl2_full_0_h_glc.png mspl2_diff_0.miffjpg
%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]" msp_l2.tiff[%mklpNUM_OCTAVES%] 
min=0.233368 max=0.871562
%IMG7%magick ^
  -script mspl2_mklp_recon.scr

%IMG7%magick ^
  %mklpRECON_FILE% msp_recon_l2.png

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_l2.png ^
  NULL: >msp_compl2.lis 2^>^&1
10.5857 (0.000161527)
msp_recon_l2.png

Another example: set the minimum block size to 0.2, so blah.

set pyPREFIX=mspl3_

call %PICTBAT%mkLapPyr %SRC% msp_l3.tiff 0.2 20
%IMG7%magick identify msp_l3.tiff 
msp_l3.tiff[0] TIFF 1335x1165 1335x1165+0+0 32-bit sRGB 24.4533MiB 0.001u 0:00.000
msp_l3.tiff[1] TIFF 668x583 668x583+0+0 32-bit sRGB 0.001u 0:00.000
msp_l3.tiff[2] TIFF 334x291 334x291+0+0 32-bit sRGB 0.001u 0:00.000
msp_l3.tiff[3] TIFF 167x146 167x146+0+0 32-bit sRGB 0.001u 0:00.000
msp_l3.tiff[4] TIFF 83x73 83x73+0+0 32-bit sRGB 0.001u 0:00.000
msp_l3.tiff[5] TIFF 42x36 42x36+0+0 32-bit sRGB 0.001u 0:00.000
msp_l3.tiff[6] TIFF 21x18 21x18+0+0 32-bit sRGB 0.001u 0:00.000
msp_l3.tiff[7] TIFF 267x233 267x233+0+0 32-bit sRGB 0.001u 0:00.000
Octave Grid Grid, resized up Histogram of resized grid Difference
6 mspl3_grd_6.miffjpg mspl3_full_6.miffjpg mspl3_full_6_h_glc.png mspl3_diff_6.miffjpg
5 mspl3_grd_5.miffjpg mspl3_full_5.miffjpg mspl3_full_5_h_glc.png mspl3_diff_5.miffjpg
4 mspl3_grd_4.miffjpg mspl3_full_4.miffjpg mspl3_full_4_h_glc.png mspl3_diff_4.miffjpg
3 mspl3_grd_3.miffjpg mspl3_full_3.miffjpg mspl3_full_3_h_glc.png mspl3_diff_3.miffjpg
2 mspl3_grd_2.miffjpg mspl3_full_2.miffjpg mspl3_full_2_h_glc.png mspl3_diff_2.miffjpg
1 mspl3_grd_1.miffjpg mspl3_full_1.miffjpg mspl3_full_1_h_glc.png mspl3_diff_1.miffjpg
0 mspl3_grd_0.miffjpg mspl3_full_0.miffjpg mspl3_full_0_h_glc.png mspl3_diff_0.miffjpg
%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]" msp_l3.tiff[%mklpNUM_OCTAVES%] 
min=0.484955 max=0.51127
%IMG7%magick ^
  -script mspl3_mklp_recon.scr

%IMG7%magick ^
  %mklpRECON_FILE% msp_recon_l3.png

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_l3.png ^
  NULL: >msp_compl3.lis 2^>^&1
0 (0)
msp_recon_l3.png

Another example: set the minimum and maximum block sizes, so we have fewer octaves.

set pyPREFIX=mspl3a_

call %PICTBAT%mkLapPyr %SRC% msp_l3a.tiff 1 4
%IMG7%magick identify msp_l3a.tiff 
msp_l3a.tiff[0] TIFF 267x233 267x233+0+0 32-bit sRGB 1.12991MiB 0.000u 0:00.000
msp_l3a.tiff[1] TIFF 134x117 134x117+0+0 32-bit sRGB 0.000u 0:00.000
msp_l3a.tiff[2] TIFF 267x233 267x233+0+0 32-bit Grayscale Gray 0.000u 0:00.000
Octave Grid Grid, resized up Histogram of resized grid Difference
1 mspl3a_grd_1.miffjpg mspl3a_full_1.miffjpg mspl3a_full_1_h_glc.png mspl3a_diff_1.miffjpg
0 mspl3a_grd_0.miffjpg mspl3a_full_0.miffjpg mspl3a_full_0_h_glc.png mspl3a_diff_0.miffjpg
%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]" msp_l3a.tiff[%mklpNUM_OCTAVES%] 
min=0.5 max=0.5
%IMG7%magick ^
  -script mspl3a_mklp_recon.scr

%IMG7%magick ^
  %mklpRECON_FILE% msp_recon_l3a.png

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_l3a.png ^
  NULL: >msp_compl3a.lis 2^>^&1
0 (0)
msp_recon_l3a.png

Another example: small block factor, so we get more frequencies.

set pyPREFIX=mspl4_

call %PICTBAT%mkLapPyr %SRC% msp_l4.tiff . . 1.5
%IMG7%magick identify msp_l4.tiff 
msp_l4.tiff[0] TIFF 267x233 267x233+0+0 32-bit sRGB 1.52441MiB 0.001u 0:00.001
msp_l4.tiff[1] TIFF 178x155 178x155+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[2] TIFF 119x104 119x104+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[3] TIFF 79x69 79x69+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[4] TIFF 53x46 53x46+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[5] TIFF 35x31 35x31+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[6] TIFF 23x20 23x20+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[7] TIFF 16x14 16x14+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[8] TIFF 10x9 10x9+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[9] TIFF 7x6 7x6+0+0 32-bit sRGB 0.001u 0:00.001
msp_l4.tiff[10] TIFF 5x4 5x4+0+0 32-bit sRGB 0.001u 0:00.000
msp_l4.tiff[11] TIFF 3x3 3x3+0+0 32-bit sRGB 0.004u 0:00.004
msp_l4.tiff[12] TIFF 267x233 267x233+0+0 32-bit Grayscale Gray 0.004u 0:00.003
Octave Grid Grid, resized up Histogram of resized grid Difference
11 mspl4_grd_11.miffjpg mspl4_full_11.miffjpg mspl4_full_11_h_glc.png mspl4_diff_11.miffjpg
10 mspl4_grd_10.miffjpg mspl4_full_10.miffjpg mspl4_full_10_h_glc.png mspl4_diff_10.miffjpg
9 mspl4_grd_9.miffjpg mspl4_full_9.miffjpg mspl4_full_9_h_glc.png mspl4_diff_9.miffjpg
8 mspl4_grd_8.miffjpg mspl4_full_8.miffjpg mspl4_full_8_h_glc.png mspl4_diff_8.miffjpg
7 mspl4_grd_7.miffjpg mspl4_full_7.miffjpg mspl4_full_7_h_glc.png mspl4_diff_7.miffjpg
6 mspl4_grd_6.miffjpg mspl4_full_6.miffjpg mspl4_full_6_h_glc.png mspl4_diff_6.miffjpg
5 mspl4_grd_5.miffjpg mspl4_full_5.miffjpg mspl4_full_5_h_glc.png mspl4_diff_5.miffjpg
4 mspl4_grd_4.miffjpg mspl4_full_4.miffjpg mspl4_full_4_h_glc.png mspl4_diff_4.miffjpg
3 mspl4_grd_3.miffjpg mspl4_full_3.miffjpg mspl4_full_3_h_glc.png mspl4_diff_3.miffjpg
2 mspl4_grd_2.miffjpg mspl4_full_2.miffjpg mspl4_full_2_h_glc.png mspl4_diff_2.miffjpg
1 mspl4_grd_1.miffjpg mspl4_full_1.miffjpg mspl4_full_1_h_glc.png mspl4_diff_1.miffjpg
0 mspl4_grd_0.miffjpg mspl4_full_0.miffjpg mspl4_full_0_h_glc.png mspl4_diff_0.miffjpg
%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]" msp_l4.tiff[%mklpNUM_OCTAVES%] 
min=0.5 max=0.5
%IMG7%magick ^
  -script mspl4_mklp_recon.scr

%IMG7%magick ^
  %mklpRECON_FILE% msp_recon_l4.png

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_l4.png ^
  NULL: >msp_compl4.lis 2^>^&1
59.0646 (0.000901268)
msp_recon_l4.png

Another example: a negative power factor gives more weight to higher frequencies.

We will want to reconstruct this example later, with a modified pyramid, so we will create a file of the variables used.

set pyWR_VAR=1
set pyPREFIX=mspl5_

call %PICTBAT%mkLapPyr %SRC% msp_l5.tiff . . . -0.5

set pyWR_VAR=
%IMG7%magick identify msp_l5.tiff 
msp_l5.tiff[0] TIFF 267x233 267x233+0+0 32-bit sRGB 1.19117MiB 0.001u 0:00.000
msp_l5.tiff[1] TIFF 134x117 134x117+0+0 32-bit sRGB 0.001u 0:00.001
msp_l5.tiff[2] TIFF 67x58 67x58+0+0 32-bit sRGB 0.001u 0:00.001
msp_l5.tiff[3] TIFF 33x29 33x29+0+0 32-bit sRGB 0.001u 0:00.001
msp_l5.tiff[4] TIFF 17x15 17x15+0+0 32-bit sRGB 0.001u 0:00.000
msp_l5.tiff[5] TIFF 8x7 8x7+0+0 32-bit sRGB 0.001u 0:00.000
msp_l5.tiff[6] TIFF 4x4 4x4+0+0 32-bit sRGB 0.001u 0:00.000
msp_l5.tiff[7] TIFF 267x233 267x233+0+0 32-bit Grayscale Gray 0.001u 0:00.000
Octave Grid Grid, resized up Histogram of resized grid Difference
6 mspl5_grd_6.miffjpg mspl5_full_6.miffjpg mspl5_full_6_h_glc.png mspl5_diff_6.miffjpg
5 mspl5_grd_5.miffjpg mspl5_full_5.miffjpg mspl5_full_5_h_glc.png mspl5_diff_5.miffjpg
4 mspl5_grd_4.miffjpg mspl5_full_4.miffjpg mspl5_full_4_h_glc.png mspl5_diff_4.miffjpg
3 mspl5_grd_3.miffjpg mspl5_full_3.miffjpg mspl5_full_3_h_glc.png mspl5_diff_3.miffjpg
2 mspl5_grd_2.miffjpg mspl5_full_2.miffjpg mspl5_full_2_h_glc.png mspl5_diff_2.miffjpg
1 mspl5_grd_1.miffjpg mspl5_full_1.miffjpg mspl5_full_1_h_glc.png mspl5_diff_1.miffjpg
0 mspl5_grd_0.miffjpg mspl5_full_0.miffjpg mspl5_full_0_h_glc.png mspl5_diff_0.miffjpg
%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]" msp_l5.tiff[%mklpNUM_OCTAVES%] 
min=0.5 max=0.5
%IMG7%magick ^
  -script mspl5_mklp_recon.scr

%IMG7%magick ^
  %mklpRECON_FILE% msp_recon_l5.png

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_l5.png ^
  NULL: >msp_compl5.lis 2^>^&1
1.79162 (2.73384e-05)
msp_recon_l5.png

Another example: a positive power factor gives more weight to low frequencies.

set pyPREFIX=mspl6_

call %PICTBAT%mkLapPyr %SRC% msp_l6.tiff . . . 0.5
%IMG7%magick identify msp_l6.tiff 
msp_l6.tiff[0] TIFF 267x233 267x233+0+0 32-bit sRGB 1.66583MiB 0.001u 0:00.000
msp_l6.tiff[1] TIFF 134x117 134x117+0+0 32-bit sRGB 0.001u 0:00.000
msp_l6.tiff[2] TIFF 67x58 67x58+0+0 32-bit sRGB 0.001u 0:00.000
msp_l6.tiff[3] TIFF 33x29 33x29+0+0 32-bit sRGB 0.001u 0:00.000
msp_l6.tiff[4] TIFF 17x15 17x15+0+0 32-bit sRGB 0.001u 0:00.000
msp_l6.tiff[5] TIFF 8x7 8x7+0+0 32-bit sRGB 0.001u 0:00.000
msp_l6.tiff[6] TIFF 4x4 4x4+0+0 32-bit sRGB 0.001u 0:00.000
msp_l6.tiff[7] TIFF 267x233 267x233+0+0 32-bit sRGB 0.001u 0:00.000
Octave Grid Grid, resized up Histogram of resized grid Difference
6 mspl6_grd_6.miffjpg mspl6_full_6.miffjpg mspl6_full_6_h_glc.png mspl6_diff_6.miffjpg
5 mspl6_grd_5.miffjpg mspl6_full_5.miffjpg mspl6_full_5_h_glc.png mspl6_diff_5.miffjpg
4 mspl6_grd_4.miffjpg mspl6_full_4.miffjpg mspl6_full_4_h_glc.png mspl6_diff_4.miffjpg
3 mspl6_grd_3.miffjpg mspl6_full_3.miffjpg mspl6_full_3_h_glc.png mspl6_diff_3.miffjpg
2 mspl6_grd_2.miffjpg mspl6_full_2.miffjpg mspl6_full_2_h_glc.png mspl6_diff_2.miffjpg
1 mspl6_grd_1.miffjpg mspl6_full_1.miffjpg mspl6_full_1_h_glc.png mspl6_diff_1.miffjpg
0 mspl6_grd_0.miffjpg mspl6_full_0.miffjpg mspl6_full_0_h_glc.png mspl6_diff_0.miffjpg
%IMG7%magick identify -format "min=%%[fx:minima] max=%%[fx:maxima]" msp_l6.tiff[%mklpNUM_OCTAVES%] 
min=0.317592 max=0.785909
%IMG7%magick ^
  -script mspl6_mklp_recon.scr

%IMG7%magick ^
  %mklpRECON_FILE% msp_recon_l6.png

%IMG7%magick compare ^
  -metric RMSE ^
  %SRC% ^
  msp_recon_l6.png ^
  NULL: >msp_compl6.lis 2^>^&1
67.6802 (0.00103273)
msp_recon_l6.png

How it works

The two scripts work in the same way, by first calling mkPyrComm.bat. This does some validation, and calls mkpVar.bat, which does the messy work of calculating from the six input parameters how many levels (octaves) the pyramid will have, and the size and weight of each level.

mkpVar.bat will create blocks up to a specified maximum size. For some applications, we care more about the number of blocks. For example, we may not want any octaves to contain fewer than ten blocks in width or height. For this, set pyMIN_BLK_WH=10. If this value is set too high, no octaves will be created, and mkpVar.bat will fail.

Or we might care more about the number of octaves in the pyramid. For this, set pyMAX_NUM_OCT=6 or whatever. If this variable is not set, the script will use a default value of 100, to prevent infinite loops. If you really want more than 100 octaves, change the script.

Weights are calculated from the variable FR, for "frequency ratio", which is calculated as sqrt( (B*B)/(W*H) ) where B is the block size, W is the required output width and H is the required output height. For larger blocks, representing lower frequencies, the value of FR decreases.

Weights are scaled so that either the maximum weight is 1.0 (set pyWEIGHTING=max), or the sum of the weights is 1.0 (set pyWEIGHTING=sum). (When pyGRAPHIC is 1, the maximum or sum will be 0.5.)

Optionally, mkpVar.bat can write data about a pyramid to a text file named {something}_blk.lis, with each line in the form:

{variable}={value}

Other scripts can read this file, to re-create the environment variables. If you want this file to be created, set pyWR_VAR=1.

The mechanism is designed for performance of animations. Storing values in environment variables, or even reading the values from a file, is quicker than recalculating them.

When mkPyrComm.bat finishes, control returns to mkGausPyr.bat or mkLapPyr.bat. That script creates two ImageMagick script files:

mkGausPyr.bat or mkLapPyr.bat then runs magick with {something}_mk.scr, supplying the output file, which typically would have a .tiff extension. Thus the pyramid is created. Optionally, it also creates single-image files and an HTML file that contains a table of those images, including histograms of enlarged grids, such as those on this page.

The basic block for building pyramids is:

previous_difference - levelled(enlarged(grid)) + 0.5 -> next_difference
shrink(new_difference) -> next_grid

Conversely, for collapsing pyramids:

previous + levelled(enlarged(grid)) - 0.5 -> next

In both directions, clipping is possible (because when 0<=x,y<=100, -50<=x-y+50<=+150). This doesn't happen (in my experience) with ordinary photographs but can happen with graphics. So I provide the pyGRAPHIC option. When this is set to 1, all amplitudes are halved at the levelling stage. (This ensures that 0<=x,y<=50 so 0<=x-y+50<=+100.) This prevents clipping. Unfortunately halving amplitudes can prevent the final difference from being zero, for both photographs and graphics, so the difference must always be included in the image reconstruction. The scripts currently do not detect clipping. Setting pyGRAPHIC to 1 is always safe.

Other circumstances will also cause non-zero final differences:

Is one difference image always enough? It seems to be, provided pyGRAPHIC=1 so the final difference carries twice the weight of the heaviest grid.

Here are sample _blk.lis, _mk.scr and _recon.scr files from an example above.

mspl5_blk.lis:

pyWEIGHTING=max
pyGRAPHIC=
pyLINEAR=
NUM_OCTAVES=7
mklpRECON_FILE=mspl5_mklp_recon.miff
IMG_WW=267
IMG_HH=233
BLK_SZ.0=1
N_BLK_W.0=267
N_BLK_H.0=233
FR.0=0.00400928019615047
S1AMP.0=0.321291657536273
M1AMP.0=1
AMP.0=1
sLEVEL.0=
BLK_SZ.1=2
N_BLK_W.1=134
N_BLK_H.1=117
FR.1=0.00801856039230093
S1AMP.1=0.227187509782565
M1AMP.1=0.70710678118655
AMP.1=0.70710678118655
sLEVEL.1=+level 14.6446609406725%,85.3553390593275%
BLK_SZ.2=4
N_BLK_W.2=67
N_BLK_H.2=58
FR.2=0.0160371207846019
S1AMP.2=0.160645828768137
M1AMP.2=0.500000000000001
AMP.2=0.500000000000001
sLEVEL.2=+level 25%,75%
BLK_SZ.3=8
N_BLK_W.3=33
N_BLK_H.3=29
FR.3=0.0320742415692037
S1AMP.3=0.113593754891283
M1AMP.3=0.353553390593275
AMP.3=0.353553390593275
sLEVEL.3=+level 32.3223304703362%,67.6776695296638%
BLK_SZ.4=16
N_BLK_W.4=17
N_BLK_H.4=15
FR.4=0.0641484831384074
S1AMP.4=0.0803229143840684
M1AMP.4=0.250000000000001
AMP.4=0.250000000000001
sLEVEL.4=+level 37.5%,62.5%
BLK_SZ.5=32
N_BLK_W.5=8
N_BLK_H.5=7
FR.5=0.128296966276815
S1AMP.5=0.0567968774456412
M1AMP.5=0.176776695296637
AMP.5=0.176776695296637
sLEVEL.5=+level 41.1611652351681%,58.8388347648319%
BLK_SZ.6=64
N_BLK_W.6=4
N_BLK_H.6=4
FR.6=0.25659393255363
S1AMP.6=0.0401614571920342
M1AMP.6=0.125
AMP.6=0.125
sLEVEL.6=+level 43.75%,56.25%

mspl5_mklp_mk.scr:

toes.png
+write mpr:SRC 
-channel RGB -resize "4x4!" +channel
+write mspl5_grd_6.miff
+write mpr:GRD6
-channel RGB -resize "267x233!" +channel
+write mspl5_full_6.miff +level 43.75%,56.25%
mpr:SRC
-channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
+write mspl5_diff_6.miff
+write mpr:DIFF6
-channel RGB -resize "8x7!" +channel
+write mspl5_grd_5.miff
+write mpr:GRD5
-channel RGB -resize "267x233!" +channel
+write mspl5_full_5.miff +level 41.1611652351681%,58.8388347648319%
mpr:DIFF6
-channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
+write mspl5_diff_5.miff
+write mpr:DIFF5
-channel RGB -resize "17x15!" +channel
+write mspl5_grd_4.miff
+write mpr:GRD4
-channel RGB -resize "267x233!" +channel
+write mspl5_full_4.miff +level 37.5%,62.5%
mpr:DIFF5
-channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
+write mspl5_diff_4.miff
+write mpr:DIFF4
-channel RGB -resize "33x29!" +channel
+write mspl5_grd_3.miff
+write mpr:GRD3
-channel RGB -resize "267x233!" +channel
+write mspl5_full_3.miff +level 32.3223304703362%,67.6776695296638%
mpr:DIFF4
-channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
+write mspl5_diff_3.miff
+write mpr:DIFF3
-channel RGB -resize "67x58!" +channel
+write mspl5_grd_2.miff
+write mpr:GRD2
-channel RGB -resize "267x233!" +channel
+write mspl5_full_2.miff +level 25%,75%
mpr:DIFF3
-channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
+write mspl5_diff_2.miff
+write mpr:DIFF2
-channel RGB -resize "134x117!" +channel
+write mspl5_grd_1.miff
+write mpr:GRD1
-channel RGB -resize "267x233!" +channel
+write mspl5_full_1.miff +level 14.6446609406725%,85.3553390593275%
mpr:DIFF2
-channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
+write mspl5_diff_1.miff
+write mpr:DIFF1
+write mspl5_grd_0.miff
+write mpr:GRD0
+write mspl5_full_0.miff 
mpr:DIFF1
-channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
+write mspl5_diff_0.miff
+write mpr:DIFF0
+delete
mpr:GRD0
mpr:GRD1
mpr:GRD2
mpr:GRD3
mpr:GRD4
mpr:GRD5
mpr:GRD6
mpr:DIFF0
-define quantum:format=floating-point
+depth
-depth 32
-define tiff:alpha=associated
-write msp_l5.tiff
-exit

mspl5_mklp_recon.scr:

( msp_l5.tiff[6]
-resize "267x233!"
+level 43.75%,56.25% )
( msp_l5.tiff[5]
-resize "267x233!"
+level 41.1611652351681%,58.8388347648319% )
-compose Mathematics -define compose:args=0,1,1,-0.5 -composite
( msp_l5.tiff[4]
-resize "267x233!"
+level 37.5%,62.5% )
-compose Mathematics -define compose:args=0,1,1,-0.5 -composite
( msp_l5.tiff[3]
-resize "267x233!"
+level 32.3223304703362%,67.6776695296638% )
-compose Mathematics -define compose:args=0,1,1,-0.5 -composite
( msp_l5.tiff[2]
-resize "267x233!"
+level 25%,75% )
-compose Mathematics -define compose:args=0,1,1,-0.5 -composite
( msp_l5.tiff[1]
-resize "267x233!"
+level 14.6446609406725%,85.3553390593275% )
-compose Mathematics -define compose:args=0,1,1,-0.5 -composite
( msp_l5.tiff[0]
 )
-compose Mathematics -define compose:args=0,1,1,-0.5 -composite
msp_l5.tiff[7]
-compose Mathematics -define compose:args=0,1,1,-0.5 -composite 
-write mspl5_mklp_recon.miff
-exit

Representation

In the literature, grids (the layers of a pyramid) are sometimes combined into a single image, like this:

Example Gaussian pyramid

%IMG7%magick ^
  msp_g1.tiff ^
  +delete ^
  ( -clone 0 +write mpr:GRD0 +delete ) ^
  -delete 0 ^
  -append +repage ^
  mpr:GRD0 ^
  +swap ^
  +append +repage ^
  msp_repres.png
msp_repres.pngjpg

Example Laplacian pyramid

%IMG7%magick ^
  msp_l1.tiff ^
  +delete ^
  ( -clone 0 +write mpr:GRD0 +delete ) ^
  -delete 0 ^
  -append +repage ^
  mpr:GRD0 ^
  +swap ^
  +append +repage ^
  msp_repres_l.png
msp_repres_l.pngjpg

Experiments

Scripts in this section operate on pyramid files. They apply some processing to level 0 then collapse the pyramid to make an image. They repeat this for every level, so there will be one image per level, each with the processing applied to just that level. Then they create HTML code that shows each resulting image. That HTML code is included in this web page, so we can see the images.

The scripts need:

By default, mkLapPyr.bat doesn't make a block list file, but will do so if set pyWR_VAR=1.

Boost narrow-band frequencies

For a Laplacian pyramid, we can selectively boost or cut the amplitude at selected frequencies. This is like the graphic equaliser on an audio system that modifies the loudness of treble, bass, or a selected frequency range. To do this properly we would make an alternative _recon.scr script. As a crude proof-of-concept test, we simply apply a sigmoidal-contrast operation to each of the grids in turn, reconstructing the image each time. The script mspBoostCut.bat does this, creating HTML code that is included below. In this crude test, we don't change the amplitude of the final difference.

"Boost" increases the mid-tone contrast, which usually increases saturation.

set pyPREFIX=mspl5_

call %PICTBAT%mspBoostCut msp_l5.tiff boost 10 msp_boost.htm
call %PICTBAT%mspBoostCut msp_l5.tiff cut 10 msp_cut.htm

For comparison, here is toes.png:

toes.pngjpg

Each image shows the effect of applying the stated process to one grid of a Laplacian pyramid, and reconstructing an image from that altered pyramid. In each set, we start at octave 0, which is the largest grid and contains the highest frequencies.

mspBoostCut: boost 10

mspl5_mbc_msp_boost_0.pngjpg mspl5_mbc_msp_boost_1.pngjpg mspl5_mbc_msp_boost_2.pngjpg mspl5_mbc_msp_boost_3.pngjpg mspl5_mbc_msp_boost_4.pngjpg mspl5_mbc_msp_boost_5.pngjpg mspl5_mbc_msp_boost_6.pngjpg mspl5_mbc_msp_boost_7.pngjpg

mspBoostCut: cut 10

mspl5_mbc_msp_cut_0.pngjpg mspl5_mbc_msp_cut_1.pngjpg mspl5_mbc_msp_cut_2.pngjpg mspl5_mbc_msp_cut_3.pngjpg mspl5_mbc_msp_cut_4.pngjpg mspl5_mbc_msp_cut_5.pngjpg mspl5_mbc_msp_cut_6.pngjpg mspl5_mbc_msp_cut_7.pngjpg

Boost more:

set pyPREFIX=mspl5_

call %PICTBAT%mspBoostCut msp_l5.tiff boost 25 msp_boost2.htm
call %PICTBAT%mspBoostCut msp_l5.tiff boost 75 msp_boost3.htm

mspBoostCut: boost 25

mspl5_mbc_msp_boost2_0.pngjpg mspl5_mbc_msp_boost2_1.pngjpg mspl5_mbc_msp_boost2_2.pngjpg mspl5_mbc_msp_boost2_3.pngjpg mspl5_mbc_msp_boost2_4.pngjpg mspl5_mbc_msp_boost2_5.pngjpg mspl5_mbc_msp_boost2_6.pngjpg mspl5_mbc_msp_boost2_7.pngjpg

mspBoostCut: boost 75

mspl5_mbc_msp_boost3_0.pngjpg mspl5_mbc_msp_boost3_1.pngjpg mspl5_mbc_msp_boost3_2.pngjpg mspl5_mbc_msp_boost3_3.pngjpg mspl5_mbc_msp_boost3_4.pngjpg mspl5_mbc_msp_boost3_5.pngjpg mspl5_mbc_msp_boost3_6.pngjpg mspl5_mbc_msp_boost3_7.pngjpg

The idea could be extended so to allow cutting some arbitrary frequencies while boosting others.

Pyramids msp_l1.tiff and msp_l5.tiff were built with power factors 0.0 and -0.5 to give an equal or greater weight to higher frequencies in the pyramid. Collapsing either pyramid in the ordinary way will restore the original images.

However, we can use the msp_l5.tiff reconstruction script on the msp_l1.tiff pyramid. We can do this very crudely by making a copy of the script, changing the name of the pyramid file from msp_l5 to msp_l1.

sed -e 's/msp_l5/msp_l1/g' mspl5_mklp_recon.scr >msp_1_sharper.scr

Then we collapse the msp_l1 pyramid with a script that was created for msp_l5.

%IMG7%magick -script msp_1_sharper.scr

%IMG7%magick %mklpRECON_FILE% msp_1_sharper.png
msp_1_sharper.pngjpg

Because low-frequency weight has reduced, so has colour saturation. We restore colours by replacing the a* and b* channels of an L*a*b* version with those from toes.png.

%IMG7%magick ^
  msp_1_sharper.png ^
  toes.png ^
  -colorspace Lab ^
  -separate ^
  -delete 1-3 ^
  -set colorspace Lab ^
  -combine ^
  -colorspace sRGB ^
  msp_1_shlab.png
msp_1_shlab.pngjpg

The effect is very heavy. We will create a lighter effect.

set pyWR_VAR=1
set pyPREFIX=mspl5a_

call %PICTBAT%mkLapPyr %SRC% msp_l5a.tiff . . . -0.1

set pyWR_VAR=

sed -e 's/msp_l5a/msp_l1/g' mspl5a_mklp_recon.scr >msp_1_sharper2.scr
%IMG7%magick -script msp_1_sharper2.scr

%IMG7%magick %mklpRECON_FILE% msp_1_sharper2.png

%IMG7%magick ^
  msp_1_sharper2.png ^
  toes.png ^
  -colorspace Lab ^
  -separate ^
  -delete 1-3 ^
  -set colorspace Lab ^
  -combine ^
  -colorspace sRGB ^
  msp_1_shlab2.png
msp_1_sharper2.pngjpg msp_1_shlab2.pngjpg

General grid processing

Above, we applied a sigmoidal-contrast operation to each of the grids. We can generalise this to apply any IM operation, implemented as mspProcGrid.bat.

For the following effects, we use the pyramid built with a negative power factor, which gives more weight to higher frequencies.

set pyPREFIX=mspl5_

for /F "tokens=*" %%L in (%pyPREFIX%blk.lis) do set %%L

call %PICTBAT%mspProcGrid msp_l5.tiff "-level 30%%%%,70%%%%" msp_lev1.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "-level 45%%%%,55%%%%" msp_lev2.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "-auto-level -auto-gamma" msp_alag.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "-equalize" msp_equal.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "-set option:modulate:colorspace HCL -modulate 100,200,100" msp_sat.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "-negate" msp_neg.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "+dither -colors 4" msp_cols.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "+dither -posterize 3" msp_post.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "-paint 3" msp_paint.htm
call %PICTBAT%mspProcGrid msp_l5.tiff "-colorspace Gray" msp_gray.htm

For comparison, here is toes.png:

toes.pngjpg

mspProcGrid: "-level 30%,70%"

mspl5_mbc_msp_lev1_0.pngjpg mspl5_mbc_msp_lev1_1.pngjpg mspl5_mbc_msp_lev1_2.pngjpg mspl5_mbc_msp_lev1_3.pngjpg mspl5_mbc_msp_lev1_4.pngjpg mspl5_mbc_msp_lev1_5.pngjpg mspl5_mbc_msp_lev1_6.pngjpg mspl5_mbc_msp_lev1_7.pngjpg

mspProcGrid: "-level 45%,55%"

mspl5_mbc_msp_lev2_0.pngjpg mspl5_mbc_msp_lev2_1.pngjpg mspl5_mbc_msp_lev2_2.pngjpg mspl5_mbc_msp_lev2_3.pngjpg mspl5_mbc_msp_lev2_4.pngjpg mspl5_mbc_msp_lev2_5.pngjpg mspl5_mbc_msp_lev2_6.pngjpg mspl5_mbc_msp_lev2_7.pngjpg

We can get extreme sharpening without ringing.

mspProcGrid: "-auto-level -auto-gamma"

mspl5_mbc_msp_alag_0.pngjpg mspl5_mbc_msp_alag_1.pngjpg mspl5_mbc_msp_alag_2.pngjpg mspl5_mbc_msp_alag_3.pngjpg mspl5_mbc_msp_alag_4.pngjpg mspl5_mbc_msp_alag_5.pngjpg mspl5_mbc_msp_alag_6.pngjpg mspl5_mbc_msp_alag_7.pngjpg

mspProcGrid: "-equalize"

mspl5_mbc_msp_equal_0.pngjpg mspl5_mbc_msp_equal_1.pngjpg mspl5_mbc_msp_equal_2.pngjpg mspl5_mbc_msp_equal_3.pngjpg mspl5_mbc_msp_equal_4.pngjpg mspl5_mbc_msp_equal_5.pngjpg mspl5_mbc_msp_equal_6.pngjpg mspl5_mbc_msp_equal_7.pngjpg

mspProcGrid: "-set option:modulate:colorspace HCL -modulate 100,200,100"

mspl5_mbc_msp_sat_0.pngjpg mspl5_mbc_msp_sat_1.pngjpg mspl5_mbc_msp_sat_2.pngjpg mspl5_mbc_msp_sat_3.pngjpg mspl5_mbc_msp_sat_4.pngjpg mspl5_mbc_msp_sat_5.pngjpg mspl5_mbc_msp_sat_6.pngjpg mspl5_mbc_msp_sat_7.pngjpg

mspProcGrid: "-negate"

mspl5_mbc_msp_neg_0.pngjpg mspl5_mbc_msp_neg_1.pngjpg mspl5_mbc_msp_neg_2.pngjpg mspl5_mbc_msp_neg_3.pngjpg mspl5_mbc_msp_neg_4.pngjpg mspl5_mbc_msp_neg_5.pngjpg mspl5_mbc_msp_neg_6.pngjpg mspl5_mbc_msp_neg_7.pngjpg

mspProcGrid: "+dither -colors 4"

mspl5_mbc_msp_cols_0.pngjpg mspl5_mbc_msp_cols_1.pngjpg mspl5_mbc_msp_cols_2.pngjpg mspl5_mbc_msp_cols_3.pngjpg mspl5_mbc_msp_cols_4.pngjpg mspl5_mbc_msp_cols_5.pngjpg mspl5_mbc_msp_cols_6.pngjpg mspl5_mbc_msp_cols_7.pngjpg

mspProcGrid: "+dither -posterize 3"

mspl5_mbc_msp_post_0.pngjpg mspl5_mbc_msp_post_1.pngjpg mspl5_mbc_msp_post_2.pngjpg mspl5_mbc_msp_post_3.pngjpg mspl5_mbc_msp_post_4.pngjpg mspl5_mbc_msp_post_5.pngjpg mspl5_mbc_msp_post_6.pngjpg mspl5_mbc_msp_post_7.pngjpg

mspProcGrid: "-paint 3"

mspl5_mbc_msp_paint_0.pngjpg mspl5_mbc_msp_paint_1.pngjpg mspl5_mbc_msp_paint_2.pngjpg mspl5_mbc_msp_paint_3.pngjpg mspl5_mbc_msp_paint_4.pngjpg mspl5_mbc_msp_paint_5.pngjpg mspl5_mbc_msp_paint_6.pngjpg mspl5_mbc_msp_paint_7.pngjpg

mspProcGrid: "-colorspace Gray"

mspl5_mbc_msp_gray_0.pngjpg mspl5_mbc_msp_gray_1.pngjpg mspl5_mbc_msp_gray_2.pngjpg mspl5_mbc_msp_gray_3.pngjpg mspl5_mbc_msp_gray_4.pngjpg mspl5_mbc_msp_gray_5.pngjpg mspl5_mbc_msp_gray_6.pngjpg mspl5_mbc_msp_gray_7.pngjpg

Some effects work better when used on pyramids built with equal weight to all frequencies.

set pyPREFIX=mspl1_

for /F "tokens=*" %%L in (%pyPREFIX%blk.lis) do set %%L

call %PICTBAT%mspProcGrid msp_l1.tiff "-level 30%%%%,70%%%%" msp_lev1q.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "-level 45%%%%,55%%%%" msp_lev2q.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "-auto-level -auto-gamma" msp_alagq.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "-equalize" msp_equalq.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "-set option:modulate:colorspace HCL -modulate 100,200,100" msp_satq.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "-negate" msp_negq.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "+dither -colors 4" msp_colsq.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "+dither -posterize 3" msp_postq.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "-paint 3" msp_paintq.htm
call %PICTBAT%mspProcGrid msp_l1.tiff "-colorspace Gray" msp_grayq.htm

mspProcGrid: "-level 30%,70%"

mspl1_mbc_msp_lev1q_0.pngjpg mspl1_mbc_msp_lev1q_1.pngjpg mspl1_mbc_msp_lev1q_2.pngjpg mspl1_mbc_msp_lev1q_3.pngjpg mspl1_mbc_msp_lev1q_4.pngjpg mspl1_mbc_msp_lev1q_5.pngjpg mspl1_mbc_msp_lev1q_6.pngjpg mspl1_mbc_msp_lev1q_7.pngjpg

mspProcGrid: "-level 45%,55%"

mspl1_mbc_msp_lev2q_0.pngjpg mspl1_mbc_msp_lev2q_1.pngjpg mspl1_mbc_msp_lev2q_2.pngjpg mspl1_mbc_msp_lev2q_3.pngjpg mspl1_mbc_msp_lev2q_4.pngjpg mspl1_mbc_msp_lev2q_5.pngjpg mspl1_mbc_msp_lev2q_6.pngjpg mspl1_mbc_msp_lev2q_7.pngjpg

mspProcGrid: "-auto-level -auto-gamma"

mspl1_mbc_msp_alagq_0.pngjpg mspl1_mbc_msp_alagq_1.pngjpg mspl1_mbc_msp_alagq_2.pngjpg mspl1_mbc_msp_alagq_3.pngjpg mspl1_mbc_msp_alagq_4.pngjpg mspl1_mbc_msp_alagq_5.pngjpg mspl1_mbc_msp_alagq_6.pngjpg mspl1_mbc_msp_alagq_7.pngjpg

mspProcGrid: "-equalize"

mspl1_mbc_msp_equalq_0.pngjpg mspl1_mbc_msp_equalq_1.pngjpg mspl1_mbc_msp_equalq_2.pngjpg mspl1_mbc_msp_equalq_3.pngjpg mspl1_mbc_msp_equalq_4.pngjpg mspl1_mbc_msp_equalq_5.pngjpg mspl1_mbc_msp_equalq_6.pngjpg mspl1_mbc_msp_equalq_7.pngjpg

mspProcGrid: "-set option:modulate:colorspace HCL -modulate 100,200,100"

mspl1_mbc_msp_satq_0.pngjpg mspl1_mbc_msp_satq_1.pngjpg mspl1_mbc_msp_satq_2.pngjpg mspl1_mbc_msp_satq_3.pngjpg mspl1_mbc_msp_satq_4.pngjpg mspl1_mbc_msp_satq_5.pngjpg mspl1_mbc_msp_satq_6.pngjpg mspl1_mbc_msp_satq_7.pngjpg

mspProcGrid: "-negate"

mspl1_mbc_msp_negq_0.pngjpg mspl1_mbc_msp_negq_1.pngjpg mspl1_mbc_msp_negq_2.pngjpg mspl1_mbc_msp_negq_3.pngjpg mspl1_mbc_msp_negq_4.pngjpg mspl1_mbc_msp_negq_5.pngjpg mspl1_mbc_msp_negq_6.pngjpg mspl1_mbc_msp_negq_7.pngjpg

mspProcGrid: "+dither -colors 4"

mspl1_mbc_msp_colsq_0.pngjpg mspl1_mbc_msp_colsq_1.pngjpg mspl1_mbc_msp_colsq_2.pngjpg mspl1_mbc_msp_colsq_3.pngjpg mspl1_mbc_msp_colsq_4.pngjpg mspl1_mbc_msp_colsq_5.pngjpg mspl1_mbc_msp_colsq_6.pngjpg mspl1_mbc_msp_colsq_7.pngjpg

mspProcGrid: "+dither -posterize 3"

mspl1_mbc_msp_postq_0.pngjpg mspl1_mbc_msp_postq_1.pngjpg mspl1_mbc_msp_postq_2.pngjpg mspl1_mbc_msp_postq_3.pngjpg mspl1_mbc_msp_postq_4.pngjpg mspl1_mbc_msp_postq_5.pngjpg mspl1_mbc_msp_postq_6.pngjpg mspl1_mbc_msp_postq_7.pngjpg

mspProcGrid: "-paint 3"

mspl1_mbc_msp_paintq_0.pngjpg mspl1_mbc_msp_paintq_1.pngjpg mspl1_mbc_msp_paintq_2.pngjpg mspl1_mbc_msp_paintq_3.pngjpg mspl1_mbc_msp_paintq_4.pngjpg mspl1_mbc_msp_paintq_5.pngjpg mspl1_mbc_msp_paintq_6.pngjpg mspl1_mbc_msp_paintq_7.pngjpg

mspProcGrid: "-colorspace Gray"

mspl1_mbc_msp_grayq_0.pngjpg mspl1_mbc_msp_grayq_1.pngjpg mspl1_mbc_msp_grayq_2.pngjpg mspl1_mbc_msp_grayq_3.pngjpg mspl1_mbc_msp_grayq_4.pngjpg mspl1_mbc_msp_grayq_5.pngjpg mspl1_mbc_msp_grayq_6.pngjpg mspl1_mbc_msp_grayq_7.pngjpg

Applications

Laplacian pyramids are useful when blending (see Blending pyramids) and filling holes, aka inpainting (see Laplacian pyramids with transparency). They can also be used to quantify levels of detail in an image; see Detail by pyramids.

Examples on this page store pyramids in TIFF files (and write images to other files, for display on the web page). There may be no need to store the pyramid in any file; the pyramid may be built and collapsed within a single magick command. Examples of this are shown on Fractal noise.

Pyramids can be animated. In particular, the image reconstructed from a pyramid ("collapsing" the pyramid) may use slightly transformed values from the grids, with the amount of transformation changing between frames. Examples of this are shown on Fractal noise animations.

Cleanup

set mkpDEBUG=
set mkpHTM=
set pyGRAPHIC=
set pyPREFIX=
set pyIM=

Scripts

For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.

mkGausPyr.bat

rem From image %1, make Gaussian pyramid %2.
@rem %2 should be capable of containing multiple images, eg type TIFF.
@rem %3 %4 %5 %6 parameters to mkPyrComm
@rem
@rem Also uses:
@rem   mkpDEBUG if 1, creates other image files (full and diff).
@rem   mkpHTM if 1, creates HTM file
@rem
@rem Updated:
@rem   5-September-2022 for IM v7. Remove %IML%.
@rem

@rem For some purposes,
@rem resizing down to the grid might be good enough if we use "-scale?


@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 mkgp


if "%pyWEIGHTING%"=="" set pyWEIGHTING=max

call %PICTBAT%mkPyrComm %1 %2 %3 %4 %5 %6
if ERRORLEVEL 1 exit /B 1

echo %INFILE% +write mpr:SRC +delete >%MK_SCR%

:: Could be optimised.
:: If DEBUG, we need to enlarge and find DIFF.
:: If octave 0 isn't full-size, we need DIFF_0
:: If neither of these, we don't need to enlarge and do maths,
:: and could create final DIFF as solid gray 50%.

for /L %%N in (%LAST_OCT%,-1,0) do (

  set GRD_FILE=%pyPREFIX%grd_%%N%fnNG_EXT%

  rem echo N_BLK_W.%%N=!N_BLK_W.%%N! N_BLK_H.%%N=!N_BLK_H.%%N! AMP.%%N=!AMP.%%N! M1AMP.%%N=!M1AMP.%%N!


  set DO_RES=1
  if !N_BLK_W.%%N!==%WW% if !N_BLK_H.%%N!==%HH% set DO_RES=0

  set WRDIFF=1
  if not %%N==0 if not "%mkpDEBUG%"=="1" set WRDIFF=0

  ( echo mpr:SRC
    if !DO_RES!==1 echo -resize "!N_BLK_W.%%N!x!N_BLK_H.%%N!^!"
    if "%mkpDEBUG%"=="1" echo +write !GRD_FILE!
    echo +write mpr:GRD%%N
    if !DO_RES!==1 echo -resize "%WW%x%HH%^!"
    if "%mkpDEBUG%"=="1" echo +write %pyPREFIX%full_%%N%fnNG_EXT%
    echo mpr:SRC
    echo -compose Mathematics -define compose:args=0,1,-1,0.5 -composite
    if "%mkpDEBUG%"=="1" echo +write %pyPREFIX%diff_%%N%fnNG_EXT%
    echo +write mpr:DIFF%%N
    echo +delete
  )>>%MK_SCR%

  if "%mkpHTM%"=="1" (
    echo ^<tr^>
    echo ^<td^>%%N^</td^>
    echo ^<td^>^<img src="!GRD_FILE!%EXTEXT%" /^>^</td^>
    echo ^<td^>^<img src="%pyPREFIX%full_%%N%fnNG_EXT%%EXTEXT%" /^>^</td^>
    echo ^<td^>^<img src="%pyPREFIX%full_%%N_h_glc.png" /^>^</td^>
    echo ^<td^>^<img src="%pyPREFIX%diff_%%N%fnNG_EXT%%EXTEXT%" /^>^</td^>
    echo ^</tr^>
  )>>%HTM_TAB%
)

:: List the MPRs, ready to write to pyramid file.
(
  for /L %%N in (0,1,%LAST_OCT%) do @(
    @echo mpr:GRD%%N
  )
  echo mpr:DIFF0
  echo +depth
  echo -write %PYR_FILE%
  echo -exit
)>>%MK_SCR%

:: Add final difference, and delinear, to reconstruction.
set DO_RES=1
if !N_BLK_W.0!==%WW% if !N_BLK_H.0!==%HH% set DO_RES=0

(
  echo %PYR_FILE%[0]
  if !DO_RES!==1 echo -resize "%WW%x%HH%^!"
  echo %PYR_FILE%[%NUM_OCTAVES%]
  echo -compose Mathematics -define compose:args=0,1,1,-0.5 -composite %sDELIN%
  echo -write %RECON_FILE%
  echo -exit
)>%RECON_SCR%

if "%mkpHTM%"=="1" echo ^</table^> >>%HTM_TAB%

if "%pyIM%"=="" (
  set pyIM=%IMG7%
)
if "%pyIM%"=="" (
  echo %0: pyIM not set
  exit /B 1
)

%pyIM%magick ^
  -script %MK_SCR%

if "%mkpHTM%"=="1" (
  for /L %%N in (0,1,%LAST_OCT%) do @(
    %IM7DEV%magick ^
      %pyPREFIX%full_%%N%fnNG_EXT% ^
      -process 'mkhisto capnumbuckets 512 norm' ^
      %pyPREFIX%full_%%N_h%fnNG_EXT%

    call %PICTBAT%graphLineCol %pyPREFIX%full_%%N_h%fnNG_EXT% . . 0
  )
)

call echoRestore

@endlocal & set mkgpOUTFILE=%PYR_FILE%& set mkgpNUM_OCTAVES=%NUM_OCTAVES%& set mkgpRECON_FILE=%RECON_FILE%

mkLapPyr.bat

rem From image %1, make Laplacian pyramid %2.
@rem %2 should be capable of containing multiple images, eg type TIFF.
@rem %3 %4 %5 %6 parameters to mkPyrComm
@rem
@rem Also uses:
@rem   mkpDEBUG if 1, creates other image files (full and diff).
@rem   mkpHTM if 1, creates HTM file
@rem   mkpKEEP_ALPHA_RES if 1, keeps alpha in grids at best resolution.
@rem   pyPREFIX prefix for working files.
@rem
@rem Updated:
@rem   5-September-2022 for IM v7.
@rem


@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 mklp


if "%pyWEIGHTING%"=="" set pyWEIGHTING=max

call %PICTBAT%mkPyrComm %1 %2 %3 %4 %5 %6
if ERRORLEVEL 1 exit /B 1

set sASSOC=-define tiff:alpha=associated

set KEEP_ALPH=1

for /L %%N in (%LAST_OCT%,-1,0) do (

  rem echo N_BLK_W.%%N=!N_BLK_W.%%N! N_BLK_H.%%N=!N_BLK_H.%%N! AMP.%%N=!AMP.%%N! M1AMP.%%N=!M1AMP.%%N!

rem  if !M1AMP.%%N!==1 (
rem    set sLEVEL=
rem  ) else (
rem    for /F "usebackq" %%L in (`%IMG7%magick identify ^
rem      -format "LO=%%[fx:50*(1-!M1AMP.%%N!)]\nHI=%%[fx:50*(1+!M1AMP.%%N!)]" ^
rem      xc:`) do set %%L
rem
rem    set sLEVEL=+level !LO!%%,!HI!%%
rem  )

  set DO_RES=1
  if !N_BLK_W.%%N!==%WW% if !N_BLK_H.%%N!==%HH% set DO_RES=0

  set WRDIFF=1
  if not %%N==0 if not "%mkpDEBUG%"=="1" set WRDIFF=0

  ( 
    if %%N==%LAST_OCT% if "%mkpKEEP_ALPHA_RES%"=="1" echo ^( +clone -alpha extract +write mpr:ALPH +delete ^)
    if !DO_RES!==1 echo -channel RGB -resize "!N_BLK_W.%%N!x!N_BLK_H.%%N!^!" +channel
    if "%mkpDEBUG%"=="1" echo +write %pyPREFIX%grd_%%N%fnNG_EXT%
    echo +write mpr:GRD%%N
    if !DO_RES!==1 echo -channel RGB -resize "%WW%x%HH%^!" +channel
    if "%mkpDEBUG%"=="1" echo +write %pyPREFIX%full_%%N%fnNG_EXT% !sLEVEL.%%N!
    echo mpr:!PREV_DIFF!
    echo -channel RGB -compose Mathematics -define compose:args=0,1,-1,0.5 -composite +channel
    if "%mkpKEEP_ALPHA_RES%"=="1" echo mpr:ALPH -alpha off -compose CopyOpacity -composite
    set PREV_DIFF=DIFF%%N
    if "!WRDIFF!"=="1" echo +write %pyPREFIX%diff_%%N%fnNG_EXT%
    echo +write mpr:!PREV_DIFF!
  )>>%MK_SCR%

  ( if %%N==%LAST_OCT% if "%mkpKEEP_ALPHA_RES%"=="1" echo %PYR_FILE%[%NUM_OCTAVES%] -alpha extract +write mpr:ALPH +delete
    echo ^( %PYR_FILE%[%%N]
    if !DO_RES!==1 echo -resize "%WW%x%HH%^!"
    if "%mkpKEEP_ALPHA_RES%"=="1" echo mpr:ALPH -alpha off -compose CopyOpacity -composite
    echo !sLEVEL.%%N! ^)

    if !DONE_FIRST!==0 (
      set DONE_FIRST=1
    ) else (
      echo -compose Mathematics -define compose:args=0,1,1,-0.5 -composite
    )
  )>>%RECON_SCR%

  if "%mkpHTM%"=="1" (
    echo ^<tr^>
    echo ^<td^>%%N^</td^>
    echo ^<td^>^<img src="%pyPREFIX%grd_%%N%fnNG_EXT%%EXTEXT%" /^>^</td^>
    echo ^<td^>^<img src="%pyPREFIX%full_%%N%fnNG_EXT%%EXTEXT%" /^>^</td^>
    echo ^<td^>^<img src="%pyPREFIX%full_%%N_h_glc.png" /^>^</td^>
    echo ^<td^>^<img src="%pyPREFIX%diff_%%N%fnNG_EXT%%EXTEXT%" /^>^</td^>
    echo ^</tr^>
  )>>%HTM_TAB%
)

:: List the MPRs, ready to write to pyramid file.
( echo +delete
  for /L %%N in (0,1,%LAST_OCT%) do @(
    @echo mpr:GRD%%N
  )
  echo mpr:DIFF0

  echo -define quantum:format=floating-point
  echo +depth
  echo -depth 32
  echo %sASSOC%
  echo -write %PYR_FILE%
  echo -exit

)>>%MK_SCR%

:: Add final difference, and delinear, to reconstruction.
(
  echo %PYR_FILE%[%NUM_OCTAVES%]
  rem if "%mkpKEEP_ALPHA_RES%"=="1" echo ^( +clone -alpha extract +write mpr:ALPH +delete ^)
  echo -compose Mathematics -define compose:args=0,1,1,-0.5 -composite %sDELIN%
  rem if "%mkpKEEP_ALPHA_RES%"=="1" echo mpr:ALPH -alpha off -compose CopyOpacity -composite
  echo -write %RECON_FILE%
  echo -exit
)>>%RECON_SCR%

if "%mkpHTM%"=="1" echo ^</table^> >>%HTM_TAB%

if "%pyIM%"=="" (
  set pyIM=%IMG7%
)
if "%pyIM%"=="" (
  echo %0: pyIM not set
  exit /B 1
)

%pyIM%magick ^
  -script %MK_SCR%

if "%mkpHTM%"=="1" (
  for /L %%N in (0,1,%LAST_OCT%) do @(
    %IM7DEV%magick ^
      %pyPREFIX%full_%%N%fnNG_EXT% ^
      -process 'mkhisto capnumbuckets 512 norm' ^
      %pyPREFIX%full_%%N_h%fnNG_EXT%

    call %PICTBAT%graphLineCol %pyPREFIX%full_%%N_h%fnNG_EXT% . . 0 %pyPREFIX%full_%%N_h_glc.png
  )
)

call echoRestore

@endlocal & set mklpOUTFILE=%PYR_FILE%& set mklpNUM_OCTAVES=%NUM_OCTAVES%& set mklpWW=%WW%& set mklpHH=%HH%& set mklpRECON_FILE=%RECON_FILE%

mkPyrComm.bat

rem Common processing for making Laplacian or Gaussian pyramids.
rem %1 is input image
rem %2 is output filename
rem %3 to %6 are block parameters
@rem
@rem Updated:
@rem   5-September-2022 for IM v7.
@rem

if "%pyIM%"=="" set pyIM=%IMG7%

if "%pyPREFIX%"=="" set pyPREFIX=mkp_
if "%pyPYR_EXT%"=="" set pyPYR_EXT=.tiff
if "%fnNG_EXT%"=="" set fnNG_EXT=.miff

if "%fnNG_EXT%"==".miff" (
  set EXTEXT=jpg
) else (
  set EXTEXT=
)

if not exist %INFILE% (
  echo %0: Can't find INFILE [%INFILE%]
  exit /B 1
)

set PYR_FILE=%2
if "%PYR_FILE%"=="." set PYR_FILE=
if "%PYR_FILE%"=="" set PYR_FILE=%pyPREFIX%%sioCODE%%pyPYR_EXT%

:: The sioCODE was set by the caller.

set TMP_BAT=%pyPREFIX%%sioCODE%.bat
set MK_SCR=%pyPREFIX%%sioCODE%_mk.scr
set HTM_TAB=%pyPREFIX%%sioCODE%.htm
set RECON_SCR=%pyPREFIX%%sioCODE%_recon.scr
set BLK_LIS=%pyPREFIX%blk.lis
set RECON_FILE=%pyPREFIX%%sioCODE%_recon.miff

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  -format "WW=%%w\nHH=%%h" ^
  %INFILE%`) do set %%L

set NUM_OCTAVES=

call %PICTBAT%mkpVar %WW% %HH% %3 %4 %5 %6
if ERRORLEVEL 1 (
  echo %0: mkpVar failed
  exit /B 1
)

rem if not exist %BLK_LIS% (
rem   echo %0: Can't find BLK_LIS [%BLK_LIS%]
rem   exit /B 1
rem )

del %RECON_SCR% 2>nul

rem
rem for /F "tokens=*" %%L in (%BLK_LIS%) do set %%L

if "%NUM_OCTAVES%"=="" (
  echo %0: NUM_OCTAVES has no value
  exit /B 1
)

set BAD_DIMS=1
if "%WW%"=="%IMG_WW%" if "%HH%"=="%IMG_HH%" set BAD_DIMS=0
if %BAD_DIMS%==1 (
  echo %0: Dimensions don't match: "%WW%" neq "%IMG_WW%" or "%HH%" neq "%IMG_HH%"
  exit /B 1
)

( echo %INFILE%
  echo +write mpr:SRC %sLIN%
) >%MK_SCR%

if "%mkpHTM%"=="1" (
  echo ^<table^>
  echo ^<th^>Octave^</th^>^<th^>Grid^</th^>^<th^>Grid, resized up^</th^>^<th^>Histogram of resized grid^</th^>^<th^>Difference^</th^>
)>%HTM_TAB%

set /A LAST_OCT=%NUM_OCTAVES%-1

set DONE_FIRST=0

set PREV_DIFF=SRC

exit /B 0

mkpVar.bat

rem Makes many variable assignments, for making pyramids.
rem Optionally also creates file of variable assignments.

@rem %1 width in pixels. [600]
@rem %2 height in pixels. [400]
@rem %3 minimum block size in pixels. [1]
@rem %4 maximum block size in pixels. [min(w,h)/2]
@rem %5 block factor: Block size is increased by this factor from min to max. >=1 [2]
@rem %6 power factor for amplitude proportional to frequency. [Default 0.0]
@rem   0 for all frequencies have same amplitude;
@rem   > 0 (eg 1, 1.5) for highest frequencies have lowest amplitude.

@rem returns fnNUM_OCTAVES number of octaves.

@rem Also uses:
@rem
@rem   pyIM location of ImageMagick programs. [%IMG7%]
@rem   pyPREFIX prefix for working files (but not output file). [py_]
@rem   pyGRAPHIC if 1, normalises amplitude so sum or maximum is 0.5.
@rem     [Otherwise normalises so sum or maximum is 1.0.]
@rem   pyWEIGHTING scaling for weights:
@rem     sum: scale so sum is 1.0 or 0.5 (default)
@rem     max: scale so maximum is 1.0 or 0.5
@rem   pyLINEAR if 1, operates in linear colorspace
@rem   pyWR_VAR if 1, writes {variable}={value} lines to %BLK_LIS%
@rem   fnNG_EXT extension for noise grid files. [.miff]
@rem   pyTXT_OUT if 1, writes some text data.
@rem   sioCODE of the caller (eg mklp, mkgp)
@rem
@rem First release: 3 October 2015.
@rem Updated:
@rem   5-September-2022 for IM v7.
@rem
@rem

@rem TODO: power defined by clut image file, so we can emphasise middle frequencies.

@rem No compulsory parameters
@rem if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

rem @setlocal enabledelayedexpansion

rem call %PICTBAT%setInOut %1 mkpv


set WW=%1
if "%WW%"=="." set WW=
if "%WW%"=="" set WW=600

set HH=%2
if "%HH%"=="." set HH=
if "%HH%"=="" set HH=400

set BLK_MIN=%3
if "%BLK_MIN%"=="." set BLK_MIN=
if "%BLK_MIN%"=="" set BLK_MIN=1

set BLK_MAX=%4
if "%BLK_MAX%"=="." set BLK_MAX=
if "%BLK_MAX%"=="" (
  set BLK_MAX=%HH%
  if !BLK_MAX! GTR %WW% set BLK_MAX=%WW%
  set /A BLK_MAX=!BLK_MAX!/2
)

set BLK_FAC=%5
if "%BLK_FAC%"=="." set BLK_FAC=
if "%BLK_FAC%"=="" set BLK_FAC=2

set POW_FAC=%6
if "%POW_FAC%"=="." set POW_FAC=
if "%POW_FAC%"=="" set POW_FAC=0


if "%pyPREFIX%"=="" set pyPREFIX=py_
if "%fnNG_EXT%"=="" set fnNG_EXT=.miff

if "%BLK_LIS%"=="" set BLK_LIS=%pyPREFIX%blk.lis
del %BLK_LIS% 2>nul

if "%pyIM%"=="" set pyIM=%IMG7%

if "%pyWEIGHTING%"=="" set pyWEIGHTING=sum

if not "%pyWEIGHTING%"=="sum" if not "%pyWEIGHTING%"=="max" (
  echo %0: pyWEIGHTING [%pyWEIGHTING%] should be sum or max.
  exit /B 1
)

if "%pyLINEAR%"=="1" (
  set sLIN=-set colorspace sRGB -colorspace RGB
  set sDELIN=-set colorspace RGB -colorspace sRGB
) else (
  set sLIN=
  set sDELIN=
)

set PREC=-precision 15

set CONST_FAC=%fnCONST_FAC%
if "%CONST_FAC%"=="" set CONST_FAC=1

:: ------------------ Pre-loop ------------------
if "%pyMAX_NUM_OCT%"=="" (
  set /A LOOP_MAX=99
) else (
  set /A LOOP_MAX=%pyMAX_NUM_OCT%-1
)

set BLK_SZ=%BLK_MIN%
set NUM_OCTAVES=0
set MAX_AMP=0
set SIG_AMP=0
set BUST_BLK_WH=0

:preloop

set BLK_SZ.%NUM_OCTAVES%=%BLK_SZ%

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  %PREC% ^
  -format "FR.%NUM_OCTAVES%=%%[fx:sqrt(%BLK_SZ%*%BLK_SZ%/%WW%/%HH%)]" ^
  xc:`) do set %%L

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  %PREC% ^
  -format "N_BLK_W.%NUM_OCTAVES%=%%[fx:int^(%WW%/!BLK_SZ.%NUM_OCTAVES%!+0.5^)]\nN_BLK_H.%NUM_OCTAVES%=%%[fx:int^(%HH%/!BLK_SZ.%NUM_OCTAVES%!+0.5^)]" ^
  xc:`) do set %%L

if "!N_BLK_W.%NUM_OCTAVES%!"=="0" set N_BLK_W=1
if "!N_BLK_H.%NUM_OCTAVES%!"=="0" set N_BLK_H=1

if not "%pyMIN_BLK_WH%"=="" (
  if /I !N_BLK_W.%NUM_OCTAVES%! LSS %pyMIN_BLK_WH% set BUST_BLK_WH=1
  if /I !N_BLK_H.%NUM_OCTAVES%! LSS %pyMIN_BLK_WH% set BUST_BLK_WH=1
)

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  %PREC% ^
  -format "AMP.%NUM_OCTAVES%=%%[fx:pow(!FR.%NUM_OCTAVES%!,%POW_FAC%)]\nBLK_SZ=%%[fx:%BLK_SZ%*%BLK_FAC%]" ^
  xc:`) do set %%L

for /F "usebackq" %%L in (`%IMG7%magick identify ^
  %PREC% ^
  -format "SIG_AMP=%%[fx:%SIG_AMP%+!AMP.%NUM_OCTAVES%!]\nFIN=%%[fx:%BLK_SZ%>=%BLK_MAX%?1:%NUM_OCTAVES%>=%LOOP_MAX%?1:!BUST_BLK_WH!]\nMAX_AMP=%%[fx:%MAX_AMP%>!AMP.%NUM_OCTAVES%!?%MAX_AMP%:!AMP.%NUM_OCTAVES%!]" ^
  xc:`) do set %%L

if "%pyTXT_OUT%"=="1" echo BLK_SZ.%NUM_OCTAVES%=!BLK_SZ.%NUM_OCTAVES%! FR.%NUM_OCTAVES%=!FR.%NUM_OCTAVES%!  AMP.%NUM_OCTAVES%=!AMP.%NUM_OCTAVES%!

set /A NUM_OCTAVES+=1

if %FIN%==0 goto preloop
:: ------------------ End Pre-loop ------------------

if "%BUST_BLK_WH%"=="1" set /A NUM_OCTAVES-=1

if %NUM_OCTAVES%==0 (
  echo %0: No octaves created
  exit /B 1
)

rem if %NUM_OCTAVES% GEQ %LOOP_MAX% (
rem   echo %0: Large number of octaves
rem   exit /B 1
rem )

set MAX_FR=%FR%

if "%pyTXT_OUT%"=="1" echo NUM_OCTAVES=%NUM_OCTAVES%  MAX_FR=%MAX_FR%  SIG_AMP=%SIG_AMP% MAX_AMP=%MAX_AMP%

if "%pyGRAPHIC%"=="1" for /F "usebackq" %%L in (`%IMG7%magick identify ^
  %PREC% ^
  -format "MAX_AMP=%%[fx:%MAX_AMP%*2]\nSIG_AMP=%%[fx:%SIG_AMP%*2]" ^
  xc:`) do set %%L

if "%pyWR_VAR%"=="1" (
  echo %0: Writing to var file [%BLK_LIS%]
)

if "%pyWR_VAR%"=="1" (
  echo pyWEIGHTING=%pyWEIGHTING%
  echo pyGRAPHIC=%pyGRAPHIC%
  echo pyLINEAR=%pyLINEAR%
  echo NUM_OCTAVES=%NUM_OCTAVES%
  echo %sioCODE%RECON_FILE=%RECON_FILE%
  echo IMG_WW=%WW%
  echo IMG_HH=%HH%
)>>%BLK_LIS%

if "%pyWR_VAR%"=="1" type %BLK_LIS%

set IMG_WW=%WW%
set IMG_HH=%HH%


:: ------------------ Main loop ------------------
set /A Nm1=%NUM_OCTAVES%-1

set ERROR=0

if "%SIG_AMP%"=="0" (
  echo %0: Error: SIG_AMP is zero.
  exit /B 1
)

for /L %%N in (0,1,%Nm1%) do (

  set OCT_NUM=%%N

rem  for /F "usebackq" %%L in (`%IMG7%magick identify ^
rem    %PREC% ^
rem    -format "N_BLK_W.%%N=%%[fx:int^(%WW%/!BLK_SZ.%%N!+0.5^)]\nN_BLK_H.%%N=%%[fx:int^(%HH%/!BLK_SZ.%%N!+0.5^)]" ^
rem    xc:`) do set %%L

  for /F "usebackq" %%L in (`%IMG7%magick identify ^
    %PREC% ^
    -format "S1AMP.%%N=%%[fx:%CONST_FAC%/%SIG_AMP%*!AMP.%%N!]\nM1AMP.%%N=%%[fx:%CONST_FAC%/%MAX_AMP%*!AMP.%%N!]" ^
    xc:`) do set %%L

  if "%pyTXT_OUT%"=="1" echo BLK_SZ.%%N=!BLK_SZ.%%N!  N_BLK_W.%%N=!N_BLK_W.%%N! N_BLK_H.%%N=!N_BLK_H.%%N!  FR.%%N=!FR.%%N!  S1AMP.%%N=!S1AMP.%%N! M1AMP.%%N=!M1AMP.%%N!

  if "%pyWEIGHTING%"=="sum" (
    set AMP.%%N=!S1AMP.%%N!
  ) else (
    set AMP.%%N=!M1AMP.%%N!
  )

  if !AMP.%%N!==1 (
    set sLEVEL.%%N=
  ) else (
    for /F "usebackq" %%L in (`%IMG7%magick identify ^
      %PREC% ^
      -format "LO=%%[fx:50*^(1-!AMP.%%N!^)]\nHI=%%[fx:50*^(1+!AMP.%%N!^)]" ^
      xc:`) do set %%L

    set sLEVEL.%%N=+level !LO!%%,!HI!%%
  )

  if "%pyWR_VAR%"=="1" (
    echo BLK_SZ.!OCT_NUM!=!BLK_SZ.%%N!
    echo N_BLK_W.!OCT_NUM!=!N_BLK_W.%%N!
    echo N_BLK_H.!OCT_NUM!=!N_BLK_H.%%N!
    echo FR.!OCT_NUM!=!FR.%%N!
    echo S1AMP.!OCT_NUM!=!S1AMP.%%N!
    echo M1AMP.!OCT_NUM!=!M1AMP.%%N!
    echo AMP.!OCT_NUM!=!AMP.%%N!
    echo sLEVEL.!OCT_NUM!=!sLEVEL.%%N!
  )>>%BLK_LIS%

rem  for /F "usebackq" %%L in (`%IMG7%magick identify ^
rem    %PREC% ^
rem    -format "BLK_SZ=%%[fx:!BLK_SZ!*%BLK_FAC%]" ^
rem    xc:`) do set %%L
)

rem @endlocal & set fnWW=%WW%& set fnHH=%HH%& set fnNUM_OCTAVES=%NUM_OCTAVES%& set fnMAX_FR=%MAX_FR%& set fnPOW_FAC=%POW_FAC%

mspBoostCut.bat

rem Boosts or cuts amplitudes of grids in a Laplacian pyramid.
rem %1 is the name of the multi-image pyramid file.
rem %2 is "boost" or "cut"
rem %3 is the -sigmoidal-contrast contrast boost or cut to be applied.
rem %4 is the name of the htm file to be generated.
@rem
@rem  Assumes pyPREFIX is correct,
@rem  so we can read the block list file (which must exist),
@rem  and run the reconstruction script.
@rem
@rem Updated:
@rem   15 May 2016 use %IML% for @script.
@rem   5-September-2022 for IM v7.
@rem


@if "%4"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 mbc

set PYR_FILE=%1
set BOOST_CUT=%2
set CONT=%3
set HTM_FILE=%4
set HTM_NAME=%~n4
set SAVED_PYR=%~n1_mbpsav%~x1

if /I "%BOOST_CUT%"=="boost" (
  set SIG_SIGN=-
) else if /I "%BOOST_CUT%"=="cut" (
  set SIG_SIGN=+
) else (
  echo %0: BOOST_CUT [%BOOST_CUT%] is neither boost nor cut
  exit /B 1
)

echo SAVED_PYR=%SAVED_PYR%

if not exist %PYR_FILE% (
  echo %0: Can't find PYR_FILE [%PYR_FILE%]
  exit /B 1
)

set RECON_SCR=%pyPREFIX%mklp_recon.scr
set BLK_LIS=%pyPREFIX%blk.lis

if not exist %BLK_LIS% (
  echo %0: Can't find BLK_LIS [%BLK_LIS%]
  exit /B 1
)

set NUM_OCTAVES=

for /F "tokens=*" %%L in (%BLK_LIS%) do set %%L

if "%NUM_OCTAVES%"=="" (
  echo %0: in %BLK_LIS%, NUM_OCTAVES has no value
  exit /B 1
)

if not exist %RECON_SCR% (
  echo %0: Can't find RECON_SCR [%RECON_SCR%]
  exit /B 1
)

if "%mklpRECON_FILE%"=="" (
  echo %0: mklpRECON_FILE is blank.
  exit /B 1
)

copy /Y %PYR_FILE% %SAVED_PYR%

( echo ^<p^>%~n0: %BOOST_CUT% %CONT%^</p^>
) >%HTM_FILE%

:: Don't process final difference.

for /L %%N in (0,1,%NUM_OCTAVES%) do (

  set sOUT=%pyPREFIX%mbc_%HTM_NAME%_%%N.png

  %IMG7%magick ^
    %PYR_FILE% ^
    ^( -clone %%N ^
       %SIG_SIGN%sigmoidal-contrast %CONT%,50%% ^
    ^) ^
    -swap -1,%%N ^
    +delete ^
    %PYR_FILE%

  %IMG7%magick ^
    -script %RECON_SCR%

  %IMG7%magick %mklpRECON_FILE% !sOUT!

  ( echo ^<img src="!sOUT!jpg" /^>
  ) >>%HTM_FILE%

)

copy /Y %SAVED_PYR% %PYR_FILE%

type %HTM_FILE%

call echoRestore

@endlocal & set mklpOUTFILE=%PYR_FILE%

mspProcGrid.bat

rem Applies an IM process to grids in a Laplacian pyramid.
rem %1 is the name of the multi-image pyramid file.
rem %2 is the IM process to be applied to each grid.
rem %3 is the name of the htm file to be generated.
@rem
@rem  Assumes pyPREFIX is correct,
@rem  so we can read the block list file,
@rem  and run the reconstruction script.
@rem
@rem Updated:
@rem   5-September-2022 for IM v7.
@rem


@if "%3"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 mpg

set PYR_FILE=%1
set IM_PROC=%~2
set HTM_FILE=%3
set HTM_NAME=%~n3
set SAVED_PYR=%~n1_mbpsav%~x1

echo %~n0: IM_PROC="%IM_PROC%"

if not exist %PYR_FILE% (
  echo %0: Can't find PYR_FILE [%PYR_FILE%]
  exit /B 1
)

set RECON_SCR=%pyPREFIX%mklp_recon.scr
set BLK_LIS=%pyPREFIX%blk.lis

if not exist %BLK_LIS% (
  echo %0: Can't find BLK_LIS [%BLK_LIS%]
  exit /B 1
)

set NUM_OCTAVES=

for /F "tokens=*" %%L in (%BLK_LIS%) do set %%L

if "%NUM_OCTAVES%"=="" (
  echo %0: in %BLK_LIS%, NUM_OCTAVES has no value
  exit /B 1
)

if not exist %RECON_SCR% (
  echo %0: Can't find RECON_SCR [%RECON_SCR%]
  exit /B 1
)

if "%mklpRECON_FILE%"=="" (
  echo %0: mklpRECON_FILE is blank.
  exit /B 1
)
copy /Y %PYR_FILE% %SAVED_PYR%

( echo ^<p^>%~n0: "%IM_PROC%"^</p^>
) >%HTM_FILE%

:: Don't process final difference.

for /L %%N in (0,1,%NUM_OCTAVES%) do (

  set sOUT=%pyPREFIX%mbc_%HTM_NAME%_%%N.png

  %IMG7%magick ^
    %PYR_FILE% ^
    ^( -clone %%N ^
       %IM_PROC% ^
    ^) ^
    -swap -1,%%N ^
    +delete ^
    %PYR_FILE%

  %IMG7%magick ^
    -script %RECON_SCR%

  %IMG7%magick %mklpRECON_FILE% !sOUT!

  ( echo ^<img src="!sOUT!jpg" /^>
  ) >>%HTM_FILE%

)

copy /Y %SAVED_PYR% %PYR_FILE%

call echoRestore

@endlocal & set mklpOUTFILE=%PYR_FILE%

All images on this page were created by the commands shown, using:

%IMG7%magick -version
Version: ImageMagick 7.1.1-20 Q16-HDRI x86 98bb1d4:20231008 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI OpenCL OpenMP(2.0) 
Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib
Compiler: Visual Studio 2022 (193532217)
%IM7DEV%magick -version
Version: ImageMagick 7.1.1-27 (Beta) Q32-HDRI x86_64 83eefaf2a:20240107 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenCL OpenMP(4.5) 
Delegates (built-in): bzlib cairo fftw fontconfig freetype heic jbig jng jpeg lcms ltdl lzma pangocairo png raqm raw rsvg tiff webp wmf x xml zip zlib zstd
Compiler: gcc (11.3)

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.

Source file for this web page is mspyr.h1. To re-create this web page, run "procH1 mspyr".


This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.

Anyone is permitted to re-publish this page, but only for non-commercial use.

Anyone is permitted to link to this page, including for commercial use.


Page version v1.0 4-October-2015.

Page created 02-Mar-2024 17:38:43.

Copyright © 2024 Alan Gibson.