snibgo's ImageMagick pages

Linear regression

A wobbly line can be simplified to a straight line using linear regression.

A graphic element may be roughly a straight line, and we want to replace it by an exactly straight line that is a "best" approximation. One technique for this is linear regression.

See Wikipedia: Simple linear regression.

Sample input

The input is a white line on a black background.

rln_clut1.png

rln_clut1.png

The method

From the input image, we make a clut:

Trim it, record the crop parameters, and spread the line downwards.

for /F "usebackq" %%L in (`%IM%convert ^
  rln_clut1.png ^
  -trim ^
  -format "WW=%%w\nHH=%%h\noffsX=%%X\noffsY=%%Y" ^
  +write info: ^
  +repage ^
  -morphology dilate:-1 rectangle:1x2+0+0 ^
  lr_clut2.png`) do set %%L

echo WW=%WW% HH=%HH% offsX=%offsX% offsY=%offsY% 
WW=600 HH=97 offsX=+0 offsY=+150 
lr_clut2.png

A clut image is usually one pixel high.
Scaling it to 20 pixels helps us to see it.

%IM%convert ^
  lr_clut2.png ^
  -scale "x1^!" ^
  +write lr_clut3.png ^
  -scale "x20^!" ^
  lr_clut3a.png

set CLUT_SRC=lr_clut3.png
lr_clut3.png
lr_clut3a.png

From the clut, we can reconstruct an approximation to the line, though this creates a non-aliased version.

%IM%convert ^
  lr_clut3.png ^
  -scale "x%HH%^!" ^
  ( +clone ^
    -sparse-color Bilinear "0,0,White 0,%%[fx:h-1],Black" ^
  ) ^
  -compose MinusDst -composite ^
  -fill White +opaque Black ^
  -negate ^
  lr_clut4.png
lr_clut4.png

From the clut, we can do a linear regression. This is the grayscale linear gradient the same size as the clut that has a minimum RMS error from the clut.

           nPoints * sumXY - sigX * sigY
slope = -----------------------------------
        nPoints * sumXsquared - sigX * sigX

intercept = meanY - slope * meanX

where, taking X and Y as zero to one:

We need the mean values of two new images. We save and show the images for interest.

for /F "usebackq" %%L in (`%IM%convert ^
  %CLUT_SRC% ^
  ^( +clone ^
     -sparse-color bilinear ^
       "0,0,Black %%[fx:w-1],0,White" ^
  ^) ^
  -compose Multiply -composite ^
  -format "meanClGr=%%[fx:mean]" ^
  +write info: ^
  -scale "x20^!" ^
  lr_ClGr.png`) do set %%L

echo meanClGr=%meanClGr% 
meanClGr=0.185304 
lr_ClGr.png
for /F "usebackq" %%L in (`%IM%convert ^
  %CLUT_SRC% ^
  -sparse-color bilinear ^
    "0,0,Black %%[fx:w-1],0,White" ^
  -evaluate Pow 2 ^
  -format "meanClSq=%%[fx:mean]" ^
  +write info: ^
  -scale "x20^!" ^
  lr_ClSq.png`) do set %%L

echo meanClSq=%meanClSq% 
meanClSq=0.333613 
lr_ClSq.png

Calculate the components for the slope and intercept:

set sFORMAT1=^
nPoints=%WW%\n^
meanY=%%[fx:mean]\n^
sigX=%%[fx:0.5*%WW%]\n^
sigY=%%[fx:mean*%WW%]\n^
sumXY=%%[fx:%WW%*%meanClGr%]\n^
sumXsquared=%%[fx:%WW%*%meanClSq%]

for /F "usebackq" %%L in (`%IM%identify ^
  -format "%sFORMAT1%" ^
  %CLUT_SRC%`) do set %%L

echo nPoints=%nPoints% meanY=%meanY% sigX=%sigX% sigY=%sigY% sumXY=%sumXY% sumXsquared=%sumXsquared% 
nPoints=600 meanY=0.484337 sigX=300 sigY=290.602 sumXY=111.182 sumXsquared=200.168 

Calculate the slope:

set sFORMAT2=^
slope=%%[fx:(%WW%*%sumXY%-%sigX%*%sigY%)/(%WW%*%sumXsquared%-%sigX%*%sigX%)]

for /F "usebackq" %%L in (`%IM%identify ^
  -format "%sFORMAT2%" ^
  xc:`) do set %%L

