package com.elphel.imagej.orthomosaic;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.cameras.ColorProcParameters;
import com.elphel.imagej.cameras.EyesisCorrectionParameters;
import com.elphel.imagej.common.GenericJTabbedDialog;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.gpu.GPUTileProcessor;
import com.elphel.imagej.gpu.GpuQuad;
import com.elphel.imagej.gpu.TpTask;
import com.elphel.imagej.ims.Imx5;
import com.elphel.imagej.readers.ElphelTiffReader;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.TDCorrTile;

import Jama.Matrix;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.PointRoi;
import ij.process.FloatPolygon;

public class ComboMatch {
	public static GPUTileProcessor GPU_TILE_PROCESSOR = null;
	public static GpuQuad   GPU_QUAD_AFFINE = null;
	public static ImagePlus imp_src1 = null;
	public static ImagePlus imp_src2 = null;
	public static ImagePlus [] imp_src = new ImagePlus[2];  
	public static ImagePlus [] imp_alt = new ImagePlus[2];  
	public static Properties [] imp_prop = new Properties[2];
	public static int gpu_max_width=  3008;
	public static int gpu_max_height= 3008;
	
	public static boolean openTestPairGps(
			 CLTParameters    clt_parameters,
			 GPUTileProcessor gpu_tile_processor, // initialized by the caller
			 final int        debugLevel) {
		GPU_TILE_PROCESSOR = gpu_tile_processor;
		/*
/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877409_353265/1697877409_353265-RECT-PIX0.01-FLAT_CLN-VERT-GEO.tiff
/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877409_353265/1697877409_353265-RECT-PIX0.01-FLAT_CLN-VERT-GEO-ALT.tiff

/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877410_420287/1697877410_420287-RECT-PIX0.01-FLAT_CLN-VERT-GEO.tiff
/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877410_420287/1697877410_420287-RECT-PIX0.01-FLAT_CLN-VERT-GEO-ALT.tiff


/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877412_004148/1697877412_004148-RECT-PIX0.01-FLAT_CLN-VERT-GEO.tiff
/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877412_004148/1697877412_004148-RECT-PIX0.01-FLAT_CLN-VERT-GEO-ALT.tiff


/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877413_137859/1697877413_137859-RECT-PIX0.01-FLAT_CLN-VERT-GEO.tiff
/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877413_137859/1697877413_137859-RECT-PIX0.01-FLAT_CLN-VERT-GEO-ALT.tiff


/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877414_404948/1697877414_404948-RECT-PIX0.01-FLAT_CLN-VERT-GEO.tiff
/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877414_404948/1697877414_404948-RECT-PIX0.01-FLAT_CLN-VERT-GEO-ALT.tiff

/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877415_521986/1697877415_521986-RECT-PIX0.01-FLAT_CLN-VERT-GEO.tiff
/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877415_521986/1697877415_521986-RECT-PIX0.01-FLAT_CLN-VERT-GEO-ALT.tiff

		 */
		String [] image_paths_pre = {
				"/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877410_420287/1697877410_420287-RECT-PIX0.01-FLAT_CLN-VERT-GEO",
				"/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_1697875868-1697879449-b/1697877412_004148/1697877412_004148-RECT-PIX0.01-FLAT_CLN-VERT-GEO"
		};
		double [][][] image_enuatr = {{{0,0,0},{0,0,0}},{{0,0,0},{0,0,0}}};
		int gpu_width=  clt_parameters.imp.rln_gpu_width; // 3008;
		int gpu_height= clt_parameters.imp.rln_gpu_height; // 3008;
		int zoom_lev = -4; // 0; // +1 - zoom in twice, -1 - zoom out twice
		boolean show_centers = true;
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Set image pair",1200,650);
		gd.addStringField ("First image path",  image_paths_pre[0], 180, "First image full path w/o ext");		
		gd.addStringField ("Second image path", image_paths_pre[1], 180, "Second image full path w/o ext");		
		for (int n = 0; n < image_enuatr.length; n++) {
			gd.addMessage("image["+n+"] pose");
			gd.addNumericField("East",   image_enuatr[n][0][0],  3,7,"m",	"Move image "+n+" East.");
			gd.addNumericField("North",  image_enuatr[n][0][1],  3,7,"m",	"Move image "+n+" North.");
			gd.addNumericField("Up",     image_enuatr[n][0][2],  3,7,"m",	"Move image "+n+" Up.");
			gd.addNumericField("Azimuth",image_enuatr[n][1][0],  3,7,"deg",	"Rotate image "+n+" around vertical clockwise.");
			gd.addNumericField("Tilt",   image_enuatr[n][1][1],  3,7,"deg",	"Rotate image "+n+" around East.");
			gd.addNumericField("Roll",   image_enuatr[n][1][2],  3,7,"deg",	"Rotate image "+n+" around North.");
		}
		gd.addNumericField("Zoom level",              zoom_lev,  0,4,"",
				"Zoom level: +1 - zoom in twice, -1 - zoom out twice");
		gd.addNumericField("GPU image width",              gpu_width,  0,4,"",
				"GPU image width");
		gd.addNumericField("GPU image height",             gpu_height,  0,4,"",
				"GPU image height");
		gd.addCheckbox    ("Show transformation centers", show_centers, "Mark verticals from the UAS on the ground.");

		gd.showDialog();
		if (gd.wasCanceled()) return false;
		image_paths_pre[0] = gd.getNextString();
		image_paths_pre[1] = gd.getNextString();
		for (int n = 0; n < image_enuatr.length; n++) {
			image_enuatr[n][0][0] = gd.getNextNumber();
			image_enuatr[n][0][1] = gd.getNextNumber();
			image_enuatr[n][0][2] = gd.getNextNumber();
			image_enuatr[n][1][0] = gd.getNextNumber();
			image_enuatr[n][1][1] = gd.getNextNumber();
			image_enuatr[n][1][2] = gd.getNextNumber();
		}
		zoom_lev =            (int) gd.getNextNumber();
		gpu_width =           (int) gd.getNextNumber();
		gpu_height =          (int) gd.getNextNumber();
		
		show_centers =              gd.getNextBoolean();
		
		ComboMap[] combo_maps = new ComboMap[imp_src.length];
		for (int n = 0; n < combo_maps.length; n++) { 
			combo_maps[n] = new ComboMap(image_paths_pre[n]+".tiff");
			combo_maps[n].readImageData();
			double [][] affine = new double[2][3]; // maybe later calculate from mage_enuatr
			combo_maps[n].setAffine(affine);
		}
		/* */
		int [] widths = new int[imp_src.length];
		int [] heights = new int[imp_src.length];
		for (int n = 0; n < imp_src.length; n++) { 
			imp_src[n] = new ImagePlus(image_paths_pre[n]+".tiff"); // may use +"-ALT.tiff"
			widths[n] = imp_src[n].getWidth();
			heights[n] = imp_src[n].getHeight();
		}
		//imp_prop
		double [][] llas = new double [imp_src.length][];
		LocalDateTime [] dts = new LocalDateTime[2];
		double [][] xy_pix_centers = new double [2][];
		double [] pix_sizes = new double[2]; // ElphelTiffReader.getPixelSize(imp_prop[0])[0];
		for (int n = 0; n < imp_src.length; n++) { 
			try {
				imp_prop[n] = ElphelTiffReader.getTiffMeta(image_paths_pre[n]+".tiff");				
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			llas[n] = ElphelTiffReader.getLLA(imp_prop[n]);
			dts[n] = ElphelTiffReader.getLocalDateTime(imp_prop[n]);
			xy_pix_centers[n] = ElphelTiffReader.getXYOffsetPixels(imp_prop[n]);
			pix_sizes[n] = ElphelTiffReader.getPixelSize(imp_prop[n])[0];
		}
		/* */
		if (show_centers) {
			for (int n = 0; n < combo_maps.length; n++) {
				combo_maps[n].getOriginalImage(true);
			}
		}
		/*
		for (int n = 0; n < imp_src.length; n++) {
			imp_src[n].show();
		}
		*/
		if (combo_maps[0].orig_zoom_level != combo_maps[1].orig_zoom_level) {
			System.out.println("openTestPairGps() does not yet handle different scales, aborting");
			return false;
		}
		double [] enu_diff = combo_maps[0].enuOffsetTo(combo_maps[0]);
		
		double pix_size1 = ElphelTiffReader.getPixelSize(imp_prop[0])[0];
		double [] pix_shift = {enu_diff[0]/pix_size1, -enu_diff[1]/pix_size1}; // E->X, N->-Y
		int [] ipix_shift=new int[2];
		for (int n = 0; n < 2; n++) {
			ipix_shift[n] = (int) Math.round(pix_shift[n]);			
		}
		System.out.println(String.format("Interscene pixel shift = [%9.3f, %9.3f]",pix_shift[0],pix_shift[1]));
		int xmin=Math.min(0, ipix_shift[0]);
		int xmax=Math.max(widths[0], widths[1]+ipix_shift[0]);
		int ymin=Math.min(0, ipix_shift[1]);
		int ymax=Math.max(heights[0], heights[1]+ipix_shift[1]);
		int width = xmax-xmin;
		int height = ymax-ymin;
		int [] x0 = new int [2];
		int [] y0 = new int [2];
		if (ipix_shift[0] > 0) 	x0[1] =  ipix_shift[0];
		else                    x0[0] = -ipix_shift[0];
		if (ipix_shift[1] > 0) 	y0[1] =  ipix_shift[1];
		else                    y0[0] = -ipix_shift[1];
		float [][] spix = new float[2][];
		for (int n = 0; n < 2; n++) {
			spix[n] = (float []) imp_src[n].getProcessor().getPixels();
		}
		if (zoom_lev < 0) {
			
		}
		
		float [][] opix = new float[2][width*height];
		for (int n = 0; n < 2; n++) {
			Arrays.fill(opix[n], Float.NaN);
			for (int line = 0; line < heights[n]; line++) {
				System.arraycopy(
						spix[n],
						line * widths[n],
						opix[n],
						(line + y0[n])*width+ x0[n],
						widths[n]);
			}
		}
        ShowDoubleFloatArrays.showArrays( 
        		opix,
        		width,
        		height,
        		true,
        		"overlap_pair");
        
		float [][] gpu_pix = new float[2][];
		double zoom_scale = 1.0;
		boolean fill_with_rand=false;
		double rnd_scale = 50;


		if (zoom_lev <= 0) {
			zoom_scale /= (1 << -zoom_lev);
			if ((gpu_width <= 0) || (gpu_height <= 0)) {
				gpu_width = Math.max(widths[0], widths[1]);
				gpu_height = Math.max(heights[0], heights[1]);
				gpu_width >>=  -zoom_lev;
				gpu_height >>= -zoom_lev;
				gpu_width =  16*((gpu_width + 15)/16);
				gpu_height = 16*((gpu_height + 15)/16);
				
			}
			for (int n = 0; n < 2; n++) {
				gpu_pix[n] = padScaleImage(
						spix[n],
						widths[n],
						-zoom_lev, // int      zoomout,
						gpu_width, // int      width,
						gpu_height); // int      height)
			}
			if (fill_with_rand && (rnd_scale > 0)) {
				fillRandomFloatNans(
						gpu_pix, // float [][] pixels,
						rnd_scale); // double scale)
			}
	        ShowDoubleFloatArrays.showArrays( 
	        		gpu_pix,
	        		gpu_width,
	        		gpu_height,
	        		true,
	        		"gpu_pair");
		}
		System.out.println("Setting up GPU");
		if (GPU_QUAD_AFFINE == null) {
			try {
				GPU_QUAD_AFFINE = new GpuQuad(//
						GPU_TILE_PROCESSOR,   // GPUTileProcessor gpuTileProcessor,
//						null,                 // QUAD_CLT_AUX,
						gpu_max_width,        // final int        max_width,
						gpu_max_height,       // final int        max_height,
						1,                    // final int        num_colors, // normally 1?
						clt_parameters.gpu_debug_level);
			} catch (Exception e) {
				System.out.println("Failed to initialize GpuQuad class");
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			} // final int debugLevel);
			
		}
		//add offsets
		final double [][][] affine = {{{1,0,0},{0,1,0}},{{1,0,0},{0,1,0}}};
		affine[0][0][2] =  0.5*pix_shift[0]*zoom_scale;
		affine[0][1][2] =  0.5*pix_shift[1]*zoom_scale;
		affine[1][0][2] = -0.5*pix_shift[0]*zoom_scale;
		affine[1][1][2] = -0.5*pix_shift[1]*zoom_scale;
		
		
		TDCorrTile [] td_corr_tiles = rectilinearCorrelate_TD( // scene0/scene1
				clt_parameters, // final CLTParameters          clt_parameters,
				gpu_pix,        // final float  [][]            fpixels, // to check for empty
				gpu_width,      // final int                    img_width,
				affine,         // final double [][][]          affine,  // [2][2][3] affine coefficients to translate common to 2 images
				false,          // final boolean                batch_mode,
				debugLevel);    // final int                    debugLevel);
//		renderFromTD (
//				true, // boolean             use_reference,
//				"aaa"); //String              suffix
				
				
				
		return true;
	}
	
	public static TDCorrTile [] rectilinearCorrelate_TD( // scene0/scene1
			final CLTParameters          clt_parameters,
			final float  [][]            fpixels, // to check for empty
			final int                    img_width,
			final double [][][]          affine,  // [2][2][3] affine coefficients to translate common to 2 images
			final boolean                batch_mode,
			final int                    debugLevel) {
		int [] wh = {img_width, fpixels[0].length/img_width};
		TpTask [][] tp_tasks = GpuQuad.setRectilinearInterTasks(
				fpixels, // final float  [][]   fpixels, // to check for empty
				img_width, // final int           img_width,
				affine); // final double [][][] affine  // [2][2][3] affine coefficients to translate common to 2 images
		boolean is_aux =  true;
		boolean is_mono = true;
		boolean is_lwir = true;
		int     erase_cltr=-1;
		int     erase_clt= -1;
		ImageDtt image_dtt = new ImageDtt(
				1, // int numSensors,
				clt_parameters.transform_size,
				clt_parameters.img_dtt,
				is_aux, // ref_scene.isAux(),
				is_mono,  // ref_scene.isMonochrome(),
				is_lwir,
				clt_parameters.getScaleStrength(is_aux), // maybe something else
				GPU_QUAD_AFFINE);
		image_dtt.setRectilinearReferenceTD(
				erase_cltr,                  // final int                 erase_clt,
				fpixels[0], // final float []            fpixels_ref,
				wh,                         // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
				clt_parameters.img_dtt,     // final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
				true,                       // final boolean             use_reference_buffer,
				tp_tasks[0],                // final TpTask[]            tp_tasks,
				clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
				clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
				clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
				clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
				debugLevel);                // final int                 globalDebugLevel)
    	int tilesX =  img_width/GPUTileProcessor.DTT_SIZE;
    	int tilesY =  wh[1]/GPUTileProcessor.DTT_SIZE;
    	if (debugLevel > 1) {
    		renderFromTD (
    				true, // boolean             use_reference,
    				"ref"); //String              suffix
    	}
		float  [][][][]     fcorr_td = new float [tilesY][tilesX][][];  
				//null; // no accumulation, use data in GPU
		final double gpu_sigma_corr =     clt_parameters.getGpuCorrSigma(image_dtt.isMonochrome());
		final double gpu_sigma_rb_corr =  image_dtt.isMonochrome()? 1.0 : clt_parameters.gpu_sigma_rb_corr;
		final double gpu_sigma_log_corr = clt_parameters.getGpuCorrLoGSigma(image_dtt.isMonochrome());
		image_dtt.interRectilinearCorrTD(
				clt_parameters.img_dtt,     //final ImageDttParameters  imgdtt_params,    // Now just extra correlation parameters, later will include, most others
				erase_clt,                  // final int                 erase_clt,
				fpixels[1],                 // final float []            fpixels,
				wh,                         // final int []              wh,               // null (use sensor dimensions) or pair {width, height} in pixels
				tp_tasks[1],                // final TpTask[]            tp_tasks,
				fcorr_td,                   // final float  [][][][]     fcorr_td,        // [tilesY][tilesX][pair][4*64] transform domain representation of 6 corr pairs
				clt_parameters.gpu_sigma_r, // final double              gpu_sigma_r,     // 0.9, 1.1
				clt_parameters.gpu_sigma_b, // final double              gpu_sigma_b,     // 0.9, 1.1
				clt_parameters.gpu_sigma_g, // final double              gpu_sigma_g,     // 0.6, 0.7
				clt_parameters.gpu_sigma_m, // final double              gpu_sigma_m,     //  =       0.4; // 0.7;
				gpu_sigma_rb_corr,          // final double              gpu_sigma_rb_corr,    //  = 0.5; // apply LPF after accumulating R and B correlation before G, monochrome ? 1.0 :
				gpu_sigma_corr,             // final double              gpu_sigma_corr,       //  =    0.9;gpu_sigma_corr_m
				gpu_sigma_log_corr,         // final double              gpu_sigma_log_corr,   // hpf to reduce dynamic range for correlations
				clt_parameters.corr_red,    // final double              corr_red, // +used
				clt_parameters.corr_blue,   // final double              corr_blue,// +used
				-1,                         // final int                 sensor_mask_inter, // The bitmask - which sensors to correlate, -1 - all.
				debugLevel);                // final int                 globalDebugLevel)
//~ 		coord_motion = image_dtt.clt_process_tl_interscene(       // convert to pixel domain and process correlations already prepared in fcorr_td and/or fcorr_combo_td
// simplified version?
		// get TD interscene correlation of 2 scenes, use only combo (all channels) data
		TDCorrTile [] corr_tiles = TDCorrTile.getFromGpu(
				GPU_QUAD_AFFINE);
		double  neib_radius =      clt_parameters.imp.rln_neib_radius;;
		boolean rln_neibs_fill =   clt_parameters.imp.rln_neibs_fill;		
		double  rln_fat_zero =     clt_parameters.imp.rln_fat_zero;
		boolean rln_use_neibs =    clt_parameters.imp.rln_use_neibs;
		double  rln_cent_radius =  clt_parameters.imp.rln_cent_radius;
		int     rln_n_recenter =   clt_parameters.imp.rln_n_recenter;
		double  rln_sngl_rstr =    clt_parameters.imp.rln_sngl_rstr;
		double  rln_neib_rstr =    clt_parameters.imp.rln_neib_rstr;
		
		double [][][] corr_tiles_pd = new double [(neib_radius>0)? 2: 1][][];
		// use TDCorrTile.calcNeibs() here to get 8-neighbors
		corr_tiles_pd[0] = TDCorrTile.convertTDtoPD(
				GPU_QUAD_AFFINE,     // final GpuQuad       gpuQuad,
				corr_tiles,          // final TDCorrTile [] tiles,
				0xFE,                // final int           corr_type, // 0xFE
				rln_fat_zero,       // final double        gpu_fat_zero,
				debugLevel);         // final int           debug_level
		if (rln_use_neibs && (neib_radius > 0)) {
			TDCorrTile [] neibs_TD = TDCorrTile.calcNeibs(
					corr_tiles,      // TDCorrTile [] tiles,
					tilesX,          // final int     tilesX,
					neib_radius,     // final double  radius
					rln_neibs_fill);    // final boolean process_all			
					
			corr_tiles_pd[1] = TDCorrTile.convertTDtoPD(
					GPU_QUAD_AFFINE, // final GpuQuad       gpuQuad,
					neibs_TD,        // final TDCorrTile [] tiles,
					0xFE,            // final int           corr_type, // 0xFE
					rln_fat_zero,    // final double        gpu_fat_zero,
					debugLevel);     // final int           debug_level
		}
		double [][][] vector_field = new double [corr_tiles_pd.length][][];
		vector_field[0] = TDCorrTile.getMismatchVector(
				corr_tiles_pd[0], // final double[][] tiles,
				rln_sngl_rstr,    // double              rmax, 
				rln_cent_radius,  // final double        centroid_radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
				rln_n_recenter);  // final int           n_recenter); //  re-center window around new maximum. 0 -no refines (single-pass)
		if (corr_tiles_pd.length > 1) {
			vector_field[1] = TDCorrTile.getMismatchVector(
					corr_tiles_pd[1], // final double[][] tiles,
					rln_neib_rstr,    // double              rmax, 
					rln_cent_radius,  // final double        centroid_radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
					rln_n_recenter);  // final int           n_recenter); //  re-center window around new maximum. 0 -no refines (single-pass)
		}
		
    	boolean show_vector_field = true;
		
    	boolean show_2d_correlations = true;
    	final int corr_size = 2 * GPUTileProcessor.DTT_SIZE -1;  
		if (show_2d_correlations) {
    		double [][] dbg_2d_corrs = ImageDtt.corr_partial_dbg( // not used in lwir
    				corr_tiles_pd,                       // final double [][][]     corr_data,       // [layer][tile][(2*transform_size-1)*(2*transform_size-1)] // if null - will not calculate
    				tilesX,                              // final int               tilesX,
    				corr_size,                           //final int               corr_size,       // 15
    				clt_parameters.corr_border_contrast, // final double            border_contrast,
    				debugLevel);                         // final int               globalDebugLevel)
    		String [] dbg_titles= {"raw","neibs"};
    		ShowDoubleFloatArrays.showArrays(
    				dbg_2d_corrs,
    				tilesX * (corr_size + 1),
    				tilesY * (corr_size + 1),
    				true,
    				"corr2d",
    				dbg_titles);
    	}
		
		if (show_vector_field) {
			double [][] dbg_vf = new double [3 * vector_field.length][tilesX * tilesY];
			String [] dbg_titles = new String[dbg_vf.length];
			String [] prefix= {"single","neibs"};
			for (int n = 0; n < vector_field.length; n++) {
				dbg_titles [3*n+0] = prefix[n]+"-vx";	
				dbg_titles [3*n+1] = prefix[n]+"-vy";	
				dbg_titles [3*n+2] = prefix[n]+"-str";	
			}
			for (int i = 0; i < dbg_vf.length; i++) {
				Arrays.fill(dbg_vf[i], Double.NaN);
				
			}
			for (int t=0; t<dbg_vf[0].length; t++) {
				for (int n = 0; n < vector_field.length; n++) {
					if (vector_field[n][t] != null) {
						for (int k = 0; k < 3; k++) {
							dbg_vf[n*3 + k][t] = vector_field[n][t][k];
						}
					}
				}
			}
    		ShowDoubleFloatArrays.showArrays(
    				dbg_vf,
    				tilesX,
    				tilesY,
    				true,
    				"vector_field",
    				dbg_titles);
		}
		
		
		return corr_tiles;
	}
	
	
	
	
	
	
	public static boolean openTestPair() {
		String [] image_paths = {
				"/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_compare/02/1697877410_420287-RECT-PIX0.01-FLAT_CLN-GEO0.tiff",
				"/media/elphel/SSD3-4GB/lwir16-proc/berdich3/linked/linked_compare/02/1697877412_004148-RECT-PIX0.01-FLAT_CLN-GEO0.tiff"
		};
		
		double [][][] image_points = {
				{   { 487.0,   637.0}, 
					{2237.5,   932.5},
					{2004.5,   1929.5},
					{ 320.5,   1951.500}
				},{
					{ 629.0,   179.0},
					{2376.5,   479.5},
					{2139.833, 1474.5},
					{ 461.5,   1493.5}}};
		boolean set_coords = true; // false;
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Set image pair",1090,900);
		gd.addStringField ("First image path",  image_paths[0], 120, "First image full path");		
		gd.addStringField ("Second image path", image_paths[1], 120, "Second image full path");		
		gd.addCheckbox    ("Set image markers to following coordinates", set_coords, "Delete markers and set them according to data.");
		for (int n = 0; n < image_points.length; n++) {
			for (int i = 0; i < image_points[n].length; i++) {
				gd.addMessage("image["+n+"] point "+i+":");
				gd.addNumericField("x =",              image_points[n][i][0],  3,7,"pix",
						"Marker "+i+ " for image "+n+" X-coordinate");
				gd.addNumericField("y =",              image_points[n][i][1],  3,7,"pix",
						"Marker "+i+ " for image "+n+" Y-coordinate");
			}
		}
		
		gd.showDialog();
		if (gd.wasCanceled()) return false;
		
		image_paths[0] = gd.getNextString();
		image_paths[1] = gd.getNextString();
		set_coords =     gd.getNextBoolean();
		for (int n = 0; n < image_points.length; n++) {
			for (int i = 0; i < image_points[n].length; i++) {
				image_points[n][i][0] = gd.getNextNumber();
				image_points[n][i][1] = gd.getNextNumber();
			}
		}
		imp_src1 = new ImagePlus(image_paths[0]);
		imp_src2 = new ImagePlus(image_paths[1]);
		
		if (set_coords) {
			PointRoi [] rois = new PointRoi[image_points.length];
			for (int n = 0; n < image_points.length; n++) {
				rois[n] = new PointRoi();
				rois[n].setOptions("label");
				for (int i = 0; i < image_points[n].length; i++) {
					rois[n].addPoint(image_points[n][i][0],image_points[n][i][1],i+1);
				}
			}
			imp_src1.setRoi(rois[0]);
			imp_src2.setRoi(rois[1]);
		}
		
		imp_src1.show();
		imp_src2.show();
		if (!(imp_src1.getRoi() instanceof PointRoi) || !(imp_src2.getRoi() instanceof PointRoi)) {
			System.out.println("At least one iomage does not have PointRoi");
			return false;
		}
		FloatPolygon [] fp = {((PointRoi) imp_src1.getRoi()).getContainedFloatPoints(),
				((PointRoi) imp_src2.getRoi()).getContainedFloatPoints()};
		int expected_points = image_points[0].length;
		for (int n = 0; n < fp.length; n++) {
			if (fp[n].xpoints.length != expected_points) {
				System.out.println("openTestPair(): expecting "+expected_points+" points, image "+n+"has "+fp[n].xpoints.length);
				return false;
			}
		}
		double [][][] points_xy= new double [fp.length][fp[0].xpoints.length][2];
		for (int n = 0; n < points_xy.length; n++) {
			for (int i = 0; i < points_xy[n].length; i++) {
				points_xy[n][i][0] = fp[n].xpoints[i];
				points_xy[n][i][1] = fp[n].ypoints[i];
			}
		}
		System.out.println(String.format("%4s\t%8s\t%8s\t%8s\t%8s\t%8s\t%8s\t%8s",
				"N","X0","Y0","X1","Y1", "length1", "length2", "ratio"));
		for (int i = 0; i < points_xy[0].length; i++) {
			int ip = (i - 1 + points_xy[0].length) % points_xy[0].length;
			double [] ll = new double[2];
			for (int n = 0; n < points_xy.length; n++) {
				double dx = points_xy[n][i][0]-points_xy[n][ip][0];
				double dy = points_xy[n][i][1]-points_xy[n][ip][1];
				ll[n] = Math.sqrt(dx*dx+dy*dy);
			}
			System.out.println(String.format("%4d\t%8.3f\t%8.3f\t%8.3f\t%8.3f\t%8.3f\t%8.3f\t%8.6f",
					i, points_xy[0][i][0], points_xy[0][i][1],points_xy[1][i][0], points_xy[1][i][1],
					ll[0], ll[1], ll[1]/ll[0]));
		}
		return true;
	}
	
	public static boolean testPair() {
		if (!(imp_src1.getRoi() instanceof PointRoi) || !(imp_src2.getRoi() instanceof PointRoi)) {
			System.out.println("At least one iomage does not have PointRoi");
			return false;
		}
		FloatPolygon [] fp = {((PointRoi) imp_src1.getRoi()).getContainedFloatPoints(),
				((PointRoi) imp_src2.getRoi()).getContainedFloatPoints()};
		int expected_points = 4; // image_points[0].length;
		for (int n = 0; n < fp.length; n++) {
			if (fp[n].xpoints.length != expected_points) {
				System.out.println("openTestPair(): expecting "+expected_points+" points, image "+n+"has "+fp[n].xpoints.length);
				return false;
			}
		}
		double [][][] points_xy= new double [fp.length][fp[0].xpoints.length][2];
		for (int n = 0; n < points_xy.length; n++) {
			for (int i = 0; i < points_xy[n].length; i++) {
				points_xy[n][i][0] = fp[n].xpoints[i];
				points_xy[n][i][1] = fp[n].ypoints[i];
			}
		}
		System.out.println(String.format("%4s\t%8s\t%8s\t%8s\t%8s\t%8s\t%8s\t%8s",
				"N","X0","Y0","X1","Y1", "length1", "length2", "ratio"));
		for (int i = 0; i < points_xy[0].length; i++) {
			int ip = (i - 1 + points_xy[0].length) % points_xy[0].length;
			double [] ll = new double[2];
			for (int n = 0; n < points_xy.length; n++) {
				double dx = points_xy[n][i][0]-points_xy[n][ip][0];
				double dy = points_xy[n][i][1]-points_xy[n][ip][1];
				ll[n] = Math.sqrt(dx*dx+dy*dy);
			}
			System.out.println(String.format("%4d\t%8.3f\t%8.3f\t%8.3f\t%8.3f\t%8.3f\t%8.3f\t%8.6f",
					i, points_xy[0][i][0], points_xy[0][i][1],points_xy[1][i][0], points_xy[1][i][1],
					ll[0], ll[1], ll[1]/ll[0]));
		}
		
		double [][] ab = getAffineMatrix(
				points_xy[1], //  double [][] x,
				points_xy[0]); // double [][] y){
        double [][] a = {{ab[0][0],ab[0][1]},{ab[1][0],ab[1][1]}};
        double []   b = {ab[2][0],ab[2][1]};
        System.out.println("Matrix A:");
        System.out.println(String.format("%10.5f\t%10.5f", a[0][0], a[0][1]));
        System.out.println(String.format("%10.5f\t%10.5f", a[1][0], a[1][1]));
        System.out.println("Matrix B:");
        System.out.println(String.format("%10.5f", b[0]));
        System.out.println(String.format("%10.5f", b[1]));
        final int [] widths = {imp_src1.getWidth(),imp_src2.getWidth()}; 
        final int [] heights = {imp_src1.getHeight(),imp_src2.getHeight()}; 
        int [][][] corners = new int[2][4][2];
        for (int n =0; n < corners.length; n++) {
        	corners[n][0][0] = 0;
        	corners[n][0][1] = 0;
        	corners[n][1][0] = widths[n]-1;
        	corners[n][1][1] = 0;
        	corners[n][2][0] = widths[n]-1;
        	corners[n][2][1] = heights[n]-1;
        	corners[n][3][0] = 0;
        	corners[n][3][1] = heights[n]-1;
        }
        double [][] fcorners = new double[corners[1].length][2];
        int [][] xy_minmax = {{0, widths[0]-1},{0, heights[0]-1}};
        int [][] xy_minmax1 = new int[2][2];
        for (int i = 0; i < corners[1].length; i++) {
        	fcorners[i][0] = corners[1][i][0] * a[0][0]+ corners[1][i][1] * a[0][1] + b[0];
        	fcorners[i][1] = corners[1][i][0] * a[1][0]+ corners[1][i][1] * a[1][1] + b[1];
        	xy_minmax[0][0] = Math.min(xy_minmax[0][0], (int) Math.floor(fcorners[i][0]));
        	xy_minmax[0][1] = Math.max(xy_minmax[0][1], (int) Math.ceil(fcorners[i][0]));
        	xy_minmax[1][0] = Math.min(xy_minmax[1][0], (int) Math.floor(fcorners[i][1]));
        	xy_minmax[1][1] = Math.max(xy_minmax[1][1], (int) Math.ceil(fcorners[i][1]));
        	if (i == 0) {
        		xy_minmax1[0][0] = (int) Math.floor(fcorners[i][0]);
        		xy_minmax1[0][1] = (int) Math.ceil(fcorners[i][0]);
        		xy_minmax1[1][0] = (int) Math.floor(fcorners[i][1]);
        		xy_minmax1[1][1] = (int) Math.ceil(fcorners[i][1]);
        	} else {
        		xy_minmax1[0][0] = Math.min(xy_minmax1[0][0], (int) Math.floor(fcorners[i][0]));
        		xy_minmax1[0][1] = Math.max(xy_minmax1[0][1], (int) Math.ceil(fcorners[i][0]));
        		xy_minmax1[1][0] = Math.min(xy_minmax1[1][0], (int) Math.floor(fcorners[i][1]));
        		xy_minmax1[1][1] = Math.max(xy_minmax1[1][1], (int) Math.ceil(fcorners[i][1]));
        	}
        }
        final int x0 = Math.max(0, -xy_minmax[0][0]);
        final int y0 = Math.max(0, -xy_minmax[1][0]);
        final int width =  x0 + xy_minmax[0][1]+1;
        final int height = y0 + xy_minmax[1][1]+1;
        double [][] opix = new double [2][width*height];
        for (int n = 0; n < opix.length; n++) {
        	Arrays.fill(opix[n], Double.NaN);
        }
		final Thread[] threads = ImageDtt.newThreadArray();
		final AtomicInteger ai = new AtomicInteger(0);
		final int width0= widths[0];
		final int npix0 = width0*heights[0];
		final float [] pixels0 = (float []) imp_src1.getProcessor().getPixels();
        for (int ithread = 0; ithread < threads.length; ithread++) {
            threads[ithread] = new Thread() {
                public void run() {
                    for (int ipix = ai.getAndIncrement(); ipix < npix0; ipix = ai.getAndIncrement()) {
                    	int y = ipix / width0;
                    	int x = ipix % width0;
                    	int p = x0 + x + (y0 + y)* width;
                    	opix[0][p] = pixels0[ipix];
                    }
                }
            };
        }		      
        ImageDtt.startAndJoin(threads);

        final int x10 = x0+ xy_minmax1[0][0]; // output image coordiantes
        final int y10 = y0+ xy_minmax1[1][0]; // output image coordiantes
        final int width1 =  x0 + xy_minmax1[0][1] + 1;
        final int height1 = y0 + xy_minmax1[0][1] + 1;
        final int npix1 = width1 * height1; 
		double [][] ab1 = getAffineMatrix(
				points_xy[0], //  double [][] x,
				points_xy[1]); // double [][] y){
        final double [][] a1 = {{ab1[0][0],ab1[0][1]},{ab1[1][0],ab1[1][1]}};
        final double []   b1 = {ab1[2][0]-x0,ab1[2][1]-y0}; // from output coordinates to image2 coordinates
        System.out.println("Matrix A1:");
        System.out.println(String.format("%10.5f\t%10.5f", a1[0][0], a1[0][1]));
        System.out.println(String.format("%10.5f\t%10.5f", a1[1][0], a1[1][1]));
        System.out.println("Matrix B1:");
        System.out.println(String.format("%10.5f", b1[0]));
        System.out.println(String.format("%10.5f", b1[1]));
        ai.set(0);
        final int img2_width = widths[1];
        final int img2_height = heights[1];
		final float [] pixels1 = (float []) imp_src2.getProcessor().getPixels();
        for (int ithread = 0; ithread < threads.length; ithread++) {
            threads[ithread] = new Thread() {
                public void run() {
                    for (int ipix = ai.getAndIncrement(); ipix < npix1; ipix = ai.getAndIncrement()) {
                    	int y = ipix / width1 + x10; // output image coordinates
                    	int x = ipix % width1 + y10; // output image coordinates
                    	int p = x + y * width;
                    	double fx = a1[0][0] * x + a1[0][1] * y + b1[0];
                    	double fy = a1[1][0] * x + a1[1][1] * y + b1[1];
                    	int ix = (int) Math.floor(fx);
                    	int iy = (int) Math.floor(fy);
                    	double dx = fx - ix;
                    	double dy = fy - iy;
                    	if ((ix >= 0) && (iy >= 0) && (ix < (img2_width -1)) && (iy < (img2_height -1))) {
                    		int indx0 = ix + iy * img2_width;
                    		double d00 = pixels1[indx0];
                    		double d01 = pixels1[indx0+1];
                    		double d10 = pixels1[indx0+img2_width];
                    		double d11 = pixels1[indx0+img2_width+1];
                    		opix[1][p] = d00*(1-dx)*(1-dy)+d01*dx*(1-dy)+d10*(1-dx)*dy+d11*dx*dy;
                    	}
                    }
                }
            };
        }		      
        ImageDtt.startAndJoin(threads);
        
        ShowDoubleFloatArrays.showArrays( 
        		opix,
        		width,
        		height,
        		true,
        		"overlap");

        
		return true;
	}
	
	public static double [][] getAffineMatrix(
			double [][] x,
			double [][] y){
		if (x.length != y.length) {
			System.out.println("getAffineMatrix(): x.length != y.length ("+x.length+" != "+y.length+")");
			return null;
		}
		/*
 Ui =  A00 * Xi + A01* Yi + B0
 Vi =  A10 * Xi + A11* Yi + B1
  
 A00^2*Xi^2 + A01^2*Yi^2 +B0^2 +Ui^2 + 2*A00*Xi*A01*Yi + 2*A00*Xi*B0 - 2*A00*Xi*Ui + 2*A01*Yi*B0 -2*A01*Yi*Ui - 2*B0*Ui
 d/2dA00 = A00*Xi^2 + A01*Xi*Yi + B0 *Xi - XiUi = 0
 d/2dA01 = A01*Yi^2 + A00*Xi*Yi + B0 *Yi - YiUi = 0
 d/2dB0 =  B0       + A00*Xi    + A01*Yi - Ui   = 0

 d/2dA10 = A10*Xi^2 + A11*Xi*Yi + B1 *Xi - XiVi = 0
 d/2dA11 = A11*Yi^2 + A10*Xi*Yi + B1 *Yi - YiVi = 0
 d/2dB1 =  B1       + A10*Xi    + A11*Yi - Vi   = 0
 
           SX2*A00 + SXY*A01 +   0*A10 +   0*A11 + SX*B0 +  0*B1  =  SXU
           SXY*A00 + SY2*A01 +   0*A10 +   0*A11 + SY*B0 +  0*B1  =  SYU
           SX *A00 + SY *A01 +   0*A10 +   0*A11 +  1*B0 +  0*B1  =  SU
           0  *A00 +  0 *A01 + SX2*A10 + SXY*A11 +  0*B0 + SX*B1  =  SXV
           0  *A00 +  0 *A01 + SXY*A10 + SY2*A11 +  0*B0 + SY*B1  =  SYV
           0  *A00 +  0 *A01 + SX *A10 + SY *A11 +  0*B0 +  1*B1  =  SV
        		 */
		double sx=0, sy=0, sx2=0, sy2=0, sxy=0,sxu=0,syu=0,sxv=0,syv=0,su=0,sv=0;
		for (int n = 0; n < x.length; n++) {
			sx += x[n][0];
			sy += x[n][1];
			sx2+= x[n][0]*x[n][0];
			sy2+= x[n][1]*x[n][1];
			sxy+= x[n][0]*x[n][1];
			
			sxu+= x[n][0]*y[n][0];
			syu+= x[n][1]*y[n][0];
			su+=  y[n][0];
			
			sxv+= x[n][0]*y[n][1];
			syv+= x[n][1]*y[n][1];
			sv+=  y[n][1];
		}
		double s0 = x.length;
		double [][] a = {
				{sx2, sxy,   0,   0, sx,  0},
				{sxy, sy2,   0,   0, sy,  0},
				{ sx,  sy,   0,   0, s0,  0},
				{  0,   0, sx2, sxy,  0, sx},
				{  0,   0, sxy, sy2,  0, sy},
				{  0,   0,  sx,  sy,  0, s0}};
		Matrix A = new Matrix(a);
		A.print(15,4);
		double [][] b = {{sxu,syu,su,sxv,syv,sv}};
		Matrix B = new Matrix(b).transpose();
		B.print(15,4);
		double [] v = A.solve(B).getRowPackedCopy();
		double [][] ab = {
				{v[0], v[1]},   // |a00, a01|,
				{v[2], v[3]},   // |a10, a11|,
				{v[4], v[5]}};  // | b0,  b1|,
		return ab;	
	}
	public static boolean testReadTiff() {
		String image_paths="";
		
		
		return true;
	}

	/**
	 * Pad images to fixed dimensions for GPU processing. If no zoom out
	 * is required - just copy, otherwise scale down towards top-left corner.
	 * All unused data is set to NaN
	 * 
	 * @param spix    source float image
	 * @param swidth  width of the source image
	 * @param zoomout zoom out level (binary logarithm), so 1 is zoom out twice
	 * @param width   width of the output image (multiple of 16?)
	 * @param height  height of the output image (multiple of 16?)
	 * @return padded (optionally scaled) image of width*height size
	 */
	
	public static float [] padScaleImage(
			float [] spix,
			int      swidth,
			int      zoomout,
			int      width,
			int      height) {
		float [] opix = new float [width*height];
		final int sheight = spix.length/swidth;
		int cwidth = Math.min(swidth,  width);
		Arrays.fill(opix, Float.NaN);
		if (zoomout == 0) {
			int cheight = Math.min(height, sheight);
			for (int line = 0; line < cheight; line++) {
				System.arraycopy(
						spix,
						line*swidth,
						opix,
						line*width,
						cwidth);
			}
		} else {
			int rscale = 1;
			for (int i = 0; i < zoomout;i++) {
				rscale *= 2;
			}
			final int frscale = rscale;
			final int tilesX = (swidth+frscale-1)/frscale;
			final int tilesY = (sheight+frscale-1)/frscale;
			final int tiles = tilesX*tilesY;
			final double [][] wnd = new double [frscale] [frscale];
			for (int i = 0; i <frscale; i++) {
				for (int j = 0; j <frscale; j++) {
					wnd[i][j] = Math.sin((i+0.5)/frscale*Math.PI);
				}
			}
			final Thread[] threads = ImageDtt.newThreadArray();
			final AtomicInteger ai = new AtomicInteger(0);
	        for (int ithread = 0; ithread < threads.length; ithread++) {
	            threads[ithread] = new Thread() {
	                public void run() {
	                    for (int tile = ai.getAndIncrement(); tile < tiles; tile = ai.getAndIncrement()) {
	                    	int tileX = tile%tilesX;
	                    	int tileY = tile/tilesX;
	                    	if ((tileX < width) && (tileY < height)) {
	                    		int tileW= (frscale*(tileX+1) <= swidth) ? frscale:(swidth - frscale*tileX);  
	                    		int tileH= (frscale*(tileY+1) <= sheight) ? frscale:(sheight - frscale*tileY);
	                    		double sw = 0.0, swd = 0.0;
	                    		for (int py = 0; py < tileH; py++) {
	                    			int ls = (tileY*frscale + py)*swidth;
	                    			for (int px = 0; px < tileW; px++) {
	                    				double d = spix[ls + tileX*frscale+px];
	                    				if (!Double.isNaN(d)) {
	                    					double w = wnd[py][px];
	                    					sw += w;
	                    					swd += w*d;
	                    				}
	                    				if (sw > 0) {
	                    					opix[tileY * width + tileX] = (float) (swd/sw);
	                    				}
	                    			}
	                    		}
	                    	}
	                    }
	                }
	            };
	        }		      
	        ImageDtt.startAndJoin(threads);
		}
		return opix;
	}
	
	public static void renderFromTD (
			boolean             use_reference,
			String              suffix
			) {
		
		GPU_QUAD_AFFINE.execImcltRbgAll(
        		(GPU_QUAD_AFFINE.num_colors <=1), // isMonochrome(),
        		use_reference,
				null); // wh); //int [] wh
		// get data back from GPU
		final float [][][] iclt_fimg = new float [GPU_QUAD_AFFINE.num_cams][][];
		for (int ncam = 0; ncam < iclt_fimg.length; ncam++) {
			iclt_fimg[ncam] = GPU_QUAD_AFFINE.getRBG(ncam); // updated window
		}
		
		int out_width =  GPU_QUAD_AFFINE.getImageWidth();//   + gpuQuad.getDttSize(); // 2022/05/12 removed margins from gpuQuad.getRBG(ncam);
		int out_height = GPU_QUAD_AFFINE.getImageHeight(); // + gpuQuad.getDttSize(); // 2022/05/12 removed margins from gpuQuad.getRBG(ncam);
		
        ShowDoubleFloatArrays.showArrays( 
        		iclt_fimg[0],
        		out_width,
        		out_height,
        		true,
        		suffix);
	}
	
	public static void fillRandomFloatNans(
			float [][] pixels,
			double scale) {
		float fscale = (float) scale;
		Random rnd=new Random();
		for (int n = 0; n < pixels.length;n++) {
			for (int i = 0; i <pixels[n].length; i++) if (Float.isNaN(pixels[n][i])){
				pixels[n][i] = rnd.nextFloat() * fscale;
			}
		}
	}
	
	
}
