#!/bin/bash
#
# Developed by Fred Weinhaus 11/2/2023 .......... revised 1/11/2025
# 
# Licensing:
# 
# Copyright © Fred Weinhaus
# 
# My scripts are available free of charge for non-commercial use, ONLY.
# 
# For use of my scripts in commercial (for-profit) environments or 
# non-free applications, please contact me (Fred Weinhaus) for 
# licensing arrangements. My email address is fmw at alink dot net.
# 
# If you: 1) redistribute, 2) incorporate any of these scripts into other 
# free applications or 3) reprogram them in another scripting language, 
# then you must contact me for permission, especially if the result might 
# be used in a commercial or for-profit environment.
# 
# My scripts are also subject, in a subordinate manner, to the ImageMagick 
# license, which can be found at: http://www.imagemagick.org/script/license.php
# 
# ------------------------------------------------------------------------------
# 
####
#
# USAGE: quadcorners1 [-p pad] [-L lower] [-U upper] [-a area] [-o openM] [-c closeM] 
# [-h hthresh] [-m maxiter] [-i images] [-k kind] [-C color] [-r radius] 
# [-t textoffset] infile
# 
# USAGE: quadcorners1 [-help]
# 
# OPTIONS:
# 
# -p     pad            border pad amount for input image to help in getting 
#                       good Hough lines; integer>=0; default=0 (no padding)
# -L     lower          lower color threshold for extracting quadrilateral; comma 
#                       separated triplet in the form of either 8-bit grayscale or 
#                       percent color values (for srgb color); if only 1 value provided, 
#                       it will be duplicated three times; default=50% (mid gray)
# -U     upper          upper color threshold for extracting quadrilateral; comma 
#                       separated triplet in the form of either 8-bit grayscale or 
#                       percent color values (for srgb color); if only 1 value provided, 
#                       it will be duplicated three times; default=100% (white)
# -a     area           area threshold in pixels for removing small regions from the 
#                       thresholded image; integer>=0; default=0 (skip connected  
#                       components filtering)
# -o     openM          morphology open diamond size for preprocessing to remove all 
#                       white regions outside the main quadrilateral in the threshold
#                       image; integer>0; default is no morphology open
# -c     closeM         morphology close disk size for preprocessing to remove all 
#                       black regions inside the main white quadrilateral in the 
#                       threshold image; integer>0; default is no morphology close
# -h     hthresh        Hough line length threshold either in pixels or percentage of 
#                       smallest input dimension; integer>=0; default=20%
# -m     maxiter        maximum stopping number of iterations; integer>0; default=0 
#                       (no iterations)
# -i     images         keep ancillary processing images (especially for debugging); 
#                       choices are: view (v) or save (s); default is neither
# -k     kind           kind of ancillary processing images; choices are: threshold (t), 
#                       cleaned (c), (convex) hull (h), (Hough) lines (l), 
#                       final (corners) (f) or all (a); default=final
# -C     color          color used when drawing on various images; any IM color value
#                       is allowed; default=red
# -r     radius         radius for circle used to mark corners; integer>0; default=2
# -t     textoffset     text offset of TL, TR, BR, BL annotation relative to corner; 
#                       +X+Y format, integers for X and Y; default="+0-15" 
# 
# 
###
# 
# NAME: QUADCORNERS1 
#  
# PURPOSE: To optionally threshold an image if not already binary and find the four 
# corners of the binary quadrilateral using the intersection of Hough lines from 
# the outline of the convex hull.
# 
# DESCRIPTION: QUADCORNERS optionally thresholds an image, if not already binary, 
# and finds the four corners of a quadrilateral in the binary image. The process is 
# 1) pad the input with black; 2) optionally color threshold the image, 3) optionally 
# apply morphology to clean and smooth the outline of the image; 3) optionally apply 
# connected components processing to remove small regions; 4) get the convex hull of 
# the binary image; 5) get 4 Hough lines from the convex hull; 6) sort the lines so 
# that they are listed in clockwise order with the top line first; 7) get the 
# intersections of successive lines in pairs; 8) get the 4 corner x,y coordinates 
# 
# The input image should be a binary quadrilateral. If not, then use the color-threshold, 
# morphology and area to threshold to clean the binary quadrilateral image.
# 
# The hthresh and pad arguments are the critical ones for getting only 4 Hough lines. 
# Unfortunately, they are rather sensitive. If too many lines, add padding and/or 
# increase the hthresh. If too few lines, remove padding and/or decrease the hthresh.
#
# Alternately, set the maximum number of iterations larger than 0 to try to converge 
# to 4 lines. The hthresh parameter will be used to set the initial starting number 
# of lines when iterating and will be adjusted accordingly during iteration. Try 
# adding padding if the convex hull is too close to the ends of image and there 
# is no convergence.
# 
# Any saved ancillary images will be name for the input_X.png, where X is the kind 
# of ancillary image.
# 
# The 4 corner X,Y coordinates of the quadrilateral (in the padded input) will be 
# listed to the terminal in clockwise order starting at the top-left corner
# 
# 
# Arguments: 
# 
# -p pad .......... PAD is the border pad amount for input image to help in getting 
#                   good Hough lines; integer>=0; default=0 (no padding)
# -L lower ........ LOWER color threshold for extracting quadrilateral; comma separated
#                   triplet in the form of either 8-bit grayscale or percent color 
#                   values (for srgb color); if only 1 value provided, it will be 
#                   duplicated three times; default=50% (mid-gray)
# -U upper ........ UPPER color threshold for extracting quadrilateral; comma separated
#                   triplet in the form of either 8-bit grayscale or percent color 
#                   values (for srgb color); if only 1 value provided, it will be 
#                   duplicated three times; default=100% (white)
# -a area ......... AREA threshold in pixels for removing small regions from the 
#                   thresholded image; integer>=0; default=0 (skip connected components 
#                   filtering)
# -o openM .........OPENM is the morphology open diamond size for preprocessing to  
#                   remove all white regions outside the main quadrilateral in the 
#                   threshold image; integer>0; default is no morphology open
# -c closeM ........CLOSEM is the morphology close disk size for preprocessing to   
#                   remove all black regions inside the main white quadrilateral in  
#                   the threshold image; integer>0; default is no morphology close
# -h hthresh ...... HOUGH line length threshold either in pixels or percentage of the
#                   smallest input dimension; integer>0; default=20%
# -m maxiter ...... MAXIMUM stopping number of INTERATIONS; Values are integers>=0; 
#                   The default=0 (no iterations)
# -i images ....... IMAGES specifies whether to keep ancillary processing images 
#                   (especially for debugging); choices are: view (v) or save (s); 
#                   default is neither
# -k kind ......... KIND of ancillary processing images; choices are: threshold (t), 
#                   cleaned (c), (convex) hull (h), (Hough) lines (l), 
#                   final (corners) (f) or all (a); default=final
# -C color ........ COLOR used when drawing on various images; any IM color value 
#                   is allowed; default=red
# -r radius ....... RADIUS for circle used to mark corner points; integer>0; default=2
# -t textoffset ... text offset of TL, TR, BR, BL annotation relative to corner points; 
#                   +X+Y format, integers for X and Y; default="+0-15" 
# 
# REQUIREMENTS: Requires IM 7 due to color thresholding and the convex hull 
# 
# CAVEAT: No guarantee that this script will work on all platforms, 
# nor that trapping of inconsistent parameters is complete and 
# foolproof. Use At Your Own Risk. 
# 
######
#