echo slope=%slope% 
slope=-0.680095 

Calculate the intercept:

set sFORMAT3=^
intercept=%%[fx:%meanY%-%slope%*0.5]

for /F "usebackq" %%L in (`%IM%identify ^
  -format "%sFORMAT3%" ^
  xc:`) do set %%L

echo intercept=%intercept% 
intercept=0.824385 

From the slope and intersept, we calculate the ends of the line.

At x==0 pixels, the regression line is at HH*intercept pixels (where zero is at the bottom) or HH*(1-intercept) pixels (where zero is at the top).

At x==WW-1 pixels, the regression line is at HH*intercept+slope pixels (where zero is at the bottom) or HH*(1-intercept-slope) pixels (where zero is at the top).

set /A A.xi=0
set /A B.xi=%WW%-1

set FORMAT4=^
A.yi=%%[fx:int(%HH%*(1-(%intercept%))+0.5)]\n^
B.yi=%%[fx:int(%HH%*(1-(%intercept%)-(%slope%))+0.5)]

for /F "usebackq" %%L in (`%IM%identify ^
  -format "%FORMAT4%" ^
  xc:`) do set %%L

echo A.xi,A.yi = %A.xi%,%A.yi%   B.xi,B.yi = %B.xi%,%B.yi% 
A.xi,A.yi = 0,17   B.xi,B.yi = 599,83 
%IM%convert ^
  lr_clut2.png ^
  -stroke Orange ^
  -strokewidth 2 ^
  -draw "line %A.xi%,%A.yi% %B.xi%,%B.yi%" ^
  lr_reglin.png
lr_reglin.png

If we add the offsets from the trim, we get the coordinates for the input image so we can draw the line on the input image. or use the coordinates for futher processing.

set /A A.xi+=%offsX%
set /A A.yi+=%offsY%
set /A B.xi+=%offsX%
set /A B.yi+=%offsY%

echo A.xi,A.yi = %A.xi%,%A.yi%   B.xi,B.yi = %B.xi%,%B.yi% 
A.xi,A.yi = 0,167   B.xi,B.yi = 599,233 
 %IM%convert ^
  rln_clut1.png ^
  -stroke Orange ^
  -strokewidth 2 ^
  -draw "line %A.xi%,%A.yi% %B.xi%,%B.yi%" ^
  lr_reglin2.png
lr_reglin2.png

Linear regression scripts

We implement the above as a pair of scripts.

The script clut2LinReg.bat takes a grayscale Nx1 clut file and sets an environment variable.

call %PICTBAT%clut2LinReg lr_clut3.png LINREG_clut3
if ERRORLEVEL 1 goto :error

set LINREG_clut3 
LINREG_clut3.intercept=0.82438278569772261
LINREG_clut3.nPoints=600
LINREG_clut3.slope=-0.68009234334936286
%IM%convert -size 1x10000 gradient: -rotate 90 lr_g1.png

call %PICTBAT%clut2LinReg lr_g1.png LINREG1
if ERRORLEVEL 1 goto :error

set LINREG1 
LINREG1.intercept=0
LINREG1.nPoints=10000
LINREG1.slope=1
%IM%convert ^
  lr_g1.png ^
  ( +clone -flop ) ^
  +append ^
  lr_g2.png

call %PICTBAT%clut2LinReg lr_g2.png LINREG2
if ERRORLEVEL 1 goto :error

set LINREG2 
LINREG2.intercept=0.49999542271250075
LINREG2.nPoints=20000
LINREG2.slope=9.1545749985121921e-006

The script linReg2xy.bat calculates the coordinates of the ends of the regression line.

call %PICTBAT%linReg2xy LINREG_clut3 %WW%x%HH% %offsX% %offsY%
set LINREG_clut3 
LINREG_clut3.A.x=0
LINREG_clut3.A.y=167.035
LINREG_clut3.B.x=599
LINREG_clut3.B.y=233.004
LINREG_clut3.intercept=0.82438278569772261
LINREG_clut3.nPoints=600
LINREG_clut3.slope=-0.68009234334936286

Verify: when the clut is a straight line, the regression line should coincide.

Noisy quadrangles

Linear regression offers an alternative method for simplifying noisy quadrangles. For example, we will consider the top "edge" of is_easy_mainland.png. We assume the edge is defined by the most westerly and easterly edge of the shape, within the top 50% of the image.

