Loading src/main/java/com/elphel/imagej/common/DoubleFHT.java +31 −8 Original line number Diff line number Diff line Loading @@ -293,6 +293,22 @@ public class DoubleFHT { return result; } public double [] getFrequencyFilter ( double [] data, // just to find out size double highPassSigma, double lowPassSigma ) { double [] filter= null; if ((highPassSigma> 0.0) || (lowPassSigma> 0.0)) { updateMaxN(data); createFrequencyFilter(highPassSigma, lowPassSigma); // for repetitive calls will reuse mask filter =this.freqMask; } return filter; } /** * Invert kernel for deconvolutions. Kernel is assumed to fade near edges * @param data source kernel, square, power of 2 sides. Data is destroyed Loading @@ -314,7 +330,6 @@ public class DoubleFHT { filter =this.freqMask; } return invert (data, fat_zero, filter); } /** Loading Loading @@ -401,8 +416,6 @@ public class DoubleFHT { } public double[] phaseCorrelate(double[] first, double phaseCoeff, double high_pass, double low_pass, // high/low // pass // filtering double[] fht_save) { // null-OK updateMaxN(first); double[] filter = null; Loading Loading @@ -503,7 +516,10 @@ public class DoubleFHT { * @return phase correlation, first still contains original transformed! */ public double[] phaseCorrelatePattern( // new double[] first, double[] secondFD, double phaseCoeff, double[] filter, // high/low pass filtering double[] first, double[] secondFD, double phaseCoeff, double[] filter, // high/low pass filtering double[] first_save) { // null-OK if (first.length != secondFD.length) { IJ.showMessage("Error", "Correlation arrays should be the same size"); Loading Loading @@ -537,7 +553,9 @@ public class DoubleFHT { */ public double[] convolvePattern( // new double[] first, double[] secondFD, double[] filter, // high/low pass filtering double[] first, double[] secondFD, double[] filter, // high/low pass filtering double[] first_save) { // null-OK if (first.length != secondFD.length) { IJ.showMessage("Error", "Correlation arrays should be the same size"); Loading Loading @@ -595,11 +613,16 @@ public class DoubleFHT { return first; } public double[] convolve(double[] first, double[] second) { public double[] convolve( double[] first, double[] second) { return convolve(first, second, null); } public double[] convolve(double[] first, double[] second, double[] filter) { // high/low pass filtering public double[] convolve( double[] first, double[] second, double[] filter) { // high/low pass filtering // System.out.println("correlate"); if (first.length != second.length) { IJ.showMessage("Error", "Correlation arrays should be the same size"); Loading src/main/java/com/elphel/imagej/orthomosaic/ComboMatch.java +26 −17 Original line number Diff line number Diff line Loading @@ -544,13 +544,13 @@ public class ComboMatch { } imp_obj.setRoi(roi); // calculate statistics for cor_ret[i] double [][] stats = new double [object_stack.length][]; CorrelationPeakStats [] stats = new CorrelationPeakStats [object_stack.length]; double search_rad = 15.0; double frac_max = 0.5; double other_rad = 25; for (int i = 0; i < object_stack.length; i++) { double [] a_cent= {corr_size/2+centers[i][0],corr_size/2+centers[i][1]}; stats[i]=ObjectLocation.getMaxLocArea( stats[i]=new CorrelationPeakStats( corr_ret[i], // double [] data, // square data a_cent, // centers[i], // double [] cent_xy, // if null, use center of the square search_rad, // double radius, // search for maximum within this radius Loading @@ -567,7 +567,8 @@ public class ComboMatch { ObjectLocation ol = object_list.get(i); System.out.println(String.format("%2d (%4d/%4d): %7.5f %7.3f %7.3f %7.3f %7.3f %7.3f %17s", i, ol.getPixels()[0], ol.getPixels()[1], stats[i][0],stats[i][1],stats[i][2],stats[i][3],stats[i][4]+centers[i][0],stats[i][5]+centers[i][1], stats[i].best_d,stats[i].eff_rad,stats[i].elong,stats[i].dist, stats[i].cent_offs[0]+centers[i][0],stats[i].cent_offs[1]+centers[i][1], ol.getName())); } System.out.println(); Loading Loading @@ -868,7 +869,8 @@ public class ComboMatch { ImagePlus imp_pat_match = maps_collection.patternMatchDualWrap ( gpu_pair, // int [] indices, // null or which indices to use (normally just 2 for pairwise comparison) affines, // double [][][] affines, // null or [indices.length][2][3] null); // warp); // FineXYCorr warp) null, // warp); // FineXYCorr warp) null); // double [][] ground_planes) TODO: add calculation of the ground plane for single images // imp_pat_match.show(); } Loading @@ -895,11 +897,16 @@ public class ComboMatch { boolean batch_mode = true; // false; // true; boolean ignore_prev_rms = true; Rectangle woi = new Rectangle(); // used to return actual woi from correlateOrthoPair() double [][] ground_planes = null; for (int zi = 0; zi < zooms.length; zi++) { zoom_lev = zooms[zi]; if (zoom_lev >=1000) { break; } boolean show_vf = render_match || pattern_match; if (render_match || pattern_match) { ground_planes = new double [gpu_pair.length][]; } // will modify affines[1], later add jtj, weight, smth. else? FineXYCorr warp = maps_collection.correlateOrthoPair( clt_parameters, // CLTParameters clt_parameters, Loading @@ -914,7 +921,8 @@ public class ComboMatch { affines, // double [][][] affines, // on top of GPS offsets woi, // Rectangle woi, zoom_lev, // int zoom_lev, show_vf, // boolean show_vf, ground_planes, // double [][] ground_planes, // null or double[2] - will return ground planes debugLevel); // final int debugLevel) if (warp == null) { System.out.println("Failed correlateOrthoPair()"); Loading Loading @@ -951,7 +959,8 @@ public class ComboMatch { ImagePlus imp_pat_match = maps_collection.patternMatchDualWrap ( gpu_pair, // int [] indices, // null or which indices to use (normally just 2 for pairwise comparison) affines, // double [][][] affines, // null or [indices.length][2][3] warp); // FineXYCorr warp) warp, // FineXYCorr warp) ground_planes); // double [][] ground_planes); // null or // imp_pat_match.show(); } if (render_match) { Loading src/main/java/com/elphel/imagej/orthomosaic/CorrelationPeakStats.java 0 → 100644 +210 −0 Original line number Diff line number Diff line package com.elphel.imagej.orthomosaic; import java.util.Arrays; import com.elphel.imagej.tileprocessor.TileNeibs; import Jama.EigenvalueDecomposition; import Jama.Matrix; public class CorrelationPeakStats { public double best_d; public double eff_rad; public double elong; public double dist; public double [] cent_offs; public double max_other; public CorrelationPeakStats( double [] data, // square data double [] cent_xy, // if null, use center of the square double radius, // search for maximum within this radius double frac_max, double other_radius, int debugLevel) { double [] stats = getStatsNaN( data, // square data cent_xy, // if null, use center of the square radius, // search for maximum within this radius frac_max, other_radius, debugLevel); best_d = stats[0]; eff_rad = stats[1]; elong = stats[2]; dist = stats[3]; cent_offs = new double [] {stats[4], stats[5]}; max_other = stats[6]; } int [] getIntOffset() { return new int[] {(int) Math.round(cent_offs[0]), (int) Math.round(cent_offs[1])}; } /** * Search for maximum within specified radius from the center or specified offset from the center * @param data square data * @param cent_xy offset from the square center to search * @param radius search for maximum within this radius * @param frac_max fraction of maximum to measure area * @param other_radius measure maximal disconnected pixel fraction of the maximum within this radius * @return {maximum, area, dist, x, y}. No interpolation yet, each returned value is integer. * Returns null if no local maximum within area. x,y are offsets from the (provided) center */ public static double [] getStatsNaN( double [] data, // square data double [] cent_xy, // if null, use center of the square double radius, // search for maximum within this radius double frac_max, double other_radius, int debugLevel) { double [] rslt = getStats( data, // square data cent_xy, // if null, use center of the square radius, // search for maximum within this radius frac_max, other_radius, debugLevel); if (rslt != null) { return rslt; } else { return new double[] {Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN}; } } public static double [] getStats( double [] data, // square data double [] cent_xy, // if null, use center of the square double radius, // search for maximum within this radius double frac_max, double other_radius, // may be 0 if not needed int debugLevel) { boolean debug = debugLevel > 1; int size = (int) Math.sqrt(data.length); if (cent_xy == null) { cent_xy = new double [] {size/2, size/2}; } double maxr2 = radius * radius; double best_d = 0; int best_indx = -1; int min_x = Math.max(1, (int) Math.floor(cent_xy[0]-radius)); int min_y = Math.max(1, (int) Math.floor(cent_xy[1]-radius)); int max_x = Math.min(size-2, (int) Math.ceil(cent_xy[0]+radius)); int max_y = Math.min(size-2, (int) Math.ceil(cent_xy[1]+radius)); for (int y = min_y; y <= max_y; y++) { // do not search on very edges double dy = (y-cent_xy[1]); double y2 = dy*dy; if (y2 < maxr2) { for (int x = min_x; x <= max_x; x++) { double dx = x - cent_xy[0]; double r2 = y2 + dx*dx; if (r2 < maxr2) { int indx = y * size + x; double d = data[indx]; if (d > best_d) { best_indx = indx; best_d = d; } } } } } if (best_indx < 0) { return null; } // is it local max? if ((data[best_indx - 1] > best_d) || (data[best_indx + 1] > best_d) || (data[best_indx - size] > best_d) || (data[best_indx + size] > best_d)) { return null; // on the edge, not a local max } boolean [] above_thresh = new boolean [data.length]; double thresh = best_d * frac_max; for (int i = 0; i < data.length; i++) { above_thresh[i] = data[i] > thresh; } int [] clusters = (new TileNeibs(size,size)).enumerateClusters( above_thresh, // boolean [] tiles, null, // int [] num_clusters, false); // boolean ordered) int center_cluster = clusters[best_indx]; int best_x = best_indx % size; int best_y = best_indx / size; double xc = best_x - cent_xy[0]; double yc = best_y - cent_xy[1]; double s0=0, sx=0, sy=0, sx2 = 0, sy2=0, sxy = 0; for (int i = 0; i < clusters.length; i++) { if (clusters[i] == center_cluster) { double y = i / size - (yc + cent_xy[1]); //(yc + cent_xy[1]) - absolute, from (0,0) double x = i % size - (xc + cent_xy[0]); double w = data[i]-thresh; s0 += w; sx += w * x; sy += w * y; sx2 += w * x * x; sy2 += w * y * y; sxy += w * x * y; } } double cxx = sx2 - sx * sx / s0, cyy= sy2 - sy * sy / s0, cxy = sxy - sx * sy / s0; /* * sum(Mi*(Xi-avg(X))^2) = SX2 - SX^2/S0 * sum(Mi*(Yi-avg(Y))^2) = SY2 - SY^2/S0 * sum(Mi*(Xi-avg(X)*(Yi-avg(Y))) = SXY - SX*SY / S0 */ Matrix covar = new Matrix(new double[][] {{cxx, cxy},{cxy,cyy}}); double [] cent_offs = {sx/s0 + xc,sy/s0 + yc}; EigenvalueDecomposition eig = covar.eig(); double [] eigval = {eig.getD().get(0, 0),eig.getD().get(1, 1)}; Arrays.sort(eigval); // ascending double elong = Math.sqrt(eigval[1]/eigval[0]); double eff_rad = Math.sqrt(Math.sqrt(eigval[1]*eigval[0])); double dist = Math.sqrt(cent_offs[0]*cent_offs[0] + cent_offs[1]*cent_offs[1]); double max_other = 0.0; if (other_radius > 0) { // only calculate if asked for double or2 = other_radius*other_radius; min_x = Math.max(1, (int) Math.floor(best_x-other_radius)); min_y = Math.max(1, (int) Math.floor(best_y-other_radius)); max_x = Math.min(size-2, (int) Math.ceil(best_x+other_radius)); max_y = Math.min(size-2, (int) Math.ceil(best_y+other_radius)); for (int y = min_y; y <= max_y; y++) { // do not search on very edges double dy = (y-best_y); double y2 = dy*dy; if (y2 < or2) { for (int x = min_x; x <= max_x; x++) { double dx = x - best_x; double r2 = y2 + dx*dx; if (r2 < or2) { int indx = y * size + x; if (clusters[indx] != center_cluster) { // only disconnected from the central area double d = data[indx]; if (d > max_other) { max_other = d; } } } } } } max_other /= best_d; } if (debug) { System.out.println("\ncenter offset ["+(sx/s0)+","+(sy/s0)+"] , from center: ["+cent_offs[0]+","+cent_offs[1]+"]"); System.out.println("Covariance matrix:"); covar.print(8, 6); System.out.println("eig.getV()"); eig.getV().print(8, 6); System.out.println("eig.getD()"); eig.getD().print(8, 6); System.out.println(String.format("best_d=%7.5f, rad=%6.3f, elong=%6.3f, dist=%6.3f, dx=%6.3f, dy=%6.3f, mo=%5.3f", best_d, eff_rad, elong, dist, cent_offs[0], cent_offs[1], max_other)); } return new double [] {best_d, eff_rad, elong, dist, cent_offs[0], cent_offs[1], max_other} ; } } src/main/java/com/elphel/imagej/orthomosaic/ItemMatch.java +290 −11 Original line number Diff line number Diff line Loading @@ -4,12 +4,111 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import com.elphel.imagej.common.ShowDoubleFloatArrays; import ij.ImagePlus; import ij.Prefs; import ij.gui.PointRoi; import ij.io.FileSaver; public class ItemMatch { public double [] combo_pxy; // combo image {pixel_x, pixel_y} public double [] lla; // add later public HashMap<String, ItemPatternMatch> pattern_matches = new HashMap<String, ItemPatternMatch>(); public double abs_contrast = 0; public ItemMatch (double [] combo_pxy) { public CorrelationPeakStats[][] filter_data; // [scene][half/full] public double [][] extracted_objects; // [scene][pix] public double [][] extracted_nodc; // [scene][pix] public double [][] extracted_masked; // [scene][pix] public double [][][] extr_corr; // [scene][pix][patt] public double [][] extr_corr_half; // [scene][pix][h/f] public boolean removed = false; public ItemMatch ( int num_scenes, double [] combo_pxy) { setComboPXY(combo_pxy); this.combo_pxy = combo_pxy; filter_data = new CorrelationPeakStats[num_scenes][2]; extracted_objects = new double [num_scenes][]; extracted_nodc = new double [num_scenes][]; extracted_masked = new double [num_scenes][]; extr_corr = new double [num_scenes][][]; extr_corr_half = new double [num_scenes][]; removed = false; } public void setRemoved (boolean removed) { this.removed = removed; } public void remove () { this.removed = true; } public boolean isRemoved() { return removed; } public void setCorrFull( int scene_num, int num_patt, double [] corr_data) { extr_corr[scene_num] = new double [num_patt][]; extr_corr[scene_num][0] = corr_data; } public void setCorr( int scene_num, int patt_index, double [] corr_data) { extr_corr[scene_num][patt_index] = corr_data; } public double [] getCorr( int scene_num, int patt_index) { return extr_corr[scene_num][patt_index]; } public double [][] getCorrs( int scene_num) { return extr_corr[scene_num]; } public void setCorrHalf( int scene_num, int best_patt, // 0-full boolean combine_full) { extr_corr_half[scene_num] = extr_corr[scene_num][best_patt].clone(); if (combine_full) { double full_max_value = filter_data[scene_num][0].best_d; // [0]; if (!Double.isNaN(full_max_value)) { // multiply by full correlation, delete by full correlation maximum for (int i = 0; i < extr_corr_half[scene_num].length; i++){ extr_corr_half[scene_num][i] *= Math.max(0, extr_corr[scene_num][0][i]/full_max_value); } } } } public void setCorrHalf( int scene_num, double [] corr_data) { extr_corr_half[scene_num] = corr_data; } public double [] getCorrHalf( int scene_num) { return extr_corr_half[scene_num]; } public void setComboPXY(double [] combo_pxy) { this.combo_pxy = combo_pxy; } Loading @@ -27,7 +126,7 @@ public class ItemMatch { return new int [] {(int) Math.round(combo_pxy[0]), (int) Math.round(combo_pxy[1])}; } public void addPatternMatches( public void addPatternMatches( // updates existing too GroundObjectPattern groundObjectPattern, double [] matches, int best_sub) { Loading Loading @@ -101,6 +200,8 @@ public class ItemMatch { return match.getMatch(indx); } /** * Return correlation value as the one of the pattern correlations pointed * by the best subpattern index. Loading @@ -126,28 +227,206 @@ public class ItemMatch { public static ArrayList<Integer> sortByMatch( ArrayList<ItemMatch> match_list, GroundObjectPattern groundObjectPattern, boolean keep_removed, int indx){ String pattern_path = groundObjectPattern.getPatternPath(); return sortByMatch(match_list, pattern_path, indx); return sortByMatch(match_list, pattern_path, keep_removed, indx); } public static ArrayList<Integer> sortByMatch( ArrayList<ItemMatch> match_list, String pattern_path, boolean keep_removed, int indx){ double large_enough = 1.0; ArrayList<Integer> result_list= new ArrayList<Integer>(match_list.size()); for (int i = 0; i < match_list.size(); i++) { if (keep_removed || !match_list.get(i).isRemoved()) { result_list.add(i); } } Collections.sort(result_list, new Comparator<Integer>() { @Override public int compare(Integer lhs, Integer rhs) { double rhsd = match_list.get(rhs).getMatchValue(pattern_path, indx); double lhsd = match_list.get(lhs).getMatchValue(pattern_path, indx); return (rhsd > lhsd) ? 1 : (rhsd < lhsd) ? -1 : 0; // recreasing ItemMatch rhsm = match_list.get(rhs); ItemMatch lhsm = match_list.get(lhs); double rhsd = rhsm.getMatchValue(pattern_path, indx) - (rhsm.isRemoved() ? large_enough:0 ); double lhsd = lhsm.getMatchValue(pattern_path, indx) - (lhsm.isRemoved() ? large_enough:0 ); return (rhsd > lhsd) ? 1 : (rhsd < lhsd) ? -1 : 0; // decreasing } }); return result_list; } public static void setExtractedObjects( int scene_num, int extr_size, GroundObjectPattern gop, ArrayList <ItemMatch> matches_list, ArrayList<Integer> match_sort, double[] data, int width, int debugLevel) { if (match_sort.isEmpty()) { System.out.println("List of detected objects is empty, nothing to process"); return; } for (int mn=0; mn < match_sort.size(); mn++) { int indx =match_sort.get(mn); ItemMatch match = matches_list.get(indx); match.extractObject( data, // double [] data, width, // int width, scene_num, // int scene_num, extr_size); // int extr_size); } } public void extractObject( double [] data, int width, int scene_num, int extr_size) { double [] center_xy = getXY(); double [] extr_data = ObjectLocation.extractObjectImage( center_xy, // double [] center_xy, extr_size, // int size, data, // double [] src_img, width); // int width) setExtractedObject(extr_data, scene_num); } public void extractCorrs( double [][] corr_data, int width, int scene_num, int extr_size) { double [] center_xy = getXY(); extr_corr[scene_num] = new double [corr_data.length][]; for (int i = 0; i < corr_data.length; i++) { extr_corr[scene_num][i] = ObjectLocation.extractObjectImage( center_xy, // double [] center_xy, extr_size, // int size, corr_data[i], // double [] src_img, width); // int width) } } public void setExtractedObject( double [] data, int scene_num) { extracted_objects[scene_num] = data; extracted_nodc[scene_num] = data.clone(); OrthoMap.removeDC(extracted_nodc[scene_num]); } public void setMaskedObject( double [] data, int scene_num) { extracted_masked[scene_num] = data; } public void setCorrData( double [] data, int scene_num, int half_full) { // 1 - half, 0 - full extr_corr[scene_num][half_full] = data; } public static ImagePlus getImageExtracts( String prefix, // include PC, filter here String suffix, // w/o .tiff boolean show, boolean save, boolean show_removed, String save_dir, int scene_num, boolean nodc, int mode, // 0 - extract, 1 - masked, 2 - corr full, 3 - corr half boolean show_centers, int extr_size, GroundObjectPattern gop, ArrayList <ItemMatch> matches_list, ArrayList<Integer> match_sort, int debugLevel) { if (match_sort.isEmpty()) { System.out.println("List of detected objects is empty, nothing to display"); return null; } PointRoi roi = new PointRoi(); roi.setOptions("nolabel"); // label"); for (int mn=0; mn < match_sort.size(); mn++) { roi.addPoint(extr_size/2,extr_size/2,mn+1); // ,1); } int num_slices = match_sort.size(); if (!show_removed) { num_slices=0; for (int mn=0; mn < match_sort.size(); mn++) { if (!matches_list.get(match_sort.get(mn)).isRemoved()) { num_slices++; } } } String [] extr_titles = new String [num_slices]; double [][] extr_data = new double [num_slices][]; String title = prefix; switch (mode) { case 0: title += "_objects_" + scene_num + (nodc ? "_no-DC":"_with-DC"); break; case 1: title += "_contrast-masks"; break; case 2: title += "_objects_"+scene_num+"_corr_full"; break; case 3: title += "_objects_"+scene_num+"_corr_halves"; break; } if (suffix != null) { title += suffix; } title += ".tiff"; int indx = 0; for (int mn=0; mn < match_sort.size(); mn++) { ItemMatch match = matches_list.get(match_sort.get(mn)); if (show_removed || !matches_list.get(match_sort.get(mn)).isRemoved()) { int [] ixy = match.getIntXY(); extr_titles[indx] = ixy[0]+"/"+ ixy[1]+":"+String.format("%7.5f", match.getMatchValue(gop)); switch (mode) { case 0: extr_data[indx] = nodc ? match.extracted_nodc[scene_num] : match.extracted_objects[scene_num]; break; case 1: extr_data[indx] = match.extracted_masked[scene_num]; break; case 2: extr_data[indx] = match.extr_corr[scene_num][0]; break; case 3: extr_data[indx] = match.extr_corr_half[scene_num]; break; } indx++; } } ImagePlus imp = ShowDoubleFloatArrays.makeArrays( extr_data, extr_size, extr_size, title, // +"-objects_"+scene_num, extr_titles); // test_titles, if (imp == null) { return null; } if (show_centers) { imp.setRoi(roi); // null } if (show) { imp.show(); } if (save && (save_dir != null)) { if (!save_dir.endsWith(Prefs.getFileSeparator())) { save_dir+=Prefs.getFileSeparator(); } String save_path = save_dir+title; FileSaver imp_fs = new FileSaver(imp); imp_fs.saveAsTiff(save_path); if (debugLevel > -4) { System.out.println("Saved "+save_path); } } return imp; } } src/main/java/com/elphel/imagej/orthomosaic/ItemPatternMatch.java +2 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,8 @@ public class ItemPatternMatch { public void setMatches(double [] matches) { sub_matches = matches; } public void setMatch(int indx, double match_value) { public void setMatch(int indx, double match_value) { // not used if (sub_matches == null) { sub_matches = new double [indx+1]; Arrays.fill(sub_matches, Double.NaN); Loading Loading
src/main/java/com/elphel/imagej/common/DoubleFHT.java +31 −8 Original line number Diff line number Diff line Loading @@ -293,6 +293,22 @@ public class DoubleFHT { return result; } public double [] getFrequencyFilter ( double [] data, // just to find out size double highPassSigma, double lowPassSigma ) { double [] filter= null; if ((highPassSigma> 0.0) || (lowPassSigma> 0.0)) { updateMaxN(data); createFrequencyFilter(highPassSigma, lowPassSigma); // for repetitive calls will reuse mask filter =this.freqMask; } return filter; } /** * Invert kernel for deconvolutions. Kernel is assumed to fade near edges * @param data source kernel, square, power of 2 sides. Data is destroyed Loading @@ -314,7 +330,6 @@ public class DoubleFHT { filter =this.freqMask; } return invert (data, fat_zero, filter); } /** Loading Loading @@ -401,8 +416,6 @@ public class DoubleFHT { } public double[] phaseCorrelate(double[] first, double phaseCoeff, double high_pass, double low_pass, // high/low // pass // filtering double[] fht_save) { // null-OK updateMaxN(first); double[] filter = null; Loading Loading @@ -503,7 +516,10 @@ public class DoubleFHT { * @return phase correlation, first still contains original transformed! */ public double[] phaseCorrelatePattern( // new double[] first, double[] secondFD, double phaseCoeff, double[] filter, // high/low pass filtering double[] first, double[] secondFD, double phaseCoeff, double[] filter, // high/low pass filtering double[] first_save) { // null-OK if (first.length != secondFD.length) { IJ.showMessage("Error", "Correlation arrays should be the same size"); Loading Loading @@ -537,7 +553,9 @@ public class DoubleFHT { */ public double[] convolvePattern( // new double[] first, double[] secondFD, double[] filter, // high/low pass filtering double[] first, double[] secondFD, double[] filter, // high/low pass filtering double[] first_save) { // null-OK if (first.length != secondFD.length) { IJ.showMessage("Error", "Correlation arrays should be the same size"); Loading Loading @@ -595,11 +613,16 @@ public class DoubleFHT { return first; } public double[] convolve(double[] first, double[] second) { public double[] convolve( double[] first, double[] second) { return convolve(first, second, null); } public double[] convolve(double[] first, double[] second, double[] filter) { // high/low pass filtering public double[] convolve( double[] first, double[] second, double[] filter) { // high/low pass filtering // System.out.println("correlate"); if (first.length != second.length) { IJ.showMessage("Error", "Correlation arrays should be the same size"); Loading
src/main/java/com/elphel/imagej/orthomosaic/ComboMatch.java +26 −17 Original line number Diff line number Diff line Loading @@ -544,13 +544,13 @@ public class ComboMatch { } imp_obj.setRoi(roi); // calculate statistics for cor_ret[i] double [][] stats = new double [object_stack.length][]; CorrelationPeakStats [] stats = new CorrelationPeakStats [object_stack.length]; double search_rad = 15.0; double frac_max = 0.5; double other_rad = 25; for (int i = 0; i < object_stack.length; i++) { double [] a_cent= {corr_size/2+centers[i][0],corr_size/2+centers[i][1]}; stats[i]=ObjectLocation.getMaxLocArea( stats[i]=new CorrelationPeakStats( corr_ret[i], // double [] data, // square data a_cent, // centers[i], // double [] cent_xy, // if null, use center of the square search_rad, // double radius, // search for maximum within this radius Loading @@ -567,7 +567,8 @@ public class ComboMatch { ObjectLocation ol = object_list.get(i); System.out.println(String.format("%2d (%4d/%4d): %7.5f %7.3f %7.3f %7.3f %7.3f %7.3f %17s", i, ol.getPixels()[0], ol.getPixels()[1], stats[i][0],stats[i][1],stats[i][2],stats[i][3],stats[i][4]+centers[i][0],stats[i][5]+centers[i][1], stats[i].best_d,stats[i].eff_rad,stats[i].elong,stats[i].dist, stats[i].cent_offs[0]+centers[i][0],stats[i].cent_offs[1]+centers[i][1], ol.getName())); } System.out.println(); Loading Loading @@ -868,7 +869,8 @@ public class ComboMatch { ImagePlus imp_pat_match = maps_collection.patternMatchDualWrap ( gpu_pair, // int [] indices, // null or which indices to use (normally just 2 for pairwise comparison) affines, // double [][][] affines, // null or [indices.length][2][3] null); // warp); // FineXYCorr warp) null, // warp); // FineXYCorr warp) null); // double [][] ground_planes) TODO: add calculation of the ground plane for single images // imp_pat_match.show(); } Loading @@ -895,11 +897,16 @@ public class ComboMatch { boolean batch_mode = true; // false; // true; boolean ignore_prev_rms = true; Rectangle woi = new Rectangle(); // used to return actual woi from correlateOrthoPair() double [][] ground_planes = null; for (int zi = 0; zi < zooms.length; zi++) { zoom_lev = zooms[zi]; if (zoom_lev >=1000) { break; } boolean show_vf = render_match || pattern_match; if (render_match || pattern_match) { ground_planes = new double [gpu_pair.length][]; } // will modify affines[1], later add jtj, weight, smth. else? FineXYCorr warp = maps_collection.correlateOrthoPair( clt_parameters, // CLTParameters clt_parameters, Loading @@ -914,7 +921,8 @@ public class ComboMatch { affines, // double [][][] affines, // on top of GPS offsets woi, // Rectangle woi, zoom_lev, // int zoom_lev, show_vf, // boolean show_vf, ground_planes, // double [][] ground_planes, // null or double[2] - will return ground planes debugLevel); // final int debugLevel) if (warp == null) { System.out.println("Failed correlateOrthoPair()"); Loading Loading @@ -951,7 +959,8 @@ public class ComboMatch { ImagePlus imp_pat_match = maps_collection.patternMatchDualWrap ( gpu_pair, // int [] indices, // null or which indices to use (normally just 2 for pairwise comparison) affines, // double [][][] affines, // null or [indices.length][2][3] warp); // FineXYCorr warp) warp, // FineXYCorr warp) ground_planes); // double [][] ground_planes); // null or // imp_pat_match.show(); } if (render_match) { Loading
src/main/java/com/elphel/imagej/orthomosaic/CorrelationPeakStats.java 0 → 100644 +210 −0 Original line number Diff line number Diff line package com.elphel.imagej.orthomosaic; import java.util.Arrays; import com.elphel.imagej.tileprocessor.TileNeibs; import Jama.EigenvalueDecomposition; import Jama.Matrix; public class CorrelationPeakStats { public double best_d; public double eff_rad; public double elong; public double dist; public double [] cent_offs; public double max_other; public CorrelationPeakStats( double [] data, // square data double [] cent_xy, // if null, use center of the square double radius, // search for maximum within this radius double frac_max, double other_radius, int debugLevel) { double [] stats = getStatsNaN( data, // square data cent_xy, // if null, use center of the square radius, // search for maximum within this radius frac_max, other_radius, debugLevel); best_d = stats[0]; eff_rad = stats[1]; elong = stats[2]; dist = stats[3]; cent_offs = new double [] {stats[4], stats[5]}; max_other = stats[6]; } int [] getIntOffset() { return new int[] {(int) Math.round(cent_offs[0]), (int) Math.round(cent_offs[1])}; } /** * Search for maximum within specified radius from the center or specified offset from the center * @param data square data * @param cent_xy offset from the square center to search * @param radius search for maximum within this radius * @param frac_max fraction of maximum to measure area * @param other_radius measure maximal disconnected pixel fraction of the maximum within this radius * @return {maximum, area, dist, x, y}. No interpolation yet, each returned value is integer. * Returns null if no local maximum within area. x,y are offsets from the (provided) center */ public static double [] getStatsNaN( double [] data, // square data double [] cent_xy, // if null, use center of the square double radius, // search for maximum within this radius double frac_max, double other_radius, int debugLevel) { double [] rslt = getStats( data, // square data cent_xy, // if null, use center of the square radius, // search for maximum within this radius frac_max, other_radius, debugLevel); if (rslt != null) { return rslt; } else { return new double[] {Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN}; } } public static double [] getStats( double [] data, // square data double [] cent_xy, // if null, use center of the square double radius, // search for maximum within this radius double frac_max, double other_radius, // may be 0 if not needed int debugLevel) { boolean debug = debugLevel > 1; int size = (int) Math.sqrt(data.length); if (cent_xy == null) { cent_xy = new double [] {size/2, size/2}; } double maxr2 = radius * radius; double best_d = 0; int best_indx = -1; int min_x = Math.max(1, (int) Math.floor(cent_xy[0]-radius)); int min_y = Math.max(1, (int) Math.floor(cent_xy[1]-radius)); int max_x = Math.min(size-2, (int) Math.ceil(cent_xy[0]+radius)); int max_y = Math.min(size-2, (int) Math.ceil(cent_xy[1]+radius)); for (int y = min_y; y <= max_y; y++) { // do not search on very edges double dy = (y-cent_xy[1]); double y2 = dy*dy; if (y2 < maxr2) { for (int x = min_x; x <= max_x; x++) { double dx = x - cent_xy[0]; double r2 = y2 + dx*dx; if (r2 < maxr2) { int indx = y * size + x; double d = data[indx]; if (d > best_d) { best_indx = indx; best_d = d; } } } } } if (best_indx < 0) { return null; } // is it local max? if ((data[best_indx - 1] > best_d) || (data[best_indx + 1] > best_d) || (data[best_indx - size] > best_d) || (data[best_indx + size] > best_d)) { return null; // on the edge, not a local max } boolean [] above_thresh = new boolean [data.length]; double thresh = best_d * frac_max; for (int i = 0; i < data.length; i++) { above_thresh[i] = data[i] > thresh; } int [] clusters = (new TileNeibs(size,size)).enumerateClusters( above_thresh, // boolean [] tiles, null, // int [] num_clusters, false); // boolean ordered) int center_cluster = clusters[best_indx]; int best_x = best_indx % size; int best_y = best_indx / size; double xc = best_x - cent_xy[0]; double yc = best_y - cent_xy[1]; double s0=0, sx=0, sy=0, sx2 = 0, sy2=0, sxy = 0; for (int i = 0; i < clusters.length; i++) { if (clusters[i] == center_cluster) { double y = i / size - (yc + cent_xy[1]); //(yc + cent_xy[1]) - absolute, from (0,0) double x = i % size - (xc + cent_xy[0]); double w = data[i]-thresh; s0 += w; sx += w * x; sy += w * y; sx2 += w * x * x; sy2 += w * y * y; sxy += w * x * y; } } double cxx = sx2 - sx * sx / s0, cyy= sy2 - sy * sy / s0, cxy = sxy - sx * sy / s0; /* * sum(Mi*(Xi-avg(X))^2) = SX2 - SX^2/S0 * sum(Mi*(Yi-avg(Y))^2) = SY2 - SY^2/S0 * sum(Mi*(Xi-avg(X)*(Yi-avg(Y))) = SXY - SX*SY / S0 */ Matrix covar = new Matrix(new double[][] {{cxx, cxy},{cxy,cyy}}); double [] cent_offs = {sx/s0 + xc,sy/s0 + yc}; EigenvalueDecomposition eig = covar.eig(); double [] eigval = {eig.getD().get(0, 0),eig.getD().get(1, 1)}; Arrays.sort(eigval); // ascending double elong = Math.sqrt(eigval[1]/eigval[0]); double eff_rad = Math.sqrt(Math.sqrt(eigval[1]*eigval[0])); double dist = Math.sqrt(cent_offs[0]*cent_offs[0] + cent_offs[1]*cent_offs[1]); double max_other = 0.0; if (other_radius > 0) { // only calculate if asked for double or2 = other_radius*other_radius; min_x = Math.max(1, (int) Math.floor(best_x-other_radius)); min_y = Math.max(1, (int) Math.floor(best_y-other_radius)); max_x = Math.min(size-2, (int) Math.ceil(best_x+other_radius)); max_y = Math.min(size-2, (int) Math.ceil(best_y+other_radius)); for (int y = min_y; y <= max_y; y++) { // do not search on very edges double dy = (y-best_y); double y2 = dy*dy; if (y2 < or2) { for (int x = min_x; x <= max_x; x++) { double dx = x - best_x; double r2 = y2 + dx*dx; if (r2 < or2) { int indx = y * size + x; if (clusters[indx] != center_cluster) { // only disconnected from the central area double d = data[indx]; if (d > max_other) { max_other = d; } } } } } } max_other /= best_d; } if (debug) { System.out.println("\ncenter offset ["+(sx/s0)+","+(sy/s0)+"] , from center: ["+cent_offs[0]+","+cent_offs[1]+"]"); System.out.println("Covariance matrix:"); covar.print(8, 6); System.out.println("eig.getV()"); eig.getV().print(8, 6); System.out.println("eig.getD()"); eig.getD().print(8, 6); System.out.println(String.format("best_d=%7.5f, rad=%6.3f, elong=%6.3f, dist=%6.3f, dx=%6.3f, dy=%6.3f, mo=%5.3f", best_d, eff_rad, elong, dist, cent_offs[0], cent_offs[1], max_other)); } return new double [] {best_d, eff_rad, elong, dist, cent_offs[0], cent_offs[1], max_other} ; } }
src/main/java/com/elphel/imagej/orthomosaic/ItemMatch.java +290 −11 Original line number Diff line number Diff line Loading @@ -4,12 +4,111 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import com.elphel.imagej.common.ShowDoubleFloatArrays; import ij.ImagePlus; import ij.Prefs; import ij.gui.PointRoi; import ij.io.FileSaver; public class ItemMatch { public double [] combo_pxy; // combo image {pixel_x, pixel_y} public double [] lla; // add later public HashMap<String, ItemPatternMatch> pattern_matches = new HashMap<String, ItemPatternMatch>(); public double abs_contrast = 0; public ItemMatch (double [] combo_pxy) { public CorrelationPeakStats[][] filter_data; // [scene][half/full] public double [][] extracted_objects; // [scene][pix] public double [][] extracted_nodc; // [scene][pix] public double [][] extracted_masked; // [scene][pix] public double [][][] extr_corr; // [scene][pix][patt] public double [][] extr_corr_half; // [scene][pix][h/f] public boolean removed = false; public ItemMatch ( int num_scenes, double [] combo_pxy) { setComboPXY(combo_pxy); this.combo_pxy = combo_pxy; filter_data = new CorrelationPeakStats[num_scenes][2]; extracted_objects = new double [num_scenes][]; extracted_nodc = new double [num_scenes][]; extracted_masked = new double [num_scenes][]; extr_corr = new double [num_scenes][][]; extr_corr_half = new double [num_scenes][]; removed = false; } public void setRemoved (boolean removed) { this.removed = removed; } public void remove () { this.removed = true; } public boolean isRemoved() { return removed; } public void setCorrFull( int scene_num, int num_patt, double [] corr_data) { extr_corr[scene_num] = new double [num_patt][]; extr_corr[scene_num][0] = corr_data; } public void setCorr( int scene_num, int patt_index, double [] corr_data) { extr_corr[scene_num][patt_index] = corr_data; } public double [] getCorr( int scene_num, int patt_index) { return extr_corr[scene_num][patt_index]; } public double [][] getCorrs( int scene_num) { return extr_corr[scene_num]; } public void setCorrHalf( int scene_num, int best_patt, // 0-full boolean combine_full) { extr_corr_half[scene_num] = extr_corr[scene_num][best_patt].clone(); if (combine_full) { double full_max_value = filter_data[scene_num][0].best_d; // [0]; if (!Double.isNaN(full_max_value)) { // multiply by full correlation, delete by full correlation maximum for (int i = 0; i < extr_corr_half[scene_num].length; i++){ extr_corr_half[scene_num][i] *= Math.max(0, extr_corr[scene_num][0][i]/full_max_value); } } } } public void setCorrHalf( int scene_num, double [] corr_data) { extr_corr_half[scene_num] = corr_data; } public double [] getCorrHalf( int scene_num) { return extr_corr_half[scene_num]; } public void setComboPXY(double [] combo_pxy) { this.combo_pxy = combo_pxy; } Loading @@ -27,7 +126,7 @@ public class ItemMatch { return new int [] {(int) Math.round(combo_pxy[0]), (int) Math.round(combo_pxy[1])}; } public void addPatternMatches( public void addPatternMatches( // updates existing too GroundObjectPattern groundObjectPattern, double [] matches, int best_sub) { Loading Loading @@ -101,6 +200,8 @@ public class ItemMatch { return match.getMatch(indx); } /** * Return correlation value as the one of the pattern correlations pointed * by the best subpattern index. Loading @@ -126,28 +227,206 @@ public class ItemMatch { public static ArrayList<Integer> sortByMatch( ArrayList<ItemMatch> match_list, GroundObjectPattern groundObjectPattern, boolean keep_removed, int indx){ String pattern_path = groundObjectPattern.getPatternPath(); return sortByMatch(match_list, pattern_path, indx); return sortByMatch(match_list, pattern_path, keep_removed, indx); } public static ArrayList<Integer> sortByMatch( ArrayList<ItemMatch> match_list, String pattern_path, boolean keep_removed, int indx){ double large_enough = 1.0; ArrayList<Integer> result_list= new ArrayList<Integer>(match_list.size()); for (int i = 0; i < match_list.size(); i++) { if (keep_removed || !match_list.get(i).isRemoved()) { result_list.add(i); } } Collections.sort(result_list, new Comparator<Integer>() { @Override public int compare(Integer lhs, Integer rhs) { double rhsd = match_list.get(rhs).getMatchValue(pattern_path, indx); double lhsd = match_list.get(lhs).getMatchValue(pattern_path, indx); return (rhsd > lhsd) ? 1 : (rhsd < lhsd) ? -1 : 0; // recreasing ItemMatch rhsm = match_list.get(rhs); ItemMatch lhsm = match_list.get(lhs); double rhsd = rhsm.getMatchValue(pattern_path, indx) - (rhsm.isRemoved() ? large_enough:0 ); double lhsd = lhsm.getMatchValue(pattern_path, indx) - (lhsm.isRemoved() ? large_enough:0 ); return (rhsd > lhsd) ? 1 : (rhsd < lhsd) ? -1 : 0; // decreasing } }); return result_list; } public static void setExtractedObjects( int scene_num, int extr_size, GroundObjectPattern gop, ArrayList <ItemMatch> matches_list, ArrayList<Integer> match_sort, double[] data, int width, int debugLevel) { if (match_sort.isEmpty()) { System.out.println("List of detected objects is empty, nothing to process"); return; } for (int mn=0; mn < match_sort.size(); mn++) { int indx =match_sort.get(mn); ItemMatch match = matches_list.get(indx); match.extractObject( data, // double [] data, width, // int width, scene_num, // int scene_num, extr_size); // int extr_size); } } public void extractObject( double [] data, int width, int scene_num, int extr_size) { double [] center_xy = getXY(); double [] extr_data = ObjectLocation.extractObjectImage( center_xy, // double [] center_xy, extr_size, // int size, data, // double [] src_img, width); // int width) setExtractedObject(extr_data, scene_num); } public void extractCorrs( double [][] corr_data, int width, int scene_num, int extr_size) { double [] center_xy = getXY(); extr_corr[scene_num] = new double [corr_data.length][]; for (int i = 0; i < corr_data.length; i++) { extr_corr[scene_num][i] = ObjectLocation.extractObjectImage( center_xy, // double [] center_xy, extr_size, // int size, corr_data[i], // double [] src_img, width); // int width) } } public void setExtractedObject( double [] data, int scene_num) { extracted_objects[scene_num] = data; extracted_nodc[scene_num] = data.clone(); OrthoMap.removeDC(extracted_nodc[scene_num]); } public void setMaskedObject( double [] data, int scene_num) { extracted_masked[scene_num] = data; } public void setCorrData( double [] data, int scene_num, int half_full) { // 1 - half, 0 - full extr_corr[scene_num][half_full] = data; } public static ImagePlus getImageExtracts( String prefix, // include PC, filter here String suffix, // w/o .tiff boolean show, boolean save, boolean show_removed, String save_dir, int scene_num, boolean nodc, int mode, // 0 - extract, 1 - masked, 2 - corr full, 3 - corr half boolean show_centers, int extr_size, GroundObjectPattern gop, ArrayList <ItemMatch> matches_list, ArrayList<Integer> match_sort, int debugLevel) { if (match_sort.isEmpty()) { System.out.println("List of detected objects is empty, nothing to display"); return null; } PointRoi roi = new PointRoi(); roi.setOptions("nolabel"); // label"); for (int mn=0; mn < match_sort.size(); mn++) { roi.addPoint(extr_size/2,extr_size/2,mn+1); // ,1); } int num_slices = match_sort.size(); if (!show_removed) { num_slices=0; for (int mn=0; mn < match_sort.size(); mn++) { if (!matches_list.get(match_sort.get(mn)).isRemoved()) { num_slices++; } } } String [] extr_titles = new String [num_slices]; double [][] extr_data = new double [num_slices][]; String title = prefix; switch (mode) { case 0: title += "_objects_" + scene_num + (nodc ? "_no-DC":"_with-DC"); break; case 1: title += "_contrast-masks"; break; case 2: title += "_objects_"+scene_num+"_corr_full"; break; case 3: title += "_objects_"+scene_num+"_corr_halves"; break; } if (suffix != null) { title += suffix; } title += ".tiff"; int indx = 0; for (int mn=0; mn < match_sort.size(); mn++) { ItemMatch match = matches_list.get(match_sort.get(mn)); if (show_removed || !matches_list.get(match_sort.get(mn)).isRemoved()) { int [] ixy = match.getIntXY(); extr_titles[indx] = ixy[0]+"/"+ ixy[1]+":"+String.format("%7.5f", match.getMatchValue(gop)); switch (mode) { case 0: extr_data[indx] = nodc ? match.extracted_nodc[scene_num] : match.extracted_objects[scene_num]; break; case 1: extr_data[indx] = match.extracted_masked[scene_num]; break; case 2: extr_data[indx] = match.extr_corr[scene_num][0]; break; case 3: extr_data[indx] = match.extr_corr_half[scene_num]; break; } indx++; } } ImagePlus imp = ShowDoubleFloatArrays.makeArrays( extr_data, extr_size, extr_size, title, // +"-objects_"+scene_num, extr_titles); // test_titles, if (imp == null) { return null; } if (show_centers) { imp.setRoi(roi); // null } if (show) { imp.show(); } if (save && (save_dir != null)) { if (!save_dir.endsWith(Prefs.getFileSeparator())) { save_dir+=Prefs.getFileSeparator(); } String save_path = save_dir+title; FileSaver imp_fs = new FileSaver(imp); imp_fs.saveAsTiff(save_path); if (debugLevel > -4) { System.out.println("Saved "+save_path); } } return imp; } }
src/main/java/com/elphel/imagej/orthomosaic/ItemPatternMatch.java +2 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,8 @@ public class ItemPatternMatch { public void setMatches(double [] matches) { sub_matches = matches; } public void setMatch(int indx, double match_value) { public void setMatch(int indx, double match_value) { // not used if (sub_matches == null) { sub_matches = new double [indx+1]; Arrays.fill(sub_matches, Double.NaN); Loading