snibgo's ImageMagick pages

Simple alignment by matching areas

When we have a list of interesting points in one image, we can locate matching areas in another image, making the assumption that any rotation and scaling is small. The assumption is true if the camera is hand-held but not moved much, such as frames from a video or stills intended for a panorama.

Scripts on this page assume that the version of ImageMagick in %IM7DEV% has been built with various process modules. See Process modules.

When we want to align one image to another, we start by matching points in one image with points in the other. We can use one of the one of the methods from Details, details to identify features in one image, ie areas of high variation, high entropy. These are likely to be distinctive in the other image, reducing false matches.

We then crop a small area around each point, and search for that crop in the other image, using the technique from Searching an image.

Set up initial conditions:

set WEB_SIZE=-resize 500x500

Sample images

We start with two images. We want to find points on the first image that correspond to points on the other image. From that list of corresponding points, we will distort the second image so it registers with the first.

The two photographs have undergone similar, but not identical, processing.

All operations on this page are performed on full-size images, roughly 7500x5000 pixels. These files are too large for me to upload. These are then reduced in size, and converted to JPG, for the web.

The source image.

set sa_ALI_SRC=%PICTLIB%20140523\AGA_1837_g.tiff

%IMG7%magick ^
  %sa_ALI_SRC% ^
  %WEB_SIZE% ^
  sa_ALI_SRC_sm.jpg
sa_ALI_SRC_sm.jpg

Another image.

set sa_OTH_SRC=%PICTLIB%20140523\AGA_1839_g.tiff

%IMG7%magick ^
  %sa_OTH_SRC% ^
  %WEB_SIZE% ^
  sa_OTH_SRC_sm.jpg
sa_OTH_SRC_sm.jpg

Match areas

We find 24 points of interest in the source image using one of the methods from Details, details. The red circles in the debug image are the exclusion zones, showing the minimum distance between features.

Use a difference between two blurs, twoBlrDiff.bat, to find detail.

call %PICTBAT%twoBlrDiff %sa_ALI_SRC%

We will make crops 100x100, so we don't want to find points within 50 pixels of an edge.

call %PICTBAT%blackEdge %tbdOUTFILE% 50

set SAVE_DIFF=%beOUTFILE%

We find the 24 lightest points.

24 is overkill for this method.
We should find plenty of matches with fewer points, but I choose 24 so I can compare the results with measdet.htm.

set nlDEBUG=1
call %PICTBAT%getPointsize %sa_ALI_SRC% 50
set nlPOINTSIZE=%gpPointsize%
set nlSTROKEWIDTH=10

call %PICTBAT%nLightest %beOUTFILE% 24 >sa_ali_coords.lis

We look at the debug image:

%IMG7%magick ^
  %nlDEBUG_FILE% ^
  %WEB_SIZE% ^
  sa_ali_coords_sm.jpg
sa_ali_coords_sm.jpg

We make crops of the first image, %sa_ALI_SRC%, centred on the coordinates:

set ncDEBUG=
call %PICTBAT%nCrop %sa_ALI_SRC% sa_ali_coords.lis 100 100

A list of the generated crop files is now in %ncLIST_FILE%.

We search for these crops within %sa_OTH_SRC%:

set MATCH_LIS=sa_match.lis
del %MATCH_LIS%
set MATCH_LIS2=sa_match2.lis

echo CropNum,Score,X,Y >%MATCH_LIS%
set CROP_NUM=0
for /F %%F in (%ncLIST_FILE%) do (
  echo %%F

  call %PICTBAT%srchImg %sa_OTH_SRC% %%F
  if ERRORLEVEL 1 exit /B 1

  echo !CROP_NUM!, !siCOMP_FLT!, !siCENT_X!, !siCENT_Y! >>%MATCH_LIS%

  set /A CROP_NUM+=1
)
CropNum,Score,X,Y 
0, 0.110896, 5671, 3771 
1, 0.117396, 6177, 3844 
2, 0.129868, 6393, 2959 
3, 0.0767293, 2006, 2270 
4, 0.0990973, 4624, 4652 
5, 0.116674, 6490, 3378 
6, 0.0907304, 7197, 2646 
7, 0.0943666, 2290, 250 
8, 0.0648876, 2026, 2811 
9, 0.121583, 5385, 3661 
10, 0.111844, 3144, 3933 
11, 0.0631738, 915, 2931 
12, 0.0739243, 7262, 2354 
13, 0.081462, 2019, 1780 
14, 0.0870536, 5911, 1781 
15, 0.0490783, 455, 3714 
16, 0.0929437, 5746, 3247 
17, 0.0641008, 2021, 1727 
18, 0.0987857, 2672, 2048 
19, 0.0845894, 6924, 4526 
20, 0.0945779, 2555, 2825 
21, 0.098023, 2269, 1971 
22, 0.0671523, 2627, 1536 
23, 0.0923491, 6784, 2102 

These are the matches found, in order of the score, so the closest matches are at the top:

cSort /i%MATCH_LIS% /o%MATCH_LIS2% /h /kScore
CropNum,Score,X,Y 
15, 0.0490783, 455, 3714 
11, 0.0631738, 915, 2931 
17, 0.0641008, 2021, 1727 
8, 0.0648876, 2026, 2811 
22, 0.0671523, 2627, 1536 
12, 0.0739243, 7262, 2354 
3, 0.0767293, 2006, 2270 
13, 0.081462, 2019, 1780 
19, 0.0845894, 6924, 4526 
14, 0.0870536, 5911, 1781 
6, 0.0907304, 7197, 2646 
23, 0.0923491, 6784, 2102 
16, 0.0929437, 5746, 3247 
7, 0.0943666, 2290, 250 
20, 0.0945779, 2555, 2825 
21, 0.098023, 2269, 1971 
18, 0.0987857, 2672, 2048 
4, 0.0990973, 4624, 4652 
0, 0.110896, 5671, 3771 
10, 0.111844, 3144, 3933 
5, 0.116674, 6490, 3378 
1, 0.117396, 6177, 3844 
9, 0.121583, 5385, 3661 
2, 0.129868, 6393, 2959 

We can markup a copy of the searched image with the locations found:

set mrCOL_X=3
set mrCOL_Y=4
set mrSTROKE=-strokewidth 10
call %PICTBAT%markRect %sa_OTH_SRC% %MATCH_LIS% 100 100

%IMG7%magick ^
  %mrOUTFILE% ^
  %WEB_SIZE% ^
  sa_srch_marked.jpg
sa_srch_marked.jpg

By looking carefully at the full-size images, we find the following matches are correct: 0, 1, 3, 6, 8, 11, 13, 14, 15, 16, 20, 22 and 23. These are incorrect: 2, 4, 5, 7, 9, 10, 12, 17, 18, 19 and 21. Out of 24 matches, 13 are correct. Number 17 has a good score but the match is false. Number 0 has a bad score but the match is correct. The score is not a good indicator of whether the match is correct.