is_easy_mainland.png

is_easy_mainland.png
%IM%convert ^
  is_easy_mainland.png ^
  -gravity North ^
  -crop x50%%+0+0 ^
  lr_nq1.png
lr_nq1.png

Trim black off, wthout removing canvas metadata.

%IM%convert ^
  lr_nq1.png ^
  -bordercolor Black -border 1 ^
  -trim ^
  lr_nq2.png
lr_nq2.png

Record the width, height and canvas offset.
Subtract 1 for offsets to account for added black border.

for /F "usebackq" %%L in (`%IM%identify ^
  -format "lrWW=%%w\nlrHH=%%h\nlrOFFX=%%X-1\nlrOFFY=%%Y-1" ^
  lr_nq2.png`) do set /A %%L

echo lrWW=%lrWW% lrHH=%lrHH%  lrOFFX=%lrOFFX% lrOFFY=%lrOFFY% 
lrWW=288 lrHH=87  lrOFFX=18 lrOFFY=13 

[No image]

%IM%convert ^
  lr_nq2.png ^
  -background Black ^
  -splice 0x1+0+0 ^
  -fill Red ^
  -draw "color 0,0 floodfill" ^
  -chop 0x1+0+0 ^
  lr_nq3.png
lr_nq3.png
%IM%convert ^
  lr_nq3.png ^
  -fill White -opaque Black ^
  -fill Black -opaque Red ^
  lr_nq4.png
lr_nq4.png
%IM%convert ^
  lr_nq4.png ^
  -scale "x1^!" ^
  +write lr_nq5.png ^
  -scale "x20^!" ^
  lr_nq5a.png
lr_nq5.png
lr_nq5a.png

The image lr_nq4.png is a first approximation to the "top edge". But this approximation can be misleading. Better (perhaps) to trim, then walk from each corner towards the opposite corner (or, more simply, at a 45° angle, or slide a ruler perpendicular to that angle) until we hit an edge. This gives us four points on the edge of the shape. Use these as the inner bounds for the four edges. But the centre of an edge might bend inwards further than that.

How about: rotate -90° and find first white. Rotate those coords back (sin cos cos sin) to give X,Y. X is the right-side of the top edge. Y is the top-side of the right edge.

Or: unroll, crop into four horizontally, flip, find first white in each. This gives radius and theta of corners.

From the Nx1 clut file, the script clut2LinReg.bat calculates the linear regression intercept and slope.

call %PICTBAT%clut2LinReg lr_nq5.png LINREG_nq5
if ERRORLEVEL 1 goto :error

set LINREG_nq5 
LINREG_nq5.intercept=1.0433506627567346
LINREG_nq5.nPoints=288
LINREG_nq5.slope=-0.2669042705046954
call %PICTBAT%linReg2xy LINREG_nq5 %lrWW%x%lrHH% %lrOFFX% %lrOFFY%

set LINREG_nq5 
LINREG_nq5.A.x=18
LINREG_nq5.A.y=9.22849
LINREG_nq5.B.x=305
LINREG_nq5.B.y=32.4492
LINREG_nq5.intercept=1.0433506627567346
LINREG_nq5.nPoints=288
LINREG_nq5.slope=-0.2669042705046954

The A and B coordinates give the ends of the regression line, with respect to the input image.

set sLINE=^
line ^
%LINREG_nq5.A.x%,%LINREG_nq5.A.y% ^
%LINREG_nq5.B.x%,%LINREG_nq5.B.y%

%IM%convert ^
  is_easy_mainland.png ^
  -stroke Orange ^
  -draw "%sLINE%" ^
  lr_1reg.png
lr_1reg.png

We implement this as a script nqlrTop.bat, which finds the linear regression of the top edge of a noisy quadrangle.

call %PICTBAT%nqlrTop is_easy_mainland.png LINREG_top

set LINREG_top 
LINREG_top.A.x=18
LINREG_top.A.y=9.22849
LINREG_top.B.x=305
LINREG_top.B.y=32.4492
LINREG_top.intercept=1.0433506627567346
LINREG_top.nPoints=288
LINREG_top.slope=-0.2669042705046954

To find the other three edges, we call this three more times, rotating the input image and output numbers. The script nqlr4edges.bat does this.

call %PICTBAT%nqlr4edges ^
  is_easy_mainland.png LINREG_4edges lr_4lines.png

set LINREG_4edges 
LINREG_4edges.bottom.B.y=305
LINREG_4edges.bottomLine.A.x=301
LINREG_4edges.bottomLine.A.y=183.47129999999999
LINREG_4edges.bottomLine.B.x=14
LINREG_4edges.bottomLine.B.y=173.7936
LINREG_4edges.leftLine.A.x=53.367
LINREG_4edges.leftLine.A.y=190
LINREG_4edges.leftLine.B.x=12.4533
LINREG_4edges.leftLine.B.y=13
LINREG_4edges.rightLine.A.x=279.2056
LINREG_4edges.rightLine.A.y=13
LINREG_4edges.rightLine.B.x=286.83420000000001
LINREG_4edges.rightLine.B.y=189
LINREG_4edges.topLine.A.x=18
LINREG_4edges.topLine.A.y=9.22849
LINREG_4edges.topLine.B.x=305
LINREG_4edges.topLine.B.y=32.4492
LINREG_4edges.topLine.intercept=1.0433506627567346
LINREG_4edges.topLine.nPoints=288
LINREG_4edges.topLine.slope=-0.2669042705046954
lr_4lines.png

The script nqlr4edges.bat doesn't calculate the rotated values of intercept and slope of the other three edges.

The script lines2Quad.bat calculates the intersection of these four lines.

call %PICTBAT%lines2Quad LINREG_4edges

set LINREG_4edges.TL 
set LINREG_4edges.TR 
set LINREG_4edges.BR 
set LINREG_4edges.BL 
LINREG_4edges.TL.liNeedExtend=1
LINREG_4edges.TL.sI=1.0242978350521272
LINREG_4edges.TL.tI=-0.02279029384659309
LINREG_4edges.TL.x=11.459185666027778
LINREG_4edges.TL.y=8.6992831957734893
LINREG_4edges.TR.liNeedExtend=0
LINREG_4edges.TR.sI=0.098996128474231448
LINREG_4edges.TR.tI=0.91275540719748605
LINREG_4edges.TR.x=279.96080186567855
LINREG_4edges.TR.y=30.423318611464737
LINREG_4edges.BR.liNeedExtend=0
LINREG_4edges.BR.sI=0.96582292397884839
LINREG_4edges.BR.tI=0.050266631505696685
LINREG_4edges.BR.x=286.57347675786508
LINREG_4edges.BR.y=182.98483462027733
LINREG_4edges.BL.liNeedExtend=0
LINREG_4edges.BL.sI=0.084722151292207545
LINREG_4edges.BL.tI=0.87491044139834162
LINREG_4edges.BL.x=49.900703318676008
LINREG_4edges.BL.y=175.00417922127926

This gives us the coordinates of the four corners of a quadrangle. TL = top-left, etc. sI and tI are measures of how far along each line is the point of intersection. liNeedExtend is whether sI or tI are outside the range 0.0 to 1.0, ie whether a line needed extending to form that intersection.

As a check, we use script drawCircs.bat to circle the calculated intersections:

call %PICTBAT%drawCircs ^
  lr_4lines.png lr_4linescircs.png . . . ^
  LINREG_4edges.TL LINREG_4edges.TR ^
  LINREG_4edges.BL LINREG_4edges.BR
lr_4linescircs.png

We can also test against an exact quadrangle ...

%IM%convert ^
  -size 300x200 xc:Black ^
  -fill White ^
  -draw "polygon 50,50 250,60 230,140 60,150" ^
  lr_ex_quad.png
lr_ex_quad.png
call %PICTBAT%nqlr4edges ^
  lr_ex_quad.png LINREG_quad lr_exq_out.png

call %PICTBAT%lines2Quad LINREG_quad

call %PICTBAT%drawCircs ^
  lr_exq_out.png lr_exq_out.png . . . ^
  LINREG_quad.TL LINREG_quad.TR ^
  LINREG_quad.BL LINREG_quad.BR

set LINREG_quad 
lr_exq_out.png
LINREG_quad.BL.liNeedExtend=0
LINREG_quad.BL.sI=0.011780379998937957
LINREG_quad.BL.tI=0.97223919489048283
LINREG_quad.BL.x=60.135748945260637
LINREG_quad.BL.y=148.8219620001062
LINREG_quad.bottom.B.y=244
LINREG_quad.bottomLine.A.x=240
LINREG_quad.bottomLine.A.y=136.35980000000001
LINREG_quad.bottomLine.B.x=55
LINREG_quad.bottomLine.B.y=149.17779999999999
LINREG_quad.BR.liNeedExtend=0
LINREG_quad.BR.sI=0.92256526662273886
LINREG_quad.BR.tI=0.058395126339815126
LINREG_quad.BR.x=229.1969016271342
LINREG_quad.BR.y=137.10830872942375
LINREG_quad.leftLine.A.x=60.2537
LINREG_quad.leftLine.A.y=150
LINREG_quad.leftLine.B.x=50.2412
LINREG_quad.leftLine.B.y=50
LINREG_quad.rightLine.A.x=242.37630000000001
LINREG_quad.rightLine.A.y=55
LINREG_quad.rightLine.B.x=228.0907
LINREG_quad.rightLine.B.y=144
LINREG_quad.TL.liNeedExtend=0
LINREG_quad.TL.sI=0.99772475481221745
LINREG_quad.TL.tI=0.0013199044622133581
LINREG_quad.TL.x=50.263980892442675
LINREG_quad.TL.y=50.227524518778253
LINREG_quad.topLine.A.x=50
LINREG_quad.topLine.A.y=50.2143
LINREG_quad.topLine.B.x=250
LINREG_quad.topLine.B.y=60.2336
LINREG_quad.topLine.intercept=0.9957145570156678
LINREG_quad.topLine.nPoints=201
LINREG_quad.topLine.slope=-0.20038615988469638
LINREG_quad.TR.liNeedExtend=0
LINREG_quad.TR.sI=0.054078400479930358
LINREG_quad.TR.tI=0.95801878801051965
LINREG_quad.TR.x=241.60375760210391
LINREG_quad.TR.y=59.812977642713804

... and an exact rectangle.

%IM%convert ^
  -size 300x200 xc:Black ^
  -fill White ^
  -draw "polygon 30,40 230,40 230,140 30,140" ^
  lr_ex_rect.png
lr_ex_rect.png
call %PICTBAT%nqlr4edges ^
  lr_ex_rect.png LINREG_rect lr_rect_out.png

call %PICTBAT%lines2Quad LINREG_rect

call %PICTBAT%drawCircs ^
  lr_rect_out.png lr_rect_out.png . . . ^
  LINREG_rect.TL LINREG_rect.TR ^
  LINREG_rect.BL LINREG_rect.BR
lr_rect_out.png

Example: photograph

A photograph of a book page. The image is about 5000x7000 pixels. All operations are done on the full-size image, and the results are resized for the web.

Source image.

set WEB_SIZE=-resize 600x600

set PHOTO_SRC=pbp_threshpap.tiff

%IM%convert ^
  %PHOTO_SRC% ^
  %WEB_SIZE% ^
  lr_ph_src_sm.jpg
lr_ph_src_sm.jpg

Blah.

Scripts

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

clut2LinReg.bat

rem From clut image %1,
rem sets linear regression information to environment variable, prefix %2.
rem 3 channels?

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

@setlocal enabledelayedexpansion

rem @call echoOffSave

call %PICTBAT%setInOut %1  c2lr

set ENV_PREF=%2
if "%ENV_PREF%"=="." set ENV_PREF=
if "%ENV_PREF%"=="" set ENV_PREF=LINREG

set meanClGr=
set meanClSq=

for /F "usebackq" %%L in (`%IM%convert ^
  -precision 19 ^
  %INFILE% ^
  ^( +clone ^
     -sparse-color bilinear ^
       "0,0,Black %%[fx:w-1],0,White" ^
  ^) ^
  -compose Multiply -composite ^
  -format "HH=%%h\nmeanClGr=%%[fx:mean]" ^
  info:`) do set %%L

if not "%HH%"=="1" exit /B 1

for /F "usebackq" %%L in (`%IM%convert ^
  -precision 19 ^
  %INFILE% ^
  -sparse-color bilinear ^
    "0,0,Black %%[fx:w-1],0,White" ^
  -evaluate Pow 2 ^
  -format "meanClSq=%%[fx:mean]" ^
  info:`) do set %%L

goto skipMult
for /F "usebackq" %%L in (`%IM%convert ^
  -precision 19 ^
  %INFILE% ^
  -duplicate 1 ^
  -compose Multiply -composite ^
  -format "meanClSq=%%[fx:mean]" ^
  info:`) do set %%L
