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.
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 |
|
The test image used below: set SRC=toes.png set pyGRAPHIC= |
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.
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 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 |
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) |
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 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 |
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) |
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. |
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 | ||||
1 | ||||
0 |
%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) |
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 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 |
%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) |
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 | ||||
0 |
%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) |
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 | ||||
10 | ||||
9 | ||||
8 | ||||
7 | ||||
6 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 |
%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) |
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 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 |
%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) |
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 | ||||
5 | ||||
4 | ||||
3 | ||||
2 | ||||
1 | ||||
0 |
%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) |
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
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 |
|
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 |
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.
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:
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
mspBoostCut: cut 10
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
mspBoostCut: boost 75
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 |
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 |
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 |
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:
mspProcGrid: "-level 30%,70%"
mspProcGrid: "-level 45%,55%"
We can get extreme sharpening without ringing.
mspProcGrid: "-auto-level -auto-gamma"
mspProcGrid: "-equalize"
mspProcGrid: "-set option:modulate:colorspace HCL -modulate 100,200,100"
mspProcGrid: "-negate"
mspProcGrid: "+dither -colors 4"
mspProcGrid: "+dither -posterize 3"
mspProcGrid: "-paint 3"
mspProcGrid: "-colorspace Gray"
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%"
mspProcGrid: "-level 45%,55%"
mspProcGrid: "-auto-level -auto-gamma"
mspProcGrid: "-equalize"
mspProcGrid: "-set option:modulate:colorspace HCL -modulate 100,200,100"
mspProcGrid: "-negate"
mspProcGrid: "+dither -colors 4"
mspProcGrid: "+dither -posterize 3"
mspProcGrid: "-paint 3"
mspProcGrid: "-colorspace Gray"
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.
set mkpDEBUG= set mkpHTM= set pyGRAPHIC= set pyPREFIX= set pyIM=
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
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%
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%
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
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%
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%
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.