To automatically find the correct matches, we calculate dx, dy and r for each coordinate pair, and remove outliers with a "modified Thompson tau technique". (For details, see the Statistics page.)

cProject /isa_match.lis /osa_match3.lis /h /kScore,X,Y
cHead /isa_match3.lis /h1 /x
call %PICTBAT%appendLines sa_match3.lis sa_ali_coords.lis >sa_coord_pairs.lis

call %PICTBAT%extendCoordPairs sa_coord_pairs.lis sa_matchMTTE.lis
set rcoAPPEND_NUM=1
set rcoDEBUG_FILE=sa_rco_debug.lis
del %rcoDEBUG_FILE% 2>nul
call removeCsvOutliers sa_matchMTTE.lis sa_matchMTTE_good.lis 6 1 7 1 8 1

The result, written to sa_matchMTTE_good.lis, is:

0.110896,5671,3771,5271,3431,-400,-340,524.97619 , 0 
0.117396,6177,3844,5769,3509,-408,-335,527.91003 , 1 
0.0767293,2006,2270,1630,1884,-376,-386,538.861763 , 3 
0.0907304,7197,2646,6804,2339,-393,-307,498.6963 , 6 
0.0648876,2026,2811,1645,2428,-381,-383,540.231432 , 8 
0.0631738,915,2931,521,2534,-394,-397,559.325487 , 11 
0.081462,2019,1780,1648,1391,-371,-389,537.551858 , 13 
0.0870536,5911,1781,5550,1456,-361,-325,485.74273 , 14 
0.0490783,455,3714,51,3316,-404,-398,567.115509 , 15 
0.0929437,5746,3247,5355,2911,-391,-336,515.535644 , 16 
0.0945779,2555,2825,2177,2448,-378,-377,533.866088 , 20 
0.0671523,2627,1536,2265,1155,-362,-381,525.552091 , 22 
0.0923491,6784,2102,6406,1790,-378,-312,490.130595 , 23 

The columns in sa_matchMTTE_good.lis are: score, xB,yB, xA,yA, dx, dy, t, point_num.

This has accepted exactly those pairs we have found visually to be good, and rejected those we found to be bad. The resulting list is perfect.

The debug output sent to sa_rco_debug.lis is:

removeCsvOutliers: Column=6 IsAbs=1 
removeOutliers: removed 18 
removeOutliers: removed 21 
removeOutliers: removed 19 
removeOutliers: removed 10 
removeOutliers: removed 7 
removeOutliers: removed 2 
removeOutliers: removed 5 
removeOutliers: removed 12 
removeOutliers: removed 17 
removeCsvOutliers: Column=7 IsAbs=1 
removeOutliers: removed 9 
removeOutliers: removed 4 
removeCsvOutliers: Column=8 IsAbs=1 
removeCsvOutliers: nVAL=24 

Aside:

To align images accurately, we must eliminate all bad matches. An algorithm may also eliminate good matches. (See example later on this page.) It is better to err on the side of over-elimination rather than under-elimination. Previous (unpublished) versions of my scripts removed samples outside the range mean ± k * standard_deviation where 1.0 < k < 2.0. This needed a good choice of k, and was over-conservative.

Finding outliers of dx, dy and r works well because the sample has a fairly constant linear displacement across the entire image. If the camera had between twisted significantly around the z-axis, from a landscape format towards a portrait format, it wouldn't have worked.

A possible solution is to read the verbose distort parameters and use them to calculate the error for each control point, then eliminate control points based on outlier errors.

We have successfully matched small areas in one image with small areas from another. The output from the process is a list of coordinate pairs: each (x,y) coordinate from one image matches an (x,y) coordinate from the other image.

Using a script

For convenience, we put much of the above into a script, alignArea.bat. This takes two images and a CSV list of coordinates of points in one image, and finds corresponding points in a second image. The list can be used for a perspective (or affine) distortiontion from one image to the other.

call %PICTBAT%alignArea ^
  %PICTLIB%20140523\AGA_1837_g.tiff ^
  %PICTLIB%20140523\AGA_1839_g.tiff ^
  sa_ali_coords.lis

type %aaCOORDS_CSV% 
5671,3771,5271,3431,-400,-340,524.97619 , 0 
6177,3844,5769,3509,-408,-335,527.91003 , 1 
2006,2270,1630,1884,-376,-386,538.861763 , 3 
7197,2646,6804,2339,-393,-307,498.6963 , 6 
2026,2811,1645,2428,-381,-383,540.231432 , 8 
915,2931,521,2534,-394,-397,559.325487 , 11 
2019,1780,1648,1391,-371,-389,537.551858 , 13 
5911,1781,5550,1456,-361,-325,485.74273 , 14 
455,3714,51,3316,-404,-398,567.115509 , 15 
5746,3247,5355,2911,-391,-336,515.535644 , 16 
2555,2825,2177,2448,-378,-377,533.866088 , 20 
2627,1536,2265,1155,-362,-381,525.552091 , 22 
6784,2102,6406,1790,-378,-312,490.130595 , 23 

Transformation

This is a complex subject, and deserves a separate page.

ImageMagick has three simple but effective methods of using a list of coordinate pairs to transform one image geometrically to match another. The methods are:

When a rectilinear camera is used to take multiple photographs from the same position, the perspective distortion is the correct one to use.

Shepards looks attractive, but image points that are not in the small list of coordinate pairs will be interpolated using all the control points. Another method divides the image into triangles and displaces according the distance from only three points. This is more complex, and easiest written in C (unpublished).

For convenience, we read the coordinate pairs into an environment variable.

set COORD_PAIR_LIST=

for /F "tokens=2-5 delims=, " %%A in (sa_matchMTTE_good.lis) do (
  set COORD_PAIR_LIST=!COORD_PAIR_LIST! %%A,%%B,%%C,%%D
)

set COORD_PAIR_LIST 
COORD_PAIR_LIST= 5671,3771,5271,3431 6177,3844,5769,3509 2006,2270,1630,1884 7197,2646,6804,2339 2026,2811,1645,2428 915,2931,521,2534 2019,1780,1648,1391 5911,1781,5550,1456 455,3714,51,3316 5746,3247,5355,2911 2555,2825,2177,2448 2627,1536,2265,1155 6784,2102,6406,1790

Affine distortion

We use the coordinate-pair list for an affine transformation. -verbose sends the six affine parameters to stderr.

%IMG7%magick ^
  %sa_OTH_SRC% ^
  -verbose ^
  -virtual-pixel None ^
  +distort Affine "%COORD_PAIR_LIST%" ^
  -write sa_xform_a.png ^
  %WEB_SIZE% ^
  sa_xform_a_sm.jpg 

We show the parameters, just for interest:

Affine projection:
  -distort AffineProjection \
    '0.999683,0.0137551,-0.018084,0.999078,-334.116,-409.422'
Equivalent scale, rotation(deg), translation:
  0.999777,0.788309,-334.116,-409.422
Affine distort, FX equivalent:
  -size 7467x5023 -page -424-410 xc: +insert \
  -fx 'ii=i+page.x+0.5; jj=j+page.y+0.5;
       xx=+1.00007*ii +0.018102*jj +341.55;
       yy=-0.0137687*ii +1.00067*jj +405.097;
       v.p{xx-v.page.x-0.5,yy-v.page.y-0.5}' \

Aside: If we wanted just the parameters without any image processing, we could:

%IMG7%magick ^
  xc: ^
  -verbose ^
  -virtual-pixel None ^
  +distort Affine "%COORD_PAIR_LIST%" ^
  NULL: 2>somewhere.lis

We make three images for the web:

%IMG7%magick ^
  %sa_ALI_SRC% ^
  ( sa_xform_a.png -channel A -evaluate Multiply 0.5 ) ^
  -layers merge +repage ^
  -write sa_xform_comp_a.png ^
  ( +clone -crop 500x500+510+4526 -write sa_xform_comp_a_cr.jpg +delete ) ^
  %WEB_SIZE% ^
  sa_xform_comp_a_sm.jpg

We show the distorted image (reduced in size for the web), then that image 50% transparent and merged over the first image, and finally a 100% crop of the merge.

sa_xform_a_sm.jpg sa_xform_comp_a_sm.jpg sa_xform_comp_a_cr.jpg

The 100% crop shows that the alignment from Affine is approximately correct, but not exact.

Perspective distortion

%IMG7%magick ^
  %sa_OTH_SRC% ^
  -verbose ^
  -virtual-pixel None ^
  +distort Perspective "%COORD_PAIR_LIST%" ^
  -write sa_xform_p.png ^
  %WEB_SIZE% ^
  sa_xform_p_sm.jpg 
Perspective Projection:
  -distort PerspectiveProjection \
      '1.02579, -0.00475227, -398.082, 0.0199438, 
       1.02372, -457.416, 2.37374e-06, 2.98293e-06'
Perspective Distort, FX Equivelent:
  -size 7464x5042 -page -416-458 xc: +insert \
  -fx 'ii=i+page.x+0.5; jj=j+page.y+0.5;
       rr=-2.25721e-06*ii -2.92431e-06*jj + 1;
       xx=(+0.976073*ii +0.00339439*jj +390.109)/rr;
       yy=(-0.0200242*ii +0.977644*jj +439.219)/rr;
       rr>0 ? v.p{xx-v.page.x-0.5,yy-v.page.y-0.5} : blue' \
%IMG7%magick ^
  %sa_ALI_SRC% ^
  ( sa_xform_p.png -channel A -evaluate Multiply 0.5 ) ^
  -layers merge +repage ^
  -write sa_xform_comp_p.png ^
  ( +clone -crop 500x500+510+4526 -write sa_xform_comp_p_cr.jpg +delete ) ^
  %WEB_SIZE% ^
  sa_xform_comp_p_sm.jpg
sa_xform_p_sm.jpg sa_xform_comp_p_sm.jpg sa_xform_comp_p_cr.jpg

The 100% crop shows that the alignment from Perspective is exactly correct.

Shepards distortion

%IMG7%magick ^
  %sa_OTH_SRC% ^
  -verbose ^
  -virtual-pixel None ^
  +distort Shepards "%COORD_PAIR_LIST%" ^
  -write sa_xform_s.png ^
  %WEB_SIZE% ^
  sa_xform_s_sm.jpg 
    
%IMG7%magick ^
  %sa_ALI_SRC% ^
  ( sa_xform_s.png -channel A -evaluate Multiply 0.5 ) ^
  -layers merge +repage ^
  -write sa_xform_comp_s.png ^
  ( +clone -crop 500x500+110+4126 -write sa_xform_comp_s_cr.jpg +delete ) ^
  %WEB_SIZE% ^
  sa_xform_comp_s_sm.jpg
sa_xform_s_sm.jpg sa_xform_comp_s_sm.jpg sa_xform_comp_s_cr.jpg

The 100% crop shows that the alignment from Shepards is worse then the affine transformation at this location, though it is exactly correct at each control point.

Triangulate distortion

This isn't (yet) part of the standard IM distribution.

goto skipTri

%IMDEV%convert ^
  %sa_OTH_SRC% ^
  -verbose ^
  -virtual-pixel None ^
  -define triangulate:outside=clamp ^
  +distort Triangulate "%COORD_PAIR_LIST%" ^
  -write sa_xform_t.png ^
  %WEB_SIZE% ^
  sa_xform_t_sm.jpg 
@sa_xform_t.lis
%IMG7%magick ^
  %sa_ALI_SRC% ^
  ( sa_xform_t.png -channel A -evaluate Multiply 0.5 ) ^
  -layers merge +repage ^
  -write sa_xform_comp_t.png ^
  ( +clone -crop 500x500+110+4126 -write sa_xform_comp_t_cr.jpg +delete ) ^
  %WEB_SIZE% ^
  sa_xform_comp_t_sm.jpg

:skipTri
sa_xform_t_sm.jpg sa_xform_comp_t_sm.jpg sa_xform_comp_t_cr.jpg

The 100% crop shows that the alignment from Triangulate is worse then the affine transformation at this location, though it is exactly correct at each control point.

Refining the search

Some of the features found on the first image were close to the bottom or right edge, so aren't on the second image, so the matches were certain to be false. We can treat the first result as an approximation, and ensure we don't find any features in non-overlapping areas. Then we repeat the entire process.

Above, we blackened the edge of an image to avoid picking points there. We can take the found result to make a mask for more blackening. (We want to blacken 50 pixels more. The simple, obvious and elegant method for doing this is by eroding the white with "-morphology erode rectangle:100x100". Sadly, it is also agonizingly slow. (I suppose it performs 100x100 operations per input pixel.) Instead, we use "-morphology Distance Chebyshev:1,1 -threshold 50".)

%IMG7%magick ^
  %sa_OTH_SRC% ^
  -fill White -colorize 100 ^
  -virtual-pixel Black -distort Affine "%COORD_PAIR_LIST%" ^
  -morphology Distance Chebyshev:1,1 -threshold 50 ^
  -write sa_xform_maskXX.miff ^
  %WEB_SIZE% ^
  sa_xform_maskXX_sm.png

BUG: sa_xform_maskXX.miff is black

sa_xform_maskXX_sm.png
%IMG7%magick ^
  %sa_OTH_SRC% ^
  -fill White -colorize 100 ^
  sa_x.miff

call %PICTBAT%blackEdge sa_x.miff  50 . sa_x2.miff

