;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: INTERACTORS; Base: 10 -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;         The Garnet User Interface Development Environment.      ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This code was written as part of the Garnet project at          ;;;
;;; Carnegie Mellon University, and has been placed in the public   ;;;
;;; domain.  If you are using this code or any part of Garnet,      ;;;
;;; please contact garnet@cs.cmu.edu to be put on the mailing list. ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;; classify.lisp
;;;
;;; Routines for training gesture classifiers and classifying gestures.   
;;; (based on Dean Rubine's C implementation)
;;;
;;; Designed and implemented by James A. Landay

#|
============================================================
Change log:
    07/26/93 James Landay - define gest-class here
    03/24/92 James Landay - added a test before divide by zero.
    02/26/92 James Landay - made gest-classify () return NIL values
                            if the passed classifier is NIL.
    01/31/92 James Landay - changed classify () to gest-classify (), and
                            new-classifier () to gest-new-classifier ()
    01/22/92 James Landay - made classify () also return the attributes
                            vector.
    12/25/91 James Landay - made classify () take an array of points
                            rather than a list.
    12/21/91 James Landay - started
============================================================
|#

(in-package "INTERACTORS" :use '("LISP") :nicknames '("INTER"))


;; structure definitions
;; structure to store a gesture class.
(defstruct g-class
    name                ;; classifier name
    average             ;; average of example feature vectors
    sumcov              ;; covariance matrix
    num-examples        ;; number of examples
)

;; structure to store a single gesture class's examples
;; used by Agate (define here so that will be defined in interactors package)
(defstruct gest-class
  (name NIL)                    ;; name of gesture class
  (examples NIL)                ;; list of examples of this gesture class
)                               ;;    (each example is an array)

;; structure to store a classifier.
(defstruct classifier
    classes             ;; the actual classes for this classifier
    num-classes         ;; number of classes
    cnst                ;; constant term of discrimination function
    weights             ;; coefficient weights
    invavgcov           ;; inverse covariance matrix
)


;; global variables definitions
(defvar last-class nil)     ;; last class looked up in class-name-lookup
(defvar last-classifier nil);; last classifier "    "     " 


;; gest-classify returns the class from the given classifier that most 
;; closely matches the features of the given gesture. If the gesture 
;; is not recognized, it returns NIL. In addition it returns the attributes 
;; vector of the gesture. It also returns the non-ambiguity of the
;; class chosen and the distance to the mean of the chosen class
;; as additional (multiple) values if those parameters are non-NIL.
;; If the class does not meet the cutoff values given by the 
;; non_ambig_prob and distance it will return the gesture as
;; unrecognized, i.e. NIL.
;;  
;; Parameters:
;;    g-points           - array of points [x1 y1 x2 y2 ...] making up 
;;                         gesture. 
;;    classifier         - classifier to match with 
;;    min-non-ambig-prob - minimum value for non-ambiguity prob. of class 
;;    max-dist           - maximum for mean distance from chosen class
;;
(defun gest-classify (g-points classifier 
                      &optional min-non-ambig-prob max-dist)
    (if (null classifier)
        (return-from gest-classify (values nil nil nil nil))
    )
    (if (null (classifier-weights classifier))
        (error "gest-classify () - classifier has not been trained")
    )

    (let ((max-class (car (classifier-classes classifier)))
          (max-disc -1e20)
          (class-found nil)
          (denom 0)
          (attributes nil)
          (fv nil)
          (nap nil)
          (distance nil)
          (disc (make-array `(,(classifier-num-classes classifier)))))

        ;; calc feature and attributes vectors for pts.
        (multiple-value-setq (fv attributes) (features g-points))

        ;; make sure the feature vector isn't empty
        (if (null fv)
            (return-from gest-classify (values nil nil nil nil))
        )
            
        ;; find the class with the maximum inner prodcut (discriminant)
        (do ((c 0 (1+ c))
             (rest-classes (cdr (classifier-classes classifier)) 
                           (cdr rest-classes))
             (cur-class (car (classifier-classes classifier)) 
                        (car rest-classes)))
            ((>= c (classifier-num-classes classifier))) ;; exit cond.
            (setf (aref disc c) 
                  (+ (aref (classifier-cnst classifier) c)
                     (inner-product 
                         fv 
                         (aref (classifier-weights classifier) c))))
            (when (> (aref disc c) max-disc)
                (setf max-disc (aref disc c))
                (setf max-class cur-class)
            )
        )

        (setf class-found (g-class-name max-class))

        ;; calculate the probability of non-ambiguity
        (when (not (null min-non-ambig-prob))
            (do ((c 0 (1+ c))
                 (d 0))
                ((>= c (classifier-num-classes classifier)))
                ;; quick check to avoid computing negligible term
                (if (> (setf d (- (aref disc c) max-disc)) -7)
                    (incf denom (exp d))
                )
            )
            (if (equalp denom 0)
                (setf nap 'INFINITY)
                (setf nap (/ 1 denom))
            )
            (when (< nap min-non-ambig-prob)
                (setf class-found nil))
        )

        ;; calculate the probability of non-ambiguity
        ;; calculate distance to mean of chosen class
        (when (not (null max-dist))
            (setf distance (mdistance fv 
                                      (g-class-average max-class) 
                                      (classifier-invavgcov classifier))) 
            (when (> distance max-dist)
                (setf class-found nil))
        )

        ;; return the best match along with an idea of how good it is
        (values class-found attributes nap distance)
    )
)


;; mdistance returns the mahalanobis distance: (v - u)' sigma (v - u)
;; given v, u, and sigma.
;;
;; Parameters:
;;    v     - feature vector
;;    u     - average vector of max-class
;;    sigma - inv. avg. covariance matrix
;;
(defun mdistance (v u sigma)
    (let* ((rows (array-dimension v 0))
           (space (make-array `(,rows))))
        (do ((i 0 (1+ i)))
            ((>= i rows))
            (setf (aref space i) (- (aref v i) (aref u i))) 
        )
        
        (quadratic-form space sigma)
    )
)


;; gest-new-classifier returns a newly allocated and initialized classifier.
;;
;; Parameters:
;;    none
;;
(defun gest-new-classifier ()
    (let ((new-classifier (make-classifier)))
        (setf (classifier-num-classes new-classifier) 0)
        new-classifier             ;; return the new classifier
    )
)
