Commit 3c990957 authored by Andrey Filippov's avatar Andrey Filippov

detecting peak near interference

parent de8d20db
......@@ -1018,6 +1018,9 @@ public class CuasMotion {
public static double [][][][] getAccumulatedCoordinates(
final double [][][] vector_fields, // centers
final float [][] accum_data, // should be around 0, no low-freq
final double lmax_fraction, // 0.7; // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
final double lmax_radius, // 3; // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation
final boolean lmax_zero, // true; // zero all data outside this radius from the maximum
final double centroid_radius,
final int n_recenter, // re-center window around new maximum. 0 -no refines (single-pass)
final int tilesX,
......@@ -1041,6 +1044,7 @@ public class CuasMotion {
final int num_iter,
final int debugLevel){
final int tile2 = 2 * GPUTileProcessor.DTT_SIZE;
final int lmax_iradius = (int) Math.floor(lmax_radius);
final int num_seq = vector_fields.length; // same as accum_data.length
final int num_tiles = vector_fields[0].length;
// final int num_pix = accum_data[0].length;
......@@ -1060,12 +1064,13 @@ public class CuasMotion {
param_select[CuasMotionLMA.INDX_RR0] = lma_fit_r;
param_select[CuasMotionLMA.INDX_K] = lma_fit_k;
final int dbg_tile = (38 + 45 * 80);
final int dbg_seq = 15;
final boolean[] fpn_mask= no_border? (new boolean[0]) : null;
final int dbg_seq = -15;
// final boolean[] fpn_mask= no_border? (new boolean[0]) : null;
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
double [] pix_tile = new double [tile2 * tile2];
TileNeibs tn = new TileNeibs(tile2,tile2);
CuasMotionLMA cuasMotionLMA = new CuasMotionLMA(
tile2, // int width,
lma_sigma, // double sigma);
......@@ -1089,15 +1094,72 @@ public class CuasMotion {
pix_tile[indx_dst++] = accum_data[nSeq][indx_src++]; // float-> double
}
}
// find max negative data_width to calculate fraction (mv[])
// find absolute maximum
int ntile_amax = TileNeibs.getAmaxTile(
pix_tile); //double [] data)
int use_max = -1;
if (!tn.isEdge(ntile_amax) && tn.isMaxIsolated(
pix_tile, // double [] data,
ntile_amax, // int ntile,
lmax_fraction, // double fraction,
lmax_iradius)) { //int radius)) {
use_max = ntile_amax;
}
if (use_max < 0) { // find alternative maximum
int [] max_indices = tn.getLocalMaxes(
pix_tile, //double [] data,
true); // boolean exclude_margins)
double best_val = Double.NaN;
for (int indx:max_indices) {
boolean isolated = tn.isMaxIsolated(
pix_tile, // double [] data,
ntile_amax, // int ntile,
lmax_fraction, // double fraction,
lmax_iradius); //int radius)) {
if (isolated) {
double max_val = pix_tile[indx];
if (!(best_val >= max_val)) {
best_val = max_val;
use_max = indx;
}
}
}
}
if (use_max < 0){
// no candidate for the centroid/lma
continue;
}
// zero out outside the circle
if (lmax_zero) {
double lmax_iradius2 = (int) Math.ceil(lmax_radius * lmax_radius);
int x0 = use_max % tile2;
int y0 = use_max / tile2;
for (int y = 0; y < tile2; y++) {
int dy = y-y0;
int dy2 = dy*dy;
for (int x = 0; x < tile2; x++) {
int dx = x - x0;
int r2 = dy2+dx*dx;
if (r2 > lmax_iradius2) {
pix_tile[x + tile2 * y] = 0.0;
}
}
}
}
double [] mv = Correlation2d.getMaxXYCm( // last, average (Will be relative to the center of the tile)
pix_tile, // corrs.length-1], // double [] data,
-tile2, // int data_width, // = 2 * transform_size - 1; // negative - will return center fraction
tile2, // int data_width, // = 2 * transform_size - 1; // negative - will return center fraction
centroid_radius, // double radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
n_recenter, // int refine, // re-center window around new maximum. 0 -no refines (single-pass)
fpn_mask, // boolean [] fpn_mask,
null, // fpn_mask, // boolean [] fpn_mask,
false, // boolean ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
no_border, // boolean exclude_margins,
true, // boolean calc_fraction,
use_max, // int imax, // index of the maximum in data[]
false); // boolean debug)
acc_coord[nSeq][ntile] = mv; // no filtering here
if (mv != null) {
cuasMotionLMA.prepareLMA(
......@@ -3258,6 +3320,10 @@ public class CuasMotion {
boolean half_step = clt_parameters.imp.cuas_half_step; // true;
int max_range = clt_parameters.imp.cuas_max_range;
int num_cycles = clt_parameters.imp.cuas_num_cycles;
double lmax_fraction = clt_parameters.imp.cuas_lmax_fraction; // 0.7; // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
double lmax_radius = clt_parameters.imp.cuas_lmax_radius; // 3.5; // Look inside ((int)cuas_lmax_radius) * 2 + 1 square for the local maximum isolation
boolean lmax_zero = clt_parameters.imp.cuas_lmax_zero; // true; // zero all data outside this radius from the maximum
double target_radius = clt_parameters.imp.cuas_target_radius;
double target_strength = clt_parameters.imp.cuas_target_strength;
double [][] target_frac = new double [clt_parameters.imp.cuas_target_frac.length][2];
......@@ -3669,6 +3735,9 @@ public class CuasMotion {
double [][][][] coord_data2 = getAccumulatedCoordinates(
motion_scan_filtered, // final double [][][] vector_fields, // centers
fpixels_accumulated, // final double [][] accum_data, // should be around 0, no low-freq
lmax_fraction, // final double cuas_lmax_fraction, // 0.7; // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
lmax_radius, // final int cuas_lmax_radius, // 3; // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation
lmax_zero, // final boolean cuas_lmax_zero, // true; // zero all data outside this radius from the maximum
target_radius, // final double centroid_radius,
n_recenter, // final int n_recenter, // re-center window around new maximum. 0 -no refines (single-pass)
cuasMotion.tilesX, // final int tilesX){
......@@ -3864,6 +3933,12 @@ public class CuasMotion {
final boolean smooth = clt_parameters.imp.cuas_smooth; // true;
final int corr_inc = half_step ? (corr_offset/2) : corr_offset;
final int half_accum_range = corr_pairs/2;
final double lmax_fraction = clt_parameters.imp.cuas_lmax_fraction; // 0.7; // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
final double lmax_radius = clt_parameters.imp.cuas_lmax_radius; // 3.5; // Look inside ((int)cuas_lmax_radius) * 2 + 1 square for the local maximum isolation
final boolean lmax_zero = clt_parameters.imp.cuas_lmax_zero; // true; // zero all data outside this radius from the maximum
final double target_radius = clt_parameters.imp.cuas_target_radius;
final int n_recenter = clt_parameters.imp.cuas_n_recenter;
final boolean no_border= clt_parameters.imp.cuas_no_border; // true;
......@@ -3961,6 +4036,10 @@ public class CuasMotion {
double [][][][] coord_data2 = getAccumulatedCoordinates(
vf_sequence, // final double [][][] vector_fields, // centers
accum, // final double [][] accum_data, // should be around 0, no low-freq
lmax_fraction, // final double lmax_fraction, // 0.7; // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
lmax_radius, // final int lmax_radius, // 3; // look inside cuas_lmax_radius* 2 + 1 square for the local maximum isolation
lmax_zero, // final boolean lmax_zero, // true; // zero all data outside this radius from the maximum
target_radius, // final double centroid_radius,
n_recenter, // final int n_recenter, // re-center window around new maximum. 0 -no refines (single-pass)
cuasMotion.tilesX, // final int tilesX){
......@@ -4377,9 +4456,11 @@ public class CuasMotion {
double lma_rrms = clt_parameters.imp.cuas_lma_rrms; // = 0.15; // Maximal relative to A rms. OK is when (RMS < cuas_lma_arms) || (RMS < cuas_lma_rrms * A)
double lma_mina = clt_parameters.imp.cuas_lma_mina; // = 1.0; // Minimal A (amplitude)
double fat_zero = clt_parameters.imp.cuas_fat_zero;
double lmax_fraction = clt_parameters.imp.cuas_lmax_fraction; // 0.7; // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
String ps = ((prefix != null)?prefix:"") + "-OFFS"+corr_offset+"-PAIRS"+corr_pairs+"-RSTR"+rstr+"-RMS"+lma_rms+
"-SRMS"+lma_arms+"-RRMS"+lma_rrms+"-AMP"+lma_mina+"-FZ"+fat_zero+"-PCRA"+precorr_ra+"-PS"+corr_ra_step+
"-TUM"+temporal_um+"-TT"+tum_threshold;
"-TUM"+temporal_um+"-TT"+tum_threshold+"-MF"+lmax_fraction;
return ps;
}
......
......@@ -50,6 +50,7 @@ public class CuasMotionLMA {
private int width;
private double [][] window;
private double pedestal;
private double [] y_vector;
private double [] weights;
private double [] full_vector = new double[INDX_LEN];
......@@ -67,15 +68,40 @@ public class CuasMotionLMA {
double sigma,
double wnd_pedestal) {
this.width = width;
this.pedestal = wnd_pedestal;
window = new double [width][width];
double k = -0.5/(sigma*sigma);
for (int i = 0; i < width; i++) {
for (int j = 0; j < width; j++) {
window[i][j] = Math.exp(k*(i*i+j*j)) + wnd_pedestal; // will be normalized when used
window[i][j] = Math.exp(k*(i*i+j*j));// + wnd_pedestal; // will be normalized when used
}
}
}
/**
* Multiply (in place) data by window. Used only when stray data (local maximum is not the absolute maximum) is present
* Center data will be multiplied by almost 1.0
* @param tile_data data array, will be modified
* @param xc relative to center =width/2
* @param yc relative to center =width/2
*/
public void applyWindowToData(
double [] tile_data,
double xc, // relative to center =width/2
double yc) { // relative to center =width/2
double x0 = Math.min(Math.max(xc + width/2, 0), width-1);
double y0 = Math.min(Math.max(yc + width/2, 0), width-1);
int ix0 = (int) Math.round(x0);
int iy0 = (int) Math.round(y0);
for (int y = 0; y < width; y++) {
int ay = Math.abs(y-iy0);
for (int x = 0; x < width; x++) {
int ax = Math.abs(x-ix0);
double w = window[ay][ax]; // window to the nearest integer x,y
tile_data[x + y*width] *= w;
}
}
}
public int prepareLMA(
boolean [] param_select,
......@@ -103,7 +129,7 @@ public class CuasMotionLMA {
int ay = Math.abs(y-iy0);
for (int x = 0; x < width; x++) {
int ax = Math.abs(x-ix0);
double w = window[ay][ax]; // window to the nearest integer x,y
double w = window[ay][ax]+pedestal; // window to the nearest integer x,y
weights[x + y*width] = w;
sw += w;
}
......
......@@ -2412,7 +2412,6 @@ public class Correlation2d {
return rslt;
}
public static double [] getMaxXYCm(
double [] data, // will be modified if fpn_mask != null;
int data_width, // = 2 * transform_size - 1; // negative - calculate fraction
......@@ -2420,28 +2419,64 @@ public class Correlation2d {
int refine, // re-center window around new maximum. 0 -no refines (single-pass)
boolean [] fpn_mask,
boolean ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
boolean debug)
{
boolean debug) {
boolean exclude_margins = false;
if ((fpn_mask!=null) && (fpn_mask.length==0)) {
exclude_margins = true;
fpn_mask = null;
}
boolean calc_fraction = data_width < 0;
if (calc_fraction) {
data_width = -data_width;
}
int imax= 0;
for (int i= 1; i < data.length;i++) {
if (data[i] > data[imax]) {
imax = i;
}
}
return getMaxXYCm(
data, // double [] data, // will be modified if fpn_mask != null;
data_width, // int data_width, // = 2 * transform_size - 1; // negative - calculate fraction
radius, // double radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
refine, // int refine, // re-center window around new maximum. 0 -no refines (single-pass)
fpn_mask, // boolean [] fpn_mask,
ignore_border, // boolean ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
exclude_margins, // boolean exclude_margins,
calc_fraction, // boolean calc_fraction,
imax, // int pix_max,
debug); // boolean debug);
}
public static double [] getMaxXYCm(
double [] data, // will be modified if fpn_mask != null;
int data_width, // = 2 * transform_size - 1; // negative - calculate fraction
double radius, // 0 - all same weight, > 0 cosine(PI/2*sqrt(dx^2+dy^2)/rad)
int refine, // re-center window around new maximum. 0 -no refines (single-pass)
boolean [] fpn_mask,
boolean ignore_border, // only if fpn_mask != null - ignore tile if maximum touches fpn_mask
boolean exclude_margins,
boolean calc_fraction,
int imax, // index of the maximum in data[]
boolean debug)
{
int data_height = data.length/data_width;
// int center_xy = (data_width - 1)/2; // = transform_size - 1;
int center_xy = data_width / 2; // = transform_size - 1;
double x0 = center_xy, y0 = center_xy;
/*
int imax= 0;
for (int i= 1; i < data.length;i++) {
if (data[i] > data[imax]) {
imax = i;
}
}
*/
double mx = data[imax];
if (mx < 0) {
return null; //negative maximum
}
......
......@@ -723,6 +723,12 @@ min_str_neib_fpn 0.35
public double cuas_speed_pref = 0.0; // preferable speed (boost weights for faster targets)
public double cuas_speed_boost = 1.0; // speed boost limit
// target filtering after constant velocity accumulation
public double cuas_lmax_fraction = 0.7; // Check if local maximum is separated from tye surrounding by this fraction of the maximum value
public double cuas_lmax_radius = 3.5; // look inside ((int)cuas_lmax_radius) * 2 + 1 square for the local maximum isolation
public boolean cuas_lmax_zero = true; // zero all data outside this radius from the maximum
public double cuas_target_radius = 3.0; // target centroids center radius
public double cuas_target_strength =0.8; // target centroids center radius
public double [][] cuas_target_frac = {{0,0.15},{2.5,0.18},{5,0.3}};
......@@ -2242,6 +2248,14 @@ min_str_neib_fpn 0.35
"Boost effective strength when speed is above this.");
gd.addNumericField("Maximal speed boost", this.cuas_speed_boost, 5,8,"",
"Maximal speed-caused effective strength boost.");
gd.addNumericField("Maximums separation fraction", this.cuas_lmax_fraction, 5,8,"",
"Check if local maximum is separated from tye surrounding by this fraction of the maximum value.");
gd.addNumericField("Maximums separation radius", this.cuas_lmax_radius, 5,8,"pix",
"Look inside ((int)cuas_lmax_radius) * 2 + 1 square for the local maximum isolation .");
gd.addCheckbox ("Zero far from maximums pixels", this.cuas_lmax_zero,
"Zero all data outside this radius from the maximum.");
gd.addNumericField("Target radius", this.cuas_target_radius, 5,8,"pix",
"Target radius, also used to calculate fraction of totals inside (windowed) to all positive values.");
gd.addNumericField("Minimal target strength", this.cuas_target_strength, 5,8,"",
......@@ -3355,6 +3369,10 @@ min_str_neib_fpn 0.35
this.cuas_speed_pref = gd.getNextNumber();
this.cuas_speed_boost = gd.getNextNumber();
this.cuas_lmax_fraction = gd.getNextNumber();
this.cuas_lmax_radius = gd.getNextNumber();
this.cuas_lmax_zero = gd.getNextBoolean();
this.cuas_target_radius = gd.getNextNumber();
this.cuas_target_strength = gd.getNextNumber();
this.cuas_target_frac = stringToDouble2d(gd.getNextString());
......@@ -4324,6 +4342,10 @@ min_str_neib_fpn 0.35
properties.setProperty(prefix+"cuas_speed_pref", this.cuas_speed_pref+""); // double
properties.setProperty(prefix+"cuas_speed_boost", this.cuas_speed_boost+""); // double
properties.setProperty(prefix+"cuas_lmax_fraction", this.cuas_lmax_fraction+""); // double
properties.setProperty(prefix+"cuas_lmax_radius", this.cuas_lmax_radius+""); // double
properties.setProperty(prefix+"cuas_lmax_zero", this.cuas_lmax_zero+""); // boolean
properties.setProperty(prefix+"cuas_target_radius", this.cuas_target_radius+""); // double
properties.setProperty(prefix+"cuas_target_strength", this.cuas_target_strength+"");// double
properties.setProperty(prefix+"cuas_target_frac", double2dToString(cuas_target_frac)+""); // double[][]
......@@ -5260,6 +5282,10 @@ min_str_neib_fpn 0.35
if (properties.getProperty(prefix+"cuas_speed_pref")!=null) this.cuas_speed_pref=Double.parseDouble(properties.getProperty(prefix+"cuas_speed_pref"));
if (properties.getProperty(prefix+"cuas_speed_boost")!=null) this.cuas_speed_boost=Double.parseDouble(properties.getProperty(prefix+"cuas_speed_boost"));
if (properties.getProperty(prefix+"cuas_lmax_fraction")!=null) this.cuas_lmax_fraction=Double.parseDouble(properties.getProperty(prefix+"cuas_lmax_fraction"));
if (properties.getProperty(prefix+"cuas_lmax_radius")!=null) this.cuas_lmax_radius=Double.parseDouble(properties.getProperty(prefix+"cuas_lmax_radius"));
if (properties.getProperty(prefix+"cuas_lmax_zero")!=null) this.cuas_lmax_zero=Boolean.parseBoolean(properties.getProperty(prefix+"cuas_lmax_zero"));
if (properties.getProperty(prefix+"cuas_target_radius")!=null) this.cuas_target_radius=Double.parseDouble(properties.getProperty(prefix+"cuas_target_radius"));
if (properties.getProperty(prefix+"cuas_target_strength")!=null) this.cuas_target_strength=Double.parseDouble(properties.getProperty(prefix+"cuas_target_strength"));
if (properties.getProperty(prefix+"cuas_target_frac")!= null) cuas_target_frac=stringToDouble2d((String) properties.getProperty(prefix+"cuas_target_frac"));
......@@ -6196,6 +6222,11 @@ min_str_neib_fpn 0.35
imp.cuas_speed_min = this.cuas_speed_min;
imp.cuas_speed_pref = this.cuas_speed_pref;
imp.cuas_speed_boost = this.cuas_speed_boost;
imp.cuas_lmax_fraction = this.cuas_lmax_fraction;
imp.cuas_lmax_radius = this.cuas_lmax_radius;
imp.cuas_lmax_zero = this.cuas_lmax_zero;
imp.cuas_target_frac = new double [this.cuas_target_frac.length][];
for (int i = 0; i < this.cuas_target_frac.length; i++) {
imp.cuas_target_frac[i] = this.cuas_target_frac[i].clone();
......
package com.elphel.imagej.tileprocessor;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -45,26 +46,47 @@ public class TileNeibs{
final public static int DIRS = DIR_XY.length; //8; // total dirs
final static int THREADS_MAX = 100;
final int sizeX;
final int sizeY;
public Rectangle inner = null;
public Rectangle full = null;
public Rectangle getFullRectangle() {
if (full == null) {
full = new Rectangle(0,0,sizeX,sizeY);
}
return full;
}
public Rectangle getInnerRectangle() {
if (inner == null) {
inner = new Rectangle(1,1,sizeX-2, sizeY-2);
}
return inner;
}
int last_grown; // actual grown until died
public int dirs = DIRS;
public static int reverseDir(int dir) {
if ((dir < 0) || (dir >= DIRS)) {
return dir;
}
return (dir+DIRS/2) % DIRS;
}
int sizeX;
int sizeY;
int last_grown; // actual grown until died
public int dirs = DIRS;
public int getLastGrown() {
return last_grown;
}
public TileNeibs(int size){
this.sizeX = size;
this.sizeY = size;
this.inner = new Rectangle(1,1,size-2, size-2);
}
public TileNeibs(int sizeX, int sizeY){
this.sizeX = sizeX;
this.sizeY = sizeY;
this.inner = new Rectangle(1,1,sizeX-2, sizeY-2);
}
public int numNeibs() // TODO: make configurable to
{
......@@ -100,6 +122,10 @@ public class TileNeibs{
int getY(int indx) {return indx / sizeX;};
Point getPoint(int indx) {
return new Point (indx % sizeX, indx / sizeX);
}
public int getSizeX() {
return sizeX;
}
......@@ -1412,4 +1438,116 @@ public class TileNeibs{
return filled;
}
/**
* Get array of indices for all local maximums (pixels that are not less than their 8 neighbors). NaNs are considered smaller than normal values.
* @param data data in line-scan order, should be [sizeX*sizeY] (not verified)
* @param exclude_margins do not allow maximums on the outer edge of the defined rectangle
* @return variable-length interger array of local maxima
*/
public int [] getLocalMaxes(
double [] data,
boolean exclude_margins) {
ArrayList<Integer> max_list = new ArrayList<Integer>();
int x0 = exclude_margins ? 1 : 0;
int y0 = exclude_margins ? 1 : 0;
int x1 = sizeX - x0 - 1;
int y1 = sizeY - y0 - 1;
for (int y = y0; y <= y1; y++) {
for (int x = x0; x <= x1; x++) {
int ntile = y * sizeX + x;
double d = data[ntile];
if (!Double.isNaN(d)) {
check_ismax: {
for (int dir = 0; dir < DIRS; dir++) {
int ntile1 = getNeibIndex(ntile,dir);
if ((ntile >=0) && (data[ntile1] > d)){
break check_ismax;
}
}
max_list.add(ntile);
}
}
}
}
return max_list.stream().mapToInt(Integer::intValue).toArray();
}
/**
* Calculate index of an absolute maximum in array, ignore NaNs. If all elements are NaN, will return -1
* @param data Data array, may contain NaN values
* @return index of a largest element (NaN is considered smaller than any normal value) or -1 if all are NaN
*/
public static int getAmaxTile(
double [] data) {
int ntile = - 1;
double maxv = Double.NaN;
for (int i = 0; i < data.length; i++) {
double d = data[i];
if (!(d <= maxv)) {
ntile = i;
maxv = d;
}
}
return ntile;
}
/**
* Verify that this local maximum is disconnected from any tile larger it by the tiles having
* value of the specified fraction of the maximal value.
* @param data data array
* @param ntile tile index
* @param fraction fraction of the maximal value that separates the maximum
* @param radius if >0, limits allowable expansion (and returns false if reached)
* @return true if it is an isolated maximum, false if not.
*/
public boolean isMaxIsolated(
double [] data,
int ntile,
double fraction,
int radius) {
if ((ntile <0) || (ntile >= data.length)) {
return false; // outside
}
int xc = getX(ntile);
int yc = getY(ntile);
Rectangle full = getFullRectangle();
Rectangle limits = (radius <=0) ? null : (full.intersection(new Rectangle(xc-radius, yc-radius, 2*radius+1,2*radius+1)));
boolean [] tried = new boolean [data.length];
double local_max = data[ntile];
double threshold = local_max * fraction;
ArrayList<Integer> wave = new ArrayList<Integer>();
wave.add(ntile);
tried[ntile] = true;
while (!wave.isEmpty()) {
ntile = wave.remove(0);
for (int dir = 0; dir < DIRS; dir++) {
int ntile1 = getNeibIndex(ntile, dir);
if ((ntile1 >= 0) && !tried[ntile1]) {
tried[ntile1] = true;
double d = data[ntile1]; // may be NaN
if (d > local_max) {
return false;
} else if (d > threshold) { // NaN will result in false
wave.add(ntile1);
if ((limits != null) && !limits.contains(getPoint(ntile1))) {
return false;
}
}
}
}
}
return true;
}
/**
* Check if the point indx is on the edge of the defined rectangle
* @param indx
* @return
*/
public boolean isEdge(int indx) {
return !getFullRectangle().contains(getPoint(indx));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment