#!/bin/bash
#
# Developed by Fred Weinhaus 11/2/2023 .......... revised 12/18/2023
# 
# 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: quadcorners2 [-L lower] [-U upper] [-a area] [-o openM] [-c closeM] 
# [-b blursize] [-f filtersize] [-s skip] [-i images] [-k kind] [-C color] [-r radius] 
# [-t textoffset] infile
# 
# USAGE: quadcorners2 [-help]
# 
# OPTIONS:
# 
# -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
# -b     blursize       size of 1D blur (smoothing) filter (pixels); integer>0; default=5
# -f     filtersize     size of 1D derivative filter (pixels); integer>0; default=5
# -s     skip           skip (in pixels) as the minimum distance between peaks  
#                       in the polar image; integer>0; default=50 (must be larger 
#                       than the filtersize)
# -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), polar (p), 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: QUADCORNERS2 
#  
# PURPOSE: To optionally threshold an image if not already binary and find the four 
# corners of the binary quadrilateral from the maximum curvature of the outline 
# obtained from the convex hull.
# 
# DESCRIPTION: QUADCORNERS2 optionally thresholds an image, if not already binary, 
# and finds the four corners of a quadrilateral in the binary image. The process is 
# 1) optionally color threshold the image; 2) optionally apply morphology to clean 
# and smooth the outline of the image; 3) optionally apply connected components 
# processing to remove small regions; 4) get a white filled convex hull on black 
# background image 5) convert the convex hull image to polar coordinates 
# and scale to 1 row; 6) extract the reverse polar coefficients; 7) convert the 
# 1D polar image heights to 1D x and y images using the reverse coefficents; 
# 8) apply 1D first and second order derivative to the 1D x and y images; 
# 9) use the x and y derivatives to compute a 1D curvature image; 10) find the 
# x locations in the 1D curvature image where the curvature is maximum skipping 
# enough between peaks; 11) look up the x and y coordinates associated with peaks 
# locations; 12) sort the top 4 peak x,y coordinates in TL, TR, BR, BL order 
# (i.e., clockwise from TL)
# 
# The input image should be a binary quadrilateral. If not, then use the color-threshold, 
# morphology and area to threshold into a clean binary quadrilateral.
# 
# 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: 
# 
# -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
# -b blursize ..... BLURSIZE is the (half) size of the 1D blur smoothing filter 
#                   (in pixels); integer>0; default=5
# -f filtersize ... FILTERSIZE is the (half) size of the 1D derivative filters 
#                   (in pixels); integer>0; default=5
# -s skip ......... SKIP (in pixels) is the minimum distance between peaks in 
#                   the polar image; integer>0; default=50 (must be larger than 
#                   the filtersize)
# -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), polar (p), 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 the use of 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;
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
blursize=5          # half size of blur (smoothing) filter
filtersize=5        # half size of derivative filters
skip=50             # skip between peaks in polar image
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
				   ;;
			-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) ---"
				   ;;
			-b)    # blursize
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID BLURSIZE SPECIFICATION ---"
				   checkMinus "$1"
				   blursize=`expr "$1" : '\([0-9]*\)'`
				   [ "$blursize" = "" ] && errMsg "--- BLURSIZE=$blursize MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   ;;
			-f)    # filtersize
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID FILTERSIZE SPECIFICATION ---"
				   checkMinus "$1"
				   filtersize=`expr "$1" : '\([0-9]*\)'`
				   [ "$filtersize" = "" ] && errMsg "--- FILTERSIZE=$filtersize MUST BE A NON-NEGATIVE INTEGER (with no sign) ---"
				   ;;
			-s)    # skip
				   shift  # to get the next parameter
				   # test if parameter starts with minus sign 
				   errorMsg="--- INVALID SKIP SPECIFICATION ---"
				   checkMinus "$1"
				   skip=`expr "$1" : '\([0-9]*\)'`
				   [ "$skip" = "" ] && errMsg "--- SKIP=$skip 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/QUADCORNERS2.$$"

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

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

# read and test input image
magick -quiet "$infile" +repage $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

# 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;"

# test that skip > filtersize
bs=$blursize
fs=$filtersize
[ $skip -le $fs ] && errMsg "--- SKIP MUST BE LARGER THAN FILTERSIZE ---"

# 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

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

# convert convex hull from cartesian to polar coordinates
magick $dir/hull.png -type bilevel -virtual-pixel Black -distort DePolar "-1 0 $centroid 0,360" \
-alpha off +write $dir/polar.png -scale x1! $dir/polar1D.pfm

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

# get depolar coefficients
data=`magick -quiet -verbose $dir/hull.png -virtual-pixel Black \
-distort DePolar "-1 0 $centroid 0,360" null: 2>&1 \
| grep -e "^  c" | sed 's/+//g' | sed -n 's/^.* = \(.*\)$/\1/p' | tr "\n" " "`
#echo "$data"
c0=`echo $data | cut -d\  -f1`
c1=`echo $data | cut -d\  -f2`
c2=`echo $data | cut -d\  -f3`
c3=`echo $data | cut -d\  -f4`
c4=`echo $data | cut -d\  -f5`
c5=`echo $data | cut -d\  -f6`
c6=`echo $data | cut -d\  -f7`
c7=`echo $data | cut -d\  -f8`
#echo "c0=$c0; c1=$c1; c2=$c2; c3=$c3; c4=$c4; c5=$c5; c6=$c6; c7=$c7;"
#echo ""

