
/****************************************************************************
 *
 * MODULE:       i.pca
 *
 * AUTHOR(S):    Original author Center for Space Research (Uni. of TX)
 *               Rewritten by Brad Douglas <rez touchofmadness com>
 *               NULL value/MASK handling and speed up by Markus Metz
 *
 * PURPOSE:      Principal Component Analysis transform of raster data.
 *
 * COPYRIGHT:    (C) 2004-2011 by the GRASS Development Team
 *
 *               This program is free software under the GNU General Public
 *               License (>=v2). Read the file COPYING that comes with GRASS
 *               for details.
 *
 ***************************************************************************/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/imagery.h>
#include <grass/gmath.h>
#include <grass/glocale.h>
#include "local_proto.h"


#undef PCA_DEBUG


/* function prototypes */
static CELL round_c(double);
static int set_output_scale(struct Option *, int *, int *, int *);
static int calc_mu_cov(int *, double **, double *, double *, int);
static int write_pca(double **, double *, double *, int *, char *, int,
                     int, int, int, int);

#ifdef PCA_DEBUG
static int dump_eigen(int, double **, double *);
#endif


int main(int argc, char *argv[])
{
    int i;			/* Loop control variables */
    int bands;			/* Number of image bands */
    int pcbands;		/* Number of pc scores to use for filtering */
    int pcperc;			/* cumulative percent to use for filtering */
    double *mu;			/* Mean vector for image bands */
    double *stddev;		/* Stddev vector for image bands */
    double **covar;		/* Covariance Matrix */
    double *eigval;
    double **eigmat;
    int *inp_fd;
    int scale, scale_max, scale_min;
    struct Ref ref;
    const char *mapset;

    struct GModule *module;
    struct Option *opt_in, *opt_out, *opt_scale, *opt_filt;
    struct Flag *flag_norm, *flag_filt;

    /* initialize GIS engine */
    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("imagery"));
    G_add_keyword(_("transformation"));
    G_add_keyword(_("PCA"));
    G_add_keyword(_("principal components analysis"));
    module->description = _("Principal components analysis (PCA) "
			    "for image processing.");
    module->overwrite = 1;

    /* Define options */
    opt_in = G_define_standard_option(G_OPT_R_INPUTS);
    opt_in->description = _("Name of two or more input raster maps or imagery group");

    opt_out = G_define_standard_option(G_OPT_R_BASENAME_OUTPUT);
    opt_out->label = _("Name for output basename raster map(s)");
    opt_out->description =
	_("A numerical suffix will be added for each component map");

    opt_scale = G_define_option();
    opt_scale->key = "rescale";
    opt_scale->type = TYPE_INTEGER;
    opt_scale->key_desc = "min,max";
    opt_scale->required = NO;
    opt_scale->answer = "0,255";
    opt_scale->label =
	_("Rescaling range for output maps");
    opt_scale->description =
	_("For no rescaling use 0,0");
    opt_scale->guisection = _("Rescale");
    
    opt_filt = G_define_option();
    opt_filt->key = "percent";
    opt_filt->type = TYPE_INTEGER;
    opt_filt->required = NO;
    opt_filt->options = "50-99";
    opt_filt->answer = "99";
    opt_filt->label =
	_("Cumulative percent importance for filtering");
    opt_filt->guisection = _("Filter");

    flag_norm = G_define_flag();
    flag_norm->key = 'n';
    flag_norm->label = (_("Normalize (center and scale) input maps"));
    flag_norm->description = (_("Default: center only"));
    
    flag_filt = G_define_flag();
    flag_filt->key = 'f';
    flag_filt->label = (_("Output will be filtered input bands"));
    flag_filt->description = (_("Apply inverse PCA after PCA"));
    flag_filt->guisection = _("Filter");

    if (G_parser(argc, argv))
	exit(EXIT_FAILURE);


    /* determine number of bands passed in */
    for (bands = 0; opt_in->answers[bands] != NULL; bands++) ;

    /* input can be either several raster maps or a group */
    if (bands > 1) {
	char name[GNAME_MAX];
	
	I_init_group_ref(&ref);
	for (i = 0; opt_in->answers[i] != NULL; i++) {
	    /* strip @mapset, do not modify opt_in->answers */
	    strcpy(name, opt_in->answers[i]);
	    mapset = G_find_raster(name, "");
	    if (!mapset)
		G_fatal_error(_("Raster map <%s> not found"),
			      opt_in->answers[i]);
	    /* Add input to group. */
	    I_add_file_to_group_ref(name, mapset, &ref);
	}
    }
    else {
	/* Maybe input is group. Try to read group file */
	if (I_get_group_ref(opt_in->answer, &ref) != 1)
	    G_fatal_error(_("Group <%s> not found"),
			  opt_in->answer);
    }
    bands = ref.nfiles;

    if (bands < 2)
	G_fatal_error(_("Sorry, at least 2 input bands must be provided"));

    /* default values */
    scale = 1;
    scale_min = 0;
    scale_max = 255;

    /* get scale parameters */
    set_output_scale(opt_scale, &scale, &scale_min, &scale_max);

    /* filter threshold */
    pcperc = -1;
    if (flag_filt->answer) {
	pcperc = atoi(opt_filt->answer);
	if (pcperc < 0)
	    G_fatal_error(_("'%s' must be positive"), opt_filt->key);
	if (pcperc > 99)
	    G_fatal_error(_("'%s' must be < 100"), opt_filt->key);
    }

    /* allocate memory */
    covar = G_alloc_matrix(bands, bands);
    mu = G_alloc_vector(bands);
    inp_fd = G_alloc_ivector(bands);
    eigmat = G_alloc_matrix(bands, bands);
    eigval = G_alloc_vector(bands);
    
    if (flag_norm->answer)
	stddev = G_alloc_vector(bands);
    else
	stddev = NULL;

    /* open and check input/output files */
    for (i = 0; i < bands; i++) {
	char tmpbuf[GNAME_MAX];

	sprintf(tmpbuf, "%s.%d", opt_out->answer, i + 1);
	G_check_input_output_name(ref.file[i].name, tmpbuf, G_FATAL_EXIT);

	inp_fd[i] = Rast_open_old(ref.file[i].name, ref.file[i].mapset);
    }

    if (!calc_mu_cov(inp_fd, covar, mu, stddev, bands))
	G_fatal_error(_("No non-null values"));

    G_math_d_copy(covar[0], eigmat[0], bands * bands);
    G_debug(1, "Calculating eigenvalues and eigenvectors...");
    G_math_eigen(eigmat, eigval, bands);