%IMG7%magick ^
  sa_x2.miff ^
  -virtual-pixel Black -distort Affine "%COORD_PAIR_LIST%" ^
  -write sa_xform_mask.miff ^
  %WEB_SIZE% ^
  sa_xform_mask_sm.png

BUG: sa_xform_mask.miff is black

sa_xform_mask_sm.png
%IMG7%magick ^
  %SAVE_DIFF% ^
  sa_xform_mask.miff ^
  -compose Darken -composite ^
  -write sa_xform_diff.miff ^
  %WEB_SIZE% ^
  sa_xform_diff_sm.jpg
sa_xform_diff_sm.jpg

Note: the following nLightest is massively slow. Why? Solution: miff.

BUG: sa_xform_diff.miff is black

call %PICTBAT%nLightest sa_xform_diff.miff 24 

%IMG7%magick ^
  %nlDEBUG_FILE% ^
  %WEB_SIZE% ^
  sa_xf_coords_sm.jpg
sa_xf_coords_sm.jpg

Make crops from the coordinates.

set ncDEBUG=
call %PICTBAT%nCrop %sa_ALI_SRC% sa_ali_coordsB.lis 100 100

We search for the crops using the method as above.

set MATCH_LIS=sa_matchB.lis
del %MATCH_LIS% 2>nul
set MATCH_LIS2=sa_match2B.lis

echo CropNum,Score,X,Y >%MATCH_LIS%
set CROP_NUM=0
for /F %%F in (%ncLIST_FILE%) do (
  echo %%F

  call %PICTBAT%srchImg %sa_OTH_SRC% %%F
  if ERRORLEVEL 1 exit /B 1

  echo !CROP_NUM!, !siCOMP_FLT!, !siCENT_X!, !siCENT_Y! >>%MATCH_LIS%

  set /A CROP_NUM+=1
)
CropNum,Score,X,Y 
0, 0.110896, 5671, 3771 
1, 0.117396, 6177, 3844 
2, 0.129868, 6393, 2959 
3, 0.0767293, 2006, 2270 
4, 0.0907304, 7197, 2646 
5, 0.0943666, 2290, 250 
6, 0.0648876, 2026, 2811 
7, 0.121583, 5385, 3661 
8, 0.0631738, 915, 2931 
9, 0.081544, 2019, 1781 
10, 0.0870536, 5911, 1781 
11, 0.0490783, 455, 3714 
12, 0.0700384, 7106, 2059 
13, 0.0929437, 5746, 3247 
14, 0.0644039, 2021, 1727 
15, 0.0845894, 6924, 4526 
16, 0.0945779, 2555, 2825 
17, 0.0671523, 2627, 1536 
18, 0.0853236, 1411, 2953 
19, 0.104048, 3217, 2604 
20, 0.0529905, 4790, 874 
21, 0.101205, 6577, 3315 
22, 0.122236, 4547, 2642 
23, 0.0728633, 3113, 2573 

These are the matches found, in order of the score, so the closest matches are at the top.

cSort /i%MATCH_LIS% /o%MATCH_LIS2% /h /kScore
CropNum,Score,X,Y 
11, 0.0490783, 455, 3714 
20, 0.0529905, 4790, 874 
8, 0.0631738, 915, 2931 
14, 0.0644039, 2021, 1727 
6, 0.0648876, 2026, 2811 
17, 0.0671523, 2627, 1536 
12, 0.0700384, 7106, 2059 
23, 0.0728633, 3113, 2573 
3, 0.0767293, 2006, 2270 
9, 0.081544, 2019, 1781 
15, 0.0845894, 6924, 4526 
18, 0.0853236, 1411, 2953 
10, 0.0870536, 5911, 1781 
4, 0.0907304, 7197, 2646 
13, 0.0929437, 5746, 3247 
5, 0.0943666, 2290, 250 
16, 0.0945779, 2555, 2825 
21, 0.101205, 6577, 3315 
19, 0.104048, 3217, 2604 
0, 0.110896, 5671, 3771 
1, 0.117396, 6177, 3844 
7, 0.121583, 5385, 3661 
22, 0.122236, 4547, 2642 
2, 0.129868, 6393, 2959 

We can markup a copy of the searched image with the locations found.

set mrCOL_X=3
set mrCOL_Y=4
call %PICTBAT%markRect %sa_OTH_SRC% sa_matchB.lis 100 100

%IMG7%magick ^
  %mrOUTFILE% ^
  %WEB_SIZE% ^
  sa_srch_markedB.jpg
sa_srch_markedB.jpg

The following matches are correct: 0, 1, 3, 4, 6, 8, 9, 10, 11, 12, 13, 16, 17, 18, 20, 21 and 23. The following matches are incorrect: 2, 5, 7, 14, 15, 19 and 22. By using a mask from the first approximation, we have increased the hit-rate from 13/24 to 17/24.

cProject /isa_matchB.lis /osa_match3B.lis /h /kScore,X,Y
cHead /isa_match3B.lis /h1 /x
call %PICTBAT%appendLines sa_match3B.lis sa_ali_coordsB.lis >sa_coord_pairsB.lis

Use "modified Thompson tau technique" to remove outliers.

call %PICTBAT%extendCoordPairs sa_coord_pairsB.lis sa_matchMTTEB.lis
set rcoAPPEND_NUM=1
set rcoDEBUG_FILE=sa_rco_debugB.lis
del %rcoDEBUG_FILE% 2>nul
call removeCsvOutliers sa_matchMTTEB.lis sa_matchMTTE_goodB.lis 6 1 7 1 8 1
0.110896,5671,3771,5271,3431,-400,-340,524.97619 , 0 
0.117396,6177,3844,5769,3509,-408,-335,527.91003 , 1 
0.0767293,2006,2270,1630,1884,-376,-386,538.861763 , 3 
0.0907304,7197,2646,6804,2339,-393,-307,498.6963 , 4 
0.0648876,2026,2811,1645,2428,-381,-383,540.231432 , 6 
0.0631738,915,2931,521,2534,-394,-397,559.325487 , 8 
0.081544,2019,1781,1648,1392,-371,-389,537.551858 , 9 
0.0870536,5911,1781,5550,1456,-361,-325,485.74273 , 10 
0.0490783,455,3714,51,3316,-404,-398,567.115509 , 11 
0.0700384,7106,2059,6727,1754,-379,-305,486.483299 , 12 
0.0929437,5746,3247,5355,2911,-391,-336,515.535644 , 13 
0.0945779,2555,2825,2177,2448,-378,-377,533.866088 , 16 
0.0671523,2627,1536,2265,1155,-362,-381,525.552091 , 17 
0.0853236,1411,2953,1022,2562,-389,-391,551.545102 , 18 
0.101205,6577,3315,6175,2992,-402,-323,515.686921 , 21 
0.0728633,3113,2573,2740,2204,-373,-369,524.680855 , 23 

