// Copyright 2003, 2004 David Hilvert <dhilvert@auricle.dyndns.org>,
//                                    <dhilvert@ugcs.caltech.edu>

/*  This file is part of the Anti-Lamenessing Engine.

    The Anti-Lamenessing Engine is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    The Anti-Lamenessing Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with the Anti-Lamenessing Engine; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*
 * ipc.h: A render subclass implementing an iterative image reconstruction
 * algorithm based on the work of Michal Irani and Shmuel Peleg.  The
 * projection and backprojection functions used are user-configurable.
 * For more information on the theory behind the algorithm, see the last
 * section and appendix of:
 *
 * 	http://www.wisdom.weizmann.ac.il/~irani/abstracts/superResolution.html
 *
 * The algorithm in the source paper looks something like this (PSF' is the
 * backprojection kernel, and corresponds to what the authors of the paper call
 * AUX):
 *
 * ===============================================================
 *    Forward         Backward           Points of comparison
 * ---------------------------------------------------------------
 *
 *    scene(n)        scene(n+1)
 *
 *      |                 ^
 *      |                 |
 *     PSF               PSF'
 *      |                 |
 *      |        ---------+              <--- difference
 *      V       /         |
 *
 *   simulated(n)       real
 *
 * ===============================================================
 *
 * This assumes a single colorspace representation.  However, consumer cameras
 * sometimes perform sharpening in non-linear colorspace, whereas lens and
 * sensor blurring occurs in linear colorspace.  Hence, there can be two
 * colorspaces involved; ALE accounts for this with linear and non-linear
 * colorspace PSFs.  Hence, the algorithm we use looks something like:
 *
 * ===============================================================
 *    Forward         Backward           Points of comparison
 * ---------------------------------------------------------------
 *
 *    scene(n)        scene(n+1)
 *
 *      |                 ^
 *      |                 |
 *    LPSF              LPSF'
 *      |                 |
 *      |       ----------+               <--- difference,
 *      V      /          |                    exposure
 *                                             re-estimation
 *  lsimulated(n)       lreal         
 *                                   
 *      |                 ^         
 *      |                 |
 *   unlinearize       linearize
 *      |                 |
 *      V                 |
 *
 *  lsim_nl(n)        lreal_nl(n)
 *   
 *      |                 ^
 *      |                 |
 *    NLPSF             NLPSF'
 *      |                 |
 *      |       ----------+               <--- difference
 *      V      /          |
 *
 *  nlsimulated(n)     real_nl
 *
 *                        ^
 *                        |
 *                    unlinearize
 *                        |
 *                        |
 *
 *                      real
 *
 * ===============================================================
 */

#ifndef __ipc_h__
#define __ipc_h__

#include "../image.h"
#include "../render.h"
#include "psf/rasterizer.h"
#include "psf/normalizer.h"
#include "psf/backprojector.h"

class ipc : public render {
protected:
        int synced;
	int inc;
        image *approximation;
	image *definition;
        render *input;
        unsigned int iterations;
	psf *lresponse, *nlresponse;
	int exposure_register;
	int use_weighted_median;
	double weight_limit;

	/*
	 * Calculate the simulated input frame SIMULATED from the image
	 * approximation APPROXIMATION, iterating over image approximation
	 * pixels.  
	 */

	struct sim_args {
		int frame_num;
		image *approximation;
		image *lsimulated;
		image *nlsimulated;
		image *lsim_weights;
		image *nlsim_weights;
		transformation t;
		const raster *lresponse;
		const raster *nlresponse;
		const exposure *exp;
		point *extents;

		sim_args() : t(transformation::eu_identity()) {
		}
	};

