﻿ 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.

## Sample input

The input is a white line on a black background.

 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 ` 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```

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 ```

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:

• nPoints is the width, WW, in pixels.
• sumXY is nPoints * (the mean value of the product of the clut and a gradient).
• sigX is meanX*nPoints = 0.5 * WW.
• sigY is meanY*nPoints.
• sumXsquared is nPoints * (the mean value of the clut squared). FIXME: NO!!!! That would be sumYsquared.
• meanY is the mean clut value, %[fx:mean].
• meanX is 0.5.

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 ` ```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 `

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```

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```

## 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.

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 ```%IM%convert ^ is_easy_mainland.png ^ -gravity North ^ -crop x50%%+0+0 ^ lr_nq1.png``` Trim black off, wthout removing canvas metadata. ```%IM%convert ^ lr_nq1.png ^ -bordercolor Black -border 1 ^ -trim ^ 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``` ```%IM%convert ^ lr_nq3.png ^ -fill White -opaque Black ^ -fill Black -opaque Red ^ lr_nq4.png``` ```%IM%convert ^ lr_nq4.png ^ -scale "x1^!" ^ +write lr_nq5.png ^ -scale "x20^!" ^ 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```

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```

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```

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 ``` ```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 ```
```LINREG_quad.BL.liNeedExtend=0

... 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 ``` ```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```

## 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 ```

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```

```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

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 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 ^
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
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.