# set default values;
pad=0               # border padding of input
lower="50%"         # lower color threshold percent on quadrilateral region
upper="100%"        # upper color threshold percent on quadrilateral region
area=0              # area threshold in pixels to remove small regions in threshold image
openM=""            # morphology open diamond size
closeM=""           # morphology close disk size
hthresh="20%"       # hough line length threshold as percent of smallest input dimension
maxiter=0           # maximum iterations to achieve 4 lines
images=""           # save or view ancillary processing images
kind="final"        # save or view final image
color=red           # color for viewing points
radius=2            # radius of circle drawn for point
textoffset="+0-15"  # offset of corner labels from corner points

# set directory for temporary files
tmpdir="."		# suggestions are tmpdir="." or tmpdir="/tmp"

# set up functions to report Usage and Usage with Description
PROGNAME=`type $0 | awk '{print $3}'`  # search for executable on path
PROGDIR=`dirname $PROGNAME`            # extract directory of program
PROGNAME=`basename $PROGNAME`          # base name of program
usage1() 
	{
	echo >&2 ""
	echo >&2 "$PROGNAME:" "$@"
	sed >&2 -e '1,/^####/d;  /^###/g;  /^#/!q;  s/^#//;  s/^ //;  4,$p' "$PROGDIR/$PROGNAME"
	}