	class simulate_linear : public thread::decompose_domain, 
	                        private sim_args {
		point *subdomain_extents;

	protected:
		void prepare_subdomains(unsigned int N) {
			subdomain_extents = new point[N * 2];

			for (unsigned int n = 0; n < N; n++) {
				point *se = subdomain_extents + 2 * n;

				for (int d = 0; d < 2; d++) {
					se[0][d] = extents[0][d];
					se[1][d] = extents[1][d];
				}
			}
		}

		void subdomain_algorithm(unsigned int thread,
				int i_min, int i_max, int j_min, int j_max) {

			point *extents = subdomain_extents + thread * 2;

			/*
			 * Linear filtering, iterating over approximation pixels
			 */

			for (int i = i_min; i < i_max; i++)
			for (int j = j_min; j < j_max; j++) {

				if (is_excluded_r(approximation->offset(), i, j, frame_num))
					continue;

				/*
				 * Obtain the position Q and dimensions D of
				 * image approximation pixel (i, j) in the coordinate
				 * system of the simulated frame.
				 */

				point p = point(i + approximation->offset()[0], j + approximation->offset()[1]);
				point q;
				ale_pos d[2];

				/*
				 * XXX: This appears to calculate the wrong thing.
				 */

				if (is_excluded_f(p, frame_num))
					continue;

				t.unscaled_map_area_inverse(p, &q, d);
				
				/*
				 * Convenient variables for expressing the boundaries
				 * of the mapped area.
				 */
				
				ale_pos top = q[0] - d[0];
				ale_pos bot = q[0] + d[0];
				ale_pos lef = q[1] - d[1];
				ale_pos rig = q[1] + d[1];

				/*
				 * Iterate over all simulated frame pixels influenced
				 * by the scene pixel (i, j), as determined by the 
				 * response function.
				 */

				int imin = (int) floor(top + (ale_pos) lresponse->min_i());
				int imax = (int) ceil(bot + (ale_pos) lresponse->max_i());
				int jmin = (int) floor(lef + (ale_pos) lresponse->min_j());
				int jmax = (int) ceil(rig + (ale_pos) lresponse->max_j());

				pixel tr = pixel(1, 1, 1) / t.get_tonal_multiplier(p);

				for (int ii = imin; ii <= imax; ii++)
				for (int jj = jmin; jj <= jmax; jj++) {

					if (ii < (int) 0
					 || ii >= (int) lsimulated->height()
					 || jj < (int) 0
					 || jj >= (int) lsimulated->width())
						continue;

					if (ii < extents[0][0])
						extents[0][0] = ii;
					if (jj < extents[0][1])
						extents[0][1] = jj;
					if (ii > extents[1][0])
						extents[1][0] = ii;
					if (jj > extents[1][1])
						extents[1][1] = jj;

					class rasterizer::psf_result r =
						(*lresponse)(top - ii, bot - ii,
							    lef - jj, rig - jj,
							    lresponse->select(ii, jj),
							    lsimulated->get_channels(ii, jj));

					lock();

					if (lsimulated->get_bayer() == IMAGE_BAYER_NONE) {
						lsimulated->add_pixel(ii, jj, 
							r(approximation->get_pixel(i, j) * tr));
						lsim_weights->add_pixel(ii, jj,
							r.weight());
					} else {
						int k = lsimulated->bayer_color(ii, jj);
						lsimulated->add_chan(ii, jj, k,
							r(approximation->get_pixel(i, j))[k] * tr[k]);
						lsim_weights->add_chan(ii, jj, k,
							r.weight()[k]);
					}

					unlock();
				}
			}
		}

		void finish_subdomains(unsigned int N) {
			/*
			 * Determine extents
			 */

			for (unsigned int n = 0; n < N; n++) {
				point *se = subdomain_extents + 2 * n;

				for (int d = 0; d < 2; d++) {
					if (se[0][d] < extents[0][d])
						extents[0][d] = se[0][d];
					if (se[1][d] > extents[1][d])
						extents[1][d] = se[1][d];
				}
			}

			delete[] subdomain_extents;
		}
	public:

		simulate_linear(sim_args s) : decompose_domain(0, s.approximation->height(),
				                               0, s.approximation->width()),
					      sim_args(s) {
		}
	};


	class simulate_nonlinear : public thread::decompose_domain, 
	                           private sim_args {
		point *subdomain_extents;

	protected:
		void prepare_subdomains(unsigned int N) {
			subdomain_extents = new point[N * 2];

			for (unsigned int n = 0; n < N; n++) {
				point *se = subdomain_extents + 2 * n;

				for (int d = 0; d < 2; d++) {
					se[0][d] = extents[0][d];
					se[1][d] = extents[1][d];
				}
			}
		}

		void subdomain_algorithm(unsigned int thread,
				int i_min, int i_max, int j_min, int j_max) {

			point *extents = subdomain_extents + thread * 2;

			/*
			 * Iterate non-linear
			 */

			for (int i = i_min; i < i_max; i++)
			for (int j = j_min; j < j_max; j++) {

				/*
				 * Convenient variables for expressing the boundaries
				 * of the mapped area.
				 */
				
				ale_pos top = i - 0.5;
				ale_pos bot = i + 0.5;
				ale_pos lef = j - 0.5;
				ale_pos rig = j + 0.5;

				/*
				 * Iterate over all simulated frame pixels influenced
				 * by the scene pixel (i, j), as determined by the 
				 * response function.
				 */

				for (int ii = (int) floor(top + (ale_pos) nlresponse->min_i());
					ii <= ceil(bot + (ale_pos) nlresponse->max_i()); ii++)
				for (int jj = (int) floor(lef + (ale_pos) nlresponse->min_j());
					jj <= ceil(rig + (ale_pos) nlresponse->max_j()); jj++) {

					if (ii < (int) 0
					 || ii >= (int) nlsimulated->height()
					 || jj < (int) 0
					 || jj >= (int) nlsimulated->width())
						continue;

					if (ii < extents[0][0])
						extents[0][0] = ii;
					if (jj < extents[0][1])
						extents[0][1] = jj;
					if (ii > extents[1][0])
						extents[1][0] = ii;
					if (jj > extents[1][1])
						extents[1][1] = jj;

					class rasterizer::psf_result r =
						(*nlresponse)(top - ii, bot - ii,
							    lef - jj, rig - jj,
							    nlresponse->select(ii, jj));

					lock();

					nlsimulated->add_pixel(ii, jj,
						r(exp->unlinearize(lsimulated->get_pixel(i, j))));
					nlsim_weights->add_pixel(ii, jj,
						r.weight());

					unlock();
				}
			}
		}

		void finish_subdomains(unsigned int N) {
			/*
			 * Determine extents
			 */

			for (unsigned int n = 0; n < N; n++) {
				point *se = subdomain_extents + 2 * n;

				for (int d = 0; d < 2; d++) {
					if (se[0][d] < extents[0][d])
						extents[0][d] = se[0][d];
					if (se[1][d] > extents[1][d])
						extents[1][d] = se[1][d];
				}
			}

			delete[] subdomain_extents;
		}
	public:

		simulate_nonlinear(sim_args s) : decompose_domain(0, s.lsimulated->height(),
						                  0, s.lsimulated->width()),
					         sim_args(s) {
		}
	};

	void _ip_frame_simulate(int frame_num, image *approximation, 
			image *lsimulated, image *nlsimulated, 
			transformation t, const raster *lresponse, 
			const raster *nlresponse, const exposure &exp,
			point *extents) {

		sim_args args;

		/*
		 * Initializations for linear filtering
		 */

		image *lsim_weights = NULL;

		if (lsimulated->get_bayer() == IMAGE_BAYER_NONE) {
			lsim_weights = new_image_ale_real(
				lsimulated->height(),
				lsimulated->width(),
				lsimulated->depth());
		} else {
			lsim_weights = new_image_bayer_ale_real(
				lsimulated->height(),
				lsimulated->width(),
				lsimulated->depth(), 
				lsimulated->get_bayer());
		}
		assert (lsim_weights);

		args.frame_num = frame_num;
		args.approximation = approximation;
		args.lsimulated = lsimulated;
		args.nlsimulated = nlsimulated;
		args.lsim_weights = lsim_weights;
		args.t = t;
		args.lresponse = lresponse;
		args.nlresponse = nlresponse;
		args.exp = &exp;
		args.extents = extents;

		/*
		 * Simulate linear
		 */

		simulate_linear sl(args);
		sl.run();

		/*
		 * Normalize linear
		 */

		for (unsigned int ii = 0; ii < lsimulated->height(); ii++)
		for (unsigned int jj = 0; jj < lsimulated->width(); jj++) {
			const ale_real weight_floor = ale_real_ip_weight_floor;
			ale_accum zero = 0;

			for (int k = 0; k < 3; k++) {
				if ((lsimulated->get_channels(ii, jj) & (1 << k)) == 0)
					continue;

				ale_real weight = lsim_weights->get_chan(ii, jj, k);

				if (!(weight > weight_floor))
					lsimulated->div_chan(ii, jj, k, 
						zero);  /* Generate a non-finite value */
				else
					lsimulated->div_chan(ii, jj, k,
						weight);
			}
		}

		/*
		 * Finalize linear
		 */

		delete lsim_weights;

		/*
		 * Return if there is no non-linear step.
		 */

		if (nlsimulated == NULL) {
			return;
		}

		/*
		 * Initialize non-linear
		 */

		image *nlsim_weights = new_image_ale_real(
				nlsimulated->height(),
				nlsimulated->width(),
				nlsimulated->depth());

		args.nlsim_weights = nlsim_weights;

		/*
		 * Simulate non-linear
		 */

		simulate_nonlinear snl(args);
		snl.run();

		/*
		 * Normalize non-linear
		 */

		for (unsigned int ii = 0; ii < nlsimulated->height(); ii++)
		for (unsigned int jj = 0; jj < nlsimulated->width(); jj++) {
			pixel weight = nlsim_weights->get_pixel(ii, jj);
			ale_real weight_floor = ale_real_ip_weight_floor;
			ale_accum zero = 0;

			for (int k = 0; k < 3; k++)
				if (!(weight[k] > weight_floor))
					nlsimulated->div_chan(ii, jj, k,
						zero);  /* Generate a non-finite value */

			nlsimulated->div_pixel(ii, jj, weight);
		}

		/*
		 * Finalize non-linear
		 */

		delete nlsim_weights;
	}

	struct correction_t {
		/*
		 * Type
		 */
		int use_weighted_median;

		/*
		 * Weighted Median
		 */
		image_weighted_median *iwm;

		/*
		 * Weight limit
		 */
		double weight_limit;

		/*
		 * Common
		 */
		image *c;
		image *cc;

		/*
		 * Create correction structures.
		 */
		correction_t(int use_weighted_median, double weight_limit, unsigned int h, unsigned int w, unsigned int d) {
			this->use_weighted_median = use_weighted_median;
			if (use_weighted_median)
				iwm = new image_weighted_median(h, w, d);
			this->weight_limit = weight_limit;
			c = new_image_ale_real(h, w, d);
			cc = new_image_ale_real(h, w, d);
		}

		/*
		 * Destroy correction structures
		 */
		~correction_t() {
			if (use_weighted_median)
				delete iwm;
			delete c;
			delete cc;
		}

		/*
		 * Correction count
		 */
		pixel get_count(int i, int j) {
			if (use_weighted_median)
				return iwm->get_weights()->get_pixel(i, j);
			else
				return cc->get_pixel(i, j);
		}

		/*
		 * Check for weight limit
		 */
		int weight_limited(int i, int j) {
			if (weight_limit
			 && get_count(i, j)[0] >= weight_limit
			 && get_count(i, j)[1] >= weight_limit
			 && get_count(i, j)[2] >= weight_limit)
				return 1;

			return 0;
		}

		/*
		 * Correction value
		 */
		pixel get_correction(int i, int j) {
			if (use_weighted_median)
				return iwm->get_colors()->get_pixel(i, j);
			else
				return (pixel) c->get_pixel(i, j)
				     / (pixel) cc->get_pixel(i, j);
		}

		/*
		 * Frame end
		 */
		void frame_end(int frame_num) {
			if (use_weighted_median) {

				for (unsigned int i = 0; i < c->height(); i++)
				for (unsigned int j = 0; j < c->width(); j++) {

					/*
					 * Update the median calculator
					 */
					pixel cval = c->get_pixel(i, j);
					pixel ccval = cc->get_pixel(i, j);
					iwm->accumulate(i, j, frame_num, cval / ccval, ccval);

					/*
					 * Reset the counters
					 */
					c->set_pixel(i, j, pixel::zero());
					cc->set_pixel(i, j, pixel::zero());
				}
			}
		}

		/*
		 * Update correction structures, using either a weighted mean update or
		 * a weighted median update.
		 */
		void update(int i, int j, pixel value_times_weight, pixel weight) {
			c->add_pixel(i, j, value_times_weight);
			cc->add_pixel(i, j, weight);
		}
	};

	/*
	 * For each pixel in APPROXIMATION, calculate the differences
	 * between SIMULATED and REAL pixels influenced by this pixel,
	 * and back-project the differences onto the correction array
	 * C.  The number of frames backprojected to each pixel in C is
	 * counted in CC.  
	 *
	 * Since APPROXIMATION can always, given sufficient computational
	 * resources, be configured to be of finer resolution than SIMULATED,
	 * we map APPROXIMATION pixels onto the SIMULATED grid rather than vice
	 * versa.  This should reduce the error incurred by approximations in
	 * d2::transformation::unscaled_map_area*().  
	 *
	 * This approach requires multiplying the resultant integral by a
	 * corrective factor, since the grids are generally of different
	 * densities.
	 */

	struct correct_args {
		int frame_num;
		image *approximation;
		correction_t *cu;
		const image *real;
		image *lreal;
		image *lsimulated;
		image *nlsimulated;
		transformation t;
		const backprojector *lresponse;
		const backprojector *nlresponse;
		double weight_limit;

		correct_args() : t(transformation::eu_identity()) {
		}
	};