This has accepted almost those pairs we have found visually to be good (it incorrectly rejected 21), and rejected all those we found to be bad. The resulting list is nearly perfect.

The debug output sent to sa_rco_debugB.lis is:

removeCsvOutliers: Column=6 IsAbs=1 
removeOutliers: removed 15 
removeOutliers: removed 22 
removeOutliers: removed 5 
removeOutliers: removed 19 
removeOutliers: removed 2 
removeOutliers: removed 20 
removeOutliers: removed 14 
removeCsvOutliers: Column=7 IsAbs=1 
removeOutliers: removed 7 
removeCsvOutliers: Column=8 IsAbs=1 
removeCsvOutliers: nVAL=24 

With these new coordinate pairs, make a perspective distortion:

set COORD_PAIR_LIST2=

for /F "tokens=2-5 delims=, " %%A in (sa_matchMTTE_good.lis) do (
  set COORD_PAIR_LIST2=!COORD_PAIR_LIST2! %%A,%%B,%%C,%%D
)

%IMG7%magick ^
  %sa_OTH_SRC% ^
  -verbose ^
  -virtual-pixel None ^
  +distort Perspective "%COORD_PAIR_LIST2%" ^
  -write sa_xform_p_B.png ^
  %WEB_SIZE% ^
  sa_xform_p_B_sm.jpg 
Perspective Projection:
  -distort PerspectiveProjection \
      '1.02579, -0.00475227, -398.082, 0.0199438, 
       1.02372, -457.416, 2.37374e-06, 2.98293e-06'
Perspective Distort, FX Equivelent:
  -size 7464x5042 -page -416-458 xc: +insert \
  -fx 'ii=i+page.x+0.5; jj=j+page.y+0.5;
       rr=-2.25721e-06*ii -2.92431e-06*jj + 1;
       xx=(+0.976073*ii +0.00339439*jj +390.109)/rr;
       yy=(-0.0200242*ii +0.977644*jj +439.219)/rr;
       rr>0 ? v.p{xx-v.page.x-0.5,yy-v.page.y-0.5} : blue' \
%IMG7%magick ^
  %sa_ALI_SRC% ^
  ( sa_xform_p_B.png -channel A -evaluate Multiply 0.5 ) ^
  -layers merge +repage ^
  -write sa_xform_comp_p_B.png ^
  ( +clone -crop 500x500+510+4526 -write sa_xform_comp_p_cr_B.jpg +delete ) ^
  %WEB_SIZE% ^
  sa_xform_comp_p_B_sm.jpg
sa_xform_p_B_sm.jpg sa_xform_comp_p_B_sm.jpg sa_xform_comp_p_cr_B.jpg

Cleanup

This has created various files in the same directory as the source files.

del %tbdOUTFILE%
del %beOUTFILE%
del %mrOUTFILE%

del sa_xform_*.miff

rem Don't delete sa_xform_mask_sm.png
del sa_xform_comp*.png
del sa_xform_a*.png
del sa_xform_c*.png
del sa_xform_d*.png
del sa_xform_p*.png
del sa_xform_s*.png
del sa_xform_t*.png

Summary

We don't normally want the debugging images or text, so the process boils down to:

set nlDEBUG=
set ncDEBUG=
set rcoAPPEND_NUM=
set rcoDEBUG_FILE=

call %PICTBAT%twoBlrDiff %sa_ALI_SRC%
call %PICTBAT%blackEdge %tbdOUTFILE% 50
call %PICTBAT%nLightest %beOUTFILE% 24 
call %PICTBAT%nCrop %sa_ALI_SRC% sa_ali_coords.lis 100 100

set MATCH_LIS=sa_match.lis
del %MATCH_LIS%

set CROP_NUM=0
for /F %%F in (%ncLIST_FILE%) do (
  echo %%F

  call %PICTBAT%srchImg %sa_OTH_SRC% %%F
  if ERRORLEVEL 1 exit /B 1

  echo !siCOMP_FLT!, !siCENT_X!, !siCENT_Y! >>%MATCH_LIS%

  set /A CROP_NUM+=1
)

call %PICTBAT%appendLines %MATCH_LIS% sa_ali_coords.lis >sa_coord_pairs.lis

call %PICTBAT%extendCoordPairs sa_coord_pairs.lis sa_matchMTTE.lis
call removeCsvOutliers sa_matchMTTE.lis sa_matchMTTE_good.lis 6 1 7 1 8 1

del %tbdOUTFILE%
del %beOUTFILE%

Scripts

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

Some commands on this page use utility programs such as cProject.exe and cHead.exe. I do not publish source or binaries of these.

alignArea.bat

rem %1 is image A, %2 is image B,
rem %3 is CSV file with list of coords from image A.
rem
rem Returns (affine or perspective transformation from B to A?)
rem    list of coordinate pairs xA,yA,xB,yB:
rem Returns environment variables:
rem   aaCOORDS list of coords
rem   aaCOORDS_CSV name of CSV file containing the coords
@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 aa

if "%aaDIM%"=="" set aaDIM=100

if not exist %1 exit /B 1
if not exist %2 exit /B 1
if not exist %3 exit /B 1

set SRC_A=%1
set SRC_B=%2
set ALI_COORDS=%3

set ncDEBUG=
call %PICTBAT%nCrop %SRC_A% %ALI_COORDS% %aaDIM% %aaDIM%
if ERRORLEVEL 1 exit /B 1

type %ncLIST_FILE%

set MATCH_LIS=%TEMP%\aa_match.lis
del %MATCH_LIS% 2>nul
set MATCH_LIS2=%TEMP%\aa_match2.lis

set TMP_B=%BASENAME%_aa_tmpb.miff

%IMG7%magick %SRC_B% +repage %TMP_B%

rem echo %0: CropNum,Score,X,Y &gt;%MATCH_LIS%
set CROP_NUM=0
for /F %%F in (%ncLIST_FILE%) do (
  echo %0: %%F

  call %PICTBAT%srchImg %TMP_B% %%F
  if ERRORLEVEL 1 exit /B 1

  echo !siCENT_X!,!siCENT_Y! >>%MATCH_LIS%

  set /A CROP_NUM+=1
)


rem cProject /i%MATCH_LIS% /oaa_match3.lis /h /kScore,X,Y
rem cHead /iaa_match3.lis /h1 /x
set COORD_PAIRS=%TEMP%\aa_coord_pairs.lis
call %PICTBAT%appendLines %MATCH_LIS% %ALI_COORDS% >%COORD_PAIRS%

rem %COORD_PAIRS% has: xB,yB,xA,yA

set MATCH_MTTE=%TEMP%\aa_matchMTTE.lis
rem call %PICTBAT%extendCoordPairs %COORD_PAIRS% %MATCH_MTTE%