:skipMult

echo %0: meanClGr=%meanClGr% meanClSq=%meanClSq%

set nPoints=0

set sFORMAT1=^
nPoints=%%w\n^
meanY=%%[fx:mean]\n^
sigX=%%[fx:0.5*w]\n^
sigY=%%[fx:mean*w]\n^
sumXY=%%[fx:w*%meanClGr%]\n^
sumXsquared=%%[fx:w*%meanClSq%]

for /F "usebackq" %%L in (`%IM%identify ^
  -precision 19 ^
  -format "%sFORMAT1%" ^
  %INFILE%`) do set %%L

echo %0: nPoints=%nPoints% meanY=%meanY% sigX=%sigX% sigY=%sigY% sumXY=%sumXY% sumXsquared=%sumXsquared% 

if %nPoints%==0 exit /B 1

set sFORMAT2=^
slope=%%[fx:(%nPoints%*%sumXY%-%sigX%*%sigY%)/(%nPoints%*%sumXsquared%-%sigX%*%sigX%)]

for /F "usebackq" %%L in (`%IM%identify ^
  -precision 19 ^
  -format "%sFORMAT2%" ^
  xc:`) do set %%L


set sFORMAT3=^
intercept=%%[fx:%meanY%-(%slope%)*0.5]

for /F "usebackq" %%L in (`%IM%identify ^
  -precision 19 ^
  -format "%sFORMAT3%" ^
  xc:`) do set %%L

rem set ENV_LIST=c2lrEnv.lis

rem set %ENV_PREF% >%ENV_LIST%


call echoRestore

rem endlocal & for /F %%L in (%ENV_LIST%) do @set %%L

endlocal & set %ENV_PREF%.nPoints=%nPoints%& set %ENV_PREF%.slope=%slope%& set %ENV_PREF%.intercept=%intercept%

linReg2xy.bat

rem Given %1 is prefix for an environment variable with intercept and slope,
rem %2 is width and height in form WxH
rem %3 is X-offset
rem %4 is Y-offset
rem sets .A.x, .A.y, .B.x, .B.y

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

@setlocal enabledelayedexpansion

rem @call echoOffSave


set PREF=%1

call parseXxY2 !%PREF%.nPoints! 10 WH %2

set offsX=%3
if "%offsX%"=="." set offsX=
if "%offsX%"=="" set offsX=0

set offsY=%4
if "%offsY%"=="." set offsY=
if "%offsY%"=="" set offsY=0

set %PREF%
set WH

set /A A.x=%offsX%
set /A B.x=%offsX%+%WH_X%-1

set FORMAT4=^
A.y=%%[fx:%offsY%+%WH_Y%*(1-(!%PREF%.intercept!))]\n^
B.y=%%[fx:%offsY%+%WH_Y%*(1-(!%PREF%.intercept!)-(!%PREF%.slope!))]

for /F "usebackq" %%L in (`%IM%identify ^
  -format "%FORMAT4%" ^
  xc:`) do set %%L

rem call echoRestore

endlocal & set %PREF%.A.x=%A.x%& set %PREF%.A.y=%A.y%& set %PREF%.B.x=%B.x%& set %PREF%.B.y=%B.y%

nqlrTop.bat

rem Given %1 is a white noisy quadrangle on a black backround,
rem %2 is the prefix of an environment variable,
rem calculates the linear regression of the top edge.

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

set nqlrtINFILE=%1

set nqlrtENV_PREF=%2

set nqlrtTEMP=nqlrt_clut.miff

for /F "usebackq" %%L in (`%IM%convert ^
  %nqlrtINFILE% ^
  -gravity North ^
  -crop x50%%+0+0 ^
-threshold 50%% ^
  -bordercolor Black -border 1 ^
  -trim ^
  -format "nqlrtWW=%%w\nnqlrtHH=%%h\nnqlrtOFFX=%%X-1\nnqlrtOFFY=%%Y-1" ^
  +write info: ^
  +repage ^
+write a1.png ^
  -background Black ^
  -splice 0x1+0+0 ^
  -fill Red ^
  -draw "color 0,0 floodfill" ^
  -chop 0x1+0+0 ^
  -fill White -opaque Black ^
  -fill Black -opaque Red ^
+write a2.png ^
  -scale "x1^!" ^
  %nqlrtTEMP%`) do set /A %%L