	class correct_nonlinear : public thread::decompose_domain, 
	                          private correct_args {

	protected:
		void subdomain_algorithm(unsigned int thread,
				int i_min, int i_max, int j_min, int j_max) {

			/*
			 * Unlinearize values from lsimulated.
			 */

			for (int i = i_min; i < i_max; i++)
			for (int j = j_min; j < j_max; j++)
				lreal->set_pixel(i, j, real->exp().unlinearize(
							lsimulated->get_pixel(i, j)));


			/*
			 * Backproject from real to lreal, iterating over all pixels
			 * in lreal.
			 */

			for (int i = i_min; i < i_max; i++)
			for (int j = j_min; j < j_max; j++) {

				/*
				 * XXX: Is this right?
				 */

				if (is_excluded_r(approximation->offset(), i, j, frame_num))
					continue;

				/*
				 * Convenient variables for expressing the boundaries
				 * of the mapped area.
				 */
				
				ale_pos top = i - 0.5;
				ale_pos bot = i + 0.5;
				ale_pos lef = j - 0.5;
				ale_pos rig = j + 0.5;

				/*
				 * Iterate over non-linear pixels influenced by linear
				 * pixels.
				 */

				for (int ii = (int) floor(top + (ale_pos) nlresponse->min_i());
					ii <= ceil(bot + (ale_pos) nlresponse->max_i()); ii++)
				for (int jj = (int) floor(lef + (ale_pos) nlresponse->min_j());
					jj <= ceil(rig + (ale_pos) nlresponse->max_j()); jj++) {

					if (ii < (int) 0
					 || ii >= (int) nlsimulated->height()
					 || jj < (int) 0
					 || jj >= (int) nlsimulated->width())
						continue;

					class backprojector::psf_result r =
						(*nlresponse)(top - ii, bot - ii,
							    lef - jj, rig - jj,
							    nlresponse->select(ii, jj));


					pixel comp_real =
						real->exp().unlinearize(real->get_pixel(ii, jj));

					pixel comp_simu =
						nlsimulated->get_pixel(ii, jj);

					if (!finite(comp_simu[0])
					 || !finite(comp_simu[1])
					 || !finite(comp_simu[2]))
						continue;

					/*
					 * Backprojection value.
					 */

					pixel bpv = r(comp_real - comp_simu);

					/*
					 * Error calculation
					 */

					lreal->add_pixel(i, j, bpv);
				}
			}

			/*
			 * Linearize lreal.
			 */

			for (int i = i_min; i < i_max; i++)
			for (int j = j_min; j < j_max; j++)
				lreal->set_pixel(i, j, real->exp().linearize(
							lreal->get_pixel(i, j)));
		}

	public:

		correct_nonlinear(correct_args c) : decompose_domain(0, c.lreal->height(),
				                                     0, c.lreal->width()),
					            correct_args(c) {
		}
	};