rem From CSV file %COORD_PAIRS%, assumed to contain X0,Y0,X1,Y1
rem writes %MATCH_MTTE% containing X0,Y0,X1,Y1,dX,dY,r
rem where:
rem   dX = X1 - X0
rem   dY = Y1 - Y0
rem   r = sqrt (dX^2 + dY^2)
rem
rem Files have no headers.

del %MATCH_MTTE% 2>nul

set sCOORD=
for /F "tokens=1-5 delims=, " %%A in (%COORD_PAIRS%) do (
  set /A dx=%%C-%%A
  set /A dy=%%D-%%B
  set /A dx2=!dx!*!dx!
  set /A dy2=!dy!*!dy!
  set /A r2=!dx2!+!dy2!

  for /F "usebackq" %%L in (`%IMG7%magick identify ^
    -precision 9 ^
    -format "r=%%[fx:sqrt(!r2!)]" ^
    xc:`) do set %%L

  echo %%A,%%B,%%C,%%D,!dx!,!dy!,!r! >>%MATCH_MTTE%
)



set rcoAPPEND_NUM=1
set rcoDEBUG_FILE=aa_rco_debug.lis
del %rcoDEBUG_FILE% 2>nul
call removeCsvOutliers %MATCH_MTTE% aa_matchMTTE_good.lis 5 1 6 1 7 1
if ERRORLEVEL 1 exit /B 1

rem type aa_matchMTTE_good.lis


rem Read coords into env var.
set sCOORD=
for /F "tokens=1-5 delims=, " %%A in (aa_matchMTTE_good.lis) do (
  set sCOORD=!sCOORD! %%A,%%B,%%C,%%D
)

@call echoRestore

@endlocal & set aaCOORDS=%sCOORD%& set aaCOORDS_CSV=aa_matchMTTE_good.lis

echo %0: Written env var aaCOORDS and file aaCOORDS_CSV [%aaCOORDS_CSV%]

StopWatch.bat

@set STOP_WATCH_SUB=
@if not "%STOP_WATCH_TIME%"=="" set STOP_WATCH_SUB=/S "%STOP_WATCH_TIME%"

@cDate %STOP_WATCH_SUB% /f"\q \H:\M:\s"

@for /F "usebackq tokens=*" %%L IN (`cDate /f"\d-\b-\Y \H:\M:\s"`) do @set STOP_WATCH_TIME=%%L
@rem echo %STOP_WATCH_TIME%

@if not "%1"=="" @echo %STOP_WATCH_TIME% %1 %2 %3 %4 %5 %6 %7 %8 %9>>stopwatch.lis

getPointsize.bat

rem %1 is image, %2 is height in pixels, sets gpPointsize to height in points.

call %PICTBAT%getDpi %1

if "%gdDpi%"=="0" (
  set gpPointsize=%2
) else (
  for /F "usebackq" %%L in (`%IMG7%magick identify -format "gpPointsize=%%[fx:%2*%gdDpi%/72]" xc:`) do set %%L
)

getDpi.bat

for /F "usebackq" %%L in (`%IMG7%magick identify -format "gdDpi=%%x\ngdUnits=%%U" %1`) do set %%L

if /I "%gdUnits%"=="PixelsPerCentimeter" (
  for /F "usebackq" %%L in (`%IMG7%magick identify -format "gdDpi=%%[fx:%gdDpi%*2.54]" xc:`) do set %%L
)

nLightest.bat

@rem From image %1, finds (%2) lightest points.
@rem
@rem After each point is found,
@rem    blacks out pixels within radius (r) of that point
@rem where r = min(width,height) * %3/100
@rem %3 defaults to 10 (percent).
@rem
@rem Returns number of points found. Could be < n, or even zero (?).
@rem
@rem Also uses:
@rem   nlDEBUG if 1, creates debugging image.
@rem   nlPOINTSIZE optional, pointsize for debug images.
@rem   nlSTROKEWIDTH for debug.
@rem   nlONLY_WHITE if 1, doesn't auto-level,
@rem      so finds only points that are exactly white.
@rem
@rem Returns:
@rem   nl_nFound number of points found, integer >= 0
@rem   echos list of coordinates.
@rem
@rem Updated:
@rem   24-May-2016 v7 needs -channel RGB for -auto-level.
@rem   7-May-2017 "+antialias" the circles.
@rem   2-August-2022 for IM v7 magick.
@rem



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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 nl

for /F "usebackq" %%L in (`cygpath %TEMP%`) do set CYGTEMP=%%L

set TMPFILE=%TEMP%\%~n1_%sioCODE%.miff
set CYGTMPFILE=%CYGTEMP%\%~n1_%sioCODE%.miff
set LISTFILE=%BASENAME%_%sioCODE%.lis
set TMPDEBUGFILE=%BASENAME%_%sioCODE%_dbg.miff
set DEBUGFILE=%BASENAME%_%sioCODE%_dbg%EXT%
set DEBUGDRAW=%TEMP%\%~n1_%sioCODE%_dbgdrw.txt

del %DEBUGDRAW% 2>nul

if "%nlPOINTSIZE%"=="" set nlPOINTSIZE=20

if "%nlSTROKEWIDTH%"=="" set nlSTROKEWIDTH=1

set MAX_FIND=%2
if "%MAX_FIND%"=="" set MAX_FIND=10

set PC_RAD=%3
if "%PC_RAD%"=="" set PC_RAD=10