usage2() 
	{
	echo >&2 ""
	echo >&2 "$PROGNAME:" "$@"
	sed >&2 -e '1,/^####/d;  /^######/g;  /^#/!q;  s/^#*//;  s/^ //;  4,$p' "$PROGDIR/$PROGNAME"
	}

# function to report error messages
errMsg()
	{
	echo ""
	echo $1
	echo ""
	usage1
	exit 1
	}

# function to test for minus at start of value of second part of option 1 or 2
checkMinus()
	{
	test=`echo "$1" | grep -c '^-.*$'`   # returns 1 if match; 0 otherwise
    [ $test -eq 1 ] && errMsg "$errorMsg"
	}

# test for correct number of arguments and get values
if [ $# -eq 0 ]
	then
	# help information
	echo ""
	usage2
	exit 0
elif [ $# -gt 27 ]
	then
	errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---"
else
	while [ $# -gt 0 ]
		do
		# get parameters
		case "$1" in
	     -help)    # help information
				   echo ""
				   usage2
				   ;;
			-p)    # pad
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID PAD SPECIFICATION ---"
				   checkMinus "$1"
				   pad=`expr "$1" : '\([0-9]*\)'`
				   [ "$pad" = "" ] && errMsg "--- PAD=$pad MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   ;;
			-L)    # lower
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID LOWER SPECIFICATION ---"
				   checkMinus "$1"
				   lower=`expr "$1" : '\([0-9%,]*\)'`
				   [ "$lower" = "" ] && errMsg "--- LOWER=$lower MUST BE A TRIPLET OF 8-bit OR INTEGER PERCENT VALUE (with %) ---"
				   ;;
			-U)    # upper
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID UPPER SPECIFICATION ---"
				   checkMinus "$1"
				   upper=`expr "$1" : '\([0-9%,]*\)'`
				   [ "$upper" = "" ] && errMsg "--- UPPER=$upper MUST BE A TRIPLET OF 8-bit OR INTEGER PERCENT VALUE (with %) ---"
				   ;;
			-a)    # area
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID AREA SPECIFICATION ---"
				   checkMinus "$1"
				   area=`expr "$1" : '\([.0-9]*\)'`
				   [ "$area" = "" ] && errMsg "--- AREA=$area MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   ;;
			-o)    # openM
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID OPENM SPECIFICATION ---"
				   checkMinus "$1"
				   openM=`expr "$1" : '\([0-9]*\)'`
				   [ "$openM" = "" ] && errMsg "--- OPENM=$openM MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   ;;
			-c)    # closeM
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID CLOSEM SPECIFICATION ---"
				   checkMinus "$1"
				   closeM=`expr "$1" : '\([0-9]*\)'`
				   [ "$closeM" = "" ] && errMsg "--- CLOSEM=$closeM MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   ;;
			-h)    # hthresh
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID HTHRESH SPECIFICATION ---"
				   checkMinus "$1"
				   hthresh=`expr "$1" : '\([0-9%]*\)'`
				   ;;
			-m)    # maxiter
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID MAXITER SPECIFICATION ---"
				   checkMinus "$1"
				   maxiter=`expr "$1" : '\([0-9]*\)'`
				   [ "$maxiter" = "" ] && errMsg "--- MAXITER=$maxiter MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   ;;
			-i)    # images
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID IMAGES SPECIFICATION ---"
				   checkMinus "$1"
				   images=`echo "$1" | tr "[:upper:]" "[:lower:]"`
				   case "$images" in 
						view|v) images=view;;
						save|s) images=save;;
						*) errMsg "--- IMAGES=$images IS AN INVALID VALUE ---" 
					esac
				   ;;
			-k)    # kind
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID KIND SPECIFICATION ---"
				   checkMinus "$1"
				   kind=`echo "$1" | tr "[:upper:]" "[:lower:]"`
				   case "$kind" in 
						threshold|t) kind=threshold;;
						cleaned|c) kind=cleaned;;
						hull|h) kind=hull;;
						lines|l) kind=lines;;
						final|f) kind=final;;
						all|a) kind=all;;
						*) errMsg "--- KIND=$kind IS AN INVALID VALUE ---" 
					esac
				   ;;

			-C)    # get color
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID COLOR SPECIFICATION ---"
				   checkMinus "$1"
				   color="$1"
				   ;;
			-r)    # radius
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID RADIUS SPECIFICATION ---"
				   checkMinus "$1"
				   radius=`expr "$1" : '\([.0-9]*\)'`
				   [ "$radius" = "" ] && errMsg "--- RADIUS=$radius MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   test1=`echo "$radius == 0" | bc`
				   [ $test1 -eq 1 ] && errMsg "--- RADIUS=$radius MUST BE A POSITIVE INTEGER ---"
				   ;;
			-t)    # get textoffset
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID TEXTOFFSET SPECIFICATION ---"
				   checkMinus "$1"
				   textoffset=`expr "$1" : '\([-+0-9]*[-+0-9]*\)'`
				   [ "$textoffset" = "" ] && errMsg "--- TEXTOFFSET=$textoffset MUST BE +-X+-Y FORMAT ---"
				   ;;
			 -)    # STDIN and end of arguments
				   break
				   ;;
			-*)    # any other - argument
				   errMsg "--- UNKNOWN OPTION ---"
				   ;;
			*)     # end of arguments
				   break
				   ;;
		esac
		shift   # next option
	done
	#
	# get infile and outfile
	infile="$1"