	class correct_linear : public thread::decompose_domain, 
	                       private correct_args {

	protected:
		void subdomain_algorithm(unsigned int thread,
				int i_min, int i_max, int j_min, int j_max) {

			/*
			 * Iterate over all pixels in the approximation.
			 */

			for (int i = i_min; i < i_max; i++)
			for (int j = j_min; j < j_max; j++) {

				/*
				 * Check correction count against any weight limit.
				 */

				if (cu->weight_limited(i, j))
					continue;

				/*
				 * Obtain the position Q and dimensions D of image
				 * approximation pixel (i, j) in the coordinate system
				 * of the simulated (and real) frame.
				 */

				point p = point(i + approximation->offset()[0], j + approximation->offset()[1]);
				point q;
				ale_pos d[2];

				t.unscaled_map_area_inverse(p, &q, d);
				
				/*
				 * Convenient variables for expressing the boundaries
				 * of the mapped area.
				 */
				
				ale_pos top = q[0] - d[0];
				ale_pos bot = q[0] + d[0];
				ale_pos lef = q[1] - d[1];
				ale_pos rig = q[1] + d[1];

				ale_pos integral_divisor = 1 / ((bot - top) * (rig - lef));

				/*
				 * Iterate over frame pixels influenced by the scene
				 * pixel.
				 */

				int imin = (int) floor(top + (ale_pos) lresponse->min_i());
				int imax = (int) ceil(bot + (ale_pos) lresponse->max_i());
				int jmin = (int) floor(lef + (ale_pos) lresponse->min_j());
				int jmax = (int) ceil(rig + (ale_pos) lresponse->max_j());

				pixel tr = t.get_tonal_multiplier(p);

				for (int ii = imin; ii <= imax; ii++)
				for (int jj = jmin; jj <= jmax; jj++) {

					if (ii < (int) 0
					 || ii >= (int) lreal->height()
					 || jj < (int) 0
					 || jj >= (int) lreal->width())
						continue;

					if (is_excluded_f(ii, jj, frame_num))
						continue;

					unsigned int selection = lresponse->select(ii, jj);

					char channels = lreal->get_channels(ii, jj);

					class backprojector::psf_result r =
						(*lresponse)(top - ii, bot - ii,
							    lef - jj, rig - jj,
							    selection, channels);


					/*
					 * R is the result of integration in the
					 * coordinate system of the simulated frame.
					 * We must rescale to get the result of
					 * integration in the coordinate system of the
					 * approximation image.
					 */

					r *= integral_divisor;

					pixel comp_lreal =
						lreal->get_raw_pixel(ii, jj);

					// pixel comp_real = 
					//	real->get_pixel(ii, jj);

					pixel comp_simu =
						lsimulated->get_raw_pixel(ii, jj);

#if 1

					/*
					 * Under the assumption that finite() testing
					 * may be expensive, limit such tests to active
					 * channels.
					 */
					int found_nonfinite = 0;
					for (int k = 0; k < 3; k++) {
						if (((1 << k) & channels)
						 && (!finite(comp_simu[k])
						  || !finite(comp_lreal[k]))) {
							found_nonfinite = 1;
							break;
						}
					}

					if (found_nonfinite)
						continue;
#else 

					if (!finite(comp_simu[0])
					 || !finite(comp_simu[1])
					 || !finite(comp_simu[2])
					 || !finite(comp_lreal[0])
					 || !finite(comp_lreal[1])
					 || !finite(comp_lreal[2]))
						continue;
#endif

					/*
					 * Backprojection value unadjusted 
					 * for confidence.
					 */

					pixel bpv = tr * r(comp_lreal - comp_simu);

					/*
					 * Confidence [equal to (1, 1, 1) when
					 * confidence is uniform].
					 */

					// Ordinary certainty
					// pixel conf = real->exp().confidence(comp_lreal);

					// One-sided certainty
					// pixel conf = real->exp().one_sided_confidence(comp_lreal, bpv);
					      // conf = real->exp().one_sided_confidence(comp_real, bpv);
					      
					// Estimate-based certainty
					// pixel conf = real->exp().confidence(comp_simu);
						
					// One-sided estimate-based certainty
					pixel conf = real->exp().one_sided_confidence(comp_simu, bpv);

					// One-sided approximation-based certainty
					// pixel conf = real->exp().one_sided_confidence(approximation->pix(i, j), bpv);
						
					/*
					 * If a color is bayer-interpolated, then we have no confidence in its
					 * value.
					 */

					if (real->get_bayer() != IMAGE_BAYER_NONE) {
						int color = real->bayer_color(ii, jj);
						conf[(color + 1) % 3] = 0;
						conf[(color + 2) % 3] = 0;
					}

					/*
					 * Error calculation
					 */

					// c->pix(i, j) += bpv * conf;

					/*
					 * Increment the backprojection weight.  When
					 * confidence is uniform, this should weight
					 * each frame's correction equally.
					 */

					// cc->pix(i, j) += conf * r.weight() 
					//	       / lresponse->integral(selection);

					cu->update(i, j, bpv * conf, conf * r.weight() / lresponse->integral(selection));
				}
			}
		}

	public:

		correct_linear(correct_args c) : decompose_domain(0, c.approximation->height(),
				                                  0, c.approximation->width()),
					         correct_args(c) {
		}
	};