#ifdef PCA_DEBUG
    /* dump eigen matrix and eigen values */
    dump_eigen(bands, eigmat, eigval);
#endif

    G_debug(1, "Ordering eigenvalues in descending order...");
    G_math_egvorder(eigval, eigmat, bands);

    G_debug(1, "Transposing eigen matrix...");
    G_math_d_A_T(eigmat, bands);

    pcbands = 0;
    if (flag_filt->answer) {
	double eigval_total = 0.0;
	double eigval_perc = 0.0;

	for (i = 0; i < bands; i++) {
	    eigval_total += eigval[i];
	}
	for (i = 0; i < bands; i++) {
	    eigval_perc += eigval[i] * 100. / eigval_total;
	    pcbands++;
	    if (eigval_perc > pcperc)
		break;
	}

	/* filtering has an effect only if at least one PC is removed  */
	if (pcbands == bands)
	    pcbands--;
	if (pcbands < 2)
	    G_fatal_error(_("Not enough principal components left for filtering"));

	G_message(_("Using %d of %d principal components for filtering"), pcbands, bands);
	scale = 0;
    }

    /* write output images */
    write_pca(eigmat, mu, stddev, inp_fd, opt_out->answer, bands,
              scale, scale_min, scale_max, pcbands);

    /* write colors and history to output */
    for (i = 0; i < bands; i++) {
	char outname[GNAME_MAX];

	/* close input files */
	Rast_unopen(inp_fd[i]);

	sprintf(outname, "%s.%d", opt_out->answer, i + 1);

	/* write colors and history to file */
	if (flag_filt->answer)
	    write_support(bands, ref.file[i].name, outname, eigmat, eigval);
	else
	    write_support(bands, NULL, outname, eigmat, eigval);
    }
    
    /* free memory */
    G_free_matrix(covar);
    G_free_vector(mu);
    G_free_ivector(inp_fd);
    G_free_matrix(eigmat);
    G_free_vector(eigval);

    exit(EXIT_SUCCESS);
}


static CELL round_c(double x)
{
    CELL n;

    if (x >= 0.0)
	n = x + .5;
    else {
	n = -x + .5;
	n = -n;
    }

    return n;
}


static int
set_output_scale(struct Option *scale_opt, int *scale, int *scale_min,
		 int *scale_max)
{
    if (scale_opt->answer) {
	sscanf(scale_opt->answers[0], "%d", (int *)scale_min);
	sscanf(scale_opt->answers[1], "%d", (int *)scale_max);

	if (*scale_min == *scale_max) {
	    if (*scale_min == 0)
		*scale = 0;
	    else {
		G_warning(_("Scale range length should be > 0. "
			    "Using default values: 0,255."));
		*scale_min = 0;
		*scale_max = 255;
	    }
	}

	if (*scale_max < *scale_min) {
	    int tmp_scale = *scale_max;

	    *scale_max = *scale_min;
	    *scale_min = tmp_scale;
	}
    }

    return 0;
}


static int calc_mu_cov(int *fds, double **covar, double *mu, 
                           double *stddev, int bands)
{
    int i, j;
    int row, col;
    int rows = Rast_window_rows();
    int cols = Rast_window_cols();
    off_t count = 0;
    DCELL **rowbuf = (DCELL **) G_malloc(bands * sizeof(DCELL *));
    double **sum2 = (double **)G_calloc(bands, sizeof(double *));
    double *sumsq, *sd, *sum;

    if (stddev) {
	sumsq = (double *)G_calloc(bands, sizeof(double));
	sd = (double *)G_calloc(bands, sizeof(double));
    }
    else {
	sumsq = NULL;
	sd = NULL;
    }

    for (i = 0; i < bands; i++) {
	rowbuf[i] = Rast_allocate_d_buf();
	sum2[i] = (double *)G_calloc(bands, sizeof(double));
    }
    sum = mu;

    G_message(_("Computing covariance matrix..."));

    for (row = 0; row < rows; row++) {
	G_percent(row, rows, 2);
	for (i = 0; i < bands; i++)
	    Rast_get_d_row(fds[i], rowbuf[i], row);

	for (col = 0; col < cols; col++) {
	    /* ignore cells where any of the maps has null value */
	    for (i = 0; i < bands; i++)
		if (Rast_is_d_null_value(&rowbuf[i][col]))
		    break;
	    if (i != bands)
		continue;
	    count++;
	    for (i = 0; i < bands; i++) {

		sum[i] += rowbuf[i][col];
		if (stddev)
		    sumsq[i] += rowbuf[i][col] * rowbuf[i][col];

		for (j = 0; j <= i; j++)
		    sum2[i][j] += rowbuf[i][col] * rowbuf[j][col];
	    }
	}
    }
    G_percent(1, 1, 1);
    
    if (count < 2)
	return 0;

    for (i = 0; i < bands; i++) {
	if (stddev) {
	    sd[i] = sqrt(count * sumsq[i] - sum[i] * sum[i]);
	    stddev[i] = sqrt((sumsq[i] - sum[i] * sum[i] / count) /
	                     (count - 1));
	}
	for (j = 0; j <= i; j++) {
	    if (stddev)
		covar[i][j] = (count * sum2[i][j] - sum[i] * sum[j]) /
		              (sd[i] * sd[j]);
	    else
		covar[i][j] = (sum2[i][j] - sum[i] * sum[j] / count) /
		              (count - 1);
	    G_debug(3, "covar[%d][%d] = %f", i, j, covar[i][j]);
	    if (j != i)
		covar[j][i] = covar[i][j];
	}

	G_free(sum2[i]);
	G_free(rowbuf[i]);
    }
    for (i = 0; i < bands; i++)
	mu[i] = sum[i] / count;

    G_free(rowbuf);
    
    G_free(sum2);
    if (sd)
	G_free(sd);
    if (sumsq)
	G_free(sumsq);

    return 1;
}