fi

# test that infile provided
[ "$infile" = "" ] && errMsg "NO INPUT FILE SPECIFIED"

# set up temporary directory
dir="$tmpdir/QUADCORNERS1.$$"

if [ ! -d "$dir" ]; then 
	mkdir "$dir" || {
	errMsg "--- UNABLE TO CREATE DIRECTORY \"$dir\" -- ABORTING ---"
	exit 10
	}
fi
trap "rm -rf $dir; exit 0" 0
trap "rm -rf $dir; exit 1" 1 2 3 15

# get im_version
im_version=`convert -list configure | \
	sed '/^LIB_VERSION_NUMBER */!d; s//,/;  s/,/,0/g;  s/,0*\([0-9][0-9]\)/\1/g' | head -n 1`

# exit if not IM 7
[ "$im_version" -lt "07000000" ] && errMsg "--- REQUIRES IM 7 ---"

# function to get the intersection point of two lines
lineIntersect()
	{
	line1="$1"
	line2="$2"
	a1=`echo "$line1" | cut -d\  -f6`
	b1=`echo "$line1" | cut -d\  -f7`
	c1=`echo "$line1" | cut -d\  -f8`
	a2=`echo "$line2" | cut -d\  -f6`
	b2=`echo "$line2" | cut -d\  -f7`
	c2=`echo "$line2" | cut -d\  -f8`
	x=`echo "scale=1; ($c2*$b1-($c1*$b2))/($a1*$b2-($a2*$b1))" | bc`
	y=`echo "scale=1; ($a2*$c1-($a1*$c2))/($a1*$b2-($a2*$b1))" | bc`
	}

# get infile name
inname=`magick -ping "$infile" -format "%t" info:`

# read and test input image
if [ $pad -gt 0 ]; then
padding="-bordercolor black -border $pad"
else
padding=""
fi

# read input and optionally pad
magick -quiet "$infile" +repage $padding $dir/infile.miff ||
	errMsg "--- FILE $infile DOES NOT EXIST OR IS NOT AN ORDINARY FILE, NOT READABLE OR HAS ZERO SIZE ---"

# test and extract lower threshold values
testA=`echo "$lower" | grep "%"`
if [ "$testA" = "" ]; then
	# no %, so 8-bit values
	lower=`echo "$lower" | sed 's/ *//g'`
	count=`echo "$lower" | tr "," " " | wc -w | sed 's/^[ ]*//'`
	if [ $count -eq 1 ]; then
		lower1=$lower
		test1=`echo "$lower1 < 0" | bc`
		test2=`echo "$lower1 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower1 MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		lower="$lower1,$lower1,$lower1"
	elif [ $count -eq 2 ]; then
		errMsg "--- INCOMPATIBLE NUMBER OF LOWER ENTRIES SPECIFIED ---"
	elif [ $count -eq 3 ]; then
		# lower unchanged
		lower1=`echo ${lower} | cut -d, -f1`
		lower2=`echo ${lower} | cut -dx -f2`
		lower3=`echo ${lower} | cut -dx -f3`
		test1=`echo "$lower1 < 0" | bc`
		test2=`echo "$lower1 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$lower2 < 0" | bc`
		test2=`echo "$lower2 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$lower3 < 0" | bc`
		test2=`echo "$lower3 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		lower="$lower1,$lower2,$lower3"
	else
		errMsg "--- INCOMPATIBLE NUMBER OF LOWER ENTRIES SPECIFIED ---"
	fi