	void _ip_frame_correct(int frame_num, image *approximation,
			correction_t *cu, const image *real, image *lsimulated, 
			image *nlsimulated, transformation t, 
			const backprojector *lresponse, 
			const backprojector *nlresponse, point *extents) {

		correct_args args;

		args.frame_num = frame_num;
		args.approximation = approximation;
		args.cu = cu;
		args.real = real;
		args.lsimulated = lsimulated;
		args.nlsimulated = nlsimulated;
		args.t = t;
		args.lresponse = lresponse;
		args.nlresponse = nlresponse;

		/*
		 * Generate the image to compare lsimulated with.
		 */

		const image *lreal;

		if (nlsimulated == NULL)
			lreal = real;
		else {

			image *new_lreal = new_image_ale_real(
					real->height(),
					real->width(),
					real->depth(),
					"IPC lreal", 
					&real->exp());

			args.lreal = new_lreal;

			correct_nonlinear cn(args);
			cn.run();

			lreal = new_lreal;
		}

		/*
		 * Perform exposure adjustment.
		 */

		if (exposure_register) {

			pixel ec;

#if 0
			ec = lsimulated->avg_channel_magnitude()
			   / lreal->avg_channel_magnitude();
#elsif 0
			pixel_accum ratio_sum;
			pixel_accum weight_sum;

			for (unsigned int i = 0; i < lreal->height(); i++)
			for (unsigned int j = 0; j < lreal->width(); j++) {

				pixel s = lsimulated->get_pixel(i, j);
				pixel r = lreal->get_pixel(i, j);
				pixel confidence = real->exp().confidence(r);

				if (s[0] > 0.001 
				 && s[1] > 0.001
				 && s[2] > 0.001
				 && r[0] > 0.001
				 && r[1] > 0.001
				 && r[2] > 0.001) {
					ratio_sum += confidence * s / r;
					weight_sum += confidence;
				}
			}

			ec = ratio_sum / weight_sum;
#else
			pixel_accum ssum, rsum;

//			for (unsigned int i = 0; i < lreal->height(); i++)
//			for (unsigned int j = 0; j < lreal->width(); j++) {
			for (unsigned int i = (unsigned int) floor(extents[0][0]); 
			                  i < (unsigned int)  ceil(extents[1][0]); i++)
			for (unsigned int j = (unsigned int) floor(extents[0][1]); 
			                  j < (unsigned int)  ceil(extents[1][1]); j++) {

				if (real->get_bayer() != IMAGE_BAYER_NONE) {
					int color = real->bayer_color(i, j);
					ale_real s = lsimulated->get_chan(i, j, color);
					ale_real r = lreal->get_chan(i, j, color);

					if (!finite(s)
					 || !finite(r))
						continue;

					ale_real confidence = real->exp().confidence(color, s);

					ssum[color] += confidence * s;
					rsum[color] += confidence * r;
				} else {
					pixel s = lsimulated->get_pixel(i, j);
					pixel r = lreal->get_pixel(i, j);

					if (!s.finite()
					 || !r.finite())
						continue;

					pixel confidence = real->exp().confidence(s);

					ssum += confidence * s;
					rsum += confidence * r;
				}

			}

			ec = ssum / rsum;

#endif

			if (ec.finite() 
			 && (ale_accum) 1000 * rsum[0] > (ale_accum) 1
			 && (ale_accum) 1000 * rsum[1] > (ale_accum) 1
			 && (ale_accum) 1000 * rsum[2] > (ale_accum) 1)
				real->exp().set_multiplier(
					real->exp().get_multiplier() * ec);
		}

		args.lreal = (d2::image *) lreal;

		correct_linear cl(args);
		cl.run();

		if (nlsimulated)
			delete lreal;
	}

	/* 
	 * Adjust correction array C based on the difference between the
	 * simulated projected frame and real frame M.  Update the correction
	 * count CC for affected pixels in C.
	 */

	virtual void _ip_frame(int frame_num, correction_t *cu, const image *real, 
			transformation t, const raster *f, const backprojector *b,
			const raster *nlf, const backprojector *nlb) {

		ui::get()->d2_irani_peleg_start();

		ale_accum_disable_casting();
		ale_pos_disable_casting();
		ale_real_disable_casting();

		/*
		 * Initialize simulated data structures
		 */

                image *lsimulated;
		
		if (real->get_bayer() == IMAGE_BAYER_NONE) {
			lsimulated = new_image_ale_real(
				real->height(),
				real->width(), 
				real->depth());
		} else {
			lsimulated = new_image_bayer_ale_real(
				real->height(),
				real->width(),
				real->depth(),
				real->get_bayer());
		}

                image *nlsimulated = NULL;

		if (nlf)
			nlsimulated = new_image_ale_real(
				real->height(),
				real->width(), 
				real->depth());

		/*
		 * Create simulated frames with forward projection.
		 */

		ui::get()->ip_frame_simulate_start();

		point extents[2] = { point::posinf(), point::neginf() };

		_ip_frame_simulate(frame_num, approximation, lsimulated, nlsimulated, t, f, nlf, real->exp(), extents);

		/*
		 * Update the correction array using backprojection.
		 */

		ui::get()->ip_frame_correct_start();
		_ip_frame_correct(frame_num, approximation, cu, real, lsimulated, nlsimulated, t, b, nlb, extents);

		/*
		 * Finalize data structures.
		 */

                delete lsimulated;
		delete nlsimulated;

		ale_accum_enable_casting();
		ale_pos_enable_casting();
		ale_real_enable_casting();

		ui::get()->d2_irani_peleg_stop();
        }

