#! /bin/bash
# Last edited on 2024-08-29 07:10:24 by stolfi

# Chenges the perspective of an image given
# coordinates of four reference points (key features) on the input image
# and their desired coordinate on the output image.
#
# Parameters:
#
#   {IMG_IN}      Filename of input image to be transformed (".png" extension).
#   {IPTS_REF_IN} File with coords of key features on that image.
#   {OPTS_REF_IN} File with coords of those features on output image.
#   {OUNIT}       Output pixels per user output coordinate unit.
#   {IMG_OUT}     Filename of output (rectified) image (".png" extension). 
#   {OPTS_UMP_IN} File with output coords to be mapped back to input coords.
#   {PTS_UMP_OUT} Filename for input and output coords of those mapped points.
#
# Assumes that each of the files {IPTS_REF_IN}, {OPTS_REF_IN}, and
# {OPTS_UMP_IN} contains the user input coordinates (IPTS) or user
# output coordinates (OPTS) of selected features on the input or output
# image. Each data line (excluding '#'-comments and blank lines) should
# have a feature tag followed by the user X and Y coordinates
# of the feature on the respective image.  The feature tag should be
# a string of letters, digits, and underscores, starting with a letter.
# 
# The projective map from input coordinates to output coordinates is
# determined by the points from {IPTS_REF_IN} and {OPTS_REF_IN} with
# matching tags.  The script ignores features that are listed in only 
# one file. There must be at least 4 such matching features.
# If there are more than 4, only the first 4 in sorted order of tag will
# be used.
#
# The script will also compute the input user and pixel coordinates of
# the point of {IMG_IN} that corresponds to the output user coordinates
# of each point listed in {OPTS_UMP_IN}. This information will be written to
# file {PTS_UMP_OUT} (or to standard error if {PTS_UMP_OUT} is "-").
# Each line of this file has fields
#
#   {TAG} {XI} {YI} {XIP} {YIP} {XOP} {YOP} {XO} {YO}
#
# where {(XI,YI)} and {(XO,YO)} are user coordinates, respectively input and output;
# and {(XIP,YIP)} and {(XOP,YOP)} are pixel coordinates, respectively input and output.


echo "${0##*/} args = $*" 1>&2

IMG_IN="$1"; shift
IPTS_REF_IN="$1"; shift
OPTS_REF_IN="$1"; shift
OUNIT="$1"; shift
IMG_OUT="$1"; shift
OPTS_UMP_IN="$1"; shift
PTS_UMP_OUT="$1"; shift

tmp="/tmp/$$"

echo "obtaining the coords of shared features ..." 1>&2
itpfile="${tmp}-in.ipts"
cat ${IPTS_REF_IN} | egrep -e '^[A-Za-z][A-Za-z0-9_]*' | sort > ${itpfile}
rtpfile="${tmp}-ref.pts"
cat ${OPTS_REF_IN} | egrep -e '^[A-Za-z][A-Za-z0-9_]*' | sort > ${rtpfile}
jpfile="${tmp}-join.jpts"
join -j 1 -o 0,1.2,1.3,2.2,2.3 ${itpfile} ${rtpfile} | head -n 4 > ${jpfile}
cat ${jpfile} 1>&2

# Get the ref points that are to be mapped back to the full image:
unmapOpts=( \
  ` egrep -e '^[A-Z]' ${OPTS_UMP_IN} \
      | gawk '//{ print $1, $2, $3; }' \
      | sed -e 's:^:-unmap :g' \
  ` \
)
rm -f  ${PTS_UMP_OUT}

timgfile="${tmp}-out.ppm"
rm -f ${timgfile}
echo "computing the projective map and mapping points from ${OPTS_UMP_IN} ..." 1>&2
convert ${IMG_IN} PPM:- \
  | pnmprojmap \
      -xAxis right \
      -yAxis down \
      -iOrg 0 0 \
      -inPrefix . \
      -points \
        `cat ${jpfile} | gawk '// { print $2, $3; }'` \
        `cat ${jpfile} | gawk '// { print $4, $5; }'` \
        -oSize 3184 2960 \
        -oOrg 0 0 \
        -oUnit ${OUNIT} \
        -mapFile ${PTS_UMP_OUT} \
        ${unmapOpts[@]} \
        -undef 0.5 \
        -maxval 65535 \
        -isMask F \
        -verbose \
  > ${timgfile}

cat ${PTS_UMP_OUT} 1>&2

if [[ ! ( -s ${timgfile} ) ]]; then
  echo "** {pnmprojmap} failed to write the output image" 1>&2; exit 1 
else
  echo "recreating mapped image ${IMG_OUT} ..." 1>&2
  rm -f ${IMG_OUT}
  cat ${timgfile} \
    | ppmtopgm \
    | pnmtopng \
    > ${IMG_OUT}
fi

rm ${itpfile} ${rtpfile} ${jpfile} ${timgfile}