call %PICTBAT%clut2LinReg %nqlrtTEMP% %nqlrtENV_PREF%
if ERRORLEVEL 1 exit /B 1

call %PICTBAT%linReg2xy %nqlrtENV_PREF% %nqlrtWW%x%nqlrtHH% %nqlrtOFFX% %nqlrtOFFY%
if ERRORLEVEL 1 exit /B 1

exit /B 0

nqlr4edges.bat

rem Given %1 is a white noisy quadrangle on a black backround,
rem %2 is the prefix of an environment variable,
rem calculates the linear regression of the four edges.
rem %3 is outfile.

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

rem @setlocal enabledelayedexpansion

rem @call echoOffSave

@rem This assumes "enabledelayedexpansion" is in effect.
set TESTDEL=abc
if not !TESTDEL!==abc exit /B 1


set nqlr4INFILE=%1

set nqlr4ENV_PREF=%2

set nqlr4OUTFILE=%3
if "%nqlr4OUTFILE%"=="." set nqlr4OUTFILE=
if "%nqlr4OUTFILE%"=="" set nqlr4OUTFILE=NULL:

set EXT=.miff
set TMP_ROT=nqlr4%EXT%

set nqlr4WW=
for /f "usebackq" %%L in (`%IM%convert ^
  %nqlr4INFILE% ^
  -format "nqlr4WW=%%w\nnqlr4HH=%%h" ^
  +write info: ^
  -rotate 90 +repage ^
  %TMP_ROT%`) do set %%L

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

echo nqlr4WW=%nqlr4WW% nqlr4HH=%nqlr4HH%


:: Do top edge.

call %PICTBAT%nqlrTop %nqlr4INFILE% %nqlr4ENV_PREF%.topLine
if ERRORLEVEL 1 exit /B 1


:: Do left edge.

call %PICTBAT%nqlrTop %TMP_ROT% nqlr4
if ERRORLEVEL 1 exit /B 1

set %nqlr4ENV_PREF%.leftLine.A.x=%nqlr4.A.y%
call :doCalc %nqlr4ENV_PREF%.leftLine.A.y "%nqlr4HH%-1-%nqlr4.A.x%"

set %nqlr4ENV_PREF%.leftLine.B.x=%nqlr4.B.y%
call :doCalc %nqlr4ENV_PREF%.leftLine.B.y "%nqlr4HH%-1-%nqlr4.B.x%"


:: Do right edge.

%IM%convert ^
  %nqlr4INFILE% ^
  -rotate -90 +repage ^
  %TMP_ROT%

call %PICTBAT%nqlrTop %TMP_ROT% nqlr4
if ERRORLEVEL 1 exit /B 1

set %nqlr4ENV_PREF%.rightLine.A.y=%nqlr4.A.x%
call :doCalc %nqlr4ENV_PREF%.rightLine.A.x "%nqlr4WW%-1-%nqlr4.A.y%"

set %nqlr4ENV_PREF%.rightLine.B.y=%nqlr4.B.x%
call :doCalc %nqlr4ENV_PREF%.rightLine.B.x "%nqlr4WW%-1-%nqlr4.B.y%"


:: Do bottom edge.

%IM%convert ^
  %nqlr4INFILE% ^
  -rotate 180 +repage ^
  %TMP_ROT%

call %PICTBAT%nqlrTop %TMP_ROT% nqlr4
if ERRORLEVEL 1 exit /B 1

call :doCalc %nqlr4ENV_PREF%.bottomLine.A.x "%nqlr4WW%-1-%nqlr4.A.x%"
call :doCalc %nqlr4ENV_PREF%.bottomLine.A.y "%nqlr4HH%-1-%nqlr4.A.y%"

set %nqlr4ENV_PREF%.bottom.B.y=%nqlr4.B.x%
call :doCalc %nqlr4ENV_PREF%.bottomLine.B.x "%nqlr4WW%-1-%nqlr4.B.x%"
call :doCalc %nqlr4ENV_PREF%.bottomLine.B.y "%nqlr4HH%-1-%nqlr4.B.y%"