	/*
	 * Iterate _ip_frame() over all frames, and update APPROXIMATION after
	 * corrections from all frames have been summed.  Repeat for the number
	 * of iterations specified by the user.
	 */

        virtual void _ip() {

		/*
		 * Create rasterized PSF and backprojection kernel AUX.
		 */

		raster **f = (raster **) malloc(image_rw::count() * sizeof(raster *));
		backprojector **b = (backprojector **) malloc(image_rw::count() * sizeof(backprojector *));

		for (unsigned int m = 0; m < image_rw::count(); m++) {

			if (!align::match(m))
				continue;

			transformation t = align::of(m);

			f[m] = new normalizer(new rasterizer(lresponse, t));
			b[m] = new backprojector(f[m]);
		}

		raster *nlf = NULL;
		backprojector *nlb = NULL;

		if (nlresponse) {
			nlf = new normalizer(new rasterizer(nlresponse, transformation::eu_identity()));
			nlb = new backprojector(nlf);
		}

                for (unsigned int n = 0; n < iterations; n++) {

			correction_t *correction = new correction_t(
					use_weighted_median,
					weight_limit,
					approximation->height(),
					approximation->width(),
					approximation->depth());

			/*
			 * Iterate over all frames
			 */

                        for (unsigned int m = 0; m < image_rw::count(); m++) {

				if (!align::match(m))
					continue;

				ui::get()->ip_frame_start(m);

				transformation t = align::of(m);
				const image *real = image_rw::open(m);

                                _ip_frame(m, correction, real,
					t, f[m], b[m], nlf, nlb);

				image_rw::close(m);

				correction->frame_end(m);
			}

			/*
			 * Update the approximation.
			 */

			ui::get()->ip_update();

			for (unsigned int i = 0; i < approximation->height(); i++)
			for (unsigned int j = 0; j < approximation->width();  j++) {

				pixel  cpix = correction->get_correction(i, j);
				pixel ccpix = correction->get_count(i, j);
				pixel  apix = approximation->get_pixel(i, j);

				for (unsigned int k = 0; k < 3; k++) {

					const ale_real cc_floor = 1e-20;

					if (ccpix[k] < cc_floor)
						continue;

					if (!finite(cpix[k]))
						continue;

					if (!finite(apix[k]))
						continue;

					ale_real new_value = cpix[k] + apix[k];

					assert (finite(apix[k]));
					assert (finite(ccpix[k]));
					assert (finite(cpix[k]));
					assert (finite(new_value));

					/*
					 * Negative light doesn't make sense.
					 */
					if (new_value < 0)
						new_value = 0;

					approximation->set_chan(i, j, k, new_value);
				}
			}

			delete correction;

			if (inc) {
				ui::get()->ip_write();
				image_rw::output(approximation);
			}

			ui::get()->ip_step_done();

                }

		for (unsigned int m = 0; m < image_rw::count(); m++) {

			if (!align::match(m))
				continue;

			delete f[m];
			delete b[m];
		}

		free(f);
		free(b);

		delete nlf;
		delete nlb;
        }

public:

        ipc(render *input, unsigned int iterations, int _inc, psf *lresponse, 
			psf *nlresponse, int exposure_register,
			int use_weighted_median, double ipwl) {
                this->input = input;
                synced = 0;
		inc = _inc;
                this->iterations = iterations;
		this->lresponse = lresponse;
		this->nlresponse = nlresponse;
		this->exposure_register = exposure_register;
		this->use_weighted_median = use_weighted_median;
		this->weight_limit = ipwl;
        }

        const image *get_image() const {
                if (synced)
                        return approximation;
                else
                        return input->get_image();
        }

        const image *get_defined() const {
		if (synced) {
			assert(0);  /* definition is not set */
			return definition;
		} else
			return input->get_defined();
        }

        void sync(int n) {
		render::sync(n);
                input->sync(n);
        }

	void step() {
		return;
	}

        virtual int sync() {
		input->sync();
                synced = 1;
		approximation = input->get_image()->clone("IPC Approximation");
		// definition = input->get_defined()->clone("IPC Definition");
		optimizations::ip_sources_obtained(this);
		ui::get()->ip_start();
                _ip();
		ui::get()->ip_done();

		/*
		 * Since we write output internally for --inc, no external
		 * update is necessary in this case.
		 */

                return 0;
        }

	virtual ~ipc() {
	}

	void free_memory() {
	}
};

#endif