else
	# %, values between 0 and 100
	lower=`echo "$lower" | sed 's/ *//g' | sed 's/%*//g'`
	count=`echo "$lower" | tr "," " " | wc -w | sed 's/^[ ]*//'`
	if [ $count -eq 1 ]; then
		lower1=$lower
		test1=`echo "$lower1 < 0" | bc`
		test2=`echo "$lower1 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower1 MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		lower="$lower1%,$lower1%,$lower1%"
	elif [ $count -eq 2 ]; then
		errMsg "--- INCOMPATIBLE NUMBER OF LOWER ENTRIES SPECIFIED ---"
	elif [ $count -eq 3 ]; then
		lower1=`echo ${lower} | cut -d, -f1`
		lower2=`echo ${lower} | cut -dx -f2`
		lower3=`echo ${lower} | cut -dx -f3`
		test1=`echo "$lower1 < 0" | bc`
		test2=`echo "$lower1 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$lower2 < 0" | bc`
		test2=`echo "$lower2 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$lower3 < 0" | bc`
		test2=`echo "$lower3 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- LOWER=$lower MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		lower="$lower1%,$lower2%,$lower3%"
	else
		errMsg "--- INCOMPATIBLE NUMBER OF LOWER ENTRIES SPECIFIED ---"
	fi
fi
#echo "lower=$lower;"

# test and extract upper threshold values
testB=`echo "$upper" | grep "%"`
if [ "$testB" = "" ]; then
	# no %, so 8-bit values
	upper=`echo "$upper" | sed 's/ *//g'`
	count=`echo "$upper" | tr "," " " | wc -w | sed 's/^[ ]*//'`
	if [ $count -eq 1 ]; then
		upper1=$upper
		test1=`echo "$upper1 < 0" | bc`
		test2=`echo "$upper1 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper1 MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		upper="$upper1,$upper1,$upper1"
	elif [ $count -eq 2 ]; then
		errMsg "--- INCOMPATIBLE NUMBER OF upper ENTRIES SPECIFIED ---"
	elif [ $count -eq 3 ]; then
		# upper unchanged
		upper1=`echo ${upper} | cut -d, -f1`
		upper2=`echo ${upper} | cut -dx -f2`
		upper3=`echo ${upper} | cut -dx -f3`
		test1=`echo "$upper1 < 0" | bc`
		test2=`echo "$upper1 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$upper2 < 0" | bc`
		test2=`echo "$upper2 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$upper3 < 0" | bc`
		test2=`echo "$upper3 > 255" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		upper="$upper1,$upper2,$upper3"
	else
		errMsg "--- INCOMPATIBLE NUMBER OF upper ENTRIES SPECIFIED ---"
	fi
else
	# %, values between 0 and 100
	upper=`echo "$upper" | sed 's/ *//g' | sed 's/%*//g'`
	count=`echo "$upper" | tr "," " " | wc -w | sed 's/^[ ]*//'`
	if [ $count -eq 1 ]; then
		upper1=$upper
		test1=`echo "$upper1 < 0" | bc`
		test2=`echo "$upper1 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper1 MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		upper="$upper1%,$upper1%,$upper1%"
	elif [ $count -eq 2 ]; then
		errMsg "--- INCOMPATIBLE NUMBER OF upper ENTRIES SPECIFIED ---"
	elif [ $count -eq 3 ]; then
		upper1=`echo ${upper} | cut -d, -f1`
		upper2=`echo ${upper} | cut -dx -f2`
		upper3=`echo ${upper} | cut -dx -f3`
		test1=`echo "$upper1 < 0" | bc`
		test2=`echo "$upper1 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$upper2 < 0" | bc`
		test2=`echo "$upper2 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		test1=`echo "$upper3 < 0" | bc`
		test2=`echo "$upper3 > 100" | bc`
		[ $test1 -eq 1 -o $test2 -eq 1 ] && errMsg "--- upper=$upper MUST BE A NON-NEGATIVE INTEGER TRIPLET WITH VALUES BETEEN 0 AND 255 ---"
		upper="$upper1%,$upper2%,$upper3%"
	else
		errMsg "--- INCOMPATIBLE NUMBER OF upper ENTRIES SPECIFIED ---"
	fi