static int
write_pca(double **eigmat, double *mu, double *stddev,
          int *inp_fd, char *out_basename, int bands, 
	  int scale, int scale_min, int scale_max, int fbands)
{
    int i, j;
    void **outbuf = (void **) G_malloc(bands * sizeof(void *));
    void **outptr = (void **) G_malloc(bands * sizeof(void *));
    double *min = (double *) G_malloc(bands * sizeof(double));
    double *max = (double *) G_malloc(bands * sizeof(double));
    double *old_range = (double *) G_calloc(bands, sizeof(double));
    double new_range = 0.;
    int pass;
    int rows = Rast_window_rows();
    int cols = Rast_window_cols();
    /* why CELL_TYPE when scaling output ? */
    int outmap_type = (scale) ? CELL_TYPE : DCELL_TYPE;
    int outcell_mapsiz = Rast_cell_size(outmap_type);
    int *out_fd = (int *) G_malloc(bands * sizeof(int));
    DCELL **inbuf = (DCELL **) G_malloc(bands * sizeof(DCELL *));
    DCELL *pcs = NULL;

    /* 2 passes for rescale.  1 pass for no rescale */
    int PASSES = (scale) ? 2 : 1;

    if (fbands)
	pcs = (DCELL *) G_malloc(fbands * sizeof(DCELL));

    /* allocate memory for row buffers */
    for (i = 0; i < bands; i++) {
	char name[GNAME_MAX];

	/* open output raster maps */
	sprintf(name, "%s.%d", out_basename, i + 1);
	out_fd[i] = Rast_open_new(name, outmap_type);

	inbuf[i] = Rast_allocate_d_buf();
	outbuf[i] = Rast_allocate_buf(outmap_type);
	min[i] = max[i] = old_range[i] = 0;
    }

    for (pass = 1; pass <= PASSES; pass++) {
	int row, col;
	int first = 1;

	if (scale && (pass == PASSES)) {
	    G_message(_("Rescaling to range %d,%d..."),
		      scale_min, scale_max);

	    for (i = 0; i < bands; i++)
		old_range[i] = max[i] - min[i];
	    new_range = (double)(scale_max - scale_min);
	}
	else {
	    G_message(_("Calculating principal components..."));
	}

	for (row = 0; row < rows; row++) {

	    G_percent(row, rows, 2);

	    for (i = 0; i < bands; i++) {
		Rast_get_d_row(inp_fd[i], inbuf[i], row);
		outptr[i] = outbuf[i];
	    }
	    for (col = 0; col < cols; col++) {
		/* ignore cells where any of the maps has null value */
		for (i = 0; i < bands; i++)
		    if (Rast_is_d_null_value(&inbuf[i][col]))
			break;
			
		if (i != bands) {
		    for (i = 0; i < bands; i++) {
			Rast_set_null_value(outptr[i], 1, outmap_type);
			outptr[i] =
			    G_incr_void_ptr(outptr[i], outcell_mapsiz);
		    }
		    continue;
		}
		
		if (fbands) {
		    /* calculate all PC scores */
		    for (i = 0; i < fbands; i++) {
			DCELL dval = 0.;

			for (j = 0; j < bands; j++) {
			    /* corresp. cell of j-th band */
			    if (stddev)
				dval += eigmat[i][j] * 
					((inbuf[j][col] - mu[j]) / stddev[j]);
			    else
				dval += eigmat[i][j] * (inbuf[j][col] - mu[j]);
			}
			pcs[i] = dval;
		    }
		}

		for (i = 0; i < bands; i++) {
		    DCELL dval = 0.;

		    if (fbands) {
			for (j = 0; j < fbands; j++) {
			    /* corresp. PC score of j-th band */
			    dval += eigmat[j][i] * pcs[j];
			}
			if (stddev)
			    dval = dval * stddev[i] + mu[i];
			else
			    dval += mu[i];
		    }
		    else {
			for (j = 0; j < bands; j++) {
			    /* corresp. cell of j-th band */
			    if (stddev)
				dval += eigmat[i][j] * 
					((inbuf[j][col] - mu[j]) / stddev[j]);
			    else
				dval += eigmat[i][j] * (inbuf[j][col] - mu[j]);
			}
		    }

		    /* the cell entry is complete */
		    if (scale && (pass == 1)) {
			if (first)
			    min[i] = max[i] = dval;
			if (dval < min[i])
			    min[i] = dval;

			if (dval > max[i])
			    max[i] = dval;
		    }
		    else if (scale) {

			if (min[i] == max[i]) {
			    Rast_set_c_value(outptr[i], 1,
						 CELL_TYPE);
			}
			else {
			    /* map data to 0, (new_range-1) and then adding new_min */
			    CELL tmpcell =
				round_c((new_range * (dval - min[i]) /
				         old_range[i]) + scale_min);

			    Rast_set_c_value(outptr[i], tmpcell,
						 outmap_type);
			}
		    }
		    else {	/* (!scale) */

			Rast_set_d_value(outptr[i], dval,
					     outmap_type);
		    }
		    outptr[i] = G_incr_void_ptr(outptr[i], outcell_mapsiz);
		}
		first = 0;
	    }
	    if (pass == PASSES) {
		for (i = 0; i < bands; i++)
		    Rast_put_row(out_fd[i], outbuf[i], outmap_type);
	    }
	}
	G_percent(1, 1, 1);

	/* close output file */
	if (pass == PASSES) {
	    for (i = 0; i < bands; i++) {
		Rast_close(out_fd[i]);
		G_free(inbuf[i]);
		G_free(outbuf[i]);
	    }
	}
    }

    G_free(inbuf);
    G_free(outbuf);
    G_free(outptr);
    G_free(min);
    G_free(max);
    G_free(old_range);

    return 0;
}


#ifdef PCA_DEBUG
static int dump_eigen(int bands, double **eigmat, double *eigval)
{
    int i, j;

    for (i = 0; i < bands; i++) {
	for (j = 0; j < bands; j++)
	    fprintf(stderr, "%f  ", eigmat[i][j]);

	fprintf(stderr, "\n");
    }

    for (i = 0; i < bands; i++)
	fprintf(stderr, "%f  ", eigval[i]);

    fprintf(stderr, "\n");

    return 0;
}
#endif