set AUTOLEV=-channel RGB -auto-level +channel
if "%nlONLY_WHITE%"=="1" set AUTOLEV=

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -format "PIX_RAD=%%[fx:int(min(w,h)*%PC_RAD%/100+0.5)]" ^
  info:`) do set %%L

%IMG7%magick %INFILE% -colorspace Gray %AUTOLEV% %TMPFILE%

if "%nlDEBUG%"=="1" %IMG7%magick %INFILE% %TMPDEBUGFILE%

set /A nFound=0

:loop

set MAX=
set whiteX=

for /F "usebackq tokens=1-3 delims=:, " %%W ^
in (`%IM7DEV%magick ^
    %TMPFILE% ^
    -process onewhite ^
    NULL: 2^>^&1`) ^
do (
  if "%%W"=="onewhite" (
    set MAX=%%W
    set whiteX=%%X
    set whiteY=%%Y
    set /A whiteX2=%%X+%PIX_RAD%
  )
)

if "!MAX!"=="onewhite" if "!whiteX!" neq "none" (
  %IMG7%magick ^
    %TMPFILE% ^
    +antialias -fill #000 ^
    -draw "circle %whiteX% %whiteY% %whiteX2% %whiteY%" ^
    %AUTOLEV% ^
    %TMPFILE%

  if "%nlDEBUG%"=="1" (
    (
      echo fill None stroke #f00 circle %whiteX%,%whiteY%,%whiteX2%,%whiteY%
      echo fill #f00 stroke None text %whiteX%,%whiteY% '!nFound!'
    ) >>%DEBUGDRAW%
  )

  set /A nFound+=1
  echo %whiteX%,%whiteY%
  if !nFound! LSS %MAX_FIND% goto loop
)

if "%nlDEBUG%"=="1" if exist %DEBUGDRAW% %IMG7%magick ^
  %INFILE% ^
  -strokewidth %nlSTROKEWIDTH% ^
  -pointsize %nlPOINTSIZE% ^
  -draw @%DEBUGDRAW% ^
  %DEBUGFILE%

rem echo %0: nFound=%nFound%

call echoRestore

@endlocal & set nl_nFound=%nFound%& set nlDEBUG_FILE=%DEBUGFILE%

nCrop.bat

rem From image %1,
rem   list of x,y coordinates in text file %2,
rem   creates crops width %3 height %4 in files *_crop_n.*
rem Also creates text file _crops.lis. Name is returned as ncLIST_FILE.
@rem
@rem Also uses:
@rem   ncDEBUG         if 1, creates a marked-up copy of image.
@rem   ncDEBUG_STROKE  eg to get thick strokes
@rem   ncCROPDIR       directory to place the crops, default \temp
@rem   ncBASE          default name is based on %1, %3 and %4, in directory ncCROPDIR
@rem   ncREPAGE        if 0, does not "+repage" each crop.
@rem   ncEXT
@rem
@rem Returns:
@rem   ncLIST_FILE name of created file with list of names of crop files
@rem
@rem See also v7 version nCrop7.bat.
@rem
@rem Updated:
@rem   13-April-2018 added ncREPAGE
@rem   14-August-2020 for IM v7.
@rem

@rem FIXME: we could also use (optional?) standard deviation of each crop.
@rem Or some other measure, such as difference from a blur.

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

@setlocal enabledelayedexpansion

@call echoOffSave

echo %0: %1 %2 %3 %4

call %PICTBAT%setInOut %1 nl


if "%ncEXT%"=="" set ncEXT=%EXT%

if "%ncDEBUG%"=="1" set DEBUG_FILE=%BASENAME%_nc_dbg%ncEXT%

if "%ncDEBUG_STROKE%"=="" set ncDEBUG_STROKE=-stroke #f00 -fill None

if "%ncCROPDIR%"=="" set ncCROPDIR=\temp

if "%ncBASE%"=="" set ncBASE=%ncCROPDIR%\%~n1_%3_%4_crop

if "%ncAtBASE%"=="" set ncAtBASE=%~n1_%3_%4_crop

if "%ncREPAGE%"=="0" (
  set sREPAGE=
) else (
  set sREPAGE=+repage
)

set CROP_FILE=%ncAtBASE%_crops.scr
del %CROP_FILE% 2>nul

set RECT_FILE=%ncBASE%_rects.txt
del %RECT_FILE% 2>nul

set LIST_FILE=%ncBASE%s.lis
del %LIST_FILE% 2>nul

echo %0: text files: %CROP_FILE% %RECT_FILE% %LIST_FILE%

set nCrop=0
set sRECT=

set /A W_2=%3/2
set /A H_2=%4/2

for /F "tokens=1,2 delims=, " %%X in (%2) do (
  set /A dx=%%X-%W_2%
  set /A dy=%%Y-%H_2%
  set IMG_FILE=%ncAtBASE%_!nCrop!%ncEXT%
  echo ^( +clone -crop %3x%4+!dx!+!dy! %sREPAGE% -write !IMG_FILE! +delete ^) >> %CROP_FILE%
  echo !IMG_FILE! >>%LIST_FILE%
  set /A nCrop+=1

  if "%ncDEBUG%"=="1" (
    set /A endX=!dx!+%3-1
    set /A endY=!dy!+%4-1
    set sRECT=!sRECT! rectangle !dx!,!dy!,!endX!,!endY!
    echo rectangle !dx!,!dy!,!endX!,!endY! >>%RECT_FILE%
    rem FIXME: also annotate the rectangles
  )
)

echo -exit >>%CROP_FILE%

if %nCrop%==0 (
  %0: coordinates text file [%2] seems to be empty.
  exit /B 1
)

%IMG7%magick ^
  %INFILE% ^
  -script %CROP_FILE%

if ERRORLEVEL 1 (
  echo %0: script CROP_FILE [%CROP_FILE%] failed
  exit /B 1
)

if "%ncDEBUG%"=="1" (
  %IMG7%magick ^
    %INFILE% ^
    %ncDEBUG_STROKE% ^
    -draw @%RECT_FILE% ^
    %DEBUG_FILE%

  if ERRORLEVEL 1 exit /B 1
)

@call echoRestore

@endlocal & set ncLIST_FILE=%LIST_FILE%& set ncDEBUG_FILE=%DEBUG_FILE%

blackEdge.bat

rem For image %1, makes black all pixels up to %2 from any edge.
rem %3: 0 means param 2 is pixels, 1 means param 2 is percentage. Default 0.
rem %4 optional output file
@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem

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

@setlocal enabledelayedexpansion

rem @call echoOffSave

call %PICTBAT%setInOut %1 be


set RTW=%2
set RTH=%2

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -format "WW=%%w\nHH=%%h\nWm1=%%[fx:w-1]\nHm1=%%[fx:h-1]" ^
  info:`) do set %%L

if "%3"=="1" (
  for /F "usebackq" %%L in (`%IMG7%magick identify ^
    -format "RTW=%%[fx:%WW%*%RTW%/100]\nRTH=%%[fx:%HH%*%RTH%/100]" ^
    xc:`) do set %%L
)

for /F "usebackq" %%L in (`%IMG7%magick ^
  %INFILE% ^
  -format "WmRT=%%[fx:%WW%-%RTW%]\nHmRT=%%[fx:%HH%-%RTH%]" ^
  info:`) do set %%L

if not "%4"=="" if not "%4"=="." set OUTFILE=%4

%IMG7%magick ^
  %INFILE% ^
  -fill #000 ^
  -draw "rectangle 0,0 %Wm1%,%RTH% rectangle 0,0 %RTW%,%Hm1% rectangle %WmRT%,0 %Wm1%,%Hm1% rectangle 0,%HmRT% %Wm1%,%Hm1% " ^
  %OUTFILE%

@call echoRestore

@endlocal & set beOUTFILE=%OUTFILE%

markRect.bat