fi
#echo "upper=$upper;"

# check if both lower and upper use % or don't use %
if [ "$testA" = "" -a "$testB" != "" ] || [ "$testA" != "" -a "$testB" = "" ]; then
	errMsg "--- LOWER AND UPPER BOTH MUST USE % OR NEITHER MUST USE % ---"
fi

# set up for morphology open
# -morphology open diamond:1
if [ "$openM" = "" ]; then
	mopen_proc=""
else
	mopen_proc="-morphology open diamond:$openM"
fi
#echo "mopen_proc=$mopen_proc;"

# set up for morphology close
# -morphology close disk:7
if [ "$closeM" = "" ]; then
	mclose_proc=""
else
	mclose_proc="-morphology close disk:$closeM"
fi
#echo "mclose_proc=$mclose_proc;"

# separate text offset
xo=`echo "$textoffset" | sed -n 's/^\([+-].*\)[+-].*$/\1/p' | sed 's/\+*//g'`
yo=`echo "$textoffset" | sed -n 's/^[+-].*\([+-].*\)$/\1/p' | sed 's/\+*//g'`
#echo "xo=$xo; yo=$yo;"

# get wd and ht of input image
declare `magick -ping $dir/infile.miff -format "wd=%w\nht=%h\n" info:`
#echo "$wd; $ht"
#echo ""

# threshold, clean and get centroid
centroid=`convert -quiet $dir/infile.miff -alpha off \
-color-threshold "srgb($lower)-srgb($upper)" +write $dir/thresh.png \
$mopen_proc $mclose_proc -type bilevel \
-define connected-components:area-threshold=$area \
-define connected-components:mean-color=true \
-define connected-components:exclude-header=true \
-define connected-components:keep-top=2 \
-define connected-components:verbose=true \
-connected-components 8 $dir/cleaned.png | grep "gray(255)"  | head -n 1 | awk '{print($3)}'`

#echo $centroid
xcent=`echo "$centroid" | cut -d, -f1`
ycent=`echo "$centroid" | cut -d, -f2`
#echo "$centroid; $xcent; $ycent;"
#echo "" 

# save threshold image
if [[ "$images" != "" && ( "$kind" == "threshold" || "$kind" == "all" ) ]]; then
	cp -f $dir/thresh.png ${inname}_thresh.png
fi

# save cleaned image
if [[ "$images" != "" && ( "$kind" == "cleaned" || "$kind" == "all" ) ]]; then
	cp -f $dir/cleaned.png ${inname}_cleaned.png
fi

# get convex hull from cleaned image
hull=`magick $dir/cleaned.png -background black -format "%[convex-hull]" info:`
magick $dir/cleaned.png -fill black -colorize 100 -fill white -draw "polygon $hull" -alpha off $dir/hull.png
magick $dir/infile.miff -fill none -stroke red -draw "polygon $hull" -alpha off $dir/hull1.png
magick $dir/hull.png -fill black -colorize 100 \
	-fill none -stroke white -draw "polygon $hull" -alpha off $dir/hull_outline.png

# save convex hull on input image
if [[ "$images" != "" && ( "$kind" == "hull" || "$kind" == "all" ) ]]; then
	cp -f $dir/hull1.png ${inname}_hull.png
fi

# get hough lines
testC=`echo "$hthresh" | grep "%"`
if [ "$testC" != "" ]; then
	hthresh=`echo "$hthresh" | sed 's/ *//g' | sed 's/%*//g'`
	hthresh2=`magick xc: -format "%[fx:$hthresh*min($wd,$ht)/100]" info:`
