Commit d8f3ec21 authored by Andrey Filippov's avatar Andrey Filippov
Browse files

Optimizing pairs generation

parent 1cc8ef8f
Loading
Loading
Loading
Loading
+66 −28
Original line number Original line Diff line number Diff line
@@ -2,6 +2,7 @@ package com.elphel.imagej.orthomosaic;


import java.awt.Point;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
@@ -48,6 +49,13 @@ public class ComboMatch {
	public static Properties [] imp_prop; //  = new Properties[2];
	public static Properties [] imp_prop; //  = new Properties[2];
	public static int gpu_max_width=  4096;
	public static int gpu_max_width=  4096;
	public static int gpu_max_height= 4096;
	public static int gpu_max_height= 4096;
	public static String [] FILES_LISTS_PATHS = {
			"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_sep12-13_50-25-50-75-100m",
			"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_19_sep13_25-50-75-100m",
			"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_nov3_50-75",
			"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_sep12-50m",
			"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_sep12-13_50-25-50-75-100m-SUBSET"};
	
	
	
	public static boolean openTestPairGps(
	public static boolean openTestPairGps(
			 CLTParameters    clt_parameters,
			 CLTParameters    clt_parameters,
@@ -110,13 +118,9 @@ public class ComboMatch {
//		String orthoMapsCollection_path = "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_10_short.data";
//		String orthoMapsCollection_path = "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_10_short.data";
//		String files_list_path =          "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_19_sep13_25-50-75-100m.list";
//		String files_list_path =          "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_19_sep13_25-50-75-100m.list";
//		String orthoMapsCollection_path = "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_19_sep13_25-50-75-100m.data";
//		String orthoMapsCollection_path = "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_19_sep13_25-50-75-100m.data";
		String [] files_lists_paths = {
//		String [] files_lists_paths = FILES_LISTS_PATHS;
				"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_sep12-13_50-25-50-75-100m",
		
				"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_19_sep13_25-50-75-100m",
//		int default_list_choice = 2; // files_lists_paths.length-1;
				"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_nov3_50-75",
				"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_sep12-50m",
				"/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_sep12-13_50-25-50-75-100m-SUBSET"};
		int default_list_choice = 0; // files_lists_paths.length-1;
//		String files_list_path =          "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_nov3_50-75.list";
//		String files_list_path =          "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_nov3_50-75.list";
//		String orthoMapsCollection_path = "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_nov3_50-75.data";
//		String orthoMapsCollection_path = "/media/elphel/SSD3-4GB/lwir16-proc/ortho_videos/maps_nov3_50-75.data";
//maps_nov3_50-75		
//maps_nov3_50-75		
@@ -155,7 +159,8 @@ public class ComboMatch {
		int gpu_height= clt_parameters.imp.rln_gpu_height; // 3008;
		int gpu_height= clt_parameters.imp.rln_gpu_height; // 3008;
		int zoom_lev = -3; // 0; // +1 - zoom in twice, -1 - zoom out twice
		int zoom_lev = -3; // 0; // +1 - zoom in twice, -1 - zoom out twice
		boolean show_combo =              false; // true;
		boolean show_combo =              false; // true;
		boolean create_overlaps =     false;
		boolean create_pairwise_matches = false;
		boolean create_pairwise_affines = false;
		boolean equalize_overlaps =       false;
		boolean equalize_overlaps =       false;
		boolean create_map =              false;
		boolean create_map =              false;
		boolean create_equalize =         false;
		boolean create_equalize =         false;
@@ -180,7 +185,7 @@ public class ComboMatch {
		String [] suffixes_bl_bc=     {"","-BL","-BC"};
		String [] suffixes_bl_bc=     {"","-BL","-BC"};
		boolean log_append =           clt_parameters.imp.pwise_log_append;
		boolean log_append =           clt_parameters.imp.pwise_log_append;
		String log_path =              clt_parameters.imp.pwise_log_path;
		String log_path =              clt_parameters.imp.pwise_log_path;
		
		String omtch_img_set =         clt_parameters.imp.omtch_img_set;
		
		
		int  suffix_bc_bl_indx =   2;
		int  suffix_bc_bl_indx =   2;
		if (!use_marked_image) {
		if (!use_marked_image) {
@@ -191,9 +196,9 @@ public class ComboMatch {
			pattern_match = false;
			pattern_match = false;
		}
		}
		
		
		
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Set image pair",1200,800);
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Set image pair",1200,800);
		gd.addChoice      ("Files list/data path (w/o extension):", files_lists_paths, files_lists_paths[default_list_choice]);
//		gd.addChoice      ("Files list/data path (w/o extension):", FILES_LISTS_PATHS, FILES_LISTS_PATHS[default_list_choice]);
		gd.addChoice      ("Files list/data path (w/o extension):", FILES_LISTS_PATHS, omtch_img_set);
		gd.addCheckbox    ("Use saved maps collection", use_saved_collection, "If false - use files list.");
		gd.addCheckbox    ("Use saved maps collection", use_saved_collection, "If false - use files list.");
		gd.addCheckbox    ("Save maps collection", save_collection, "Save maps collection to be able to restore.");
		gd.addCheckbox    ("Save maps collection", save_collection, "Save maps collection to be able to restore.");
		gd.addCheckbox    ("Process correlations", process_correlation, "false to skip to just regenerate new save file.");
		gd.addCheckbox    ("Process correlations", process_correlation, "false to skip to just regenerate new save file.");
@@ -230,7 +235,8 @@ public class ComboMatch {
//		gd.addCheckbox    ("Show transformation centers",      show_centers, "Mark verticals from the UAS on the ground.");
//		gd.addCheckbox    ("Show transformation centers",      show_centers, "Mark verticals from the UAS on the ground.");
//		gd.addCheckbox    ("Show statistics for ortho images", show_map_stats, "Generate and show statistics for ortho maps.");
//		gd.addCheckbox    ("Show statistics for ortho images", show_map_stats, "Generate and show statistics for ortho maps.");
		gd.addCheckbox    ("Show combo maps/stats",            show_combo, "Generate/save combo maps and stats.");
		gd.addCheckbox    ("Show combo maps/stats",            show_combo, "Generate/save combo maps and stats.");
		gd.addCheckbox    ("Create overlap pairs",             create_overlaps, "Create scene pairs overlaps.");
		gd.addCheckbox    ("Create overlap pairs",             create_pairwise_matches, "Create scene pairs overlaps.");
		gd.addCheckbox    ("Create pairwise affines",          create_pairwise_affines, "Create affines for scene pairs.");
		gd.addCheckbox    ("Equalize overlap pairs",           equalize_overlaps, "Equalize intensities in overlaps.");
		gd.addCheckbox    ("Equalize overlap pairs",           equalize_overlaps, "Equalize intensities in overlaps.");
		gd.addCheckbox    ("Create map",                       create_map, "Create combined map from pairwise matches.");
		gd.addCheckbox    ("Create map",                       create_map, "Create combined map from pairwise matches.");
		gd.addCheckbox    ("Equalize intensities",             create_equalize, "Create map intensities equalization from pairwise matches.");
		gd.addCheckbox    ("Equalize intensities",             create_equalize, "Create map intensities equalization from pairwise matches.");
@@ -257,9 +263,14 @@ public class ComboMatch {
//		update_kernel_patterns
//		update_kernel_patterns
		gd.showDialog();
		gd.showDialog();
		if (gd.wasCanceled()) return false;
		if (gd.wasCanceled()) return false;
		int choice_index = gd.getNextChoiceIndex();
		
		String files_list_path =         files_lists_paths[choice_index]+".list";
		
		String orthoMapsCollection_path =files_lists_paths[choice_index]+".data";
//		int choice_index = gd.getNextChoiceIndex();
//		String files_list_path =         FILES_LISTS_PATHS[choice_index]+".list";
//		String orthoMapsCollection_path =FILES_LISTS_PATHS[choice_index]+".data";
		omtch_img_set = ComboMatch.FILES_LISTS_PATHS[gd.getNextChoiceIndex()];
		String files_list_path =         omtch_img_set+".list";
		String orthoMapsCollection_path =omtch_img_set+".data";
		use_saved_collection =     gd.getNextBoolean();
		use_saved_collection =     gd.getNextBoolean();
		save_collection =          gd.getNextBoolean();
		save_collection =          gd.getNextBoolean();
		String orthoMapsCollection_savepath = save_collection?orthoMapsCollection_path:null;
		String orthoMapsCollection_savepath = save_collection?orthoMapsCollection_path:null;
@@ -289,7 +300,8 @@ public class ComboMatch {
		OrthoMap.setGPUWidthHeight(gpu_width,gpu_height);
		OrthoMap.setGPUWidthHeight(gpu_width,gpu_height);
		
		
		show_combo =                gd.getNextBoolean();
		show_combo =                gd.getNextBoolean();
		create_overlaps =           gd.getNextBoolean();
		create_pairwise_matches =   gd.getNextBoolean();
		create_pairwise_affines =   gd.getNextBoolean();
		equalize_overlaps =         gd.getNextBoolean();
		equalize_overlaps =         gd.getNextBoolean();
		create_map =                gd.getNextBoolean();
		create_map =                gd.getNextBoolean();
		create_equalize =           gd.getNextBoolean();
		create_equalize =           gd.getNextBoolean();
@@ -753,7 +765,9 @@ public class ComboMatch {
//		String [] gpu_spair = {names[gpu_ipair[0]],names[gpu_ipair[1]]}; 
//		String [] gpu_spair = {names[gpu_ipair[0]],names[gpu_ipair[1]]}; 
		int [] origin = new int[2];
		int [] origin = new int[2];
		if (show_combo) { // now all modes, and stats?
		if (show_combo) { // now all modes, and stats?
			maps_collection.processComboMap(debugLevel);
			maps_collection.processComboMap(
					clt_parameters, // CLTParameters    clt_parameters,
					debugLevel);
			return true;
			return true;
		}
		}
		
		
@@ -790,11 +804,22 @@ public class ComboMatch {
					orthoMapsCollection_savepath); // String orthoMapsCollection_path
					orthoMapsCollection_savepath); // String orthoMapsCollection_path
			return (ok >= 0);
			return (ok >= 0);
		}
		}
		if (create_pairwise_matches) {
			boolean ok =maps_collection.getOverlapPairs(
					clt_parameters, // CLTParameters    clt_parameters,
					orthoMapsCollection_savepath); // String orthoMapsCollection_path);
			return ok; // Just exit, do not try other commands.  if (!ok) return false;
		}
		
		
		if (create_overlaps) {
		if (create_pairwise_affines) {
			boolean ok =maps_collection.getIntersectedPairs(
//			boolean ok =maps_collection.getIntersectedPairs(
//					clt_parameters, // CLTParameters    clt_parameters,
//					orthoMapsCollection_savepath); // String orthoMapsCollection_path);
			
			boolean ok =maps_collection.generatePairwiseAffines(
					clt_parameters, // CLTParameters    clt_parameters,
					clt_parameters, // CLTParameters    clt_parameters,
					orthoMapsCollection_savepath); // String orthoMapsCollection_path);
					orthoMapsCollection_savepath); // String orthoMapsCollection_path);
			
			return ok; // Just exit, do not try other commands.  if (!ok) return false;
			return ok; // Just exit, do not try other commands.  if (!ok) return false;
		}
		}
		if (equalize_overlaps) {
		if (equalize_overlaps) {
@@ -1005,7 +1030,7 @@ public class ComboMatch {
        				if (process_correlation) {
        				if (process_correlation) {
        					if (update_match) {
        					if (update_match) {
        						pairwiseOrthoMatch.zoom_lev = zoom_lev;
        						pairwiseOrthoMatch.zoom_lev = zoom_lev;
        						pairwiseOrthoMatch.affine = affines[1];
        						pairwiseOrthoMatch.setAffine(affines[1]);
        						maps_collection.ortho_maps[gpu_pair[0]].setMatch(
        						maps_collection.ortho_maps[gpu_pair[0]].setMatch(
        								maps_collection.ortho_maps[gpu_pair[1]].getName(),
        								maps_collection.ortho_maps[gpu_pair[1]].getName(),
        								pairwiseOrthoMatch);
        								pairwiseOrthoMatch);
@@ -1272,6 +1297,13 @@ public class ComboMatch {
		}
		}
		for (int i = 0; i < filenames.length; i++) {
		for (int i = 0; i < filenames.length; i++) {
			maps_collection.ortho_maps[i].setFileName(filenames[i]);
			maps_collection.ortho_maps[i].setFileName(filenames[i]);
			String path = maps_collection.ortho_maps[i].getPath();
			if (!(new File(path)).exists()) {
				System.out.println("updateBlBcFileNames() does not exist - fix manually: "+path);
				System.out.println("This can happen if files have different resolution for new run.");
				System.out.println("Verify path, then modify filenames[i], break at line above: 'maps_collection.ortho_maps[i].setFileName(filenames[i])'.");
				System.out.println("Reduce i by 1 and continue");
			}
		}		
		}		
		return true;
		return true;
	}
	}
@@ -1482,16 +1514,22 @@ adjusted affines[1] for a pair: 1694564291_293695/1694564778_589341
					points_xy[nn][1] = fp.ypoints[p];
					points_xy[nn][1] = fp.ypoints[p];
					pair_slices[nn] = pRoi.getPointPosition(p); // works
					pair_slices[nn] = pRoi.getPointPosition(p); // works
				}
				}
				PairwiseOrthoMatch match= new PairwiseOrthoMatch();
//				PairwiseOrthoMatch match= new PairwiseOrthoMatch();
				match.affine = new double[][] {
				double [][] affine = {
					{1,0,(points_xy[1][0]-points_xy[0][0])*pix_size},
					{1,0,(points_xy[1][0]-points_xy[0][0])*pix_size},
					{0,1,(points_xy[1][1]-points_xy[0][1])*pix_size}};
					{0,1,(points_xy[1][1]-points_xy[0][1])*pix_size}};
				PairwiseOrthoMatch match= new PairwiseOrthoMatch(
						affine, // double [][] affine,
						new double [6][6], // double [][] jtj,
						Double.NaN, // double rms,
						zoom_level, // int zoom_lev,
						1.0); // double overlap);
				for (int n = 0; n < points_xy.length; n++) {	
				for (int n = 0; n < points_xy.length; n++) {	
					names[n] = stack_scenes.getSliceLabel(pair_slices[n]).substring(0,17);
					names[n] = stack_scenes.getSliceLabel(pair_slices[n]).substring(0,17);
				}
				}
				System.out.println("names = [\""+names[0]+"\", \""+names[1]+"\"]");
				System.out.println("names = [\""+names[0]+"\", \""+names[1]+"\"]");
				System.out.println("match.affine = [["+match.affine[0][0]+", "+match.affine[0][1]+", "+match.affine[0][2]+"],");
				System.out.println("match.affine = [["+affine[0][0]+", "+affine[0][1]+", "+affine[0][2]+"],");
				System.out.println("                ["+match.affine[1][0]+", "+match.affine[1][1]+", "+match.affine[1][2]+"]]");
				System.out.println("                ["+affine[1][0]+", "+affine[1][1]+", "+affine[1][2]+"]]");
				return match;
				return match;
			}
			}
		}
		}
+93 −2
Original line number Original line Diff line number Diff line
@@ -216,9 +216,33 @@ public class OrthoMap implements Comparable <OrthoMap>, Serializable{
		pairwise_matches.remove(name);
		pairwise_matches.remove(name);
	}
	}


	public PairwiseOrthoMatch getMatch(
			String name,
			boolean with_undefined) {
		PairwiseOrthoMatch match = pairwise_matches.get(name);
		if (match == null) return null;
		if (with_undefined || match.isDefined()) return match;
		return null;
	}

	
	
	
	public PairwiseOrthoMatch getMatch(String name) {
	public PairwiseOrthoMatch getMatch(String name) {
		return pairwise_matches.get(name);
		return getMatch(name, false); // old - do not count undefined
//		return pairwise_matches.get(name);
	}
	
	public void unsetMatches(boolean undefined_only) {
		if (undefined_only) {
			String [] matches = pairwise_matches.keySet().toArray(new String[0]);
			for (String match:matches) {
				if (!getMatch(match).isDefined()) {
					unsetMatch(match);
				}
			}
		} else {
			pairwise_matches = new HashMap<String, PairwiseOrthoMatch>();
		}
	}
	}
	
	
	public int getOriginalZoomLevel() {
	public int getOriginalZoomLevel() {
@@ -480,7 +504,9 @@ public class OrthoMap implements Comparable <OrthoMap>, Serializable{
		averageImagePixel = Double.NaN; // average image pixel value (to combine with raw)
		averageImagePixel = Double.NaN; // average image pixel value (to combine with raw)
	}
	}
	
	
	
	public double getTimeStamp() {
		return ts;
	}
	public LocalDateTime getLocalDateTime() {
	public LocalDateTime getLocalDateTime() {
		return dt;
		return dt;
	}
	}
@@ -5244,6 +5270,71 @@ public class OrthoMap implements Comparable <OrthoMap>, Serializable{
		return pattern;
		return pattern;
	}
	}
	
	
	/**
	 * Decomposing linear transform into rotation
	 * R1={{cos(beta),sin(beta)},{-sin(beta), cos(beta)}} ,
	 * non-uniform scale transform W={{w1,0}{0,w2}}, and rotation
	 * R2={{cos(gamma),sin(gamma)},{-sin(gamma), cos(gamma)}}
	 * A=R1*W*R2 using singular value decomposition
	 * https://math.stackexchange.com/questions/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation   
	 * @param A - input 2x2 matrix
	 * @return {s,beta,w,gamma,beta+gamma}
	 * A = B + C
	 * b00= b11; c00=-c01
	 * b10=-b01; c10= c01
	 */
	public static double [] singularValueDecompose(
			double [][] A) {
		double a00=A[0][0],a01=A[0][1],a10=A[1][0],a11=A[1][1];
		double b00=(a00+a11)/2; // , b11 = b00;
		double c00=(a00-a11)/2; //, c11 =-c00;
		double b01=(a01-a10)/2; //, b10 =-b01;
		double c01=(a01+a10)/2; //, c10 = c01;
		double w1_p_w2_2= Math.sqrt(b00*b00+b01*b01);
		double w1_m_w2_2= Math.sqrt(c00*c00+c01*c01);
		double w1 = w1_p_w2_2 + w1_m_w2_2;
		double w2 = w1_p_w2_2 - w1_m_w2_2;
		double g_p_b = Math.atan2(b01, b00);
		double g_m_b = Math.atan2(c01, c00);
		double gamma = (g_p_b + g_m_b)/2; 
		double beta =  (g_p_b - g_m_b)/2; 
		return new double [] {beta,w1,w2,gamma,g_p_b};
	}
	/**
	 * Use singular value decomposition and then split scaling {{w1,0},{0,w1}}
	 * into overall scaling caused by zoom != 1.0 because of altitude error
	 * and unidirectional scaling caused by tilted projection plane. As the
	 * input linear transformation matrix converts ground coordinates to source
	 * image coordinates, the scale in the tilt direction is > than scale in the
	 * perpendicular direction (tilt axis).
	 * Matrix R1 is additionally rotated by PI/2 if needed so W={{w1,0},{0,w2}}
	 * has w2>=w1 and W={{s,0},{0,s/t}}, where t <= 1.0 and equals to cos(tilt) 
	 * 
	 * @param A - linear transformation matrix from rectified ground coordinates
	 *        to source image coordinates. OK to use 2x3 affine matrix,extra
	 *        components will be ignored. 
	 * @return {beta, s, t, beta+gamma}, beta+gamma - total rotation
	 */
	public static double [] singularValueDecomposeScaleTilt(
			double [][] A) {
		double [] svd=singularValueDecompose(A);
		double w1 = svd[1], w2 = svd[2], beta=svd[0], g_p_b=svd[4];
		
		// considering tilt in y direction, it should have higher scale
		// (and source image coordinates), while X should correspond to
		// the axis of rotation, and scale is just scale caused by error in
		// altitude.
		double s = Math.min(w1, w2);
		double t = w1/w2; // <=1.0, ==cos(tilt)
		if (w1 > w2) { // rotate tilt by PI/2
			t = w2/w1;
			beta += Math.PI/2;
			if (beta > Math.PI) {
				beta -= 2* Math.PI;
			}
		}
		return new double [] {beta, s,t, g_p_b};
	}
	
	
	
	
	
	
+853 −11

File changed.

Preview size limit exceeded, changes collapsed.

+597 −0

File added.

Preview size limit exceeded, changes collapsed.

+17 −5
Original line number Original line Diff line number Diff line
@@ -9,7 +9,7 @@ import Jama.Matrix;


public class PairwiseOrthoMatch implements Serializable {
public class PairwiseOrthoMatch implements Serializable {
	private static final long serialVersionUID = 1L;
	private static final long serialVersionUID = 1L;
	public double [][]           affine = new double[2][3];
	private double [][]           affine = new double[2][3];
	public transient double [][] jtj =    new double [6][6];
	public transient double [][] jtj =    new double [6][6];
	public int                   zoom_lev;
	public int                   zoom_lev;
	public double                rms = Double.NaN;
	public double                rms = Double.NaN;
@@ -17,12 +17,14 @@ public class PairwiseOrthoMatch implements Serializable {
	public transient double      overlap = 0.0;
	public transient double      overlap = 0.0;
	public transient double []   equalize1to0 = {1,0}; // value1 = equalize2to1[0]*value2+equalize2to1[1]
	public transient double []   equalize1to0 = {1,0}; // value1 = equalize2to1[0]*value2+equalize2to1[1]


	public PairwiseOrthoMatch() {
//	public PairwiseOrthoMatch() {}
		
	}
	public double getOverlap() {
	public double getOverlap() {
		return overlap;
		return overlap;
	}
	}
	public void setOverlap(double overlap) {
		this.overlap =overlap;
	}
	
	public PairwiseOrthoMatch(
	public PairwiseOrthoMatch(
			double [][] affine,
			double [][] affine,
			double [][] jtj,
			double [][] jtj,
@@ -36,6 +38,9 @@ public class PairwiseOrthoMatch implements Serializable {
		this.overlap = overlap;
		this.overlap = overlap;
	}
	}
	
	
	public boolean isDefined() {
		return affine != null;
	}
	public double [] getEqualize2to1() {
	public double [] getEqualize2to1() {
		return equalize1to0;
		return equalize1to0;
	}
	}
@@ -52,7 +57,7 @@ public class PairwiseOrthoMatch implements Serializable {
	}
	}
	
	
	public PairwiseOrthoMatch clone() {
	public PairwiseOrthoMatch clone() {
		double [][] affine = {this.affine[0].clone(),this.affine[1].clone()};
		double [][] affine = (this.affine==null) ? null: (new double [][] {this.affine[0].clone(),this.affine[1].clone()});
		double [][] jtj = new double [this.jtj.length][];
		double [][] jtj = new double [this.jtj.length][];
		for (int i = 0; i < this.jtj.length; i++) {
		for (int i = 0; i < this.jtj.length; i++) {
			jtj[i] = this.jtj[i].clone();
			jtj[i] = this.jtj[i].clone();
@@ -221,9 +226,16 @@ public class PairwiseOrthoMatch implements Serializable {
	}	
	}	
	
	
	public double [][] getAffine(){
	public double [][] getAffine(){
		if (affine == null) {
			return new double [][] {{1,0,0},{0,1,0}};
		}
		return affine;
		return affine;
	}
	}
	
	
	public void setAffine(double [][] affine) {
		this.affine= affine;
	}
	
	private void writeObject(ObjectOutputStream oos) throws IOException {
	private void writeObject(ObjectOutputStream oos) throws IOException {
		oos.defaultWriteObject();
		oos.defaultWriteObject();
		for (int i = 0; i < jtj.length; i++) {
		for (int i = 0; i < jtj.length; i++) {
Loading