set sLINES=^
line ^
!%nqlr4ENV_PREF%.leftLine.A.x!,!%nqlr4ENV_PREF%.leftLine.A.y!,^
!%nqlr4ENV_PREF%.leftLine.B.x!,!%nqlr4ENV_PREF%.leftLine.B.y! ^
line ^
!%nqlr4ENV_PREF%.topLine.A.x!,!%nqlr4ENV_PREF%.topLine.A.y!,^
!%nqlr4ENV_PREF%.topLine.B.x!,!%nqlr4ENV_PREF%.topLine.B.y! ^
line ^
!%nqlr4ENV_PREF%.rightLine.A.x!,!%nqlr4ENV_PREF%.rightLine.A.y!,^
!%nqlr4ENV_PREF%.rightLine.B.x!,!%nqlr4ENV_PREF%.rightLine.B.y! ^
line ^
!%nqlr4ENV_PREF%.bottomLine.A.x!,!%nqlr4ENV_PREF%.bottomLine.A.y!,^
!%nqlr4ENV_PREF%.bottomLine.B.x!,!%nqlr4ENV_PREF%.bottomLine.B.y!

if not "%nqlrOUTFILE%"=="NULL:" (
  %IM%convert ^
    %nqlr4INFILE% ^
    -stroke Orange ^
    -draw "%sLINES%" ^
    %nqlr4OUTFILE%
)

exit /B 0

::----------------------------------------------------
:: Subroutine

:doCalc
set %1=
for /F "usebackq" %%L in (`%IM%identify -precision 19 -format "%1=%%[fx:%~2]" xc:`) do set %%L

rem echo %1 is !%1!

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

exit /B 0

lines2Quad.bat

rem Given %1 is environment variable prefix
rem   with leftLine, topLine, rightLine and BottomLine
rem     each with A and B
rem       each with x and y,
rem calculates intersections.
rem %2 is badness threshold for intersection.


set ENV_PREF=%1

set l2qBAD_THRESH=%2
if "%l2qBAD_THRESH%"=="" set l2qBAD_THRESH=.

call %PICTBAT%lineIntersect %ENV_PREF%.leftLine %ENV_PREF%.topLine %ENV_PREF%.TL %l2qBAD_THRESH%
if ERRORLEVEL 1 exit /B 1

call %PICTBAT%lineIntersect %ENV_PREF%.rightLine %ENV_PREF%.topLine %ENV_PREF%.TR %l2qBAD_THRESH%
if ERRORLEVEL 1 exit /B 1

call %PICTBAT%lineIntersect %ENV_PREF%.leftLine %ENV_PREF%.bottomLine %ENV_PREF%.BL %l2qBAD_THRESH%
if ERRORLEVEL 1 exit /B 1

call %PICTBAT%lineIntersect %ENV_PREF%.rightLine %ENV_PREF%.bottomLine %ENV_PREF%.BR %l2qBAD_THRESH%
if ERRORLEVEL 1 exit /B 1

exit /B 0

drawCircs.bat

rem From image %1, writes output %2
rem with circles colour %3 radius %4 strokewidth %5
rem at centres given by env var prefixes %6 onwards.

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

@setlocal enabledelayedexpansion

@call echoOffSave

call %PICTBAT%setInOut %1 nqc

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

set COL=%3
if "%COL%"=="." set COL=
if "%COL%"=="" set COL=Orange

set RAD=%4
if "%RAD%"=="." set RAD=
if "%RAD%"=="" set RAD=10

set SW=%5
if "%SW%"=="." set SW=
if "%SW%"=="" set SW=1

set sCIRCS=

:loop
rem echo %6

set CX=!%6.x!
set CY=!%6.y!

if "%CX%"=="" (
  echo %6 has no .x
  exit /B 1
)
if "%CY%"=="" (
  echo %6 has no .y
  exit /B 1
)

for /F "usebackq" %%L in (`%IM%identify ^
  -format "DX=%%[fx:%CX%+%RAD%]" ^
  xc:`) do set %%L

set sCIRCS=%sCIRCS% circle %CX%,%CY% %DX%,%CY%

shift /6
if not "%6"=="" goto loop

%IM%convert ^
  %INFILE% ^
  -fill None ^
  -stroke %COL% ^
  -strokewidth %SW% ^
  -draw "%sCIRCS%" ^
  %OUTFILE%


call echoRestore

@endlocal & set dcOUTFILE=%OUTFILE%

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

%IM%identify -version
Version: ImageMagick 6.9.2-5 Q16 x64 2015-10-31 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Visual C++: 180031101
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib

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

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


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 5-July-2016.

Page created 21-Jul-2016 18:54:43.

Copyright © 2016 Alan Gibson.