# use coefficients to project polar coordinates to cartesian coordindates
# Note: polar1D.pfm has coordinates after dividing by 255 that is 1 larger than get from txt: directly, so subtract 1
# Note: the result fits better if subtract another 1, but not sure why. So subtract 2 total from division by 255)
magick $dir/polar1D.pfm -fx \
"val=(quantumrange/255-2)*u*$ht/255; aa=(i+.5)*$c6+$c4; rr=(val+.5)*$c7+$c1; (rr*sin(aa)+$c2-0.5)*quantumscale" \
$dir/xcoords.pfm

xArr=(`magick $dir/xcoords.pfm txt: | tail -n +2 | awk '{print $2}' | sed 's/[^0-9.,]*//g' | cut -d, -f2`)
numx=${#xArr[*]}

magick $dir/polar1D.pfm -fx \
"val=(quantumrange/255-2)*u*$ht/255; aa=(i+.5)*$c6+$c4; rr=(val+.5)*$c7+$c1;  (rr*cos(aa)+$c3-0.5)*quantumscale" \
$dir/ycoords.pfm

yArr=(`magick $dir/ycoords.pfm txt: | tail -n +2 | awk '{print $2}' | sed 's/[^0-9.,]*//g' | cut -d, -f2`)
numy=${#yArr[*]}
#echo "$numx, $numy"


: <<COMMENT
# Alternate, has issue:
# raw values that are whole numbers in range 0 to 255 have corresponding gray() values that are not expressed in percent but as gray(x) color names

# use coefficients to project polar coordinates to cartesian coordindates
magick $dir/polar1D.pfm -fx \
"val=(quantumrange/255)*u*$ht/255; aa=(i+.5)*$c6+$c4; rr=(val+.5)*$c7+$c1; (rr*sin(aa)+$c2-0.5)/100" \
$dir/xcoords.pfm

xArr=(`magick $dir/xcoords.pfm txt: | awk '{print $4}' | sed 's/[^0-9.,]*//g' | cut -d, -f2`)
numx=${#xArr[*]}

magick $dir/polar1D.pfm -fx \
"val=(quantumrange/255)*u*$ht/255; aa=(i+.5)*$c6+$c4; rr=(val+.5)*$c7+$c1;  (rr*cos(aa)+$c3-0.5)/100" \
$dir/ycoords.pfm

yArr=(`magick $dir/ycoords.pfm txt: | awk '{print $4}' | sed 's/[^0-9.,]*//g' | cut -d, -f2`)
numy=${#yArr[*]}
echo "$numx, $numy"
COMMENT


ptArr=()
# combine arrays in x,y format
for ((i=0; i<$numx; i++)) do
xx=${xArr[$i]}
yy=${yArr[$i]}
ptArr[$i]="$xx,$yy"
#echo "$xx,$yy"
done
pts=${ptArr[*]}
#echo "$pts"

# draw pts on input
magick $dir/infile.miff \
-fill none -stroke red -draw "polygon $pts" \
-alpha off $dir/contour.png

# create blur filter
bs=5
numb=$((2*bs+1))
ibs=`echo "scale=6; 1/($numb)" | bc`
for ((i=0;i<numb;i++)); do
filtb[$i]=1
done
filtb="${numb}x1:${filtb[*]}"
#echo "$filtb"
# 1 1 1 1 1

# smooth points with blur filter
magick $dir/xcoords.pfm -virtual-pixel horizontaltile -define convolve:scale="$ibs" -morphology convolve "$filtb" $dir/xscoords.pfm
magick $dir/ycoords.pfm -virtual-pixel horizontaltile -define convolve:scale="$ibs" -morphology convolve "$filtb" $dir/yscoords.pfm

# extract x and y smoothed point arrays
xsArr=(`magick $dir/xscoords.pfm txt:  | tail -n +2 | awk '{print $2}' | sed 's/[^0-9.,]*//g' | cut -d, -f2`)
ysArr=(`magick $dir/yscoords.pfm txt:  | tail -n +2 | awk '{print $2}' | sed 's/[^0-9.,]*//g' | cut -d, -f2`)

# combine smoothed x and y values into spts 
sptArr=()
# combine arrays in x,y format
for ((i=0; i<$numx; i++)) do
xs=${xsArr[$i]}
ys=${ysArr[$i]}
sptArr[$i]="$xs,$ys"
#echo "$xx,$yy"
done
spts=${sptArr[*]}
#echo "$spts"
#echo ""

# draw smoothed pts on input
magick $dir/infile.miff \
-fill none -stroke red -draw "polygon $spts" \
-alpha off $dir/contour2.png

# get x and y first and second derivatives and then curvature (K)
# dd is distance between center and either end
# see https://en.wikipedia.org/wiki/Finite_difference (dd=h)
numd=$((2*fs+1))
idel=`echo "scale=6; 1/(2*$fs)" | bc`
idel2=`echo "scale=6; 1/($fs*$fs)" | bc`
#echo "$idel, $idel2"

# create deriv1 filter
for ((i=0;i<numd;i++)); do
filt1[$i]=0
done
filt1[0]=-1
filt1[$numd-1]=1
filt1="${numd}x1:${filt1[*]}"
#echo "$filt1"
# -1 0 0 0 0 0 0 0 0 0 1

# create deriv2 filter
for ((i=0;i<numd;i++)); do
filt2[$i]=0
done
filt2[0]=1
filt2[$numd-1]=1
filt2[$fs]=-2
filt2="${numd}x1:${filt2[*]}"
#echo "$filt2"
# 1 0 0 0 0 -2 0 0 0 0 1

# get first and second derivatives and curvature from smoothed points
magick $dir/xscoords.pfm -virtual-pixel horizontaltile -define convolve:scale="$idel" -morphology convolve "$filt1" $dir/xderiv.pfm
magick $dir/yscoords.pfm -virtual-pixel horizontaltile -define convolve:scale="$idel" -morphology convolve "$filt1" $dir/yderiv.pfm
magick $dir/xscoords.pfm -virtual-pixel horizontaltile -define convolve:scale="$idel2" -morphology convolve "$filt2" $dir/xderiv2.pfm
magick $dir/yscoords.pfm -virtual-pixel horizontaltile -define convolve:scale="$idel2" -morphology convolve "$filt2" $dir/yderiv2.pfm
magick $dir/xderiv.pfm $dir/yderiv.pfm $dir/xderiv2.pfm $dir/yderiv2.pfm \
-fx "(u[0]*u[3]-u[1]*u[2])/pow((u[0]*u[0]+u[1]*u[1]),1.5)" $dir/curvature.pfm

# find top 4 matches, get x,y
magick -size ${skip}x1 xc:black $dir/black.pfm
magick $dir/curvature.pfm $dir/curvature2.pfm
for ((i=0; i<4; i++)); do
data2=`magick identify -precision 6 -define identify:locate=maximum -define identify:limit=1 $dir/curvature2.pfm | \
tail -n +2 | sed 's/^ *//g'`
location=`echo "$data2" | cut -d\  -f4`
value=`echo "$data2" | cut -d\  -f3 | tr -cs "[0-9.]*" " "`
cx=`echo $location | cut -d, -f1`
#echo "i=$i; location=$location; value=$value;"
magick $dir/curvature2.pfm $dir/black.pfm -geometry "+%[fx:$cx-$skip/2]+0" -compose over -composite $dir/curvature2.pfm
xx=${xArr[$cx]}
yy=${yArr[$cx]}
xx=${xArr[$cx]}
yy=${yArr[$cx]}
scornerArr[$i]="$xx,$yy"
done
#echo "${cornerArr[*]}"

# find point on pre-smoothed contour that is closest to each of the 4 corners in the smoothed contour
for ((i=0; i<4; i++)); do
# get corner is smoothed contour
scorner="${scornerArr[$i]}"
scx=`echo $scorner | cut -d, -f1`
scy=`echo $scorner | cut -d, -f2`
# load pre-smoothed contour into AWK and search given smoothed corner for closest point
cornerArr[$i]=`echo "${ptArr[*]}" |\
awk -v scx=$scx -v scy=$scy '
BEGIN { FS = ","; RS = " "; OFS=","; ptx=0; pty=0; mindist=100; }
{ dist=sqrt(($1-scx)*($1-scx) + ($2-scy)*($2-scy)); if (dist<=mindist) {mindist=dist; ptx=$1; pty=$2} }
END { print ptx, pty } '`
done
#echo "${cornerArr[*]}"
#echo ""

# sort x,y array by increasing y then increasing x
# then swap the first two coordinates so that the order is TL, TR, BR, BL
sortArr=(`echo "${cornerArr[*]}" | tr " " "\n" | sort -n -t "," -k2,2 -k1,1 | tr "\n" " "`)
temp0="${sortArr[1]}"
temp1="${sortArr[0]}"
sortArr[0]="$temp0"
sortArr[1]="$temp1"
#echo "${sortArr[*]}"

# Assign so that order is TL, TR, BR, BL
xtl=`echo ${sortArr[0]} | cut -d, -f1`
ytl=`echo ${sortArr[0]} | cut -d, -f2`
xtr=`echo ${sortArr[1]} | cut -d, -f1`
ytr=`echo ${sortArr[1]} | cut -d, -f2`
xbr=`echo ${sortArr[2]} | cut -d, -f1`
ybr=`echo ${sortArr[2]} | cut -d, -f2`
xbl=`echo ${sortArr[3]} | cut -d, -f1`
ybl=`echo ${sortArr[3]} | cut -d, -f2`
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

