Commit d15a47db authored by Andrey Filippov's avatar Andrey Filippov

Improving cuas

parent 0630471a
......@@ -168,6 +168,10 @@ public class CalibrationFileManagement {
if ((dir!=null) && (!dir.isDirectory())){
dir=dir.getParentFile();
}
if (!smart) { // 05.29.2025
return null;
}
JFileChooser fc= new JFileChooser();
......
......@@ -16,7 +16,11 @@ import com.elphel.imagej.tileprocessor.OpticalFlow;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.TileProcessor;
import ij.ImagePlus;
public class Cuas {
/*
@Deprecated
public static QuadCLT createCenterClt( // assuming cuas_rotation is true
CLTParameters clt_parameters,
QuadCLT [] quadCLTs,
......@@ -121,6 +125,217 @@ public class Cuas {
// center_CLT.setDSI(center_combo_dsi); // WRONG!
String rslt_suffix = "-INTER-INTRA";
rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
// fixing NaN in strengths. It is uses to return RMS in Not needed - NaN was from Arrays.fill(combo_dsn_final[i], Double.NaN);
// OpticalFlow:10348
for (int slice: OpticalFlow.COMBO_DSN_NONNAN) { // new int[] {COMBO_DSN_INDX_STRENGTH,COMBO_DSN_INDX_STRENGTH_BG}) {
if (center_combo_dsi[slice] != null) {
for (int i = 0; i <center_combo_dsi[slice].length; i++) {
if (Double.isNaN(center_combo_dsi[slice][i])) {
center_combo_dsi[slice][i] = 0.0;
}
}
}
}
center_CLT.saveDoubleArrayInModelDirectory( // error
rslt_suffix, // String suffix,
OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null, // String [] labels, // or null
center_combo_dsi, // dbg_data, // double [][] data,
tilesX, // int width,
tilesY); // int height)
// center_CLT.saveDSI();
center_CLT.saveCenterClt();
boolean save_clt = true;
boolean save_dsi = true;
boolean save_in_ref = true;
boolean save_in_last = true;
if (save_clt) {
final int height_clt = combo_seq_clt[0].length/width_clt;
String [] clt_titles = new String [combo_seq_clt_w.length];
for (int i = 0; i < combo_seq_clt.length; i++) {
clt_titles[i] = "chn-"+i;
if (save_weights) {
clt_titles[i+combo_seq_clt.length] = "weight-"+i;
}
}
String suffix_clt = "-clt_combo";
if (save_weights) {
suffix_clt+="_weights";
}
if (save_in_ref) {
ref_clt.saveFloatArrayInModelDirectory( // error
suffix_clt, // String suffix,
clt_titles, // combo_dsn_titles_full, // null, // String [] labels, // or null
combo_seq_clt_w, // dbg_data, // float [][] data,
width_clt, // int width,
height_clt); // int height)
}
if (save_in_last) {
quadCLTs[quadCLTs.length - 1].saveFloatArrayInModelDirectory( // error
suffix_clt, // String suffix,
clt_titles, // combo_dsn_titles_full, // null, // String [] labels, // or null
combo_seq_clt_w, // dbg_data, // float [][] data,
width_clt, // int width,
height_clt); // int height)
}
}
if (save_dsi) {
// final double [][] combo_dsi_cli = ref_clt.restoreComboDSI (true);
TileProcessor tp = quadCLTs[quadCLTs.length - 1].getTileProcessor();
final int transform_size = tp.getTileSize();
final int tilesX = tp.getTilesX();
final int tilesY = tp.getTilesY();
final double [][] center_dsi = interpolateDSI(
ref_pXpYD, // final double [][] ref_pXpYD,
ref_dsi, // combo_dsi_cli, // final double [][] dsi_in,
transform_size, // final int transform_size,
tilesX, // final int tilesX,
tilesY); //final int tilesY);
String rslt_suffix = "-CENTER-INTER-INTRA";
// use quadCLTs[quadCLTs.length - 1].restoreComboDSI ("-CENTER",true); to restore
rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
if (save_in_ref) {
ref_clt.saveDoubleArrayInModelDirectory( // error
rslt_suffix, // String suffix,
OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null, // String [] labels, // or null
center_dsi, // dbg_data, // double [][] data,
tilesX, // int width,
tilesY); // int height)
}
if (save_in_last) {
quadCLTs[quadCLTs.length - 1].saveDoubleArrayInModelDirectory( // error
rslt_suffix, // String suffix,
OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null, // String [] labels, // or null
center_dsi, // dbg_data, // double [][] data,
tilesX, // int width,
tilesY); // int height)
}
}
return center_CLT;
}
*/
@Deprecated
public static QuadCLT createCenterClt_old( // assuming cuas_rotation is true
CLTParameters clt_parameters,
QuadCLT [] quadCLTs,
QuadCLT ref_scene, // where combo_dsi is
int [] range, // or null
double [][] ref_combo_dsi, // DSI data for the reference scene (or null to read it from file)
boolean condition_dsi,
int sensor_mask, // -1 - all;
int debugLevel) {
QuadCLT last_clt = quadCLTs[quadCLTs.length-1]; // save center with the latest timestamp
if (range == null) {
range = new int [] {0, quadCLTs.length-1};
}
double [][] center_ATR = CuasCenterLma.getCenterATR( // relative to ref_scene
quadCLTs, // QuadCLT [] quadCLTs,
ref_scene, // QuadCLT ref_scene, //
range, //int [] range,
debugLevel); // int debugLevel);
double [] cuas_xyz = new double[3]; //maybe use later
double [] cuas_atr = new double [] { center_ATR[0][0], center_ATR[0][1], center_ATR[0][2]};
double [][] cuas_xyzatr = {cuas_xyz, cuas_atr};
if (ref_combo_dsi == null) {
ref_combo_dsi =ref_scene.restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
}
double [][] dls = {
ref_combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP],
ref_combo_dsi[OpticalFlow.COMBO_DSN_INDX_LMA],
ref_combo_dsi[OpticalFlow.COMBO_DSN_INDX_STRENGTH]
};
double [][] ds = new double [][] {dls[0],dls[2]}; // {disparity, strength}
if (condition_dsi) {
ds = OpticalFlow.conditionInitialDS( // only
true, // boolean use_conf, // use configuration parameters, false - use following
clt_parameters, // CLTParameters clt_parameters,
dls, // double [][] dls
ref_scene, // QuadCLT scene,
debugLevel);
}
// getRefPxPyD: pX, pY reference image X,Y corresponding to the uniform grid of the center view
final double [][] ref_pXpYD = getRefPxPyD( // Use to interpolate disparity layers
clt_parameters, // CLTParameters clt_parameters,
true, // boolean mode_cuas,
null, // Rectangle fov_tiles,
OpticalFlow.ZERO3, // double [] stereo_xyz, // offset reference camera {x,y,z}
cuas_atr, // double [] stereo_atr_in, // offset reference orientation (cuas)
ds[0], // double [] ref_disparity,
ref_scene, // QuadCLT refCLT, // should be the same instance if one of quadCLTs
debugLevel); // int debugLevel)
boolean save_weights = true; // always
float [][][] center_clt_w = getTDComboSceneSequence(
clt_parameters, // CLTParameters clt_parameters,
ref_pXpYD, // double [][] ref_pXpYD,
save_weights, // boolean save_weights, // output corresponding weights for each data
true, // boolean merge_all,
sensor_mask, // int sensor_mask,
null, // Rectangle fov_tiles,
OpticalFlow.ZERO3, // double [] stereo_xyz, // offset reference camera {x,y,z}
cuas_atr, // double [] stereo_atr_in, // offset reference orientation (cuas)
null, // ds[0], // double [] ref_disparity, // may be null if ref_pXpYD != null
quadCLTs, // QuadCLT [] quadCLTs,
ref_scene, // QuadCLT refCLT, // should be the same instance if one of quadCLTs
debugLevel); // int debugLevel)
// remove weights
//// float [][] center_clt = save_weights? new float [center_clt_w.length/2][]: center_clt_w;
//// int [][] center_clt_num = save_weights? new int [center_clt_w.length/2][]: null;
/*
int [][] center_clt_num = save_weights? new int [center_clt_w.length/2][]: null;
if (save_weights) { // remove second half (weights as integer numbers)
for (int i = 0; i< center_clt.length; i++) {
center_clt[i] = center_clt_w[i];
center_clt_num[i] = new int [center_clt_w[i + center_clt.length].length];
for (int j = 0; j < center_clt_num[i].length; j++) {
center_clt_num[i][j] = (int) Math.round(center_clt_w[i + center_clt.length][j]);
}
}
}
int [] wh_c = ref_scene.getWHC(false);
//final int tile_size_td = 4 * GPUTileProcessor.DTT_SIZE * GPUTileProcessor.DTT_SIZE;
final int width_clt = wh_c[0] * 2; // to make image dimensions similar
*/
String center_name = QuadCLT.getCenterDirName(last_clt.getImageName()); // make name from last timestamp, not reference
String ref_dir_path = ref_scene.getX3dDirectory(center_name);
File cdir = new File(ref_dir_path);
QuadCLT center_CLT = new QuadCLT(ref_scene, center_name); //null
cdir.mkdirs();
center_CLT.setImagePath(cdir.getPath());
/*
center_CLT.setCenterClt(
center_clt[0], // float [] clt,
center_clt_num[0], // int [] clt_num,
width_clt); // int clt_width);
*/
center_CLT.setCenterClt( // only for merged sensors
center_clt_w[0][0], // float [] clt,
center_clt_w[1][0]); // int [] clt_num, // Index 1 out of bounds for length 1
// TileProcessor tp = quadCLTs[quadCLTs.length - 1].getTileProcessor();
final int transform_size = ref_scene.getTileSize();
final int tilesX = ref_scene.getTilesX();
final int tilesY = ref_scene.getTilesY();
final double [][] center_combo_dsi = interpolateDSI(
ref_pXpYD, // final double [][] ref_pXpYD,
ref_combo_dsi, // combo_dsi_cli, // final double [][] dsi_in,
transform_size, // final int transform_size,
tilesX, // final int tilesX,
tilesY); //final int tilesY);
center_CLT.setDSIFromCombo(center_combo_dsi); // reformat
// center_CLT.setDSI(center_combo_dsi); // WRONG!
String rslt_suffix = "-INTER-INTRA";
rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
/*
// fixing NaN in strengths. It is uses to return RMS in Not needed - NaN was from Arrays.fill(combo_dsn_final[i], Double.NaN);
// OpticalFlow:10348
......@@ -218,7 +433,227 @@ public class Cuas {
*/
return center_CLT;
}
public static double [][] interpolateDSI( // uses combo_dsi, not this.dsi
public static QuadCLT createCenterClt( // assuming cuas_rotation is true
CLTParameters clt_parameters,
QuadCLT [] quadCLTs,
QuadCLT ref_scene, // where combo_dsi is
int [] range, // or null
double [][] ref_combo_dsi, // DSI data for the reference scene (or null to read it from file)
boolean condition_dsi,
int sensor_mask, // -1 - all;
int debugLevel) {
QuadCLT last_clt = quadCLTs[quadCLTs.length-1]; // save center with the latest timestamp
if (range == null) {
range = new int [] {0, quadCLTs.length-1};
}
double [][] center_ATR = CuasCenterLma.getCenterATR( // relative to ref_scene
quadCLTs, // QuadCLT [] quadCLTs,
ref_scene, // QuadCLT ref_scene, //
range, //int [] range,
debugLevel); // int debugLevel);
double [] cuas_xyz = new double[3]; //maybe use later
double [] cuas_atr = new double [] { center_ATR[0][0], center_ATR[0][1], center_ATR[0][2]};
double [][] cuas_xyzatr = {cuas_xyz, cuas_atr};
String center_name = QuadCLT.getCenterDirName(quadCLTs[quadCLTs.length - 1].getImageName());
QuadCLT center_CLT = changeReference(
quadCLTs, // QuadCLT [] quadCLTs,
ref_scene, // QuadCLT ref_old,
null, // QuadCLT ref_new,
center_name, // String name_new,
cuas_xyzatr); // double[][] new_xyzatr);
if (ref_combo_dsi == null) {
ref_combo_dsi =ref_scene.restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
}
double [][] dls = {
ref_combo_dsi[OpticalFlow.COMBO_DSN_INDX_DISP],
ref_combo_dsi[OpticalFlow.COMBO_DSN_INDX_LMA],
ref_combo_dsi[OpticalFlow.COMBO_DSN_INDX_STRENGTH]
};
double [][] ds = new double [][] {dls[0],dls[2]}; // {disparity, strength}
if (condition_dsi) {
ds = OpticalFlow.conditionInitialDS( // only
true, // boolean use_conf, // use configuration parameters, false - use following
clt_parameters, // CLTParameters clt_parameters,
dls, // double [][] dls
ref_scene, // QuadCLT scene,
debugLevel);
}
// getRefPxPyD: pX, pY reference image X,Y corresponding to the uniform grid of the center view
final double [][] ref_pXpYD = getRefPxPyD( // Use to interpolate disparity layers
clt_parameters, // CLTParameters clt_parameters,
true, // boolean mode_cuas,
null, // Rectangle fov_tiles,
OpticalFlow.ZERO3, // double [] stereo_xyz, // offset reference camera {x,y,z}
cuas_atr, // double [] stereo_atr_in, // offset reference orientation (cuas)
ds[0], // double [] ref_disparity,
ref_scene, // QuadCLT refCLT, // should be the same instance if one of quadCLTs
debugLevel); // int debugLevel)
double [] disparity_center = new double [ref_pXpYD.length];
Arrays.fill(disparity_center, Double.NaN);
for (int i = 0; i < ref_pXpYD.length; i++) if (ref_pXpYD[i] != null) {
disparity_center[i] = ref_pXpYD[i][2];
}
// TODO: extrapolate, fill gaps in disparity_center
boolean save_weights = true; // always
/*
float [][][] center_clt_w = getTDComboSceneSequence(
clt_parameters, // CLTParameters clt_parameters,
ref_pXpYD, // double [][] ref_pXpYD,
save_weights, // boolean save_weights, // output corresponding weights for each data
true, // boolean merge_all,
sensor_mask, // int sensor_mask,
null, // Rectangle fov_tiles,
OpticalFlow.ZERO3, // double [] stereo_xyz, // offset reference camera {x,y,z}
cuas_atr, // double [] stereo_atr_in, // offset reference orientation (cuas)
null, // ds[0], // double [] ref_disparity, // may be null if ref_pXpYD != null
quadCLTs, // QuadCLT [] quadCLTs,
ref_scene, // QuadCLT refCLT, // should be the same instance if one of quadCLTs
debugLevel); // int debugLevel)
*/
float [][][] center_clt_w = getTDComboSceneSequence(
clt_parameters, // CLTParameters clt_parameters,
null, // ref_pXpYD, // double [][] ref_pXpYD,
save_weights, // boolean save_weights, // output corresponding weights for each data
true, // boolean merge_all,
sensor_mask, // int sensor_mask,
null, // Rectangle fov_tiles,
OpticalFlow.ZERO3, // double [] stereo_xyz, // offset reference camera {x,y,z}
OpticalFlow.ZERO3, // cuas_atr, // double [] stereo_atr_in, // offset reference orientation (cuas)
disparity_center, // ds[0], // double [] ref_disparity, // may be null if ref_pXpYD != null
quadCLTs, // QuadCLT [] quadCLTs,
center_CLT, // ref_scene, // QuadCLT refCLT, // should be the same instance if one of quadCLTs
debugLevel); // int debugLevel)
/*
String center_name = QuadCLT.getCenterDirName(last_clt.getImageName()); // make name from last timestamp, not reference
String ref_dir_path = ref_scene.getX3dDirectory(center_name);
File cdir = new File(ref_dir_path);
QuadCLT center_CLT = new QuadCLT(ref_scene, center_name); //null
cdir.mkdirs();
center_CLT.setImagePath(cdir.getPath());
*/
center_CLT.setCenterClt( // only for merged sensors
center_clt_w[0][0], // float [] clt,
center_clt_w[1][0]); // int [] clt_num, // Index 1 out of bounds for length 1
final int transform_size = ref_scene.getTileSize();
final int tilesX = ref_scene.getTilesX();
final int tilesY = ref_scene.getTilesY();
final double [][] center_combo_dsi = interpolateDSI(
ref_pXpYD, // final double [][] ref_pXpYD,
ref_combo_dsi, // combo_dsi_cli, // final double [][] dsi_in,
transform_size, // final int transform_size,
tilesX, // final int tilesX,
tilesY); //final int tilesY);
center_CLT.setDSIFromCombo(center_combo_dsi); // reformat
// center_CLT.setDSI(center_combo_dsi); // WRONG!
String rslt_suffix = "-INTER-INTRA";
rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
/*
// fixing NaN in strengths. It is uses to return RMS in Not needed - NaN was from Arrays.fill(combo_dsn_final[i], Double.NaN);
// OpticalFlow:10348
for (int slice: OpticalFlow.COMBO_DSN_NONNAN) { // new int[] {COMBO_DSN_INDX_STRENGTH,COMBO_DSN_INDX_STRENGTH_BG}) {
if (center_combo_dsi[slice] != null) {
for (int i = 0; i <center_combo_dsi[slice].length; i++) {
if (Double.isNaN(center_combo_dsi[slice][i])) {
center_combo_dsi[slice][i] = 0.0;
}
}
}
}
*/
center_CLT.saveDoubleArrayInModelDirectory( // error
rslt_suffix, // String suffix,
OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null, // String [] labels, // or null
center_combo_dsi, // dbg_data, // double [][] data,
tilesX, // int width,
tilesY); // int height)
// center_CLT.saveDSI();
center_CLT.saveCenterClt();
/*
boolean save_clt = true;
boolean save_dsi = true;
boolean save_in_ref = true;
boolean save_in_last = true;
if (save_clt) {
final int height_clt = combo_seq_clt[0].length/width_clt;
String [] clt_titles = new String [combo_seq_clt_w.length];
for (int i = 0; i < combo_seq_clt.length; i++) {
clt_titles[i] = "chn-"+i;
if (save_weights) {
clt_titles[i+combo_seq_clt.length] = "weight-"+i;
}
}
String suffix_clt = "-clt_combo";
if (save_weights) {
suffix_clt+="_weights";
}
if (save_in_ref) {
ref_clt.saveFloatArrayInModelDirectory( // error
suffix_clt, // String suffix,
clt_titles, // combo_dsn_titles_full, // null, // String [] labels, // or null
combo_seq_clt_w, // dbg_data, // float [][] data,
width_clt, // int width,
height_clt); // int height)
}
if (save_in_last) {
quadCLTs[quadCLTs.length - 1].saveFloatArrayInModelDirectory( // error
suffix_clt, // String suffix,
clt_titles, // combo_dsn_titles_full, // null, // String [] labels, // or null
combo_seq_clt_w, // dbg_data, // float [][] data,
width_clt, // int width,
height_clt); // int height)
}
}
if (save_dsi) {
// final double [][] combo_dsi_cli = ref_clt.restoreComboDSI (true);
TileProcessor tp = quadCLTs[quadCLTs.length - 1].getTileProcessor();
final int transform_size = tp.getTileSize();
final int tilesX = tp.getTilesX();
final int tilesY = tp.getTilesY();
final double [][] center_dsi = interpolateDSI(
ref_pXpYD, // final double [][] ref_pXpYD,
ref_dsi, // combo_dsi_cli, // final double [][] dsi_in,
transform_size, // final int transform_size,
tilesX, // final int tilesX,
tilesY); //final int tilesY);
String rslt_suffix = "-CENTER-INTER-INTRA";
// use quadCLTs[quadCLTs.length - 1].restoreComboDSI ("-CENTER",true); to restore
rslt_suffix += (clt_parameters.correlate_lma?"-LMA":"-NOLMA");
if (save_in_ref) {
ref_clt.saveDoubleArrayInModelDirectory( // error
rslt_suffix, // String suffix,
OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null, // String [] labels, // or null
center_dsi, // dbg_data, // double [][] data,
tilesX, // int width,
tilesY); // int height)
}
if (save_in_last) {
quadCLTs[quadCLTs.length - 1].saveDoubleArrayInModelDirectory( // error
rslt_suffix, // String suffix,
OpticalFlow.COMBO_DSN_TITLES, // combo_dsn_titles_full, // null, // String [] labels, // or null
center_dsi, // dbg_data, // double [][] data,
tilesX, // int width,
tilesY); // int height)
}
}
*/
return center_CLT;
}
public static double [][] interpolateDSI( // uses combo_dsi, not this.dsi
final double [][] ref_pXpYD,
final double [][] combo_dsi_in,
final int transform_size,
......@@ -328,6 +763,7 @@ public class Cuas {
return combo_dsi;
}
/*
public static float [][] getTDComboSceneSequence( // never used
CLTParameters clt_parameters,
final boolean save_weights, // output corresponding weights for each data
......@@ -368,42 +804,69 @@ public class Cuas {
refCLT, // QuadCLT refCLT, // should be the same instance if one of quadCLTs
debugLevel); // int debugLevel);
}
public static float [][] getTDComboSceneSequence(
*/
// TODO: Use only center disparity, ignore pX,pY
public static float [][][] getTDComboSceneSequence(
CLTParameters clt_parameters,
double [][] ref_pXpYD,
double [][] ref_pXpYD, // TODO: Use disparity, ignore pXpYD?
final boolean save_weights, // output corresponding weights for each data
final boolean merge_all,
final int sensor_mask,
Rectangle fov_tiles,
double [] stereo_xyz, // offset reference camera {x,y,z}
double [] stereo_atr_in, // offset reference orientation (cuas)
double [] ref_disparity,
double [] ref_disparity, // null
QuadCLT [] quadCLTs,
QuadCLT refCLT, // should be the same instance if one of quadCLTs
QuadCLT refCLT, // should be the same instance if one of quadCLTs?
int debugLevel) {
boolean debug_pxpyd = false;
int dbg_slices = 3;
double [][][] dbg_PxPyD = debug_pxpyd? (new double [dbg_slices][quadCLTs.length][]):null;
double [][][] dbg_PxPyD_slice = debug_pxpyd? (new double [1][][]):null;
int dbg_scene = -95;
if (ref_pXpYD == null) {
ref_pXpYD = OpticalFlow.transformToScenePxPyD( // now should work with offset ref_scene
fov_tiles, // final Rectangle [] extra_woi, // show larger than sensor WOI (or null)
ref_disparity, // final double [] disparity_ref, // invalid tiles - NaN in disparity
OpticalFlow.ZERO3, // stereo_xyz, // ZERO3, // final double [] scene_xyz, // camera center in world coordinates
OpticalFlow.ZERO3, // stereo_atr, // ZERO3, // final double [] scene_atr, // camera orientation relative to world frame
refCLT, // final QuadCLT scene_QuadClt,
refCLT, // final QuadCLT reference_QuadClt, // now - may be null - for testing if scene is rotated ref
ImageDtt.THREADS_MAX); // int threadsMax)
if (debug_pxpyd) {
refCLT.show_pXpYD(
ref_pXpYD, // double [][] pXpYD,
"-getTDComboSceneSequence", // String suffix,
true); // boolean show)
}
}
double [] stereo_atr = (stereo_atr_in != null)? stereo_atr_in: OpticalFlow.ZERO3; // maybe later play with rotated camera
boolean mode_cuas = (stereo_atr[0] != 0) || (stereo_atr[1] != 0) || (stereo_atr[2] != 0);
double [][] ref_pXpYD_or_null = mode_cuas ? ref_pXpYD : null; // debugging cuas mode keeping old
double [][] ref_pXpYD_or_null = null; // mode_cuas ? ref_pXpYD : null; // debugging cuas mode keeping old
boolean mb_en = clt_parameters.imp.mb_en && (fov_tiles==null);
double mb_tau = clt_parameters.imp.mb_tau; // 0.008; // time constant, sec
double mb_max_gain = clt_parameters.imp.mb_max_gain; // 5.0; // motion blur maximal gain (if more - move second point more than a pixel
int cuas_discard_border = clt_parameters.imp.cuas_discard_border;
double cuas_max_fold = clt_parameters.imp.cuas_max_fold;
int cuas_min_in_row_col = clt_parameters.imp.cuas_min_in_row_col;
ErsCorrection ers_reference = refCLT.getErsCorrection();
// quadCLTs[0].getCltLength(boolean use_ref)
int sc0 = -1;
for (int nscene = 0; nscene < quadCLTs.length ; nscene++) if (quadCLTs[nscene] != null){
sc0 = nscene;
break;
}
final int num_slices = merge_all? 1 : quadCLTs[sc0].getNumSensors();
final float [][] sumFclt = new float [num_slices * (save_weights? 2:1)][quadCLTs[sc0].getCltSize(false)];
final int [][] numAcc = new int [num_slices][sumFclt[0].length];
// next two to improve multithreading performance
final int tile_size_td = 4 * GPUTileProcessor.DTT_SIZE * GPUTileProcessor.DTT_SIZE;
final int tiles_td = sumFclt[0].length/tile_size_td;
final int tiles_td_all = tiles_td * num_slices; // usually sumFclt.length==1
final int num_tiles = quadCLTs[sc0].getNumTiles(false);
final int tile_length = quadCLTs[sc0].getCltTileLength(); // includes color
final float [][] sumFclt = new float [num_slices][tile_length * num_tiles];
final float [][] sum_weights = new float [num_slices][num_tiles];
final Thread[] threads = ImageDtt.newThreadArray(ImageDtt.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
......@@ -414,10 +877,9 @@ public class Cuas {
String ts = quadCLTs[nscene].getImageName();
double [] scene_xyz = OpticalFlow.ZERO3;
double [] scene_atr = OpticalFlow.ZERO3;
// if (nscene != ref_index) { // Check even for raw, so video frames will match in all modes
if (quadCLTs[nscene] != refCLT) { // Check even for raw, so video frames will match in all modes
scene_xyz = ers_reference.getSceneXYZ(ts);
scene_atr = ers_reference.getSceneATR(ts);
scene_xyz = ers_reference.getSceneXYZ(ts); // saved @ reference, relative to reference
scene_atr = ers_reference.getSceneATR(ts); // saved @ reference, relative to reference
if ((scene_atr==null) || (scene_xyz == null)) {
continue;
}
......@@ -464,6 +926,10 @@ public class Cuas {
fclt = QuadCLT.getTDCombo(
sm, // final int sensor_mask,
merge_all, // final boolean merge_channels,
cuas_discard_border, // final int discard_border,
cuas_max_fold, // final double max_fold,
cuas_min_in_row_col, // final int min_in_row_col, // Minimal number of defined tiles in a row/column
null, // final Rectangle full_woi_in, // show larger than sensor WOI (or null)
clt_parameters, // CLTParameters clt_parameters,
ref_disparity, // double [] disparity_ref,
......@@ -477,6 +943,7 @@ public class Cuas {
quadCLTs[nscene], // final QuadCLT scene,
refCLT, // quadCLTs[ref_index], // final QuadCLT ref_scene, // now - may be null - for testing if scene is rotated ref
true, // final boolean show_nan,
dbg_PxPyD_slice, // final double [][][] dbg_PxPyD_slice,
QuadCLT.THREADS_MAX, // int threadsMax,
debugLevel); // final int debugLevel)
......@@ -484,6 +951,9 @@ public class Cuas {
fclt = QuadCLT.getTDCombo(
sm, // final int sensor_mask,
merge_all, // final boolean merge_channels,
cuas_discard_border, // final int discard_border,
cuas_max_fold, // final double max_fold,
cuas_min_in_row_col, // final int min_in_row_col, // Minimal number of defined tiles in a row/column
null, // final Rectangle full_woi_in, // show larger than sensor WOI (or null)
clt_parameters, // CLTParameters clt_parameters,
ref_disparity, // double [] disparity_ref,
......@@ -497,24 +967,51 @@ public class Cuas {
quadCLTs[nscene], // final QuadCLT scene,
refCLT, // quadCLTs[ref_index], // final QuadCLT ref_scene, // now - may be null - for testing if scene is rotated ref
true, // final boolean show_nan,
dbg_PxPyD_slice, // final double [][][] dbg_PxPyD_slice,
QuadCLT.THREADS_MAX, // int threadsMax,
debugLevel); // final int debugLevel)
}
if (dbg_PxPyD_slice != null) {
for (int slice = 0; slice < dbg_PxPyD.length; slice++) { // 3
dbg_PxPyD[slice][nscene] = new double [dbg_PxPyD_slice[0].length]; // tiles
Arrays.fill(dbg_PxPyD[slice][nscene], Double.NaN);
}
for (int ntile = 0; ntile < dbg_PxPyD_slice[0].length; ntile++) if (dbg_PxPyD_slice[0][ntile] != null) {
for (int slice = 0; slice < dbg_PxPyD.length; slice++) {
dbg_PxPyD[slice][nscene][ntile] = dbg_PxPyD_slice[0][ntile][slice];
}
}
}
final float [][] ffclt = fclt;
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int nTileAll = ai.getAndIncrement(); nTileAll < tiles_td_all; nTileAll = ai.getAndIncrement()) {
int ntile = nTileAll % tiles_td;
int nsens = nTileAll / tiles_td;
int indx0 = ntile * tile_size_td;
int indx1 = indx0 + tile_size_td;
for (int indx = indx0; indx < indx1; indx++) {
float d =ffclt[nsens][indx];
if (!Float.isNaN(d)){
sumFclt[nsens][indx] += d;
numAcc[nsens][indx]++ ;
for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
for (int slice = 0; slice < num_slices; slice++) { // normally just one
int fclt_offs = nTile * tile_length;
float [] fclt_slice =ffclt[slice];
float [] sumFclt_slice = sumFclt[slice];
boolean no_nans = true;
int indx = fclt_offs;
for (int i = 0; i < tile_length; i++) {
if (Float.isNaN(fclt_slice[indx++])) {
no_nans = false;
break;
}
}
if (no_nans) {
indx = fclt_offs;
float w = 1.0f; // constant weight
for (int i = 0; i < tile_length; i++) {
if (Float.isNaN(fclt_slice[indx])) {
sumFclt_slice[indx] += 0;
} else {
sumFclt_slice[indx] += w * fclt_slice[indx];
}
indx++;
}
sum_weights[slice][nTile] += w;
}
}
}
......@@ -528,19 +1025,21 @@ public class Cuas {
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int nTileAll = ai.getAndIncrement(); nTileAll < tiles_td_all; nTileAll = ai.getAndIncrement()) {
int ntile = nTileAll % tiles_td;
int nsens = nTileAll / tiles_td;
int indx0 = ntile * tile_size_td;
int indx1 = indx0 + tile_size_td;
for (int indx = indx0; indx < indx1; indx++) {
if (numAcc[nsens][indx] > 0) {
sumFclt[nsens][indx]/=numAcc[nsens][indx];
for (int nTile = ai.getAndIncrement(); nTile < num_tiles; nTile = ai.getAndIncrement()) {
for (int slice = 0; slice < num_slices; slice++) { // normally just one
int fclt_offs = nTile * tile_length;
float [] sumFclt_slice = sumFclt[slice];
int indx = fclt_offs;
indx = fclt_offs;
float w = sum_weights[slice][nTile]; // 1.0f; // constant weight
if (w > 0) {
for (int i = 0; i < tile_length; i++) {
sumFclt_slice[indx++] /= w;
}
} else {
sumFclt[nsens][indx] = Float.NaN;
}
if (save_weights) {
sumFclt[nsens + num_slices][indx] = numAcc[nsens][indx];
for (int i = 0; i < tile_length; i++) {
sumFclt_slice[indx++] =Float.NaN;
}
}
}
}
......@@ -548,7 +1047,64 @@ public class Cuas {
};
}
ImageDtt.startAndJoin(threads);
return sumFclt;
if (dbg_PxPyD != null) {
String [] debug_frame_titles = {"pX","pY","Disparity"};
String [] debug_titles = new String[quadCLTs.length];
for (int nscene = 0; nscene < quadCLTs.length; nscene++) {
debug_titles[nscene] = quadCLTs[nscene].getImageName();
}
String debugTitle = refCLT.getImageName()+"-pXpYD-discard"+cuas_discard_border+"_fold"+cuas_max_fold+"_min"+cuas_min_in_row_col;
ShowDoubleFloatArrays.showArraysHyperstack(
dbg_PxPyD, // double[][][] pixels,
refCLT.getTilesX(), // int width,
debugTitle, // String title, "time_derivs-rt"+diff_time_rt+"-rxy"+diff_time_rxy,
debug_titles, // String [] titles, // all slices*frames titles or just slice titles or null
debug_frame_titles, // String [] frame_titles, // frame titles or null
true); // boolean show)
}
return new float [][][] {sumFclt,sum_weights};
}
/**
* Convert scenes to a new reference (does not need to be one of the series, such as a virtual
* center in CUAS mode)
* @param quadCLTs array of scene instances,
* @param ref_old old reference scene instance with a list of reference positions and velocities
* @param ref_new new reference scene (does not need to be one of quadCLTs)
* @param name_new only needed if ref_new == null
* @param new_xyzatr new reference offset and rotation relative to the old reference scene
* @return true if no errors
*/
public static QuadCLT changeReference(
QuadCLT [] quadCLTs,
QuadCLT ref_old,
QuadCLT ref_new,
String name_new,
double[][] new_xyzatr) {
ErsCorrection ers_old_reference = ref_old.getErsCorrection();
double [][] inv_new_xyzatr = ErsCorrection.invertXYZATR(new_xyzatr);
if (ref_new == null) {
String ref_dir_path = ref_old.getX3dDirectory(name_new);
File cdir = new File(ref_dir_path);
ref_new = new QuadCLT(ref_old, name_new); // empty scenes_poses
cdir.mkdirs();
ref_new.setImagePath(cdir.getPath());
}
ErsCorrection ers_new_reference = ref_new.getErsCorrection();
for (int scene_index = 0; scene_index < quadCLTs.length; scene_index++) {
double [][] scene_xyzatr,dxyzatr_dt;
String ts = quadCLTs[scene_index].getImageName();
scene_xyzatr = ers_old_reference.getSceneXYZATR(ts);
dxyzatr_dt = ers_old_reference.getSceneErsXYZATR_dt(ts);
double [][] scene_new_xyzatr = ErsCorrection.combineXYZATR(
scene_xyzatr, // double [][] reference_xyzatr,
inv_new_xyzatr); // double [][] scene_xyzatr)
ers_new_reference.addScene(
ts, // String timestamp,
scene_new_xyzatr, // double [][] xyzatr,
dxyzatr_dt); // double [][] ers_xyzatr_dt)
}
return ref_new;
}
public static double [][] getRefPxPyD(
......@@ -816,5 +1372,9 @@ public class Cuas {
return reference_pXpYD; // ref_pXpYD_0; //
}
}
......@@ -42,8 +42,9 @@ public class CuasCenterLma {
private double [] good_or_bad_rms = null; // just for diagnostics, to read last (failed) rms
private double [] initial_rms = null; // {rms, rms_pure}, first-calcualted rms
private double [] last_ymfx = null;
private double [][] last_jt = null;
private double [][] last_jt = null;
/*
@Deprecated
public static double [][] getCenterATR(
QuadCLT [] quadCLTs,
int ref_index,
......@@ -83,6 +84,46 @@ public class CuasCenterLma {
}
return rslt;
}
*/
public static double [][] getCenterATR(
QuadCLT [] quadCLTs,
QuadCLT ref_scene,
int [] range,
int debugLevel) {
boolean [] param_select = new boolean[PARAMETER_NAMES.length];
Arrays.fill(param_select, true);
CuasCenterLma cuasCenterLma = new CuasCenterLma(
param_select, // boolean [] param_select,
quadCLTs, // QuadCLT [] quadCLTs,
ref_scene, // QuadCLT ref_scene,
range, // int [] range,
debugLevel); // int debugLevel)
double lambda = 0.1;
double lambda_scale_good = 0.5;
double lambda_scale_bad = 8.0;
double lambda_max = 100;
double rms_diff = 0.001;
int num_iter = 20;
int lmaResult = cuasCenterLma.runLma(
lambda, // double lambda, // 0.1
lambda_scale_good,// double lambda_scale_good,// 0.5
lambda_scale_bad, // double lambda_scale_bad, // 8.0
lambda_max, // double lambda_max, // 100
rms_diff, // double rms_diff, // 0.001
num_iter, // int num_iter, // 20
debugLevel); // int debug_level)
double [][] rslt = {cuasCenterLma.getCenter(),cuasCenterLma.getRadius()};
if (debugLevel > -3) {
System.out.println("lmaResult =" +lmaResult+" iterations, RMSE ="+
cuasCenterLma.getRMS()+" ("+cuasCenterLma.getInitialRMS()+")");
System.out.println("azimuth_center = "+ rslt[0][0]);
System.out.println(" tilt_center = "+ rslt[0][1]);
System.out.println(" average roll = "+ rslt[0][2]);
System.out.println("azimuth_radius = "+ rslt[1][0]);
System.out.println(" tilt_radius = "+ rslt[1][1]);
}
return rslt;
}
public static double [][] getCenterATR(
double [][] scenes_atr,
......@@ -122,6 +163,8 @@ public class CuasCenterLma {
return rslt;
}
/*
@Deprecated
public CuasCenterLma(
boolean [] param_select,
QuadCLT [] quadCLTs,
......@@ -136,7 +179,24 @@ public class CuasCenterLma {
range, // int [] range,
debugLevel); //int debugLevel)
}
*/
public CuasCenterLma(
boolean [] param_select,
QuadCLT [] quadCLTs,
QuadCLT ref_scene,
int [] range,
int debugLevel
) {
prepareLMA(
param_select, // boolean [] param_select,
quadCLTs, // QuadCLT [] quadCLTs,
ref_scene, //int ref_index,
range, // int [] range,
debugLevel); //int debugLevel)
}
public CuasCenterLma(
boolean [] param_select,
double [][] scenes_atr,
......@@ -149,8 +209,8 @@ public class CuasCenterLma {
debugLevel); //int debugLevel)
}
/*
@Deprecated
public int prepareLMA(
boolean [] param_select,
QuadCLT [] quadCLTs,
......@@ -160,7 +220,7 @@ public class CuasCenterLma {
this.param_select = param_select;
earliest_scene = range[0];
last_scene = range[1];
int num_scenes = last_scene- earliest_scene + 1;
// int num_scenes = last_scene- earliest_scene + 1;
ErsCorrection ers_reference = quadCLTs[ref_index].getErsCorrection();
double [][] scenes_atr = new double [quadCLTs.length][];
scenes_atr[ref_index] = new double[3]; // all zeros
......@@ -180,6 +240,37 @@ public class CuasCenterLma {
new int [] {earliest_scene,last_scene}, // int [] range,
debugLevel); // int debugLevel);
}
*/
public int prepareLMA(
boolean [] param_select,
QuadCLT [] quadCLTs,
QuadCLT ref_scene,
int [] range,
int debugLevel) {
this.param_select = param_select;
earliest_scene = range[0];
last_scene = range[1];
// int num_scenes = last_scene- earliest_scene + 1;
ErsCorrection ers_reference = ref_scene.getErsCorrection();
double [][] scenes_atr = new double [quadCLTs.length][];
// scenes_atr[ref_index] = new double[3]; // all zeros should not be needed, it should be in ers_reference.getSceneATR(ts) for "normal scenes
for (int nscene = last_scene; nscene >= earliest_scene; nscene--) {
// just checking it is not isolated
if (quadCLTs[nscene] == null) {
earliest_scene = nscene + 1;
break;
}
// now reference scene should also be in ers_reference.getSceneXYZ(ts)
String ts = quadCLTs[nscene].getImageName();
scenes_atr[nscene] = ers_reference.getSceneATR(ts);
}
return prepareLMA(
param_select, // boolean [] param_select,
scenes_atr, // double [][] scenes_atr,
new int [] {earliest_scene,last_scene}, // int [] range,
debugLevel); // int debugLevel);
}
public int prepareLMA(
boolean [] param_select,
......
......@@ -1132,6 +1132,16 @@ public class GpuQuad{ // quad camera description
int tilesY = wh[1] / GPUTileProcessor.DTT_SIZE;
return tilesY*tilesX*num_colors* 4 * GPUTileProcessor.DTT_SIZE * GPUTileProcessor.DTT_SIZE;
}
public int getNumTiles(boolean use_ref) {
int [] wh = use_ref ? gpu_clt_ref_wh : gpu_clt_wh;
int tilesX = wh[0] / GPUTileProcessor.DTT_SIZE;
int tilesY = wh[1] / GPUTileProcessor.DTT_SIZE;
return tilesY*tilesX; // *num_colors* 4 * GPUTileProcessor.DTT_SIZE * GPUTileProcessor.DTT_SIZE;
}
public int getCltTileLength() { // per camera, in floats
return num_colors * 4 * GPUTileProcessor.DTT_SIZE * GPUTileProcessor.DTT_SIZE;
}
/*
public int getCltLength(boolean use_ref) {
int [] wh = use_ref ? gpu_clt_ref_wh : gpu_clt_wh;
......
......@@ -1859,7 +1859,7 @@ public class Interscene {
public static double [][] getXyzatrImsCenter(
public static double [][] getXyzatrImsCenter( // Not used
final CLTParameters clt_parameters,
final boolean compensate_ims_rotation,
final boolean inertial_only,
......
......@@ -654,8 +654,10 @@ min_str_neib_fpn 0.35
public double mb_tau = 0.008; // time constant, sec
public double mb_max_gain = 5.0; // motion blur maximal gain (if more - move second point more than a pixel
public double mb_max_gain_inter = 2.0; // same for interscene correlation for pose adjustment
//CUAS mode
public int cuas_discard_border = 8; // Discard this number of pixels from each side when merging
public double cuas_max_fold = 50;
public int cuas_min_in_row_col = 4; // Minimal number of defined tiles in a row/column
// TODO: move next parameters elsewhere - they are not the motion blur ones.
public int mb_gain_index_pose = 5; // pose readjust pass to switch to full mb_max_gain from mb_max_gain_inter
public int mb_gain_index_depth = 5; // depth map refine pass (SfM) to switch to full mb_max_gain from mb_max_gain_inter
......@@ -1963,6 +1965,13 @@ min_str_neib_fpn 0.35
"Maximal gain for motion blur correction (if needed more for 1 pixel, increase offset). Will be forced fro the last adjustment");
gd.addNumericField("Maximal gain pose", this.mb_max_gain_inter, 5,7,"x",
"Maximal gain for motion blur correction during interscene correlation. Will be used for all but the last adjustment.");
gd.addTab("CUAS","CUAS Parameters");
gd.addNumericField("Discard margins", this.cuas_discard_border, 0,3,"pix",
"Discards this number of pixels from each side when merging images.");
gd.addNumericField("Maximal X,Y fold", this.cuas_max_fold, 5,7,"pix",
"Maximal non-monotonic Px, Py in PxPyD (usually near image edges).");
gd.addNumericField("Minimal tiles in a row/column", this.cuas_min_in_row_col, 0,3,"tiles",
"Discards rows then columns that have less defined tiles (noticed in a diagonal after folds removal).");
gd.addTab("LMA sequence","Interscene LMA sequence control");
gd.addMessage("Parameters for control of the LMA pose adjustment sequence");
gd.addNumericField("Pose readjust number for full mb_gain", this.mb_gain_index_pose, 0,3,"",
......@@ -2845,6 +2854,11 @@ min_str_neib_fpn 0.35
this.mb_tau = gd.getNextNumber();
this.mb_max_gain = gd.getNextNumber();
this.mb_max_gain_inter = gd.getNextNumber();
this.cuas_discard_border= (int) gd.getNextNumber();
this.cuas_max_fold = gd.getNextNumber();
this.cuas_min_in_row_col= (int) gd.getNextNumber();
this.mb_gain_index_pose = (int) gd.getNextNumber();
this.mb_gain_index_depth =(int) gd.getNextNumber();
......@@ -3659,6 +3673,10 @@ min_str_neib_fpn 0.35
properties.setProperty(prefix+"mb_max_gain", this.mb_max_gain+""); // double
properties.setProperty(prefix+"mb_max_gain_inter", this.mb_max_gain_inter+""); // double
properties.setProperty(prefix+"cuas_discard_border", this.cuas_discard_border+""); // int
properties.setProperty(prefix+"cuas_max_fold", this.cuas_max_fold+""); // double
properties.setProperty(prefix+"cuas_min_in_row_col", this.cuas_min_in_row_col+""); // int
properties.setProperty(prefix+"mb_gain_index_pose", this.mb_gain_index_pose+""); // int
properties.setProperty(prefix+"mb_gain_index_depth", this.mb_gain_index_depth+""); // int
......@@ -4448,6 +4466,10 @@ min_str_neib_fpn 0.35
if (properties.getProperty(prefix+"mb_max_gain")!=null) this.mb_max_gain=Double.parseDouble(properties.getProperty(prefix+"mb_max_gain"));
if (properties.getProperty(prefix+"mb_max_gain_inter")!=null) this.mb_max_gain_inter=Double.parseDouble(properties.getProperty(prefix+"mb_max_gain_inter"));
if (properties.getProperty(prefix+"cuas_discard_border")!=null) this.cuas_discard_border=Integer.parseInt(properties.getProperty(prefix+"cuas_discard_border"));
if (properties.getProperty(prefix+"cuas_max_fold")!=null) this.cuas_max_fold=Double.parseDouble(properties.getProperty(prefix+"cuas_max_fold"));
if (properties.getProperty(prefix+"cuas_min_in_row_col")!=null) this.cuas_min_in_row_col=Integer.parseInt(properties.getProperty(prefix+"cuas_min_in_row_col"));
if (properties.getProperty(prefix+"mb_gain_index_pose")!=null) this.mb_gain_index_pose=Integer.parseInt(properties.getProperty(prefix+"mb_gain_index_pose"));
if (properties.getProperty(prefix+"mb_gain_index_depth")!=null) this.mb_gain_index_depth=Integer.parseInt(properties.getProperty(prefix+"mb_gain_index_depth"));
......@@ -5239,6 +5261,10 @@ min_str_neib_fpn 0.35
imp.mb_max_gain = this.mb_max_gain;
imp.mb_max_gain_inter = this.mb_max_gain_inter;
imp.cuas_discard_border = this.cuas_discard_border;
imp.cuas_max_fold = this.cuas_max_fold;
imp.cuas_min_in_row_col = this.cuas_min_in_row_col;
imp.mb_gain_index_pose = this.mb_gain_index_pose;
imp.mb_gain_index_depth = this.mb_gain_index_depth;
......
......@@ -5786,16 +5786,15 @@ public class OpticalFlow {
double [] cuas_atr = ZERO3;
if (extract_center_orientation && clt_parameters.imp.lock_position) {
center_ATR = CuasCenterLma.getCenterATR(
quadCLTs, // QuadCLT [] quadCLTs,
ref_index, //int ref_index,
new int [] {earliest_scene, last_index}, //int [] range,
quadCLTs, // QuadCLT [] quadCLTs,
quadCLTs[ref_index], // QuadCLT ref_scene,ref_index, //int ref_index,
new int [] {earliest_scene, last_index}, // int [] range,
debugLevel); // int debugLevel);
// cuas_atr = new double [] {-center_ATR[0][0],-center_ATR[0][1],-center_ATR[0][2]};
cuas_atr = new double [] { center_ATR[0][0], center_ATR[0][1], center_ATR[0][2]};
}
boolean combine_clt = clt_parameters.imp.cuas_rotation; // (cuas_atr[0] != 0) || (cuas_atr[1] != 0) || (cuas_atr[2] != 0);
// TODO: Refine center_CLT if it exists, not crete
// TODO: Refine center_CLT if it exists, not create
if (combine_clt) {
......@@ -5810,10 +5809,11 @@ public class OpticalFlow {
if (combo_dsn_final == null) {
combo_dsn_final =quadCLTs[ref_index].restoreComboDSI(true); // also sets quadCLTs[ref_index].dsi and blue sky
}
center_CLT = Cuas.createCenterClt( // assuming cuas_rotation is true
clt_parameters, // CLTParameters clt_parameters,
quadCLTs, // QuadCLT [] quadCLTs,
ref_index, // int ref_index,
quadCLTs[ref_index], // QuadCLT ref_scene, // where combo_dsi is
null, // int [] range, // or null
combo_dsn_final, // double [][] ref_dsi, // DSI data for the reference scene (or null to read it from file)
condition_dsi, // boolean condition_dsi,
......@@ -5828,38 +5828,16 @@ public class OpticalFlow {
}
}
// just for verification
boolean apply_clt = true;
boolean show_clt = true;
int [] whc = new int[3];
if (apply_clt) { // set GPU with data
center_CLT.setQuadClt(); //2025 mark that GPU is used for center_CLT
// quadCLTs[ref_index].setComboToTD(
center_CLT.setComboToTD(
null, // new float [][] {center_CLT.getCenterClt()}, // ,combo_seq_clt, // final float [][] fclt,
true, // merge_clt, // final boolean merge_channels, // duplicate same data to all selected channels
sensor_mask_clt, // final int sensor_mask, // only if merge_channels
whc, // final int [] whc, // if int[2], will return width, height
false); // final boolean use_reference);
if (show_clt) {
String suffix="-virtual";
// ImagePlus imp_virtual = ref_clt.renderFromTD ( // do we need to update gpuQuad ?
ImagePlus imp_virtual = center_CLT.renderFromTD ( // do we need to update gpuQuad ?
sensor_mask_clt, // final int sensor_mask,
true, // merge_clt, // boolean merge_channels,
clt_parameters, // CLTParameters clt_parameters,
// clt_parameters.getColorProcParameters(ref_clt.isAux()), //ColorProcParameters colorProcParameters,
clt_parameters.getColorProcParameters(center_CLT.isAux()), //ColorProcParameters colorProcParameters,
clt_parameters.getRGBParameters(), //EyesisCorrectionParameters.RGBParameters rgbParameters,\
whc, // null, // int [] wh,
false, // toRGB, // boolean toRGB,
false, // use_reference, // boolean use_reference
suffix); // String suffix)
imp_virtual.show();
}
boolean show_clt = false;// true;
if (show_clt) {
center_CLT.showCenterClt(
clt_parameters); // CLTParameters clt_parameters,
center_CLT.showCenterCltWeights(
clt_parameters); // CLTParameters clt_parameters,
}
}
if (generate_egomotion) {
boolean ego_show = !clt_parameters.batch_run; //true;
String ego_path = quadCLTs[ref_index].getX3dDirectory()+Prefs.getFileSeparator()+
......
......@@ -123,6 +123,53 @@ public class QuadCLT extends QuadCLTCPU {
}
}
public ImagePlus showCenterClt(
CLTParameters clt_parameters) {
if (getCenterClt() == null) {
System.out.println("showCenterClt(): not a center CLT");
return null;
}
int sensor_mask_clt = 1; // just one
setQuadClt();
int [] whc = new int[3];
setComboToTD(
null, // new float [][] {center_CLT.getCenterClt()}, // ,combo_seq_clt, // final float [][] fclt,
true, // merge_clt, // final boolean merge_channels, // duplicate same data to all selected channels
sensor_mask_clt, // final int sensor_mask, // only if merge_channels
whc, // final int [] whc, // if int[2], will return width, height
false); // final boolean use_reference);
String suffix="-virtual";
ImagePlus imp_virtual = renderFromTD ( // do we need to update gpuQuad ?
sensor_mask_clt, // final int sensor_mask,
true, // merge_clt, // boolean merge_channels,
clt_parameters, // CLTParameters clt_parameters,
clt_parameters.getColorProcParameters(isAux()), //ColorProcParameters colorProcParameters,
clt_parameters.getRGBParameters(), //EyesisCorrectionParameters.RGBParameters rgbParameters,\
whc, // null, // int [] wh,
false, // toRGB, // boolean toRGB,
false, // use_reference, // boolean use_reference
suffix); // String suffix)
imp_virtual.show();
return imp_virtual;
}
public ImagePlus showCenterCltWeights(
CLTParameters clt_parameters) {
if (getCenterCltWeights() == null) {
System.out.println("showCenterCltWeights(): not a center CLT");
return null;
}
String title = getImageName()+"-center_clt_weights";
ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
getCenterCltWeights(), // float[] pixels,
getTilesX(), // int width,
getTilesY(), // int height,
title); // String title)
imp.show();
return imp;
}
/**
* Remove weak non-LMA tiles if they do not have any LMA or strong neighbors and
* too few weak neighbors. Single strong neighbor within range is enough, strong/LMA
......@@ -1515,6 +1562,10 @@ public class QuadCLT extends QuadCLTCPU {
preRenderGPUFromDSI(
sensor_mask, // final int sensor_mask,
merge_channels, // final boolean merge_channels,
0, // final int discard_border,
0, // final double max_fold,
0, // final int min_in_row_col, // Minimal number of defined tiles in a row/column
full_woi_in, // final Rectangle full_woi_in, // show larger than sensor WOI in tiles (or null)
clt_parameters, // CLTParameters clt_parameters,
disparity_ref, // double [] disparity_ref,
......@@ -1552,6 +1603,9 @@ public class QuadCLT extends QuadCLTCPU {
public static float [][] getTDCombo(
final int sensor_mask,
final boolean merge_channels,
final int discard_border,
final double max_fold,
final int min_in_row_col, // Minimal number of defined tiles in a row/column
final Rectangle full_woi_in, // show larger than sensor WOI in tiles (or null)
CLTParameters clt_parameters,
double [] disparity_ref,
......@@ -1567,12 +1621,15 @@ public class QuadCLT extends QuadCLTCPU {
final QuadCLT ref_scene, // now - may be null - for testing if scene is rotated ref
// final boolean toRGB,
final boolean show_nan,
// String suffix,
final double [][][] dbg_PxPyD_slice,
int threadsMax,
final int debugLevel){
preRenderGPUFromDSI(
double [][] PxPyD = preRenderGPUFromDSI(
sensor_mask, // final int sensor_mask,
merge_channels, // final boolean merge_channels,
discard_border, // final int discard_border,
max_fold, // final double max_fold,
min_in_row_col, // final int min_in_row_col, // Minimal number of defined tiles in a row/column
full_woi_in, // final Rectangle full_woi_in, // show larger than sensor WOI in tiles (or null)
clt_parameters, // CLTParameters clt_parameters,
disparity_ref, // double [] disparity_ref,
......@@ -1586,11 +1643,12 @@ public class QuadCLT extends QuadCLTCPU {
scene_atr, // double [] scene_atr, // camera orientation relative to world frame
scene, //final QuadCLT scene,
ref_scene, // final QuadCLT ref_scene, // now - may be null - for testing if scene is rotated ref
// toRGB, // final boolean toRGB,
show_nan, // final boolean show_nan,
// suffix, // String suffix,
threadsMax, // int threadsMax,
debugLevel); // final int debugLevel)
if (dbg_PxPyD_slice != null) {
dbg_PxPyD_slice[0] = PxPyD;
}
int [] whc = new int [3];
float [][] fclt = scene.getComboFromTD(
sensor_mask, // final int sensor_mask, // only if merge_channels
......@@ -1604,9 +1662,12 @@ public class QuadCLT extends QuadCLTCPU {
public static void preRenderGPUFromDSI(
public static double [][] preRenderGPUFromDSI( // will return PxPyD
final int sensor_mask,
final boolean merge_channels,
final int discard_border,
final double max_fold,
final int min_in_row_col, // Minimal number of defined tiles in a row/column
final Rectangle full_woi_in, // show larger than sensor WOI in tiles (or null)
CLTParameters clt_parameters,
double [] disparity_ref, // may be null if ref_pXpYD!=null
......@@ -1620,12 +1681,15 @@ public class QuadCLT extends QuadCLTCPU {
double [] scene_atr, // camera orientation relative to world frame
final QuadCLT scene,
final QuadCLT ref_scene, // now - may be null - for testing if scene is rotated ref
// final boolean toRGB,
final boolean show_nan,
// String suffix,
int threadsMax,
final int debugLevel){
double [][] pXpYD;
// int tile_size = ref_scene.getTileSize();
int width=ref_scene.getTilesX()*ref_scene.getTileSize();
int height=ref_scene.getTilesY()*ref_scene.getTileSize();
// window in pixels!
final Rectangle window = (discard_border > 0)? (new Rectangle(discard_border,discard_border,width-2*discard_border,height-2*discard_border)): null;
if (ref_pXpYD != null) { // cuas mode, ref_pXpYD defines offset reference scene
pXpYD=OpticalFlow.transformToScenePxPyD(
ref_pXpYD, // final double [][] reference_pXpYD, // invalid tiles - NaN in disparity. Should be no nulls, no NaN disparity
......@@ -1659,6 +1723,14 @@ public class QuadCLT extends QuadCLTCPU {
threadsMax); // int threadsMax)
}
}
if (window != null) {
ref_scene.windowPsPyD(
pXpYD, // final double [][] pXpYD,
window, // final Rectangle window) // window in pixels!
max_fold, // final double max_fold)
min_in_row_col); // final int min_in_row_col, // Minimal number of defined tiles in a row/column
}
int rendered_width = scene.getErsCorrection().getSensorWH()[0];
if (full_woi_in != null) {
rendered_width = full_woi_in.width * GPUTileProcessor.DTT_SIZE;
......@@ -1770,15 +1842,183 @@ public class QuadCLT extends QuadCLTCPU {
threadsMax, // final int threadsMax, // maximal number of threads to launch
debugLevel); // final int globalDebugLevel);
}
return;
return pXpYD;
}
public void windowPsPyD(
final double [][] pXpYD,
final Rectangle window, // window in pixels!
final double max_fold,
final int min_in_row_col) { // Minimal number of defined tiles in a row/column
final Thread[] threads = ImageDtt.newThreadArray(OpticalFlow.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
// final int tile_size = getTileSize();
final int tilesX = getTilesX();
final int tilesY = getTilesY();
if (window == null) {
return;
}
final double x0 = window.x;///dtile_size;
final double y0 = window.y;//dtile_size;
final double x1 = x0 + window.width;// /dtile_size;
final double y1 = y0 + window.height;// /dtile_size;
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int nTile = ai.getAndIncrement(); nTile < pXpYD.length; nTile = ai.getAndIncrement()){
double [] xyd = pXpYD[nTile];
if ((xyd != null) && ((xyd[0] < x0) || (xyd[0] > x1) || (xyd[1] < y0) || (xyd[1] > y1))) {
pXpYD[nTile] = null;
}
}
}
};
}
ImageDtt.startAndJoin(threads);
if (max_fold > 0) {
// horizontal near-monotonic
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
int [] num_mono = new int [tilesX];
for (int nTileY = ai.getAndIncrement(); nTileY < tilesY; nTileY = ai.getAndIncrement()){
Arrays.fill(num_mono,0);
int max_len_indx = 0;
double prev = 0;
boolean has_inv = false;
for (int tileX = 1; tileX < tilesX; tileX++) {
int indx = tileX + tilesX * nTileY;
if (pXpYD[indx] == null) {
num_mono[tileX]=num_mono[tileX-1];
} else if (pXpYD[indx][0] > (prev - max_fold)) {
num_mono[tileX]=num_mono[tileX-1]+1;
if (num_mono[tileX] > num_mono[max_len_indx]) {
max_len_indx = tileX;
}
prev = pXpYD[indx][0];
} else {
num_mono[tileX]=0;
has_inv = true; // at least one inversion
prev = pXpYD[indx][0];
}
}
if (has_inv) { // if there is at least one (significant) inversion - keep only longest
int offs = nTileY*tilesX;
for (int i = max_len_indx +1; i < tilesX; i++) {
pXpYD[offs+i] = null;
}
int start_i = max_len_indx - num_mono[max_len_indx];
for (; start_i >= 0 ; start_i--){
if ((pXpYD[offs + start_i] != null) && (num_mono[start_i]==0)) {
break; // first in monotonic sequence;
}
for (int i = 0; i < start_i; i++) {
pXpYD[offs+i] = null;
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
// vertical near-monotonic
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
int [] num_mono = new int [tilesY];
for (int nTileX = ai.getAndIncrement(); nTileX < tilesX; nTileX = ai.getAndIncrement()){
Arrays.fill(num_mono,0);
int max_len_indx = 0;
double prev = 0;
boolean has_inv = false;
for (int tileY = 1; tileY < tilesY; tileY++) {
int indx = nTileX + tilesX * tileY;
if (pXpYD[indx] == null) {
num_mono[tileY] = num_mono[tileY-1];
} else if (pXpYD[indx][0] > (prev - max_fold)) {
num_mono[tileY]=num_mono[tileY-1]+1;
if (num_mono[tileY] > num_mono[max_len_indx]) {
max_len_indx = tileY;
}
prev = pXpYD[indx][0];
} else {
num_mono[tileY]=0;
has_inv = true; // at least one inversion
prev = pXpYD[indx][0];
}
}
if (has_inv) { // if there is at least one (significant) inversion - keep only longest
for (int i = max_len_indx +1; i < tilesX; i++) {
pXpYD[nTileX + i * tilesX] = null;
}
int start_i = max_len_indx - num_mono[max_len_indx];
for (; start_i >= 0 ; start_i--){
if ((pXpYD[nTileX + start_i * tilesX] != null) && (num_mono[start_i]==0)) {
break; // first in monotonic sequence;
}
for (int i = 0; i < start_i; i++) {
pXpYD[nTileX+i * tilesX] = null;
}
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
}
//final int min_in_row_col, // Minimal number of defined tiles in a row/column
if (min_in_row_col > 0) {
// horizontal too few non-null in a row
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int nTileY = ai.getAndIncrement(); nTileY < tilesY; nTileY = ai.getAndIncrement()){
int num_defined = 0;
for (int tileX = 0; tileX < tilesX; tileX++) {
if (pXpYD[tileX + tilesX * nTileY] != null) {
num_defined++;
}
}
if (num_defined < min_in_row_col) {
for (int tileX = 0; tileX < tilesX; tileX++) {
pXpYD[tileX + tilesX * nTileY] = null;
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
// vertical too few non-null in a column
ai.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int nTileX = ai.getAndIncrement(); nTileX < tilesX; nTileX = ai.getAndIncrement()){
int num_defined = 0;
for (int tileY = 0; tileY < tilesY; tileY++) {
if (pXpYD[nTileX + tilesX * tileY] != null) {
num_defined++;
}
}
if (num_defined < min_in_row_col) {
for (int tileY = 0; tileY < tilesY; tileY++) {
pXpYD[nTileX + tilesX * tileY] = null;
}
}
}
}
};
}
ImageDtt.startAndJoin(threads);
}
return;
}
public static double [][] getScenePxPyD(
final Rectangle full_woi_in, // show larger than sensor WOI in tiles (or null)
......@@ -2622,6 +2862,48 @@ public class QuadCLT extends QuadCLTCPU {
return gpuQuad.getCltSize(use_ref);
}
public int getNumTiles(boolean use_ref) {
return gpuQuad.getNumTiles(use_ref);
}
public int getCltTileLength() { // per camera, in floats
return gpuQuad.getCltTileLength();
}
public boolean saveCenterClt() {
if (!hasCenterClt()) {
System.out.println("saveCenterCli(): center CLT data is not set");
return false;
}
int fclt_tile_length = getCltTileLength();
float [] fcenter_clt_data_weights = new float [center_clt.length + center_clt_weights.length]; // to include weights
System.arraycopy(center_clt, 0, fcenter_clt_data_weights, 0, center_clt.length);
System.arraycopy(center_clt_weights, 0, fcenter_clt_data_weights, center_clt.length, center_clt_weights.length);
saveFloatArrayInModelDirectory( // error
CENTER_CLT_SUFFIX, // String suffix,
new String[1], // String [] labels, // or null
new float [][] {fcenter_clt_data_weights}, // dbg_data, // float [][] data,
(fclt_tile_length+1), // getCenterCltWH()[0], // int width,
center_clt.length / fclt_tile_length); // getCenterCltWH()[1]); // int height)
/*
float [] fcenter_clt_num = new float [center_clt_num.length];
for (int i = 0; i <center_clt_num.length; i++) {
fcenter_clt_num[i] = center_clt_num[i];
}
String [] clt_titles= {"CLT","num-acc"};
float [][] fdata = {center_clt, fcenter_clt_num};
saveFloatArrayInModelDirectory( // error
CENTER_CLT_SUFFIX, // String suffix,
clt_titles, // String [] labels, // or null
fdata, // dbg_data, // float [][] data,
getCenterCltWH()[0], // int width,
getCenterCltWH()[1]); // int height)
*/
return true;
}
public int getNumSensors() { // Use QCC - this one may be null
if (gpuQuad != null) {
return gpuQuad.getNumSensors();
......@@ -4506,9 +4788,10 @@ public class QuadCLT extends QuadCLTCPU {
titles);
}
public void showCentersXY(String title) {
int tilesX = tp.getTilesX();
int tilesY = tp.getTilesY();
int tilesX = getTilesX();
int tilesY = getTilesY();
String [] titles = {"centerX","centerY"};
double [][] centers_xy = getCenterXY();
double [][] data = new double [2][tilesX*tilesY];
......
......@@ -212,8 +212,9 @@ public class QuadCLTCPU {
// combined clt for center view, only used in cuas mode
public float [] center_clt = null; // clt data (single accumulated channel)
public int [] center_clt_num = null; // same size as combo_clt - number of accumulated scenes for this point
public int center_clt_width = 0; // image width to save combo_clt (and combo_clt_num) as image
/// public int [] center_clt_num = null; // same size as combo_clt - number of accumulated scenes for this point
public float [] center_clt_weights= null; // 1/64 of center_clt (tilesX*tilesY)
/// public int center_clt_width = 0; // image width to save combo_clt (and combo_clt_num) as image
public boolean isPhotometricUpdated() {
......@@ -238,9 +239,10 @@ public class QuadCLTCPU {
}
public boolean hasCenterClt() {
return (center_clt != null) && (center_clt_num != null) && (center_clt_width != 0);
return (center_clt != null) && (center_clt_weights != null);
}
/*
public void setCenterClt(
float [] clt,
int [] clt_num,
......@@ -249,17 +251,31 @@ public class QuadCLTCPU {
center_clt_num = clt_num;
center_clt_width = clt_width;
}
*/
public void setCenterClt(
float [] clt,
float [] clt_weights) {
center_clt = clt;
center_clt_weights = clt_weights;
}
public float [] getCenterClt() {
return center_clt;
}
public float [] getCenterCltWeights() {
return center_clt_weights;
}
/*
public int [] getCenterCltNum() {
return center_clt_num;
}
public int [] getCenterCltWH() {
return new int [] {center_clt_width, center_clt.length / center_clt_width};
}
public boolean saveCenterClt() {
public boolean saveCenterClt_old() {
if (!hasCenterClt()) {
System.out.println("saveCenterCli(): center CLT data is not set");
return false;
......@@ -278,12 +294,15 @@ public class QuadCLTCPU {
getCenterCltWH()[1]); // int height)
return true;
}
*/
public static String getCenterDirName(String ref_name) {
return ref_name + CENTER_DIR_SUFFIX;
}
public static QuadCLT restoreCenterClt(QuadCLT ref_clt) {
/*
public static QuadCLT restoreCenterClt_old(QuadCLT ref_clt) {
String center_name = QuadCLT.getCenterDirName(ref_clt.getImageName());
String ref_dir_path = ref_clt.getX3dDirectory(center_name);
File cdir = new File(ref_dir_path);
......@@ -312,20 +331,49 @@ public class QuadCLTCPU {
num, // int [] clt_num,
wh[0]); // int clt_width);
}
// TwoQuadCLT.DSI_COMBO_SUFFIX
/*
center_CLT.restoreDSI(
TwoQuadCLT.DSI_COMBO_SUFFIX, // String suffix,
true); // boolean silent)
*/
center_CLT.restoreComboDSI(true);
return center_CLT;
}
*/
public static QuadCLT restoreCenterClt(QuadCLT ref_clt) {
String center_name = QuadCLT.getCenterDirName(ref_clt.getImageName());
String ref_dir_path = ref_clt.readX3dDirectory(center_name);
if (ref_dir_path == null) {
System.out.println("restoreCenterClt(): directory does not exist: "+ref_dir_path);
return null;
}
File cdir = new File(ref_dir_path);
if (!cdir.exists() || !cdir.isDirectory()) {
System.out.println("restoreCenterClt(): directory does not exist: "+ref_dir_path);
return null;
}
QuadCLT center_CLT = new QuadCLT(ref_clt, center_name);
center_CLT.setImagePath(cdir.getPath());
int [] wh = new int[2];
float [][] fclt_w = center_CLT.readFloatArrayFromModelDirectory(
CENTER_CLT_SUFFIX, // String suffix,
0, // int num_slices, // (0 - all)
wh); // int [] wh)
if (fclt_w != null) {
if (fclt_w.length != 1) {
System.out.println("restoreCenterClt(): expected a single-slice data, got "+fclt_w.length+" slices.");
return null;
}
int fclt_tile_length = center_CLT.getCltTileLength();
float [] center_clt_weights = new float [fclt_w[0].length/(fclt_tile_length + 1)];
float [] center_clt = new float [fclt_w[0].length/(fclt_tile_length + 1) * fclt_tile_length];
System.arraycopy(fclt_w[0], 0, center_clt, 0, center_clt.length);
System.arraycopy(fclt_w[0], center_clt.length, center_clt_weights, 0, center_clt_weights.length);
center_CLT.setCenterClt(
center_clt, // float [] clt,
center_clt_weights); // int clt_width);
}
center_CLT.restoreComboDSI(true);
return center_CLT;
}
public String getReferenceTimestamp() {
return timestamp_reference;
}
......@@ -2848,6 +2896,25 @@ public class QuadCLTCPU {
public TileProcessor getTileProcessor() {
return tp;
}
/*
public int getTileLength() {
return tp.getTileSize()*tp.getTileSize();
}
public int getCltTileLength() {
return 4*tp.getTileSize()*tp.getTileSize();
}
*/
public int getTileSize() {
return tp.getTileSize();
}
public int getTilesX() {
return tp.getTilesX();
}
public int getTilesY() {
return tp.getTilesY();
}
public int getNumSensors() {
if (geometryCorrection == null) {
System.out.println("*** BUG! geometryCorrection is not set, number of sensors is unknown! Will use 4 sensors ****");
......@@ -3050,6 +3117,14 @@ public class QuadCLTCPU {
true); //newAllowed, // save
return x3d_path;
}
public String readX3dDirectory(String name) { // replace direct calculations
String x3d_path = correctionsParameters.selectX3dDirectory( // for x3d and obj
name, // quad timestamp. Will be ignored if correctionsParameters.use_x3d_subdirs is false
correctionsParameters.x3dModelVersion,
false, // smart,
false); //newAllowed, // save
return x3d_path;
}
/**
* Discriminate "blue sky" areas with no details at infinity. Such areas
......@@ -11848,6 +11923,37 @@ public class QuadCLTCPU {
}
}
public ImagePlus show_pXpYD(
double [][] pXpYD,
String suffix,
boolean show) {
if (suffix == null) {
suffix = "";
}
String [] titles = {"pX","pY","Disparity"};
double [][] dbg_img = new double [titles.length][pXpYD.length];
for (int i = 0; i < dbg_img.length; i++) {
Arrays.fill(dbg_img[i], Double.NaN);
}
for (int nTile = 0; nTile < pXpYD.length; nTile++){
if (pXpYD[nTile] != null) {
for (int i = 0; i < pXpYD[nTile].length; i++) {
dbg_img[i][nTile] = pXpYD[nTile][i];
}
}
}
ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
dbg_img,
getTilesX(), // int width,
getTilesY(), // int height,
getImageName()+"-pXpYD"+suffix,
titles);
if (show) {
imp.show();
}
return imp;
}
public static void showPimuOffsets(CLTParameters clt_parameters) {
showPimuOffsets( clt_parameters.imp.get_pimu_offs());
}
......
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