fi
#echo "hthresh=$hthresh; hthresh2=$hthresh2;"
magick $dir/hull.png \
	\( $dir/hull_outline.png \
	-background none -fill red -hough-lines 9x9+$hthresh2 \) \
	-colorspace sRGB -compose over -composite $dir/hull_lines.png
magick -quiet $dir/hull_outline.png -hough-lines 9x9+$hthresh2 MVG:$dir/lines.mvg
OIFS=$IFS
IFS=$'\n'
lineArr=(`cat "$dir/lines.mvg" | tail -n +4 | cut -d\  -f2-3`)
IFS=$OIFS
nlines=${#lineArr[*]}
#echo "nlines=$nlines;"
#echo "${lineArr[0]}"
#echo "${lineArr[1]}"
#echo "${lineArr[2]}"
#echo "${lineArr[3]}"

# iterate if maxiter > 0
if [ $maxiter -gt 0 ]; then
	low=0
	high=100
	iter=0
	while [ $nlines -ne 4 -a $iter -lt $maxiter ]; do
		if [ $nlines -lt 4 ]; then
			high=$hthresh
			hthresh=`echo "scale=0; ($hthresh-$low)/2" | bc`
		elif [ $nlines -gt 4 ]; then
			low=$hthresh
			hthresh=`echo "scale=0; ($high-$hthresh/2)" | bc`
		fi
		hthresh2=`magick xc: -format "%[fx:$hthresh*min($wd,$ht)/100]" info:`
		im7 magick -quiet $dir/hull_outline.png -hough-lines 9x9+$hthresh2 MVG:$dir/lines.mvg
		nlines=`cat $dir/lines.mvg | tail -n +4 | wc -l | sed 's/ *//g'`
		#echo "nlines=$nlines; low=$low; high=$high; hthresh=$hthresh; hthresh2=$hthresh2"
		iter=$((iter+1))
	done
	#echo "$hthresh; $hthresh2; $nlines"

	im7 magick $dir/hull.png \
	\( $dir/hull_outline.png -background none -fill red -hough-lines 9x9+$hthresh2 \) \
	-colorspace sRGB -compose over -composite $dir/hull_lines.png
	
	#iter=$((iter-1))
	[ $iter -eq $maxiter ] && echo "--- WARNING: MAXITER ($maxiter) REACHED ---"

fi

# save hough lines on hull image
if [[ "$images" != "" && ( "$kind" == "lines" || "$kind" == "all" ) ]]; then
	cp -f $dir/hull_lines.png ${inname}_houghlines.png
fi

# test if 4 lines
[ $nlines -ne 4 ] && errMsg "--- NUMBER OF LINES ($nlines) NOT EQUAL TO 4 ---"

# get point on line perpendicular from centroid and convert to polar coordinates relative to centroid
# see https://en.wikipedia.org/wiki/Linear_equation
# see https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
# use equation of line as ax+by+c=0
pi=`magick xc: -format "%[fx:pi]" info:`
for ((i=0;i<nlines;i++)); do
#echo "${lineArr[$i]}"
x1=`echo "${lineArr[$i]}" | cut -d\  -f1 | cut -d, -f1`
y1=`echo "${lineArr[$i]}" | cut -d\  -f1 | cut -d, -f2`
x2=`echo "${lineArr[$i]}" | cut -d\  -f2 | cut -d, -f1`
y2=`echo "${lineArr[$i]}" | cut -d\  -f2 | cut -d, -f2`
# equation of line
a=`echo "scale=6; ($y1-($y2))/1" | bc`
b=`echo "scale=6; ($x2-($x1))/1" | bc`
c=`echo "scale=6; ($x1*$y2-($x2*$y1))/1" | bc`
# intersection of perpendicular from centroid to line
xi=`echo "scale=6; ($b*($b*$xcent-($a*$ycent)) - ($a*$c)) / ($a*$a+$b*$b)" | bc`
yi=`echo "scale=6; ($a*(($a*$ycent)-($b*$xcent)) - ($b*$c)) / ($a*$a+$b*$b)" | bc`
# distance and (clockwise) angle of line between intersection and centroid 
dist=`echo "scale=6; sqrt( ($xi-$xcent)*($xi-$xcent) + ($yi-$ycent)*($yi-$ycent) )/1" | bc`
#angle=`echo "scale=6; (180/$pi)*atan2(($yi-$ycent),($xi-$xcent))" | bc -l`
angle=`convert xc: -format "%[fx:(180/pi)*atan2(($yi-$ycent),($xi-$xcent)]" info:`
[[ $angle < 0 ]] && angle=`echo "scale=6; $angle + 360" | bc`
radArr[$i]="$radius"
angArr[$i]="$angle"
dataArr[$i]="$i $x1 $y1 $x2 $y2 $a $b $c $xi $yi $dist $angle"
#echo "${dataArr[$i]}"
done
#echo ""

# sort dataArr by angle and find angle closest positive x axis (smallest of min or 360-max angle)
min_angle=400
max_angle=0
OLDIFS=$IFS
IFS=$'\n'
sortArr=(`echo "${dataArr[*]}" | sort -n -k12,12`)
IFS=$OLDIFS
for((i=0; i<nlines; i++)); do
#echo "${sortArr[$i]}"
angle=`echo "${sortArr[$i]}" | cut -d\  -f12`
if [ $(echo "$angle <= $min_angle" | bc) -eq 1 ]; then
min_angle=$angle
min_indx=`echo "${sortArr[$i]}" | cut -d\  -f1`
fi
if [ $(echo "(360-$angle) <= (360-$max_angle)" | bc) -eq 1 ]; then
max_angle=$angle
max_indx=`echo "${sortArr[$i]}" | cut -d\  -f1`
fi
#echo "$angle; $min_angle; $indx"
done
best_indx=`magick xc: -format "%[fx:($min_angle<=(360-$max_angle))?$min_indx:$max_indx]" info:`
#echo "$min_angle; $min_indx; $max_angle; $max_indx; $best_indx;"
#echo ""

# rotate array so best_indx becomes new second row (so lines ordered top, right, bottom, left)
# see https://unix.stackexchange.com/questions/248522/rotate-element-of-array-in-shell-script
indxArr=(0 1 2 3)
j=$((best_indx+1))
[ $j -lt 0 ] && j=$((j+nlines))
newIndxArr=("${indxArr[@]:$j:$nlines}" "${indxArr[@]:0:$j}")
#echo ${newIndxArr[*]}
#echo ""
for ((i=0;i<nlines;i++)); do
k=${newIndxArr[$i]}
newSortArr[$i]="${sortArr[$k]}"
#echo "${newSortArr[$i]}"
done
#echo ""

# get corner points from intersections of lines
# clockwise starting at top-left corner

# top-left corner --- intersection of lines 3 and 0
lineIntersect "${newSortArr[3]}" "${newSortArr[0]}"
xtl=$x
ytl=$y

# top-right corner --- intersection of lines 0 and 1
lineIntersect "${newSortArr[0]}" "${newSortArr[1]}"
xtr=$x
ytr=$y

# bottom-right corner --- intersection of lines 1 and 2
lineIntersect "${newSortArr[1]}" "${newSortArr[2]}"
xbr=$x
ybr=$y

# bottom-left corner --- intersection of lines 2 and 3
lineIntersect "${newSortArr[2]}" "${newSortArr[3]}"
xbl=$x
ybl=$y

# print 4 corner coordinates to terminal
echo "$xtl,$ytl $xtr,$ytr $xbr,$ybr $xbl,$ybl"

# draw corners on copy of input
magick $dir/infile.miff $dir/corners.png
magick $dir/corners.png -fill "$color" \
-draw "translate $xtl,$ytl circle 0,0 0,2" \
-draw "text %[fx:($xtl+$xo)],%[fx:($ytl+$yo)] 'TL'" \
-draw "translate $xtr,$ytr circle 0,0 0,2" \
-draw "text %[fx:($xtr+$xo)],%[fx:($ytr+$yo)] 'TR'" \
-draw "translate $xbr,$ybr circle 0,0 0,2" \
-draw "text %[fx:($xbr+$xo)],%[fx:($ybr+$yo)] 'BR'" \
-draw "translate $xbl,$ybl circle 0,0 0,2" \
-draw "text %[fx:($xbl+$xo)],%[fx:($ybl+$yo)] 'BL'" \
-alpha off $dir/corners.png

# save corners points on input image
if [[ "$images" != "" && ( "$kind" == "final" || "$kind" == "all" ) ]]; then
	cp -f $dir/corners.png ${inname}_corners.png
fi

exit 0