rem Given image %1, creates marked-up version with numbered rectangles.
rem Takes coordinates of rectangle centres from file %2.
rem %3 width of rectangle, default 1
rem %4 height of rectangle, default 1
rem %5 output file
@rem
@rem %2 is CSV file with columns X,Y{,N}
@rem If third column is present, uses this for annotation.
@rem Otherwise numbers seqentially.
@rem
@rem Also uses:
@rem   mrSTROKE  eg "-strokewidth 10" to get thick strokes
@rem   mrPIX_HT  height of font in pixels. If 0, no numbering.
@rem   mrCOL_X   column number in %2 for x-coordinate, counting from 1
@rem   mrCOL_Y   column number in %2 for y-coordinate, counting from 1
@rem   mrNhead   number of lines on header, to skip
@rem
@rem Updated:
@rem   10-December-2017 Stroke has defaults.
@rem   26-August-2018 Fixed bug in skip setting.
@rem   14-August-2020 for IM v7.
@rem


@rem FIXME: Also option for circles? Ellipes?

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 mr


if "%mrPIX_HT%"=="" set mrPIX_HT=20

if "%mrCOL_X%"=="" set mrCOL_X=1

if "%mrCOL_Y%"=="" set mrCOL_Y=2

set RW=%3
if "%RW%"=="" set RW=1

set RH=%4
if "%RW%"=="" set RH=1

if not "%5"=="" if not "%5"=="." set OUTFILE=%5

set sSKIP=
if not "%mrNhead%"=="" if not "%mrNhead%"=="0" (
  set sSKIP=skip=%mrNhead%
)

set /A W_2=%RW%/2
set /A H_2=%RH%/2

set nCrop=0
set sRECT=
set sNUMB=

call %PICTBAT%getPointSize %INFILE% %H_2%
set mrTEXT=-pointsize %gpPointsize% -fill #f00 -stroke None

for /F "tokens=%mrCOL_X%,%mrCOL_Y% %sSKIP% delims=, " %%X in (%2) do (
  if not "%%X" GTR "a" (
    set /A dx=%%X-%W_2%
    set /A dy=%%Y-%H_2%

    set /A endX=!dx!+%RW%-1
    set /A endY=!dy!+%RH%-1
    set sRECT=!sRECT! rectangle !dx!,!dy!,!endX!,!endY!
    set ANNOT=%%Z
    if "%ANNOT%"=="" set ANNOT=!nCrop!
    if not "%mrPIX_HT%"=="0" set sNUMB=!sNUMB! text %%X,%%Y '!ANNOT!'

    set /A nCrop+=1
  )
)

if ERRORLEVEL 1 exit /B 1

if "%sRECT%"=="" exit /B 1

if "%mrPIX_HT%"=="0" (
  set sTEXT=
) else (
  set sTEXT=%mrTEXT% -draw "%sNUMB%"
)

%IMG7%magick ^
  %INFILE% ^
  -stroke #f00 -strokewidth 1 -fill None ^
  %mrSTROKE% ^
  -draw "%sRECT%" ^
  %sTEXT% ^
  %OUTFILE%

if ERRORLEVEL 1 exit /B 1

@call echoRestore

@endlocal & set mrOUTFILE=%OUTFILE%

extendCoordPairs.bat

rem From CSV file %1, assumed to contain Score,X0,Y0,X1,Y1
rem writes %2 containing Score,X0,Y0,X1,Y1,dX,dY,r
rem where:
rem   dX = X1 - X0
rem   dY = Y1 - Y0
rem   r = sqrt (dX^2 + dY^2)
rem
rem Files have no headers.

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

@setlocal enabledelayedexpansion

@call echoOffSave


set INFILE=%1
set OUTFILE=%2

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

del %OUTFILE% 2>nul

for /F "tokens=1-5 delims=, " %%A in (%INFILE%) do (
  set /A dx=%%D-%%B
  set /A dy=%%E-%%C
  set /A dx2=!dx!*!dx!
  set /A dy2=!dy!*!dy!
  set /A r2=!dx2!+!dy2!

  for /F "usebackq" %%L in (`%IM%identify ^
    -precision 9 ^
    -format "r=%%[fx:sqrt(!r2!)]" ^
    xc:`) do set %%L

  echo %%A,%%B,%%C,%%D,%%E,!dx!,!dy!,!r! >>%OUTFILE%
)

@call echoRestore

@endlocal

appendLines.bat

@rem For text files, appends each line of %2 to end of %1, writing output to stdout.
@rem Inserts alSEP then a space character between each input pair.
@rem Returns number of lines in alOUTNUM

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

@setlocal

@call echoOffSave

set NumA=0
for /F "tokens=*" %%L in (%1) do (
  set lineA_!NumA!=%%L
  set /A NumA+=1
)

set NumB=0
for /F "tokens=*" %%L in (%2) do (
  set lineB_!NumB!=%%L
  set /A NumB+=1
)

set OutNum=%NumA%
if %OutNum% LSS %NumB% set OutNum=%NumB%

set /A nLast=%OutNum%-1

for /L %%i in (0,1,%nLast%) do echo.!lineA_%%i!%alSEP% !lineB_%%i!


call echoRestore

@endlocal & set alOUTNUM=%OutNum%

twoBlrDiff.bat

rem Difference of two blurs, resulting in monochrome RMS.
@rem
@rem %2 is first (smallest) blur sigma in pixels.
@rem %3 is second (largest) blur sigma in pixels.
@rem %4, if given, is final blur sigma in pixels.
@rem %5, if given, is output filename.
@rem
@rem Also uses:
@rem   tbdAUTO if 0, doesn't "-auto-level -auto-gamma"
@rem
@rem See also *BlrDiff.
@rem
@rem Updated:
@rem   2-August-2022 for IM v7 magick.
@rem

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 2bd

set BLUR_SIG1=%2
if "%BLUR_SIG1%"=="." set BLUR_SIG1=
if "%BLUR_SIG1%"=="" set BLUR_SIG1=4

set BLUR_SIG2=%3
if "%BLUR_SIG2%"=="." set BLUR_SIG2=
if "%BLUR_SIG2%"=="" set BLUR_SIG2=6

if "%4"=="" (
  set FINAL_BLUR=
) else (
  if not "%4"=="." set FINAL_BLUR=-blur 0x%4
)

if not "%5"=="" set OUTFILE=%5


if "%tbdAUTO%"=="0" (
  set AUTO=
) else (
  set AUTO=-auto-level -auto-gamma
)


%IMG7%magick ^
  %INFILE% ^
  ^( -clone 0 -blur 0x%BLUR_SIG1% ^) ^
  ^( -clone 0 -blur 0x%BLUR_SIG2% ^) ^
  -delete 0 ^
  -compose Difference -composite ^
  -grayscale RMS ^
  %FINAL_BLUR% ^
  %AUTO% ^
  %OUTFILE%


call echoRestore

@endlocal & set tbdOUTFILE=%OUTFILE%

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

%IMG7%magick -version
Version: ImageMagick 7.1.0-42 Q16-HDRI x64 396d87c:20220709 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI OpenCL 
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 (193231332)

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


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 29-May-2014.

Page created 29-Aug-2022 09:32:58.

Copyright © 2022 Alan Gibson.