#!/bin/sh
#
# im_fx_curves [option]  x1,y1 x2,y2 ...
#
# Given a set of control points figure out a mathematical histogram curve
# that fits those points exactly.
#
# The points are given as pairs of numbers, which may be comma or space
# seperared, on the command line. All the numbers must be between 0 and 1, as
# floting point numbers or percentages (if -p option is given).
#
# Options:
#    -p | --percent     Control points are percentage values
#    -d | --magick display     Display the fitted graph for 5 seconds (DEBUGING)
#    -t | --terms n     Limit number of terms to n, (default is given points)
#    -c                 Return the coefficents of the fitted polynomial
#
# For example...
#    im_fx_curves  -p  0,20   100,90   20,80   70,50
# results in...
#    7.560*u^3-11.946*u^2+5.087*u+0.200
#
# Adding a "-c" option to output just the polynomials coefficients
# which can be used in a "-function polynomial" operation.
# That is it will return...
#      7.560,-11.946,5.087,0.200
#
# This implementation uses "gnuplot" with its output parsed by "awk" to
# magick it into appropriate arguments for the ImageMagick "-fx" function or
# "-function polynomal"
#
####
#
# NOTE: Input arguments are NOT tested for correctness.
# This script represents a security risk if used ONLINE.
# I accept no responsiblity for misuse. Use at own risk.
#
# Anthony Thyssen    November 2005
# Updated for -c     January  2009
#
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
Usage() {                              # output the script comments as docs
  echo >&2 "$PROGNAME:" "$@"
  sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 3s/^/Usage: /; 2,$ p' \
          "$PROGDIR/$PROGNAME"
  exit 10;
} 

percent=
terms=

while [ $# -gt 0 ]; do
  case "$1" in
  -h|--help|--doc)  Usage ;;       # Documentation
  -p|--percent) percent=true ;;    # control points are percentage values
  -d|--display) display=true ;;    # magick display the fitted gnuplot graph
  -t|--terms)   terms=$2; shift ;; # limit number of terms to this number
  -c)           coeff=true ;;      # return just the coefficents

  --) shift; break ;;    # end of user options
  -*) Usage "Unknown option \"$1\"" ;;
  *)  break ;;           # end of user options

  esac
  shift   # next option
done

# magick the rest of the arguments into
# space separated pairs of comma separated values.
set - `echo $* | sed 's/[, ][, ]*/ /g; s/ /,/;
                      s/ \([^ ]*\) \([^ ]*\)/ \1,\2/g'`

# Number of control points = number of parameters
points=$#
#echo >&2 "DEBUG: $points control points"
[ $points -lt 2 ] && Usage "At least two or more control points are needed"

# generate the gnuplot function and parameter lists
p=${terms:-$points}  # number of terms or parameters
while [ $p -gt 0 ]; do
  p=`expr $p - 1`
  if [ "$f" ]; then
    f="$f + "
    v="$v, "
  fi
  v="${v}p${p}"
  case $p in
    0) f="${f}p${p}"
       ;;
    1) f="${f}p${p}*x" ;;
    *) f="${f}p${p}*x**${p}" ;;
  esac
done
function="$f"
params="$v"
#echo >&2 "DEBUG: function = $function"
#echo >&2 "DEBUG: params = $params"

# -----------------------------------------------

# Prevent gnuplot logging the curve fit
FIT_LOG=/dev/null
export FIT_LOG

# Generate gnuplot commands an parse the output into a IM -fx function
{ echo "f(x) = $function";
  echo "fit f(x) '-' via $params"

  # Insert output control points directly into gnuplot input
  m=1;  [ "$percent" ] && m=100
  for i in "$@"; do
    echo "$i" | awk -F, '{ print $1/'"$m"', $2/'"$m"' }'
  done
  echo "e"

  if [ "$display" ]; then
    echo "plot [0:1] [0:1] f(x)"
    echo "pause 5"
  fi
} | gnuplot 2>&1 | sed '1,/^===/d' |
if [ "$coeff" ]; then
  awk '# Parse the gnuplot parameter list into a list of values
    /^p/  { p = substr($1, 2)+0     # parameter number (as a number)
            printf("%.3f", $3)     # Output the coefficents value
            if ( p != 0 ) printf ","
          }'
else
  awk '# Parse the gnuplot parameter list into a IM -fx function
    #{ printf "\n%s\n", $0 }  # debugging
    /^p/  { p = substr($1, 2)+0       # parameter number (as a number)
            v = sprintf("%.3f", $3)   # format parameter value
            #v = sprintf("%.3g", $3)   # has problems with "e"

            # junk terms with a zero (or very small) parameter
            if ( v == "0" || match(v, "e-") ) next

            # sign of the parameter, and intra-term spacing
            if ( substr(v,1,1) != "-" )
              printf seen ? "+" : ""

            # print parameter if not 1 or constant
            if ( v != "1" &&  v != "-1" || p == 0 ) {
              printf v
              if ( p > 0 )  printf "*"
            }

            # the term of the parameter
            if ( p == 1 ) printf "u"
            if ( p >= 2 ) printf "u^%d", p

            seen = 1  # we have seen the first parameter
          }'
fi
echo

exit 0;

# ASIDE:
#
# Gabe Schaffer created a perl version using the Math::Polynomial library
# (not a standard perl install) in   "im_fx_curve.pl"
#

