package com.elphel.imagej.calibration; /* ** ** Distortions.java - Calculate lens distortion parameters from the pattern image ** ** Copyright (C) 2011-2014 Elphel, Inc. ** ** -----------------------------------------------------------------------------** ** ** Distortions.java is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see <http://www.gnu.org/licenses/>. ** -----------------------------------------------------------------------------** ** */ import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.util.ArrayList; //import java.util.Arrays; //import java.io.StringWriter; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.SwingUtilities; import com.elphel.imagej.calibration.hardware.CamerasInterface; import com.elphel.imagej.cameras.EyesisCameraParameters; import com.elphel.imagej.cameras.EyesisSubCameraParameters; import com.elphel.imagej.common.DoubleGaussianBlur; import com.elphel.imagej.common.PolynomialApproximation; import com.elphel.imagej.common.ShowDoubleFloatArrays; import com.elphel.imagej.common.WindowTools; import com.elphel.imagej.jp4.JP46_Reader_camera; import Jama.LUDecomposition; import Jama.Matrix; import ij.IJ; import ij.ImagePlus; import ij.ImageStack; import ij.Prefs; //import ij.process.*; import ij.gui.GenericDialog; import ij.gui.PointRoi; import ij.io.FileSaver; import ij.io.Opener; import ij.process.FloatProcessor; import ij.process.ImageProcessor; import ij.text.TextWindow; //import src.java.org.apache.commons.configuration.*; // to work both in Eclipse and ImageJ: // 1 - put commons-configuration-1.7.jar under ImageJ plugins directory (I used ImageJ-Elphel) // 2 - in Eclipse project properties -> Build Path -> Libraries -> Add External jar public class Distortions { final public double hintedMaxRelativeRadius=1.2; // make adjustable? private ShowDoubleFloatArrays SDFA_INSTANCE=new ShowDoubleFloatArrays(); // just for debugging? // int numInputs=27; // with A8...// 24; // parameters in subcamera+... // int numOutputs=16; // with A8...//13; // parameters in a single camera public PatternParameters patternParameters; public LensDistortionParameters lensDistortionParameters; public RefineParameters refineParameters= new RefineParameters(); //create with default values public FittingStrategy fittingStrategy=null; public double [][][][] gridOnSensor =null; // [v][u][px,py][0-value, 1..14 - derivative] public double [][] interParameterDerivatives=null; //new double[this.numInputs][]; //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) public double [] currentVector; // current variable parameter vector public double [] Y=null; // array of "y" - for each grid image, each defined grid node - 2 elements public int [] imageStartIndex=null; // elements containing index of the start point of the selected image, first element 0, last - total number of points. public double [] weightFunction=null; // array of weights for pixels (to fade values near borders), corresponding to Y array public double sumWeights; public double [][] targetXYZ=null; // array of target {x,y,z} matching each image each grid point public double [][] jacobian=null; // partial derivatives of fX (above) by parameters to be adjusted (rows) public double [] nextVector; // next variable parameter vector public double [] currentfX=null; // array of "f(x)" - simulated data for all images, combining pixel-X and pixel-Y (odd/even) public double [] nextfX=null; // array of "f(x)" - simulated data for all images, combining pixel-X and pixel-Y (odd/even) public double currentRMS=-1.0; // calculated RMS for the currentVector->currentfX public double nextRMS=-1.0; // calculated RMS for the nextVector->nextfX public double firstRMS=-1.0; // RMS before current series of LMA started public double currentRMSPure=-1.0; // calculated RMS for the currentVector->currentfX public double nextRMSPure=-1.0; // calculated RMS for the nextVector->nextfX public double firstRMSPure=-1.0; // RMS before current series of LMA started public double lambdaStepUp= 8.0; // multiply lambda by this if result is worse public double lambdaStepDown= 0.5; // multiply lambda by this if result is better public double thresholdFinish=0.001; // (copied from series) stop iterations if 2 last steps had less improvement (but not worsening ) public int numIterations= 100; // maximal number of iterations public double maxLambda= 100.0; // max lambda to fail public double lambda=0.001; // copied from series public double [] lastImprovements= {-1.0,-1.0}; // {last improvement, previous improvement}. If both >0 and < thresholdFinish - done public int iterationStepNumber=0; public boolean stopEachStep= true; // open dialog after each fitting step public boolean stopEachSeries=true; // open dialog when each fitting series finished public boolean stopOnFailure= true; // open dialog when fitting series failed public boolean showParams= false; // show modified parameters public boolean showThisImages=false; // show debug images for the current ("this" state,before correction) state of parameters public boolean showNextImages=false; // show debug images for the current (after correction) state of parameters public boolean askFilter= false; // show debug images for the current (after correction) state of parameters // public boolean showGridCorr= true; // show grid correction // public boolean showIndividual=true; // show individual image residuals // public double corrScale= 1.0; // scale grid correction before applying public int seriesNumber=0; // just for the dialog public boolean saveSeries=false; // just for the dialog public double [][][] pixelCorrection=null; // for each sensor: corr-X, corr-Y, mask, flat-field-Red, flat-field-Green, flat-field-Blue public String [] pathNames=null; // Will have to chage for different resolution // public int [][] pixelCorrectionWHD= null; // For each sensor -width, height, decimation // public int defaultPixelCorrectionDecimation= 1; // public int defaultPixelCorrectionWidth= 2592; // public int defaultPixelCorrectionHeight= 1936; // @Deprecated // public int pixelCorrectionDecimation= 1; // @Deprecated // public int pixelCorrectionWidth= 2592; // @Deprecated // public int pixelCorrectionHeight= 1936; public double RMSscale=Math.sqrt(2.0); // errors for x and y are calculated separately, so actual error is larger public boolean showIndex=true; public boolean showRMS=true; public boolean showPoints=true; public boolean showLensLocation=true; public boolean showEyesisParameters=true; public boolean showIntrinsicParameters=true; public boolean showExtrinsicParameters=true; public int extraDecimals=0; public boolean threadedLMA=true; // use threaded/partial method to solve LMA public LMAArrays lMAArrays=null; public LMAArrays savedLMAArrays=null; public long startTime=0; public int debugLevel=2; public boolean updateStatus=true; public int threadsMax=100; public AtomicInteger stopRequested=null; // 1 - stop now, 2 - when convenient public String [] status ={"",""}; public int getSensorWidth(int subCam) { return fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth(subCam);} // for the future? different sensors public int getSensorHeight(int subCam) { return fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight(subCam);}// for the future? different sensors public int getDecimateMasks(int subCam) { return fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getDecimateMasks(subCam);}// for the future? different sensors public int getSensorWidth() { return fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth();} // for the future? different sensors public int getSensorHeight() { return fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight();}// for the future? different sensors public int getDecimateMasks() { return fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getDecimateMasks();}// for the future? different sensors public int getSensorCorrWidth(int subCam) { return(getSensorWidth(subCam)-1)/getDecimateMasks(subCam)+1;} public int getSensorCorrWidth() { return(getSensorWidth()-1)/getDecimateMasks()+1;} public void setSensorWidth(int subCam, int v) { fittingStrategy.distortionCalibrationData.eyesisCameraParameters.setSensorWidth(subCam, v); } public void setSensorHeight(int subCam, int v) {fittingStrategy.distortionCalibrationData.eyesisCameraParameters.setSensorHeight(subCam, v);} public void setDecimateMasks(int subCam, int v){fittingStrategy.distortionCalibrationData.eyesisCameraParameters.setDecimateMasks(subCam, v);} public class LMAArrays { public double [][] jTByJ= null; // jacobian multiplied by Jacobian transposed public double [] jTByDiff=null; // jacobian multiplied difference vector @Override public LMAArrays clone() { LMAArrays lma=new LMAArrays(); lma.jTByJ = this.jTByJ.clone(); for (int i=0;i<this.jTByJ.length;i++) lma.jTByJ[i]=this.jTByJ[i].clone(); lma.jTByDiff=this.jTByDiff.clone(); return lma; } } public Distortions (){} public Distortions ( LensDistortionParameters lensDistortionParameters, PatternParameters patternParameters, RefineParameters refineParameters, AtomicInteger stopRequested ){ // this.patternParameters=patternParameters.clone(); // why clone here? // this.lensDistortionParameters=lensDistortionParameters.clone(); this.patternParameters=patternParameters; // why clone here? this.lensDistortionParameters=lensDistortionParameters; this.refineParameters=refineParameters; this.stopRequested=stopRequested; if (this.lensDistortionParameters!=null) { interParameterDerivatives=new double[this.lensDistortionParameters.getNumInputs()][]; } } // public int getNumInputs(){return numInputs;} // public int getNumOutputs(){return numOutputs;} /** * Prerequisites: * this.patternParameters, this.fittingStrategy are already initialized * */ /* private void initImageSetAndGrids(){ // never used?? // Calculate patter x,y,z==0 and alpha (1.0 - inside, 0.0 - outside) for the grid // TODO: and save/restore to file to account for non-perfect grid patternParameters.calculateGridGeometry(); // Read all grid data files (4-slice TIFF images) and create pixelsXY and pixelsUV arrays fittingStrategy.distortionCalibrationData.readAllGrids(patternParameters); if (this.debugLevel>3) { for (int n=0;n<fittingStrategy.distortionCalibrationData.pixelsXY.length;n++) { for (int i=0;i<fittingStrategy.distortionCalibrationData.pixelsXY[n].length;i++){ System.out.println(n+":"+i+" "+ fittingStrategy.distortionCalibrationData.pixelsUV[n][i][0]+"/"+ fittingStrategy.distortionCalibrationData.pixelsUV[n][i][1]+" "+ IJ.d2s(fittingStrategy.distortionCalibrationData.pixelsXY[n][i][0], 2)+"/"+ IJ.d2s(fittingStrategy.distortionCalibrationData.pixelsXY[n][i][1], 2) ); } } } } */ public DistortionCalibrationData getDistortionCalibrationData() { return (fittingStrategy == null)?null:fittingStrategy.distortionCalibrationData; } public void resetGridImageMasks(){ int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); System.out.println("resetGridImageMasks()"); for (int imgNum=0;imgNum<numImg;imgNum++){ fittingStrategy.distortionCalibrationData.gIP[imgNum].resetMask(); } } // TODO - make station-dependent? Pass sensor mask and combine it? public void calculateGridImageMasks( final double minContrast, final double shrinkBlurSigma, final double shrinkBlurLevel, final int threadsMax, final boolean updateStatus ){ final int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); final DistortionCalibrationData.GridImageParameters [] distortionCalibrationData=this.fittingStrategy.distortionCalibrationData.gIP; if (updateStatus) IJ.showStatus("Calculating grid image masks..."); System.out.print("Calculating grid image masks..."); System.out.print(" minContrast="+minContrast+" shrinkBlurSigma="+shrinkBlurSigma+" shrinkBlurLevel="+shrinkBlurLevel); final AtomicInteger imageNumberAtomic = new AtomicInteger(0); final AtomicInteger imageFinishedAtomic = new AtomicInteger(0); final Thread[] threads = newThreadArray(threadsMax); for (int ithread = 0; ithread < threads.length; ithread++) { threads[ithread] = new Thread() { @Override public void run() { for (int imgNum=imageNumberAtomic.getAndIncrement(); imgNum<numImg;imgNum=imageNumberAtomic.getAndIncrement()){ // if (imgNum == 443) { // System.out.println("calculateGridImageMasks(), imgNum="+imgNum); // } distortionCalibrationData[imgNum].calculateMask( minContrast, shrinkBlurSigma, shrinkBlurLevel); final int numFinished=imageFinishedAtomic.getAndIncrement(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (updateStatus) IJ.showProgress(numFinished,numImg); } }); } // for (int numImage=imageNumberAtomic.getAndIncrement(); ... } // public void run() { }; } startAndJoin(threads); if (updateStatus) IJ.showProgress(0); if (updateStatus) IJ.showStatus("Calculating grid image masks... DONE"); System.out.println(" Done"); } /** * once per fitting strategy series: * 1) repeat for each image/point patternParameters.getXYZM(int u, int v) and create * this.targetXYZ; * * 2)fittingStrategy.buildParameterMap (int numSeries) * Creates map from the parameter vector index to the {grid image number, parameter number} * When the parameter is shared by several images, the map points to the one which value will be used * (they might be different). Timestamp of the masterImages[] is used to determine which image to use. * Simultaneously creates this.reverseParameterMap that maps each of the image/parameter to the parameter vector * Needs to be run for each new strategy series * * 3)this.currentVector=fittingStrategy.getSeriesVector(); // and save it in the class instance * Calculate vector of the parameters used in LMA algorithm, extracted from the * individual data, using parameter map (calculated once after changing series) * * public double [] currentVector; // current variable parameter vector * */ final public int filterMulti= 1; final public int filterContrast= 2; final public int filterSensor= 4; final public int filterTargetMask= 8; final public int filterTargetAlpha= 16; final public int filterTargetErrors= 32; final public int filterMaskBadNodes= 64; final public int filterDiameter= 128; // use measured grid "diameter" to change image weight final public int filterChannelWeights= 256; // different weights for channels (higher weight for bottom sensors) final public int filterYtoX= 512; // different weights for channels (higher weight for bottom sensors) final public int filterForAll= filterMulti+filterContrast+filterSensor+filterTargetMask+filterTargetAlpha+filterTargetErrors+filterMaskBadNodes+ filterDiameter+filterChannelWeights+filterYtoX; final public int filterForSensor= filterMulti+filterContrast +filterTargetMask+filterTargetAlpha+filterTargetErrors+filterMaskBadNodes+ filterDiameter+filterChannelWeights+filterYtoX; final public int filterForTargetGeometry= filterMulti+filterContrast+filterSensor+filterMaskBadNodes+filterDiameter+filterChannelWeights+filterYtoX; final public int filterForTargetFlatField= filterMulti+filterContrast+filterSensor+filterMaskBadNodes+filterDiameter+filterChannelWeights+filterYtoX; public int selectFilter(int dfltFilter){ GenericDialog gd = new GenericDialog("Select series to process"); int filter= dfltFilter; gd.addCheckbox("filterMulti", (filterForAll & filterMulti)!=0); gd.addCheckbox("filterContrast", (filterForAll & filterContrast)!=0); gd.addCheckbox("filterSensor", (filterForAll & filterSensor)!=0); gd.addCheckbox("filterTargetMask", (filterForAll & filterTargetMask)!=0); gd.addCheckbox("filterTargetAlpha", (filterForAll & filterTargetAlpha)!=0); gd.addCheckbox("filterTargetErrors", (filterForAll & filterTargetErrors)!=0); gd.addCheckbox("filterMaskBadNodes", (filterForAll & filterMaskBadNodes)!=0); gd.addCheckbox("filterDiameter", (filterForAll & filterDiameter)!=0); gd.addCheckbox("filterChannelWeights",(filterForAll & filterChannelWeights)!=0); gd.addCheckbox("filterYtoX", (filterForAll & filterYtoX)!=0); gd.showDialog(); if (gd.wasCanceled()) return filter; filter=0; if (gd.getNextBoolean()) filter |= filterMulti; if (gd.getNextBoolean()) filter |= filterContrast; if (gd.getNextBoolean()) filter |= filterSensor; if (gd.getNextBoolean()) filter |= filterTargetMask; if (gd.getNextBoolean()) filter |= filterTargetAlpha; if (gd.getNextBoolean()) filter |= filterTargetErrors; if (gd.getNextBoolean()) filter |= filterMaskBadNodes; if (gd.getNextBoolean()) filter |= filterDiameter; if (gd.getNextBoolean()) filter |= filterChannelWeights; if (gd.getNextBoolean()) filter |= filterYtoX; if (this.debugLevel>1) System.out.println("Using filter bitmap: "+filter); return filter; } public void initFittingSeries( boolean justSelection, // use series to get selection only int filter, int numSeries) { if (initFittingSeries( justSelection, // use series to get selection only filter, numSeries, 1)){ initFittingSeries( justSelection, // use series to get selection only filter, numSeries, 2); } } //returns true if some images were disabled and re-calculation is needed public boolean initFittingSeries( boolean justSelection, // use series to get selection only int filter, int numSeries, int pass) { boolean skipMinVal=this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.minimalValidNodes<0; if ((pass>1) && skipMinVal){ System.out.println("initFittingSeries("+justSelection+","+filter+","+numSeries+"), skipMinVal="+skipMinVal); return false;} // debug - skipping new functionality System.out.println("initFittingSeries("+justSelection+","+filter+","+numSeries+"), pass="+pass); //TODO: ********* Implement comments above ************ // calculate total number of x/y pairs in the selected images if (numSeries<0)justSelection=true; if ((pass==1) && (numSeries>=0)) fittingStrategy.invalidateSelectedImages(numSeries); // next selectedImages() will select all, including empty if (!justSelection) { fittingStrategy.buildParameterMap (numSeries); // also sets currentSeriesNumber } else{ fittingStrategy.currentSeriesNumber=numSeries; } int numXYPairs=0; int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); if (this.debugLevel>3) System.out.println("initFittingSeries("+numSeries+"), numImg="+numImg); if ((pass==1) && (numSeries>=0) && !skipMinVal) fittingStrategy.initSelectedValidImages(numSeries); // copy from selected images boolean [] selectedImages=fittingStrategy.selectedImages(numSeries); // -1 OK, will select all if (this.debugLevel>3) System.out.println("initFittingSeries("+numSeries+"), selectedImages.length="+selectedImages.length); for (int imgNum=0;imgNum<numImg;imgNum++) if (selectedImages[imgNum]) { numXYPairs+=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length; } this.targetXYZ=new double[numXYPairs][3]; this.Y= new double[numXYPairs*2]; this.weightFunction=new double[numXYPairs*2]; this.sumWeights=0.0; this.imageStartIndex=new int [numImg+1]; // added here, was using pixelCorrectionDecimation==1 /// this.pixelCorrectionDecimation=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getDecimateMasks(); /// this.pixelCorrectionWidth= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth(); /// this.pixelCorrectionHeight= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight(); /// int sensorCorrWidth= (this.pixelCorrectionWidth-1)/this.pixelCorrectionDecimation+1; double [] multiWeight=new double [numImg]; for (int imgNum=0;imgNum<numImg;imgNum++) multiWeight[imgNum]=0.0; double minimalGridContrast=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.minimalGridContrast; double shrinkBlurSigma=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.shrinkBlurSigma; double shrinkBlurLevel=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.shrinkBlurLevel; calculateGridImageMasks( minimalGridContrast, // final double minContrast, shrinkBlurSigma, //final double shrinkBlurSigma, shrinkBlurLevel, //final double shrinkBlurLevel, 100, //final int threadsMax, true //final boolean updateStatus ); // this.imageSetWeight=new double[this.fittingStrategy.distortionCalibrationData.gIS.length]; if ((filter & this.filterChannelWeights)!=0) calculateChannelsWeights( this.fittingStrategy.currentSeriesNumber, fittingStrategy.distortionCalibrationData.eyesisCameraParameters.balanceChannelWeightsMode); for (int imgSet=0;imgSet<this.fittingStrategy.distortionCalibrationData.gIS.length;imgSet++){ this.fittingStrategy.distortionCalibrationData.gIS[imgSet].setWeight=0.0; int numUsed=0; int stationNumber=0; int numInSet=((this.fittingStrategy.distortionCalibrationData.gIS[imgSet].imageSet!=null)? this.fittingStrategy.distortionCalibrationData.gIS[imgSet].imageSet.length:0); for (int i=0;i<numInSet;i++){ if (this.fittingStrategy.distortionCalibrationData.gIS[imgSet].imageSet[i]!=null) { stationNumber=this.fittingStrategy.distortionCalibrationData.gIS[imgSet].imageSet[i].getStationNumber(); // should be the same for all images int imgNum=this.fittingStrategy.distortionCalibrationData.gIS[imgSet].imageSet[i].imgNumber; if ((imgNum>=0) && selectedImages[imgNum]) numUsed++; // counting only selected in this fitting series, not all enabled ! } } if (numUsed>0) { double d; switch (fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightMultiImageMode){ case 0: d=1.0; break; case 1: d=Math.pow(numUsed,fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightMultiExponent); break; case 2: d=(numUsed>1)?(Math.pow(numUsed,fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightMultiExponent)):0.001; break; // virtually eliminate single-image sets, but prevent errors case 3: d=numUsed*numUsed; break; default: d=1.0; } d*=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.stationWeight[stationNumber]; // set weight will be calculated as sum of all points weights // this.fittingStrategy.distortionCalibrationData.gIS[imgSet].setWeight=d; for (int i=0;i<numInSet;i++){ if (this.fittingStrategy.distortionCalibrationData.gIS[imgSet].imageSet[i]!=null) { int imgNum=this.fittingStrategy.distortionCalibrationData.gIS[imgSet].imageSet[i].imgNumber; if ((imgNum>=0) && selectedImages[imgNum]) multiWeight[imgNum]= d; } } } } int patternMaskIndex=3; int patternAlphaIndex=7; int patternErrorMaskIndex=8; int index=0; double weightScaleX=1.0,weightScaleY=1.0; if (((filter & this.filterYtoX)!=0) && (this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightYtoX!=1.0)) { weightScaleX/=Math.sqrt(this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightYtoX); weightScaleY*=Math.sqrt(this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightYtoX); } double weightSumXY=weightScaleX+weightScaleY; for (int imgNum=0;imgNum<numImg;imgNum++){ this.imageStartIndex[imgNum]=index; if (selectedImages[imgNum]) { int chnNum=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera int station=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera int setNumber=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].getSetNumber(); double [] gridWeight=fittingStrategy.distortionCalibrationData.gIP[imgNum].getGridWeight(); double gridImageWeight=1.0; if (((filter & this.filterDiameter)!=0) && (fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightDiameterExponent>0.0)) { gridImageWeight*=Math.pow(setImageDiameter(imgNum),fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightDiameterExponent); } if ((filter & this.filterChannelWeights)!=0) { gridImageWeight*=this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[station][chnNum].getChannelWeightCurrent(); } for (int pointNumber=0;pointNumber<fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length;pointNumber++){ double [] XYZMP=patternParameters.getXYZMPE( fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[pointNumber][0], fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[pointNumber][1], station, chnNum, false); // * @return null if out of grid, otherwise X,Y,Z,mask (binary),R (~0.5..1.2),G,B,alpha (0.0..1.0) /* double [] XYZM=patternParameters.getXYZM( // will throw if outside or masked out fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[pointNumber][0], fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[pointNumber][1]);*/ this.targetXYZ[index][0]=XYZMP[0]; this.targetXYZ[index][1]=XYZMP[1]; this.targetXYZ[index][2]=XYZMP[2]; double weight=1.0; if ((filter & this.filterSensor)!=0) { weight*=fittingStrategy.distortionCalibrationData.getMask( // returns 1.0 if sensor mask is not available chnNum, fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][0], fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][1]); } // Individual image mask is needed as some parts can be obscured by moving parts - not present on all images. // grid "contrast" may be far from 1.0 but probably should work OK /// double gridContrast= fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][2]-minimalGridContrast;//minimalGridContrast\ double dbg; // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; } if ((filter & this.filterContrast)!=0) { double gridContrast= gridWeight[pointNumber]; weight*=gridContrast; if (Double.isNaN(gridContrast) && (this.debugLevel>1)) System.out.println("gridContrast=NaN, imgNum="+imgNum); } // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; } if ((filter & this.filterTargetMask)!=0) { weight*=XYZMP[patternMaskIndex];//DONE: Use grid mask also (fade out outer grid nodes?) if (Double.isNaN(XYZMP[patternMaskIndex]) && (this.debugLevel>1)) System.out.println("XYZMP["+patternMaskIndex+"]=NaN, imgNum="+imgNum); } // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; } if ((filter & this.filterTargetAlpha)!=0) { weight*=XYZMP[patternAlphaIndex];//DONE: Use grid mask also (fade out outer grid nodes?) if (Double.isNaN(XYZMP[patternAlphaIndex]) && (this.debugLevel>1)) System.out.println("XYZMP["+patternAlphaIndex+"]=NaN, imgNum="+imgNum); } // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; } if ((filter & this.filterTargetErrors)!=0) { weight*=XYZMP[patternErrorMaskIndex];//DONE: Use grid mask also (fade out outer grid nodes?) if (Double.isNaN(XYZMP[patternErrorMaskIndex]) && (this.debugLevel>1)) System.out.println("XYZMP["+patternErrorMaskIndex+"]=NaN, imgNum="+imgNum); } // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; } if ((filter & this.filterMulti)!=0) { weight*=multiWeight[imgNum]; if (Double.isNaN(multiWeight[imgNum]) && (this.debugLevel>1)) System.out.println("multiWeight["+imgNum+"]=NaN, imgNum="+imgNum); } // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; } if ((filter & this.filterMaskBadNodes)!=0) { if (fittingStrategy.distortionCalibrationData.gIP[imgNum].isNodeBad(pointNumber)) weight=0.0; } // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; // got here } //fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightMultiExponent) if (((filter & this.filterDiameter)!=0) && (fittingStrategy.distortionCalibrationData.eyesisCameraParameters.weightDiameterExponent>0.0)) { weight*=gridImageWeight; if (Double.isNaN(gridImageWeight) && (this.debugLevel>1)) System.out.println("gridImageWeight=NaN, imgNum="+imgNum); } // if (weight > 0) { if ((weight > 0) && (imgNum == 244)) { dbg = weight; } if (Double.isNaN(weight)) { weight=0.0; // find who makes it NaN if (Double.isNaN(multiWeight[imgNum])) System.out.println("weight is null, imgNum="+imgNum); } this.weightFunction[2*index]= weight*weightScaleX; this.weightFunction[2*index+1]=weight*weightScaleY; this.sumWeights+= weight*weightSumXY; this.fittingStrategy.distortionCalibrationData.gIS[setNumber].setWeight+=2.0*weight; // used for variances - proportional to the set weight if (this.pixelCorrection==null){ this.Y[2*index]= fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][0]; this.Y[2*index+1]=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][1]; } else { // TODO: remove and use new code (if tested OK) double [] pXY={ fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][0], fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][1] }; // TODO: Should it be interpolated? Correction is normally small/smooth, so it may be not important int indexXY=((int) Math.floor(pXY[0]/getDecimateMasks(chnNum))) + ((int) Math.floor(pXY[1]/getDecimateMasks(chnNum)))*getSensorCorrWidth(chnNum); if (this.pixelCorrection[chnNum][0].length<=indexXY){ System.out.println("initFittingSeries("+numSeries+") bug:"); System.out.println("this.pixelCorrection["+chnNum+"][0].length="+this.pixelCorrection[chnNum][0].length); System.out.println("indexXY="+indexXY+" pXY[0]="+pXY[0]+", pXY[1]="+pXY[1]+" sensorCorrWidth="+getSensorCorrWidth(chnNum)); } else { this.Y[2*index]= pXY[0]-this.pixelCorrection[chnNum][0][indexXY]; //java.lang.ArrayIndexOutOfBoundsException: 3204663 this.Y[2*index+1]=pXY[1]-this.pixelCorrection[chnNum][1][indexXY]; } // TODO: remove above and un-comment below (after testing) /* double [] vector=interpolateCorrectionVector( chnNum, fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][0], fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[pointNumber][1]); this.Y[2*index]= pXY[0]-vector[0]; this.Y[2*index+1]=pXY[1]-vector[1]; */ } index++; } // numXYPairs+=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length; ?? } } this.imageStartIndex[numImg]=index; // one after last if ((pass==1) && (numSeries>=0) && !skipMinVal){ // count non-zero weight nodes for each image, disable image if this number is less than int needReCalc=0; for (int imgNum=0;imgNum<numImg;imgNum++) if (selectedImages[imgNum]) { index=this.imageStartIndex[imgNum]; int numValidNodes=0; for (int pointNumber=0;pointNumber<fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length;pointNumber++){ if (2*(index+pointNumber)>=this.weightFunction.length){ System.out.println("BUG@535: this.weightFunction.length="+this.weightFunction.length+" index="+index+ " pointNumber="+pointNumber+" imgNum="+imgNum+" pixelsUV.length="+ fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length+ " numXYPairs="+numXYPairs); continue; } if (this.weightFunction[2*(index+pointNumber)]>0.0) { numValidNodes++; //OOB 5064 } } if (numValidNodes<this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.minimalValidNodes){ this.fittingStrategy.invalidateSelectedImage(numSeries,imgNum); needReCalc++; if (this.debugLevel>1){ System.out.println("Number of valid nodes in image #"+imgNum+" is "+numValidNodes+" < "+ this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.minimalValidNodes+ ", this image will be temporarily disabled"); } } } if (needReCalc>0) { if (this.debugLevel>1) System.out.println("Number of temporarily disabled images="+needReCalc ); return true; // will need a second pass } else { if (this.debugLevel>1) System.out.println("No images disabled, no need for pass #2"); } } // Normalize set weights int numSetsUsed=0; double totalSetWeight=0.0; for (int imgSet=0;imgSet<this.fittingStrategy.distortionCalibrationData.gIS.length;imgSet++){ if (this.fittingStrategy.distortionCalibrationData.gIS[imgSet].setWeight>0){ numSetsUsed++; totalSetWeight+=this.fittingStrategy.distortionCalibrationData.gIS[imgSet].setWeight; } } double setWeightScale=numSetsUsed/totalSetWeight; if (numSetsUsed>0){ for (int imgSet=0;imgSet<this.fittingStrategy.distortionCalibrationData.gIS.length;imgSet++){ if (this.fittingStrategy.distortionCalibrationData.gIS[imgSet].setWeight>0){ numSetsUsed++; this.fittingStrategy.distortionCalibrationData.gIS[imgSet].setWeight*=setWeightScale; } } } // last? not here! // this.imageStartIndex[numImg]=index; if (justSelection) { this.currentVector = null; this.lambda=0.0; } else { this.currentVector =fittingStrategy.getSeriesVector(); // here? // for now - use common parameters, later maybe restore /add individual // this.lambda=fittingStrategy.getLambda(); // was commented out??? this.lambda=fittingStrategy.getLambda(); if ((this.fittingStrategy.varianceModes!=null) && (this.fittingStrategy.varianceModes[numSeries]!=this.fittingStrategy.varianceModeDisabled)) fittingStrategy.buildVariancesMaps (numSeries); // return value lost } // this.thresholdFinish=fittingStrategy.getStepDone(); this.iterationStepNumber=0; // should be calculated after series weights are set // public int [] imageStartIndex=null; // elements containing index of the start point of the selected image, first element 0, last - total number of points. // TODO: add copying lambdaStepUp,lambdaStepDown? return false; } public void calculateChannelsWeights( int numSeries, double balanceChannelWeightsMode){ if (balanceChannelWeightsMode==0) return; // keep current weights int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); int numStations=fittingStrategy.distortionCalibrationData.getNumStations(); int numChannels=fittingStrategy.distortionCalibrationData.getNumChannels(); if (balanceChannelWeightsMode<0) { //copy specified defaults to current values for (int station=0;station<numStations;station++){ for (int chn=0;chn<numChannels;chn++){ this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[station][chn].setChannelWeightCurrent( this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][chn].getChannelWeightDefault()); // from station 0 } } } else { double exp=balanceChannelWeightsMode; double [][] sumChnWeights=new double [numStations][numChannels]; double [] avgWeights=new double [numStations]; int [] numNonzeroChannels=new int [numStations]; for (int station=0;station<numStations;station++){ avgWeights[station] =0.0; numNonzeroChannels[station] =0; for (int chn=0;chn<numChannels;chn++) sumChnWeights[station][chn] =0.0; } boolean [] selectedImages=fittingStrategy.selectedImages(numSeries); // -1 OK, will select all for (int imgNum=0;imgNum<numImg;imgNum++)if (selectedImages[imgNum]) { int chn=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera int station=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera sumChnWeights[station][chn]+=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].getNumContrastNodes( this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.minimalGridContrast); } for (int station=0;station<numStations;station++){ for (int chn=0;chn<numChannels;chn++) if (sumChnWeights[station][chn]>0) { avgWeights[station]+=sumChnWeights[station][chn]; numNonzeroChannels[station]++; } if (numNonzeroChannels[station]>0) avgWeights[station]/=numNonzeroChannels[station]; } for (int station=0;station<numStations;station++){ for (int chn=0;chn<numChannels;chn++) if (sumChnWeights[station][chn]>0) { double weight=(sumChnWeights[station][chn]>0.0)?Math.pow(avgWeights[station]/sumChnWeights[station][chn],exp):0.0; this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[station][chn].setChannelWeightCurrent( weight); } } } } public double setImageDiameter(int imgNum){ int debugThreshold=2; int chnNum=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera double minimalGridContrast=this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.minimalGridContrast; int station=this.fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera EyesisSubCameraParameters esp=this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[station][chnNum]; double r0pix=1000.0*esp.distortionRadius/esp.pixelSize; this.fittingStrategy.distortionCalibrationData.gIP[imgNum].setImageDiameter( // need to get image center px,py. Maybe r0 - use to normalize result diameter esp.px0, // double xc, esp.py0, // double yc, r0pix, // double r0, minimalGridContrast,// double minContrast (this.debugLevel>debugThreshold)?imgNum:-1); return this.fittingStrategy.distortionCalibrationData.gIP[imgNum].getGridDiameter(); } public void listImageSets(int mode){ // TODO: use series -1 - should work now // boolean [] oldSelection=this.fittingStrategy.selectAllImages(0); // enable all images in series 0 if (this.fittingStrategy.distortionCalibrationData.gIS!=null){ if (this.debugLevel>2){ System.out.println("listImageSets() 1: "); for (int is=0;is<this.fittingStrategy.distortionCalibrationData.gIS.length;is++){ System.out.println("listImageSets() 1: "+is+ ": tilt="+ this.fittingStrategy.distortionCalibrationData.gIS[is].goniometerTilt+ " axial="+ this.fittingStrategy.distortionCalibrationData.gIS[is].goniometerAxial+ " interAxis="+this.fittingStrategy.distortionCalibrationData.gIS[is].interAxisAngle+ " estimated="+this.fittingStrategy.distortionCalibrationData.gIS[is].orientationEstimated); } } } int filter=this.filterForAll; if (this.askFilter) filter=selectFilter(filter); initFittingSeries(false,filter,-1); // first step in series if (this.fittingStrategy.distortionCalibrationData.gIS!=null){ if (this.debugLevel>2){ System.out.println("listImageSets() 2: "); for (int is=0;is<this.fittingStrategy.distortionCalibrationData.gIS.length;is++){ System.out.println("listImageSets() 2: "+is+ ": tilt="+ this.fittingStrategy.distortionCalibrationData.gIS[is].goniometerTilt+ " axial="+ this.fittingStrategy.distortionCalibrationData.gIS[is].goniometerAxial+ " interAxis="+this.fittingStrategy.distortionCalibrationData.gIS[is].interAxisAngle+ " estimated="+this.fittingStrategy.distortionCalibrationData.gIS[is].orientationEstimated); } } } // initFittingSeries(true,this.filterForAll,0); // will set this.currentVector this.currentfX=calculateFxAndJacobian(this.currentVector, false); // is it always true here (this.jacobian==null) double [] errors=calcErrors(calcYminusFx(this.currentfX)); int [] numPairs=calcNumPairs(); int [][] imageSets=this.fittingStrategy.distortionCalibrationData.listImages(false); // true - only enabled images boolean hasLWIR = this.fittingStrategy.distortionCalibrationData.hasSmallSensors(); int [] numSetPoints=new int [imageSets.length*(hasLWIR?2:1)]; double [] rmsPerSet=new double[imageSets.length*(hasLWIR?2:1)]; int [][] numImgPoints=new int [imageSets.length][this.fittingStrategy.distortionCalibrationData.getNumSubCameras()]; double [][] rmsPerImg=new double[imageSets.length][this.fittingStrategy.distortionCalibrationData.getNumSubCameras()]; boolean [] hasNaNInSet=new boolean[imageSets.length*(hasLWIR?2:1)]; if (hasLWIR) { for (int setNum=0;setNum<imageSets.length;setNum++){ double [] error2= {0.0,0.0}; int [] numInSet= {0,0}; hasNaNInSet[2*setNum]=false; hasNaNInSet[2*setNum+1]=false; for (int imgInSet=0;imgInSet<imageSets[setNum].length;imgInSet++) { int imgNum=imageSets[setNum][imgInSet]; int isLwir = this.fittingStrategy.distortionCalibrationData.isSmallSensor(imgNum)?1:0; int num=numPairs[imgNum]; rmsPerImg[setNum][imgInSet] = errors[imgNum]; numImgPoints[setNum][imgInSet] = num; if (Double.isNaN(errors[imgNum])){ hasNaNInSet[2 * setNum + isLwir]=true; } else { error2[isLwir]+=errors[imgNum]*errors[imgNum]*num; numInSet[isLwir]+=num; } } numSetPoints[2 * setNum + 0]= numInSet[0]; rmsPerSet [2 * setNum + 0]= Math.sqrt(error2[0]/numInSet[0]); numSetPoints[2 * setNum + 1]= numInSet[1]; rmsPerSet [2 * setNum + 1]= Math.sqrt(error2[1]/numInSet[1]); } } else { for (int setNum=0;setNum<imageSets.length;setNum++){ double error2=0.0; int numInSet=0; hasNaNInSet[setNum]=false; for (int imgInSet=0;imgInSet<imageSets[setNum].length;imgInSet++) { int imgNum=imageSets[setNum][imgInSet]; int num=numPairs[imgNum]; rmsPerImg[setNum][imgInSet] = errors[imgNum]; numImgPoints[setNum][imgInSet] = num; if (Double.isNaN(errors[imgNum])){ hasNaNInSet[setNum]=true; } else { error2+=errors[imgNum]*errors[imgNum]*num; numInSet+=num; } } numSetPoints[setNum]=numInSet; rmsPerSet[setNum]=Math.sqrt(error2/numInSet); } } this.fittingStrategy.distortionCalibrationData.listImageSet( mode, numSetPoints, rmsPerSet, hasNaNInSet, numImgPoints, rmsPerImg ); // this.fittingStrategy.setImageSelection(0, oldSelection); // restore original selection in series 0 } public void updateSensorMasks(){ int alphaIndex=2; if (this.pixelCorrection==null){ System.out.println("Sensor data is null, can not update sensor masks"); return; } if (this.debugLevel>0) System.out.println("Updating sensor masks in sensor data"); for (int i=0;(i<this.fittingStrategy.distortionCalibrationData.sensorMasks.length) && (i<this.pixelCorrection.length);i++){ this.pixelCorrection[i][alphaIndex]=this.fittingStrategy.distortionCalibrationData.sensorMasks[i].clone(); } } public boolean correctPatternFlatField(boolean enableShow){ if (this.debugLevel>0) System.out.println("=== Performing pattern flat field correction"); this.patternParameters.updateNumStations(this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getNumStations()); // if (this.refineParameters.flatFieldUseSelectedChannels && (ABERRATIONS_PARAMETERS!=null))selectedChannels=ABERRATIONS_PARAMETERS.selectedChannels; double [][] masks= nonVignettedMasks( this.refineParameters.flatFieldShrink, this.refineParameters.flatFieldNonVignettedRadius, this.refineParameters.flatFieldMinimalAlpha); /* if (selectedChannels!=null){ for (int nChn=0;nChn<masks.length;nChn++) if ((nChn<selectedChannels.length)&&!selectedChannels[nChn]) masks[nChn]=null; } */ boolean same_size = true; for (int nChn=1; nChn < masks.length; nChn++) same_size &= (masks[nChn].length == masks[0].length); if (enableShow && this.refineParameters.flatFieldShowSensorMasks) { if (same_size) { (new ShowDoubleFloatArrays()).showArrays( //java.lang.ArrayIndexOutOfBoundsException: 313632 masks, getSensorWidth(0)/ getDecimateMasks(0), getSensorHeight(0)/getDecimateMasks(0), true, "nonVinetting masks"); } else { System.out.println ("Can not display different saze masks in a stack"); } } double [][][][] sensorGrids=calculateGridFlatField( this.refineParameters.flatFieldSerNumber, masks, this.refineParameters.flatFieldMinimalContrast, this.refineParameters.flatFieldMinimalAccumulate, this.refineParameters.flatFieldUseInterpolate, this.refineParameters.flatFieldMaskThresholdOcclusion, // suspect occlusion only if grid is missing in the area where sensor mask is above this threshold this.refineParameters.flatFieldShrinkOcclusion, this.refineParameters.flatFieldFadeOcclusion, this.refineParameters.flatFieldIgnoreSensorFlatField); double [][][] geometry= patternParameters.getGeometry(); if (enableShow && this.refineParameters.flatFieldShowIndividual){ for (int station=0;station<sensorGrids.length;station++) if (sensorGrids[station]!=null){ for (int i=0;i<sensorGrids[station].length;i++) if (sensorGrids[station][i]!=null){ (new ShowDoubleFloatArrays()).showArrays( sensorGrids[station][i], geometry[0].length, geometry.length, true, "chn"+i+":"+station+"-pattern"); } } } double [][][][] patternArray= combineGridFlatField( this.refineParameters.flatFieldReferenceStation, sensorGrids, this.refineParameters.flatFieldShrinkForMatching, this.refineParameters.flatFieldResetMask, this.refineParameters.flatFieldMaxRelDiff, this.refineParameters.flatFieldShrinkMask, this.refineParameters.flatFieldFadeBorder); if (enableShow && this.refineParameters.flatFieldShowResult) { String [] titles={"Alpha","Red","Green","Blue","Number of images used"}; for (int station=0;station<patternArray.length;station++) if (patternArray[station]!=null){ for (int nView=0;nView<patternArray[station].length;nView++) if (patternArray[station][nView]!=null){ (new ShowDoubleFloatArrays()).showArrays( patternArray[station][nView], geometry[0].length, geometry.length, true, "St"+station+"_V"+nView+"_Pattern_Colors "+this.refineParameters.flatFieldMaxRelDiff, titles); } } } if (this.refineParameters.flatFieldApplyResult) applyGridFlatField(patternArray); // {alpha, red,green,blue, number of images used}[pixel_index] return true; } public boolean modifyPixelCorrection( boolean enableShow, int threadsMax, boolean updateStatus, int debugLevel ){ int filter=this.filterForSensor; if (this.askFilter) filter=selectFilter(filter); initFittingSeries(true,filter,this.seriesNumber); // first step in series now uses pattern alpha // initFittingSeries(true,this.filterForSensor,this.seriesNumber); // first step in series now uses pattern alpha this.currentfX=calculateFxAndJacobian(this.currentVector, false); // this.currentRMS= calcError(calcYminusFx(this.currentfX)); if (this.debugLevel>2) { System.out.println("this.currentVector"); for (int i=0;i<this.currentVector.length;i++){ System.out.println(i+": "+ this.currentVector[i]); } } boolean [] selectedImages=fittingStrategy.selectedImages(); double [][][] sensorXYRGBCorr= allImagesCorrectionMapped( selectedImages, enableShow && this.refineParameters.showPerImage, this.refineParameters.showIndividualNumber, threadsMax, updateStatus, debugLevel); String [] titles={"X-corr(pix)","Y-corr(pix)","weight","Red","Green","Blue"}; if (enableShow && this.refineParameters.showUnfilteredCorrection) { for (int numChn=0;numChn<sensorXYRGBCorr.length;numChn++) if (sensorXYRGBCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; //this.SDFA_INSTANCE.showArrays(sensorXYRGBCorr[numChn], sWidth, sHeight, true, "chn_"+numChn+"_extra_correction", titles); showWithRadialTangential( titles, "chn_"+numChn+"_extra_correction", sensorXYRGBCorr[numChn], // [0] - dx, [1] - dy sWidth, decimate, fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numChn].px0, // using station 0 fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numChn].py0); } } //eyesisSubCameras //extrapolate // TODO: different extrapolation for FF - not circular where shades are in effect (top/bottom) if (!this.refineParameters.sensorExtrapolateDiff) { // add current correction BEFORE extrapolating/blurring addOldXYCorrectionToCurrent( this.refineParameters.correctionScale, sensorXYRGBCorr ); } if (this.refineParameters.extrapolate) { allSensorsExtrapolationMapped( 0, //final int stationNumber, // has to be selected sensorXYRGBCorr, //final double [][][] gridPCorr, this.refineParameters.sensorShrinkBlurComboSigma, this.refineParameters.sensorShrinkBlurComboLevel, this.refineParameters.sensorAlphaThreshold, this.refineParameters.sensorStep, this.refineParameters.sensorInterpolationSigma, this.refineParameters.sensorTangentialRadius, this.refineParameters.sensorScanDistance, this.refineParameters.sensorResultDistance, this.refineParameters.sensorInterpolationDegree, threadsMax, updateStatus, enableShow && this.refineParameters.showExtrapolationCorrection, //final boolean showDebugImages, debugLevel ); } if (this.refineParameters.smoothCorrection) { boolean [] whichBlur={true,true,false,true,true,true}; // all but weight IJ.showStatus("Bluring sensor corrections..."); for (int numChn=0;numChn<sensorXYRGBCorr.length;numChn++) if (sensorXYRGBCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; int sHeight=(getSensorHeight(numChn)-1)/decimate+1; DoubleGaussianBlur gb=new DoubleGaussianBlur(); for (int m=0;m<whichBlur.length;m++) if (whichBlur[m]){ gb.blurDouble( sensorXYRGBCorr[numChn][m], sWidth, sHeight, this.refineParameters.smoothSigma/decimate, this.refineParameters.smoothSigma/decimate, 0.01); } IJ.showProgress(numChn+1, sensorXYRGBCorr.length); } IJ.showProgress(1.0); } if (enableShow && this.refineParameters.showThisCorrection ) { for (int numChn=0;numChn<sensorXYRGBCorr.length;numChn++) if (sensorXYRGBCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; // this.SDFA_INSTANCE.showArrays(sensorXYRGBCorr[numChn], sWidth, sHeight, true, "chn_"+numChn+"_filtered", titles); showWithRadialTangential( titles, "chn_"+numChn+"_filtered", sensorXYRGBCorr[numChn], // [0] - dx, [1] - dy sWidth, decimate, fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numChn].px0, // using station 0 fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numChn].py0); } } if (this.refineParameters.sensorExtrapolateDiff) { // add current correction AFTER extrapolationg/bluring addOldXYCorrectionToCurrent( this.refineParameters.correctionScale, sensorXYRGBCorr ); } // if (!selectCorrectionScale()) return false; IJ.showStatus("Applying corrections:"+((!this.refineParameters.applyCorrection && !this.refineParameters.applyFlatField)? "none ":((this.refineParameters.applyCorrection?"geometry ":"")+(this.refineParameters.applyFlatField?"flat field":"")))); boolean result=applySensorCorrection( this.refineParameters.applyCorrection, this.refineParameters.applyFlatField, this.refineParameters.correctionScale, sensorXYRGBCorr, //sensorXYCorr, // modified to accept both 7(old) and 6(new) entries fittingStrategy.distortionCalibrationData); if (enableShow && this.refineParameters.showCumulativeCorrection) { for (int numChn=0;numChn<sensorXYRGBCorr.length;numChn++) if (sensorXYRGBCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; // this.SDFA_INSTANCE.showArrays(sensorXYRGBCorr[numChn], sWidth, sHeight, true, "Cumulative_chn_"+numChn+"_corrections", titles); showWithRadialTangential( titles, "Cumulative_chn_"+numChn+"_corrections", sensorXYRGBCorr[numChn], // [0] - dx, [1] - dy sWidth, decimate, fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numChn].px0, // using station 0 fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numChn].py0); } } if (result) { updateCameraParametersFromCalculated(false); // update camera parameters from enabled only images (may overwrite some of the above) } IJ.showStatus(""); return result; } public void showWithRadialTangential( String [] preTitles, String title, double [][] preData, // [0] - dx, [1] - dy int width, int deciamte, double x0, double y0){ int indexDx=0; int indexDy=1; int indexDr=0; int indexDt=1; int indexDa=2; String [] extraTitles={"R-corr(pix)","T-corr{pix)","A-corr(pix)"}; int newImages=extraTitles.length; int length=preData[0].length; int height=length/width; double [][] data= new double [preData.length+newImages] [length]; String [] titles= new String [preTitles.length+newImages]; for (int i=0;i<preData.length;i++){ data[i+newImages]=preData[i]; titles[i+newImages]=preTitles[i]; } for (int i=0;i<newImages;i++){ titles[i]=extraTitles[i]; data[i]=new double[length]; } Point2D Z=new Point2D.Double(0.0,0.0); for (int i=0;i<length;i++){ Point2D R=new Point2D.Double((deciamte*(i%width))-x0,(deciamte*(i/width))-y0); double r=R.distance(Z); Point2D uR=new Point2D.Double(1.0,0.0); if (r>0) uR.setLocation(R.getX()/r,R.getY()/r); Point2D dXY=new Point2D.Double(preData[indexDx][i],preData[indexDy][i]); data[indexDr][i]= dXY.getX()*uR.getX()+dXY.getY()*uR.getY(); data[indexDt][i]=-dXY.getX()*uR.getY()+dXY.getY()*uR.getX(); data[indexDa][i]=dXY.distance(Z); } this.SDFA_INSTANCE.showArrays(data, width, height, true, title, titles); } public void addOldXYCorrectionToCurrent( double scale, double [][][] sensorXYCorr ){ if (this.pixelCorrection==null) return; // no modifications are needed for (int i=0;i<sensorXYCorr.length;i++) if ((sensorXYCorr[i]!=null) && (this.pixelCorrection[i]!=null)) { for (int j=0;j<sensorXYCorr[i][0].length;j++){ sensorXYCorr[i][0][j]=this.pixelCorrection[i][0][j]+scale*sensorXYCorr[i][0][j]; sensorXYCorr[i][1][j]=this.pixelCorrection[i][1][j]+scale*sensorXYCorr[i][1][j]; } } } public void patternErrors( final int threadsMax, final boolean updateStatus, final int debugLevel ){ GenericDialog gd=new GenericDialog("Setup pattern errors map"); gd.addNumericField("Series number", this.seriesNumber, 0,2,""); gd.addCheckbox ("Show map", true); gd.addNumericField("Minimal RMS", .07, 3,6,"pix"); gd.addNumericField("Maximal RMS", 0.12, 3,6,"pix"); gd.addNumericField("Expand EMS mask", 1, 0,2,"nodes"); gd.addCheckbox ("Update pattern weights", false); gd.addCheckbox ("Reset error-based target map", false); gd.showDialog(); if (gd.wasCanceled()) return; this.seriesNumber = (int) gd.getNextNumber(); boolean showMap= gd.getNextBoolean(); double minRMS = gd.getNextNumber(); double maxRMS = gd.getNextNumber(); int expandMask = (int) gd.getNextNumber(); boolean updateMap= gd.getNextBoolean(); boolean resetMap= gd.getNextBoolean(); if (resetMap){ this.patternParameters.resetPatternErrorMask(); return; } else { double [] worstImageNumber=calculatePatterErrorRMS( this.seriesNumber, threadsMax, updateStatus, debugLevel); this.patternParameters.savePatternErrorMask(); double [] savedMask=this.patternParameters.getSavedPatternErrorMask(); this.patternParameters.calculatePatternErrorMask(maxRMS,minRMS); for (int i=0;i<expandMask;i++)this.patternParameters.expandPatternErrorMask(); if (showMap){ String [] titles={"mask","rms","worst image number","savedMask"}; double [][] debugData={ this.patternParameters.getPatternErrorMask(), this.patternParameters.getPatternErrors(), worstImageNumber, savedMask}; Rectangle gridDimensions=patternParameters.getUVDimensions(); (new ShowDoubleFloatArrays()).showArrays( debugData, gridDimensions.width, gridDimensions.height, true, "TM_"+maxRMS+":"+minRMS, titles); } if (!updateMap) { System.out.println("Restoring mask to the previous state"); this.patternParameters.restorePatternErrorMask(); } } } public double [] calculatePatterErrorRMS( // returns worst image number array final int series, final int threadsMax, final boolean updateStatus, final int debugLevel ){ if (fittingStrategy==null) { String msg="Fitting strategy does not exist, exiting"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if (fittingStrategy.distortionCalibrationData.eyesisCameraParameters==null){ String msg="Eyesis camera parameters (and sensor dimensions) are not defined"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } // fittingStrategy.distortionCalibrationData.readAllGrids(); // if (! selectGridEnhanceParameters()) return false; // if (series<0) return null; // false; // make "all " later? this.seriesNumber=series; initFittingSeries(true,this.filterForTargetGeometry,this.seriesNumber); // first step in series now uses pattern alpha this.currentfX=calculateFxAndJacobian(this.currentVector, false); // this.currentRMS= calcError(calcYminusFx(this.currentfX)); if (this.debugLevel>2) { System.out.println("this.currentVector"); for (int i=0;i<this.currentVector.length;i++){ System.out.println(i+": "+ this.currentVector[i]); } } final boolean [] selectedImages=fittingStrategy.selectedImages(); final Rectangle gridDimensions=patternParameters.getUVDimensions(); final int width= gridDimensions.width; final int height= gridDimensions.height; // final int U0= gridDimensions.x; // final int V0= gridDimensions.y; final double [][] gridErrors=new double [4][width*height]; // added debug features - worst image number for (int n=0;n<gridErrors.length;n++) for (int i=0;i<gridErrors[n].length;i++) gridErrors[n][i]=0.0; int numSelected=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) numSelected++; final int finalSelected=numSelected; if (updateStatus) IJ.showStatus("Calculating pattern grid errors..."); final AtomicInteger imageNumberAtomic = new AtomicInteger(0); final AtomicInteger imageFinishedAtomic = new AtomicInteger(0); final Thread[] threads = newThreadArray(threadsMax); for (int ithread = 0; ithread < threads.length; ithread++) { threads[ithread] = new Thread() { @Override public void run() { double [][] partialGridErrors=new double [4][width*height]; for (int n=0;n<partialGridErrors.length;n++) for (int i=0;i<partialGridErrors[n].length;i++) partialGridErrors[n][i]=0.0; for (int imgNum=imageNumberAtomic.getAndIncrement(); imgNum<selectedImages.length;imgNum=imageNumberAtomic.getAndIncrement()){ if (selectedImages[imgNum]){ accumulatePatternErrors( partialGridErrors, imgNum, gridDimensions); final int numFinished=imageFinishedAtomic.getAndIncrement(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (updateStatus) IJ.showProgress(numFinished,finalSelected); } }); } //if (selectedImages[numImage]){ } // for (int numImage=imageNumberAtomic.getAndIncrement(); ... combinePatternErrors(partialGridErrors,gridErrors); } // public void run() { }; } startAndJoin(threads); for (int i=0;i<gridErrors[0].length;i++){ gridErrors[0][i]=(gridErrors[0][i]>0.0)?Math.sqrt(gridErrors[0][i]/gridErrors[1][i]):Double.NaN; } patternParameters.setPatternErrors(gridErrors[0]); return gridErrors[2]; // worst image number for target grid nodes } public void accumulatePatternErrors( double [][] errorMap, int imgNum, Rectangle gridDimensions){ int width= gridDimensions.width; // int height= gridDimensions.height; int U0= gridDimensions.x; // location of the grid center (U==0,V==0) int V0= gridDimensions.y; double [] diff=calcYminusFx(this.currentfX, 2*this.imageStartIndex[imgNum],2*this.imageStartIndex[imgNum+1]); int [][] imgUV= this.fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV; for (int i=0;i<imgUV.length;i++){ int index=width*(imgUV[i][1]+V0) + (imgUV[i][0]+U0); double w=this.weightFunction[2*(this.imageStartIndex[imgNum]+i)]; double dX=diff[2*i]; double dY=diff[2*i+1]; double e2w=w*(dX*dX+dY*dY); errorMap[0][index]+=e2w; errorMap[1][index]+=w; if (e2w>errorMap[3][index]){ errorMap[3][index]=e2w; // worst error for this node errorMap[2][index]=imgNum; // worst (for that particular grig node) image number } } } public synchronized void combinePatternErrors( double [][] partialErrorMap, double [][] fullErrorMap ){ // for (int n=0;n<fullErrorMap.length;n++) for (int i=0;i<fullErrorMap[n].length;i++) fullErrorMap[n][i]+=partialErrorMap[n][i]; for (int i=0;i<fullErrorMap[0].length;i++){ fullErrorMap[0][i]+=partialErrorMap[0][i]; fullErrorMap[1][i]+=partialErrorMap[1][i]; if (fullErrorMap[3][i]<partialErrorMap[3][i]){ fullErrorMap[2][i]=partialErrorMap[2][i]; fullErrorMap[3][i]=partialErrorMap[3][i]; } } } /** * Calculate each sensor correction increment for geometry and photometry contributed by all images selected in a series * @param selectedImages process only selected images * @param showIndividual show per-image intermediate results * @param threadsMax maximal number of concurrent threads * @param updateStatus update IJ status/progress * @param debugLevel debug level * @return [sensor]{dpX,dpY,alpha,R,G,B}[pixelIndex] . dpX, dpY - correction to previous, RGB - total FF, not increment! */ public double [][][] allImagesCorrectionMapped( final boolean [] selectedImages, final boolean showIndividual, final int showIndividualNumber, final int threadsMax, final boolean updateStatus, final int debugLevel ){ int numChannels= fittingStrategy.distortionCalibrationData.getNumChannels(); // number of used channels final double [][][] gridPCorr=new double [numChannels][][]; for (int chnNum=0;chnNum<gridPCorr.length;chnNum++) gridPCorr[chnNum]=null; int numSelected=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) numSelected++; final int finalSelected=numSelected; if (updateStatus) IJ.showStatus("Calculating sensor corrections..."); final AtomicInteger imageNumberAtomic = new AtomicInteger(0); final AtomicInteger imageFinishedAtomic = new AtomicInteger(0); final Thread[] threads = newThreadArray(threadsMax); final AtomicInteger stopRequested=this.stopRequested; final AtomicBoolean interruptedAtomic=new AtomicBoolean(); final int alphaIndex=2; for (int ithread = 0; ithread < threads.length; ithread++) { threads[ithread] = new Thread() { @Override public void run() { for (int imgNum=imageNumberAtomic.getAndIncrement(); (imgNum<selectedImages.length) && !interruptedAtomic.get();imgNum=imageNumberAtomic.getAndIncrement()){ if (selectedImages[imgNum]){ int chnNum=fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera double [][] singleCorr= singleImageCorrectionMapped( imgNum, // image number showIndividual && ((showIndividualNumber<0) || (showIndividualNumber==chnNum)), debugLevel); combineImageCorrection( chnNum, gridPCorr, singleCorr ); final int numFinished=imageFinishedAtomic.getAndIncrement(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (updateStatus) IJ.showProgress(numFinished,finalSelected); } }); if (stopRequested.get()==1){ // ASAP interruptedAtomic.set(true); } } //if (selectedImages[numImage]){ } // for (int numImage=imageNumberAtomic.getAndIncrement(); ... } // public void run() { }; } startAndJoin(threads); // divide by weight; for (int nChn=0;nChn<gridPCorr.length;nChn++) if (gridPCorr[nChn]!=null){ for (int i=0;i<gridPCorr[nChn].length;i++) { if (i!=alphaIndex){ for (int j=0; j<gridPCorr[nChn][i].length;j++){ if (gridPCorr[nChn][alphaIndex][j]>0) gridPCorr[nChn][i][j]/=gridPCorr[nChn][alphaIndex][j]; } } } } if (updateStatus) IJ.showProgress(0); if (interruptedAtomic.get()) { System.out.println("allImagesCorrection() aborted by user request"); return null; } return gridPCorr; } public void allSensorsExtrapolationMapped( final int stationNumber, // has to be selected final double [][][] gridPCorr, final double shrinkBlurComboSigma, final double shrinkBlurComboLevel, final double alphaThreshold, final double step, final double interpolationSigma, final double tangentialRadius, final int scanDistance, final int resultDistance, final int interpolationDegree, final int threadsMax, final boolean updateStatus, final boolean showDebugImages, final int debugLevel ){ if (updateStatus) IJ.showStatus("Extrapolating sensor corrections..."); final AtomicInteger sensorNumberAtomic = new AtomicInteger(0); final AtomicInteger sensorFinishedAtomic = new AtomicInteger(0); final Thread[] threads = newThreadArray(threadsMax); final AtomicInteger stopRequested=this.stopRequested; final AtomicBoolean interruptedAtomic=new AtomicBoolean(); final EyesisSubCameraParameters [] eyesisSubCameras = this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[stationNumber]; final double [][] sensorMasks=this.fittingStrategy.distortionCalibrationData.sensorMasks; final int alphaIndex=2; // final int sensorWidth= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth; // final int sensorHeight= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight; // final int decimation=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks; // final int width= (sensorWidth-1)/decimation+1; // decimated width (648) // final int height= (sensorHeight-1)/decimation+1; // decimated width (648) final boolean extraShowDebug=showDebugImages&& (debugLevel>2); for (int ithread = 0; ithread < threads.length; ithread++) { threads[ithread] = new Thread() { @Override public void run() { DoubleGaussianBlur gb=null; double [][] debugMasks1=null; double [][] debugMasks2=null; String [] debugMaskTitles={"original","blured"}; if (extraShowDebug){ debugMasks1=new double[2][]; debugMasks2=new double[2][]; } if (shrinkBlurComboSigma>0.0) gb=new DoubleGaussianBlur(); for (int sensorNum=sensorNumberAtomic.getAndIncrement(); (sensorNum<gridPCorr.length) && !interruptedAtomic.get();sensorNum=sensorNumberAtomic.getAndIncrement()){ int sensorWidth= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth(sensorNum); int sensorHeight= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight(sensorNum); int decimation=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getDecimateMasks(sensorNum); int width= (sensorWidth-1)/decimation+1; // decimated width (648) int height= (sensorHeight-1)/decimation+1; // decimated width (648) if (gridPCorr[sensorNum]!=null){ final double [] centerPXY={ eyesisSubCameras[sensorNum].px0, eyesisSubCameras[sensorNum].py0 }; if (shrinkBlurComboSigma>0.0){ double sigma=shrinkBlurComboSigma/decimation; int margin=(int) (2*sigma); int width1=width+2*margin; int height1=height+2*margin; if (extraShowDebug) debugMasks2[0]=gridPCorr[sensorNum][alphaIndex].clone(); double [] mask= addMarginsThreshold( gridPCorr[sensorNum][alphaIndex], // double [] data, 0.0, // double threshold, width, height, margin); if (extraShowDebug) debugMasks1[0]=mask.clone(); gb.blurDouble( mask, width1, height1, sigma, sigma, 0.01); double k=1.0/(1.0-shrinkBlurComboLevel); for (int i=0;i<mask.length;i++) { mask[i]=k*(mask[i]-shrinkBlurComboLevel); mask[i]=(mask[i]>0.0)?(mask[i]*mask[i]):0.0; } if (extraShowDebug) debugMasks1[1]=mask.clone(); gridPCorr[sensorNum][alphaIndex]=removeMargins( mask, //double [] data, width, // w/o margins height, margin); //mask; // replace with 0.0 .. 1.0 mask if (extraShowDebug) debugMasks2[1]=gridPCorr[sensorNum][alphaIndex].clone(); if (extraShowDebug) { (new ShowDoubleFloatArrays()).showArrays( debugMasks1, width1, height1, true, "M1-"+sensorNum, debugMaskTitles); (new ShowDoubleFloatArrays()).showArrays( debugMasks2, width, height, true, "M2-"+sensorNum, debugMaskTitles); } } singleSensorExtrapolationMapped( sensorNum, gridPCorr[sensorNum], sensorMasks[sensorNum], width, decimation, alphaThreshold, step, centerPXY, interpolationSigma, tangentialRadius, scanDistance, resultDistance, interpolationDegree, (shrinkBlurComboSigma>0.0), showDebugImages, debugLevel); final int numFinished=sensorFinishedAtomic.getAndIncrement(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (updateStatus) IJ.showProgress(numFinished,gridPCorr.length); } }); if (stopRequested.get()==1){ // ASAP interruptedAtomic.set(true); } } //if (selectedImages[numImage]){ } // for (int numImage=imageNumberAtomic.getAndIncrement(); ... } // public void run() { }; } startAndJoin(threads); if (updateStatus) IJ.showProgress(0); return; } public double [] addMargins( double [] data, double marginData, int width, int height, int margin){ int width1= width+ 2*margin; int height1=height+2*margin; int length1=width1*height1; double [] result = new double [length1]; for (int i=0;i<length1;i++) result[i] = marginData; int indexDest=margin*(width1+1); int indexSrc=0; for (int y=0;y<height;y++){ for (int x=0;x<width;x++){ result[indexDest++]=data[indexSrc++]; } indexDest+=2*margin; } return result; } public double [] addMarginsThreshold( double [] data, double threshold, int width, int height, int margin){ int width1= width+ 2*margin; int height1=height+2*margin; int length1=width1*height1; double [] result = new double [length1]; for (int i=0;i<length1;i++) result[i] = -1.0; int indexDest=margin*(width1+1); int indexSrc=0; for (int y=0;y<height;y++){ for (int x=0;x<width;x++){ result[indexDest++]=(data[indexSrc++]>threshold)?1.0:-1.0; } indexDest+=2*margin; } return result; } public double [] removeMargins( double [] data, int width, // w/o margins int height, int margin){ int width1= width+ 2*margin; // int height1=height+2*margin; int length=width*height; double [] result = new double [length]; int indexSrc=margin*(width1+1); int indexDest=0; for (int y=0;y<height;y++){ for (int x=0;x<width;x++){ result[indexDest++]=data[indexSrc++]; } indexSrc+=2*margin; } return result; } public void singleSensorExtrapolationMapped( int sensoNum, double [][] gridPCorr, double [] sensorMask, int width, int decimation, double alphaThreshold, double step, double [] centerPXY, double interpolationSigma, // sensor pixels double tangentialRadius, int scanDistance, // sensor pixels int resultDistance, int interpolationDegree, boolean useAlpha, // false - sensor mask boolean showDebugImages, int debugLevel ){ int dxIndex=0; int alphaIndex=2; int rIndex=3; int height=gridPCorr[0].length/width; double gaussianK=-0.5/(interpolationSigma*interpolationSigma); double tangR0=tangentialRadius*Math.sqrt(width*height)*decimation/2; // sigma in tangential direction is interpolationSigma*(1+r/tangR0), in radial - interpolationSigma PolynomialApproximation polynomialApproximation =new PolynomialApproximation(0);// no debug int length=gridPCorr[0].length; DirInc dirInc= new DirInc(width,height); int [] iMap = new int[length]; for (int i=0;i<length;i++) iMap[i]= (gridPCorr[alphaIndex][i]>=alphaThreshold)?1:0; List <Integer>waveList=new ArrayList<Integer>(1000); for (int index0=0;index0<length;index0++) if (iMap[index0]==0){ for (int iDir=0;iDir<8;iDir+=2){ int index=dirInc.newIndex(index0,iDir); if ((index>=0) && (iMap[index]==1)){ iMap[index0]=2; waveList.add(new Integer(index0)); break; } } } // decimate the wave list List <Integer> seedList=new ArrayList<Integer>(1000); int oldIndex=0; // find better start? int s2= (int) Math.floor(step*step); while (waveList.size()>0){ int oldX=oldIndex%width; int oldY=oldIndex/width; int bestD2=height*height+width*width; int nBest=-1; for (int n=0;n<waveList.size();n++){ int index=waveList.get(n); int dx=index%width-oldX; int dy=index/width-oldY; int d2=dx*dx+dy*dy; if (d2<bestD2){ bestD2=d2; nBest=n; } } oldIndex=waveList.remove(nBest); seedList.add(new Integer(oldIndex)); oldX=oldIndex%width; oldY=oldIndex/width; // remove all closer than step for (int n=0;n<waveList.size();n++){ // size will change int index=waveList.get(n); int dx=index%width-oldX; int dy=index/width-oldY; int d2=dx*dx+dy*dy; if (d2<s2){ waveList.remove(n); } } } //while (waveList.size()>0) // debug show waves? Rectangle full=new Rectangle (0,0,width,height); double [][] extrapolated=new double [gridPCorr.length][length]; for (int n=0;n<extrapolated.length;n++) for (int i=0;i<extrapolated[n].length;i++) extrapolated[n][i]=0.0; int halfScanSize=scanDistance/decimation+1; int halfInterpolteSize=resultDistance/decimation+1; for (int n=0; n<seedList.size();n++) { int index0=seedList.get(n); int x0=index0%width; int y0=index0/width; double [] dCxy0={ x0*decimation-centerPXY[0], y0*decimation-centerPXY[1] }; double r0=Math.sqrt(dCxy0[0]*dCxy0[0]+dCxy0[1]*dCxy0[1]); final Rectangle scan =full.intersection(new Rectangle (x0-halfScanSize,y0-halfScanSize,2*halfScanSize+1,2*halfScanSize+1)); waveList.clear(); for (int y=scan.y;y<(scan.y+scan.height);y++) for (int x=scan.x;x<(scan.x+scan.width);x++) { int index=y*width+x; if (iMap[index]==1) waveList.add(new Integer(index)); } double [][][] data = new double [5][waveList.size()][3]; // x,y,w double sumWeights=0.0; double rScaleTangSigma=1.0/(1.0+r0/tangR0); // for (int i=0;i<data[0].length;i++){ int index=waveList.get(i); int x=index%width; int y=index/width; double [] dCxy={ x*decimation-centerPXY[0], y*decimation-centerPXY[1] }; double [] ddCxy={ dCxy[0]-dCxy0[0], dCxy[1]-dCxy0[1] }; double rc=Math.sqrt(dCxy[0]*dCxy[0]+dCxy[1]*dCxy[1]); // distance from lens center (in sensor pixels) double rDiff=rc-r0; double [] uRadVect={(rc>0.0)?(dCxy[0]/rc):0.0, (rc>0.0)?(dCxy[1]/rc):0.0}; double distRad= ddCxy[0]*uRadVect[0]+ddCxy[1]*uRadVect[1]; // radial distance form the center (seed point) double distTan=-ddCxy[0]*uRadVect[1]+ddCxy[1]*uRadVect[0]; // tangential distance form the center (seed point) distTan*=rScaleTangSigma; // // for the center (seed point). was distTan/=(1.0+rc/tangR0); double w=Math.exp(gaussianK*(distRad*distRad+distTan*distTan))*gridPCorr[alphaIndex][index]; sumWeights+=w; double dRad= gridPCorr[dxIndex+0][index]*uRadVect[0]+gridPCorr[dxIndex+1][index]*uRadVect[1]; // radial component double dTan=-gridPCorr[dxIndex+0][index]*uRadVect[1]+gridPCorr[dxIndex+1][index]*uRadVect[0]; // tangential component data[0][i][1]=dRad; data[1][i][1]=dTan; data[2][i][1]=gridPCorr[rIndex+0][index]; // R data[3][i][1]=gridPCorr[rIndex+1][index]; // G data[4][i][1]=gridPCorr[rIndex+2][index]; // B for (int j=0;j<data.length;j++){ data[j][i][0]=rDiff; data[j][i][2]=w; } } sumWeights*=rScaleTangSigma; // normalize for expanded in one dimension gaussian double [][] poly=new double [data.length][]; for (int j=0;j<poly.length;j++) { poly[j]=polynomialApproximation.polynomialApproximation1d(data[j],interpolationDegree); } if (poly[0]==null) { // all will be either null, or not - [0] testing is enough System.out.println("singleSensorExtrapolationMapped() BUG - poly[0]==null"); // stageReprojPXY[index0]=null; continue; } final Rectangle rInterpolate =full.intersection(new Rectangle (x0-halfInterpolteSize,y0-halfInterpolteSize,2*halfInterpolteSize+1,2*halfInterpolteSize+1)); for (int y=rInterpolate.y;y<(rInterpolate.y+rInterpolate.height);y++) for (int x=rInterpolate.x;x<(rInterpolate.x+rInterpolate.width);x++) { int index=y*width+x; double [] dCxy={ x*decimation-centerPXY[0], y*decimation-centerPXY[1] }; double [] ddCxy={ dCxy[0]-dCxy0[0], dCxy[1]-dCxy0[1] }; double rc=Math.sqrt(dCxy[0]*dCxy[0]+dCxy[1]*dCxy[1]); // distance from lens center (in sensor pixels) double rDiff=rc-r0; double [] uRadVect={(rc>0.0)?(dCxy[0]/rc):0.0, (rc>0.0)?(dCxy[1]/rc):0.0}; double distRad= ddCxy[0]*uRadVect[0]+ddCxy[1]*uRadVect[1]; // radial distance form the center (seed point) double distTan=-ddCxy[0]*uRadVect[1]+ddCxy[1]*uRadVect[0]; // tangential distance form the center (seed point) distTan*=rScaleTangSigma; double w=Math.exp(gaussianK*(distRad*distRad+distTan*distTan)); //*gridPCorr[alphaIndex][index]; w*=sumWeights; // more points were used in coefficients calculation, more trust to that extrapolation // extrapolate each value using polynomial coefficients double [] results= new double [poly.length]; for (int nPar=0;nPar<results.length;nPar++){ double rN=1.0; results[nPar]=0.0; for (int dgr=0;dgr<poly[nPar].length;dgr++){ results[nPar]+=poly[nPar][dgr]*rN; rN*=rDiff; } } // restore dX, dY from radial/tangential double [] diffPXY={ results[0]*uRadVect[0]-results[1]*uRadVect[1], results[0]*uRadVect[1]+results[1]*uRadVect[0]}; //accumulate extrapolated[dxIndex+0][index]+=diffPXY[0]*w; extrapolated[dxIndex+1][index]+=diffPXY[1]*w; extrapolated[rIndex+0][index]+=results[2]*w; extrapolated[rIndex+1][index]+=results[3]*w; extrapolated[rIndex+2][index]+=results[4]*w; extrapolated[alphaIndex][index]+=w; } } // for (int n=0; n<seedList.size();n++) { // divide by weight for (int index=0;index<length;index++) if (extrapolated[alphaIndex][index]>0.0){ for (int i=0;i<extrapolated.length;i++) if (i!=alphaIndex){ extrapolated[i][index]/=extrapolated[alphaIndex][index]; } } // debug show here extrapolated if (showDebugImages){ String [] debugTiles={"dX","dY","alpha","R","G","B","mask"}; double [] debugMask=new double[length]; for (int i=0;i<length;i++) debugMask[i]=iMap[i]; for (int n=0; n<seedList.size();n++) { int index=seedList.get(n); debugMask[index]+=3.0; } //iMap[index0] double [][] debugData={ extrapolated[0], extrapolated[1], extrapolated[2], extrapolated[3], extrapolated[4], extrapolated[5], debugMask}; (new ShowDoubleFloatArrays()).showArrays( debugData, width, height, true, "EX-"+sensoNum, debugTiles); } // mix interpolated with original data // double [] sensorMask, //gridPCorr for (int index=0;index<length;index++) if (extrapolated[alphaIndex][index]>0.0){ for (int i=0;i<extrapolated.length;i++) if (i!=alphaIndex){ double w=useAlpha?(gridPCorr[alphaIndex][index]):((gridPCorr[alphaIndex][index]>0.0)?sensorMask[index]:0.0); gridPCorr[i][index]=gridPCorr[i][index]*w+extrapolated[i][index]*(1.0-w); } } } public synchronized void combineImageCorrection( int chnNum, double [][][] gridPCorr, double [][] singleCorr ){ int alphaIndex=2; if (gridPCorr[chnNum]==null){ gridPCorr[chnNum]=new double [singleCorr.length][singleCorr[0].length]; for (int i=0;i<singleCorr.length;i++) for (int j=0; j<singleCorr[i].length;j++){ gridPCorr[chnNum][i][j]=0.0; } } for (int i=0;i<singleCorr.length;i++) { if (i==alphaIndex){ for (int j=0; j<singleCorr[i].length;j++) gridPCorr[chnNum][i][j]+=singleCorr[i][j]; } else { for (int j=0; j<singleCorr[i].length;j++) gridPCorr[chnNum][i][j]+=singleCorr[i][j]*singleCorr[alphaIndex][j]; } } } /** * Calculate sensor correction increment for geometry and photometry contributed by a single image * @param imgNum number of image * @param maxSensorMask maximal value of the sensor mask for this sensor to start extrapolating * @param minContrast minimal measured grid contrast to seed extrapolating - to prevent expansion in the areas where this particular sensor has bad data * @param minTargetAlpha - minimal alpha of the target node * @param useTargetAlpha false - only use contrast of the detected grid, true - multiply contrast by grid alpha * @param showIntermediate - show intermediate data as images * @param debugLevel debug level * @return scan-line pixels additional correction arrays {dpX,dpY,alpha,R,G,B}[pixelIndex] */ public double [][] singleImageCorrectionMapped( int imgNum, // image number boolean showIntermediate, int debugLevel ){ CorrectionInNodes correctionInNodes=extractNodeCorrections( imgNum, // image number showIntermediate, debugLevel); if (showIntermediate) correctionInNodes.show("finNode-"+imgNum); int chnNum=fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; int sensorWidth= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth(chnNum); int sensorHeight= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight(chnNum); int decimation=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getDecimateMasks(chnNum); double [][] additionalCorrection=correctionInNodes.mapToPixels( decimation, sensorWidth, sensorHeight, debugLevel); if (showIntermediate){ String [] dbgTitles={"dPX","dPY","alpha","R","G","B"}; (new ShowDoubleFloatArrays()).showArrays( additionalCorrection, sensorWidth/decimation, sensorHeight/decimation, true, "AC-"+imgNum, dbgTitles); } return additionalCorrection; } /** * @param imgNum number of image * @param showIntermediate - show intermediate images * @param debugLevel debug level * @return CorrectionInNodes data correction, image and grid data for some target grid nodes */ public CorrectionInNodes extractNodeCorrections( int imgNum, // image number boolean showIntermediate, int debugLevel ){ // int debugThreshold=2; int imgRGBIndex= 3; int chnNum=fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera int station=fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera LensDistortionParameters lensDistortionParameters= setupLensDistortionParameters( imgNum, debugLevel); // Axial - may be Double.NaN int [][] imgUV= fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV; double [][] imgXY=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY; // for each image, each grid node - a set of of {px,py,contrast,vignR,vignG,vignB} vign* is in the 0..1.0 range if ((imgUV==null) || (imgUV.length==0)) { System.out.println("expandMeasuredGrid("+imgNum+",..) empty image"); return null; } int minU=imgUV[0][0]; int minV=imgUV[0][1]; int maxU=minU; int maxV=minV; for (int i=1;i<imgUV.length;i++){ if (minU>imgUV[i][0]) minU=imgUV[i][0]; if (minV>imgUV[i][1]) minV=imgUV[i][1]; if (maxU<imgUV[i][0]) maxU=imgUV[i][0]; if (maxV<imgUV[i][1]) maxV=imgUV[i][1]; } int extraMargins=1; int [] uv0= {minU-extraMargins,minV-extraMargins}; // target U,V at the stageXYA[0] int width= maxU-minU+1+2*extraMargins; int height=maxV-minV+1+2*extraMargins; double [][] stagePXY= new double [width*height][]; //reprojected {px,py} double [][] stageDiffPXY= new double [width*height][]; // difference between corrected measured and reprojected (to add to correction) double [][] stageDiffRGB= new double [width*height][]; // difference (measured RGB)/(grid RGB) and current correction RGB (pixel sensitivity RGB) double [] stageMask= new double [width*height]; // weight for (int i=0;i<stagePXY.length;i++) { stagePXY[i]=null; stageDiffPXY[i] =null; stageDiffRGB[i] = null; } // int vignRIndex=3; // in measured data int corrRIndex=3; // in correction vector // int gridRIndex=3; // in reprojected vector double [] diff=calcYminusFx(this.currentfX, 2*this.imageStartIndex[imgNum],2*this.imageStartIndex[imgNum+1]); double [][] photometrics=patternParameters.getPhotometricBySensor(station,chnNum); // head/bottom grid intensity/alpha int targetGridWidth=getGridWidth(); double [][] debugRGB=null; if (showIntermediate){ debugRGB = new double [12][width*height]; for (int n=0;n<debugRGB.length;n++) for (int i=0;i<debugRGB[n].length;i++) debugRGB[n][i]=0.0; } for (int i=0;i<imgUV.length;i++){ int index=width*(imgUV[i][1]-uv0[1]) + (imgUV[i][0]-uv0[0]); int targetGridIndex=targetGridWidth*(imgUV[i][1]+patternParameters.V0) +(imgUV[i][0]+patternParameters.U0); // index in photometrics[][] int doublePairIndex=2*(this.imageStartIndex[imgNum]+i); // number of a pair in a full vector stageMask[index]=this.weightFunction[doublePairIndex]; stagePXY[index]=null; double [] debugCorrVector=null; if (showIntermediate) { debugCorrVector=interpolateCorrectionVector ( // vector of {corrX, corrY, alpha, flatfield_red, flatfield_green, flatfield_blue} chnNum, imgXY[i][0], //double px, measured imgXY[i][1]); //double py, measured); } double [] reprojectedNode= reprojectGridNode( //{pX,pY,grid mask (binary), grid R, grid G, grid B, alpha} lensDistortionParameters, imgNum, imgUV[i][0], //int u, // grid signed u,v imgUV[i][1]); //int v if (reprojectedNode==null) { continue; // out of grid - should not happen here (now - also: target point behind the camera sensor)? } // double [] reprojPXY={reprojectedNode[0],reprojectedNode[1]}; double [] nodePXY={this.Y[doublePairIndex],this.Y[doublePairIndex+1]}; stagePXY[index]=nodePXY;// measured pixels Px,Py with correction applied // reprojPXY; // double [] diffPXY= {imgXY[i][0]-corrVector[0]-reprojectedNode[0],imgXY[i][1]-debugCorrVector[1]-reprojectedNode[1]}; double [] diffPXY= {diff[2*i],diff[2*i+1]}; stageDiffPXY[index]=diffPXY; //{px,py,contrast,vignR,vignG,vignB} double [] diffRGB={0.0,0.0,0.0}; for (int c=0;c<diffRGB.length;c++){ double gridPhotometrics=photometrics[c][targetGridIndex]; // if (gridPhotometrics>0.0) diffRGB[c]=imgXY[i][imgRGBIndex+c]/gridPhotometrics-debugCorrVector[corrRIndex+c]; if (gridPhotometrics>0.0) diffRGB[c]=imgXY[i][imgRGBIndex+c]/gridPhotometrics; // don't use old correction at all! } stageDiffRGB[index]=diffRGB; stageMask[index]=this.weightFunction[2*(this.imageStartIndex[imgNum]+i)]; if (showIntermediate) for (int c=0;c<3;c++){ debugRGB[4*c+0][index]=photometrics[c][targetGridIndex]; debugRGB[4*c+1][index]=imgXY[i][imgRGBIndex+c]; debugRGB[4*c+2][index]=debugCorrVector[corrRIndex+c]; debugRGB[4*c+3][index]=imgXY[i][imgRGBIndex+c]/photometrics[c][targetGridIndex]; } } if (showIntermediate){ double [][] debugData = new double [8][width*height]; String [] dbgTitles={"rep-X","rep-Y","dX","dY","R","G","B","Weight"};// for (int i=0;i<debugData[0].length;i++){ if (stagePXY[i]==null){ for (int j=0;j<debugData.length;j++) { debugData[j][i]=Double.NaN; // 0.0? } } else { debugData[0][i]=stagePXY[i][0]; debugData[1][i]=stagePXY[i][1]; debugData[2][i]= stageDiffPXY[i][0]; debugData[3][i]= stageDiffPXY[i][1]; debugData[4][i]= stageDiffRGB[i][0]; debugData[5][i]= stageDiffRGB[i][1]; debugData[6][i]= stageDiffRGB[i][2]; debugData[7][i]= stageMask[i]; } } (new ShowDoubleFloatArrays()).showArrays( debugData, width, height, true, "PRE_EXP-"+imgNum+"-"+chnNum, dbgTitles); String [] dbgTitles1={"R-tar","R-grid","R-corr","R-FF","G-tar","G-grid","G-corr","G-FF","B-tar","B-grid","B-corr","B-FF",};// (new ShowDoubleFloatArrays()).showArrays( debugRGB, width, height, true, "CORR-RGB-"+imgNum+"-"+chnNum, dbgTitles1); } CorrectionInNodes correctionInNodes=new CorrectionInNodes( imgNum, uv0[0], uv0[1], width, height, stagePXY, stageDiffPXY, stageDiffRGB, stageMask ); return correctionInNodes; } class CorrectionInNodes{ public int numImg; public Rectangle uv0; public double [][] reprojPXY; //= new double [width*height][]; //reprojected {px,py} public double [][] diffPXY; //= new double [width*height][]; // difference between corrected measured and reprojected (to add to correction) public double [][] diffRGB; //= new double [width*height][]; // difference (measured RGB)/(grid RGB) and current correction RGB (pixel sensitivity RGB) public double [] mask; // public int stageMasksSensor=0, stageMasksTarget=1, stageMasksContrast=2; public CorrectionInNodes ( int numImg, int u0, int v0, int width, int height, double [][] reprojPXY, //= new double [width*height][]; //reprojected {px,py} double [][] diffPXY, //= new double [width*height][]; // difference between corrected measured and reprojected (to add to correction) double [][] diffRGB, //= new double [width*height][]; // difference (measured RGB)/(grid RGB) and current correction RGB (pixel sensitivity RGB) double [] mask //= new double [width*height][]; // {sensor mask, target mask, measured contrast} ){ this.numImg=numImg; this.uv0=new Rectangle(u0,v0,width,height); this.reprojPXY=reprojPXY; //= new double [width*height][]; //reprojected {px,py} this.diffPXY=diffPXY; //= new double [width*height][]; // difference between corrected measured and reprojected (to add to correction) this.diffRGB=diffRGB; //= new double [width*height][]; // difference (measured RGB)/(grid RGB) and current correction RGB (pixel sensitivity RGB) this.mask=mask; //= new double [width*height][]; // {sensor mask, target mask, measured contrast} } public void show( String title ){ double [][] debugData = new double [8][this.uv0.width*this.uv0.height]; String [] dbgTitles={"rep-X","rep-Y","dX","dY","R","G","B","Weight"}; for (int i=0;i<debugData[0].length;i++){ if (this.reprojPXY[i]==null){ for (int j=0;j<debugData.length;j++) { debugData[j][i]=Double.NaN; // 0.0? } } else { debugData[0][i]=this.reprojPXY[i][0]; debugData[1][i]=this.reprojPXY[i][1]; debugData[2][i]= this.diffPXY[i][0]; debugData[3][i]= this.diffPXY[i][1]; debugData[4][i]= this.diffRGB[i][0]; debugData[5][i]= this.diffRGB[i][1]; debugData[6][i]= this.diffRGB[i][2]; debugData[7][i]= this.mask[i]; } } (new ShowDoubleFloatArrays()).showArrays( debugData, this.uv0.width, this.uv0.height, true, title, dbgTitles); } /** * Convert correction for grid nodes (detected and extrapolated) into decimated pixel array * result should be added to the current (prior) correction. Use alpha as weight when accumulating for multiple images * @param decimation decimate correction pixels from sensor pixels * @param sensorWidth sensor width in pixels (2592) * @param sensorHeight sensor height in pixels (1936) * @param debugLevel debug level (verbose if >3) * @return scan-line pixels correction arrays {dpX,dpY,alpha,R,G,B}[pixelIndex] */ public double [][] mapToPixels( int decimation, int sensorWidth, int sensorHeight, int debugLevel){ int debugThreshold=2; int sWidth= (sensorWidth-1)/decimation+1; // decimated width (648) int sHeight=(sensorHeight-1)/decimation+1; // decimated height (484) int [] uvInc={0,1,this.uv0.width,this.uv0.width+1}; // four corners as vu index int [][] cycles={ // counter-clockwise corners bounding the area (only orthogonal sides?) {1,0,2}, {2,3,1}, {0,2,3}, {3,1,0}}; double [][] thisPCorr= new double [6][sWidth*sHeight]; // calculate for a single (this) image, accumulate in the end int [] thisCounted=new int [sWidth*sHeight]; // some pixels accumulated twice - divide in the end for (int n=0;n<thisPCorr.length;n++) for (int i=0;i<thisPCorr[0].length;i++) thisPCorr[n][i]=0.0; for (int i=0;i<thisCounted.length;i++) thisCounted[i]=0; // now use imgData array to fill thisPCorr by linear interpolation for (int v=0;v<(this.uv0.height-1); v++) for (int u=0; u<(this.uv0.width-1);u++){ int vu=u+this.uv0.width*v; double [][] cornerXY =new double[4][]; for (int i=0;i<uvInc.length;i++){ int vu1=vu+uvInc[i]; cornerXY[i]=null; if (this.reprojPXY[vu1]!=null){ double w=this.mask[vu1]; if (w>0.0) { cornerXY[i]=new double[2]; cornerXY[i][0]=this.reprojPXY[vu1][0]; cornerXY[i][1]=this.reprojPXY[vu1][1]; } } } boolean [] cycleFits=new boolean[cycles.length]; boolean anyFits=false; for (int i=0;i<cycles.length;i++){ cycleFits[i]=true; for (int j=0;j<cycles[i].length;j++) if (cornerXY[cycles[i][j]]==null) { cycleFits[i]=false; break; } anyFits |=cycleFits[i]; } if (!anyFits) continue; // not a single cycle if (debugLevel>debugThreshold) { String debugString="cycleFits "; for (int i =0;i<cycleFits.length; i++) debugString+=" "+cycleFits[i]; System.out.println(debugString); } if (cycleFits[0]&&cycleFits[1]){ // remove overlaps cycleFits[2]=false; cycleFits[3]=false; } boolean minMaxUndefined=true; double minX=0,maxX=0,minY=0,maxY=0; // find bounding rectangle; for (int nCycle=0;nCycle<cycles.length;nCycle++) if (cycleFits[nCycle]){ int [] cycle=cycles[nCycle]; for (int corner=0; corner<cycle.length;corner++){ if (minMaxUndefined || (minX>cornerXY[cycle[corner]][0])) minX=cornerXY[cycle[corner]][0]; if (minMaxUndefined || (maxX<cornerXY[cycle[corner]][0])) maxX=cornerXY[cycle[corner]][0]; if (minMaxUndefined || (minY>cornerXY[cycle[corner]][1])) minY=cornerXY[cycle[corner]][1]; if (minMaxUndefined || (maxY<cornerXY[cycle[corner]][1])) maxY=cornerXY[cycle[corner]][1]; minMaxUndefined=false; } } int iMinX=(int) Math.floor(minX/decimation); int iMinY=(int) Math.floor(minY/decimation); int iMaxX=(int) Math.ceil(maxX/decimation); int iMaxY=(int) Math.ceil(maxY/decimation); // not sure if these checks are needed, got out of bounds wheriDy was =484=sHeight if (iMinX<0) iMinX=0; if (iMaxX>=sWidth) iMaxX=sWidth-1; if (iMinY<0) iMinY=0; if (iMaxY>=sHeight) iMaxY=sHeight-1; double [] originXY=new double [2]; double [] endXY=new double [2]; boolean debugHadPixels=false; //TODO: scan X,Y in this rectangle, for points in defined squares/triangles find if the point is inside (accurate not to loose any). for (int idY=iMinY; idY<=iMaxY;idY++){ double pY=idY*decimation; // in sensor pixels for (int idX=iMinX; idX<=iMaxX;idX++){ double pX=idX*decimation; // in sensor pixels // scan allowed triangles, usually 2 for (int nCycle=0;nCycle<cycles.length;nCycle++) if (cycleFits[nCycle]){ int [] cycle=cycles[nCycle]; // is this point inside? boolean inside=true; for (int nEdge=0;nEdge<cycle.length;nEdge++){ int nextNEdge=(nEdge==(cycle.length-1))?0:(nEdge+1); originXY[0]=this.reprojPXY[vu+uvInc[cycle[nEdge]]][0]; // imgData[2][vu+uvInc[cycle[nEdge]]]; originXY[1]=this.reprojPXY[vu+uvInc[cycle[nEdge]]][1]; // imgData[3][vu+uvInc[cycle[nEdge]]]; endXY[0]= this.reprojPXY[vu+uvInc[cycle[nextNEdge]]][0]; // imgData[2][vu+uvInc[cycle[nextNEdge]]]; endXY[1]= this.reprojPXY[vu+uvInc[cycle[nextNEdge]]][1]; // imgData[3][vu+uvInc[cycle[nextNEdge]]]; if (((pX-originXY[0])*(endXY[1]-originXY[1]) - (pY-originXY[1])*(endXY[0]-originXY[0]))<0.0){ inside=false; break; } } if (!inside) continue; // point is outside of the interpolation area, try next triangle (if any) if (debugLevel>debugThreshold) { System.out.println("idX="+idX+" idY="+idY+" nCycle="+nCycle); String debugString1="cycle:"; for (int i =0;i<cycle.length; i++) debugString1+=" "+cycle[i]; System.out.println(debugString1); } /* interpolate: 1. taking cycles[0] as origin and two (non co-linear) edge vectors - V1:from 0 to 1 and V2 from 1 to 2 find a1 and a2 so that vector V (from 0 to pXY) = a1*V1+ a2*V2 2. if F0 is the value of the interpolated function at cycles[0], F1 and F2 - at cycles[1] and cycles2 then F=F0+(F1-F0)*a1 +(F2-F1)*a2 */ double [] XY0={this.reprojPXY[vu+uvInc[cycle[0]]][0],this.reprojPXY[vu+uvInc[cycle[0]]][1]}; double [] XY1={this.reprojPXY[vu+uvInc[cycle[1]]][0],this.reprojPXY[vu+uvInc[cycle[1]]][1]}; double [] XY2={this.reprojPXY[vu+uvInc[cycle[2]]][0],this.reprojPXY[vu+uvInc[cycle[2]]][1]}; double [] V= {pX-XY0[0],pY-XY0[1]}; double [][] M={ {XY1[0]-XY0[0],XY2[0]-XY1[0]}, {XY1[1]-XY0[1],XY2[1]-XY1[1]}}; double det=M[0][0]*M[1][1]-M[1][0]*M[0][1]; double [][] MInverse={ { M[1][1]/det,-M[0][1]/det}, {-M[1][0]/det, M[0][0]/det}}; double [] a12={ MInverse[0][0]*V[0]+MInverse[0][1]*V[1], MInverse[1][0]*V[0]+MInverse[1][1]*V[1]}; int pCorrIndex=idY*sWidth+idX; // some points may be accumulated multiple times - thisPCorr[3] will take care of this if (debugLevel>debugThreshold) { System.out.println("XY0="+IJ.d2s(XY0[0],3)+":"+IJ.d2s(XY0[1],3)); System.out.println("XY1="+IJ.d2s(XY1[0],3)+":"+IJ.d2s(XY1[1],3)); System.out.println("XY2="+IJ.d2s(XY2[0],3)+":"+IJ.d2s(XY2[1],3)); System.out.println("M00="+IJ.d2s(M[0][0],3)+" M01="+IJ.d2s(M[0][1],3)); System.out.println("M10="+IJ.d2s(M[1][0],3)+" M11="+IJ.d2s(M[1][1],3)); System.out.println("MInverse00="+IJ.d2s(MInverse[0][0],5)+" MInverse01="+IJ.d2s(MInverse[0][1],5)); System.out.println("MInverse10="+IJ.d2s(MInverse[1][0],5)+" MInverse11="+IJ.d2s(MInverse[1][1],5)); System.out.println("a12="+IJ.d2s(a12[0],3)+":"+IJ.d2s(a12[1],3)); System.out.println("this.diffPXY[vu+uvInc[cycle[0]]][0]="+IJ.d2s(this.diffPXY[vu+uvInc[cycle[0]]][0],3)+ "this.diffPXY[vu+uvInc[cycle[0]]][1]="+IJ.d2s(this.diffPXY[vu+uvInc[cycle[0]]][1],3)); System.out.println("this.diffPXY[vu+uvInc[cycle[1]]][0]="+IJ.d2s(this.diffPXY[vu+uvInc[cycle[1]]][0],3)+ "this.diffPXY[vu+uvInc[cycle[1]]][1]="+IJ.d2s(this.diffPXY[vu+uvInc[cycle[1]]][1],3)); System.out.println("this.diffPXY[vu+uvInc[cycle[2]]][0]="+IJ.d2s(this.diffPXY[vu+uvInc[cycle[2]]][0],3)+ "this.diffPXY[vu+uvInc[cycle[2]]][1]="+IJ.d2s(this.diffPXY[vu+uvInc[cycle[2]]][1],3)); } double [] corr={ this.diffPXY[vu+uvInc[cycle[0]]][0]+ // dPx (this.diffPXY[vu+uvInc[cycle[1]]][0]-this.diffPXY[vu+uvInc[cycle[0]]][0])*a12[0]+ (this.diffPXY[vu+uvInc[cycle[2]]][0]-this.diffPXY[vu+uvInc[cycle[1]]][0])*a12[1], this.diffPXY[vu+uvInc[cycle[0]]][1]+ // dPy (this.diffPXY[vu+uvInc[cycle[1]]][1]-this.diffPXY[vu+uvInc[cycle[0]]][1])*a12[0]+ (this.diffPXY[vu+uvInc[cycle[2]]][1]-this.diffPXY[vu+uvInc[cycle[1]]][1])*a12[1], this.mask[vu+uvInc[cycle[0]]]+ // alpha (this.mask[vu+uvInc[cycle[1]]]-this.mask[vu+uvInc[cycle[0]]])*a12[0]+ (this.mask[vu+uvInc[cycle[2]]]-this.mask[vu+uvInc[cycle[1]]])*a12[1], this.diffRGB[vu+uvInc[cycle[0]]][0]+ // Red measured/pattern (this.diffRGB[vu+uvInc[cycle[1]]][0]-this.diffRGB[vu+uvInc[cycle[0]]][0])*a12[0]+ (this.diffRGB[vu+uvInc[cycle[2]]][0]-this.diffRGB[vu+uvInc[cycle[1]]][0])*a12[1], this.diffRGB[vu+uvInc[cycle[0]]][1]+ // Red measured/pattern (this.diffRGB[vu+uvInc[cycle[1]]][1]-this.diffRGB[vu+uvInc[cycle[0]]][1])*a12[0]+ (this.diffRGB[vu+uvInc[cycle[2]]][1]-this.diffRGB[vu+uvInc[cycle[1]]][1])*a12[1], this.diffRGB[vu+uvInc[cycle[0]]][2]+ // Red measured/pattern (this.diffRGB[vu+uvInc[cycle[1]]][2]-this.diffRGB[vu+uvInc[cycle[0]]][2])*a12[0]+ (this.diffRGB[vu+uvInc[cycle[2]]][2]-this.diffRGB[vu+uvInc[cycle[1]]][2])*a12[1]}; if (debugLevel>debugThreshold) { System.out.println("corr="+IJ.d2s(corr[0],3)+" "+IJ.d2s(corr[1],3)+" "+IJ.d2s(corr[2],3)); } if (pCorrIndex>thisPCorr[0].length) { // System.out.println("imgNum=" + imgNum+": "+ fittingStrategy.distortionCalibrationData.gIP[imgNum].path); System.out.println("thisPCorr[0].length="+thisPCorr[0].length+" pCorrIndex="+pCorrIndex+" sWidth="+sWidth+" idY="+idY+" idX="+idX); } for (int i=0;i<corr.length;i++) { thisPCorr[i][pCorrIndex]+= corr[i]; // OOB: -8, -1433 } thisCounted[pCorrIndex]++; if (debugLevel>debugThreshold) { debugHadPixels=true; } } } // idX // use same order in calculations, make sure no gaps } // idY if ((debugLevel>debugThreshold) && (debugHadPixels)){ // if (!debugExit) { System.out.println( " minX="+IJ.d2s(minX,1)+ " maxX="+IJ.d2s(maxX,1)); System.out.println( " minY="+IJ.d2s(minY,1)+ " maxY="+IJ.d2s(maxY,1)); System.out.println( " iMinX="+iMinX+ " iMaxX="+iMaxX); System.out.println( " iMinY="+iMinY+ " iMaxY="+iMaxY); // } // if (!debugExit) debugCntr--; // if (debugCntr==0) debugExit=true; // exit after first non-empty tile } } //for (int v=0;v<(this.uv0.height-1); v++) for (int u=0; u<(this.uv0.width-1);u++){ for (int i=0;i<thisCounted.length;i++) if (thisCounted[i]>1) { for (int j=0;j<thisPCorr[i].length;j++) thisPCorr[j][i]/= thisCounted[i]; } return thisPCorr; } } class DirInc{ private int top= 1 | 2 | 4 | 8 | 16; private int bottom=1 | 16 | 32 | 64 | 128; private int left= 1 | 2 | 64 | 128; private int right= 4 | 8 | 16 | 32 | 64; private int [] inc=null; private int [] validDirs=null; private double [][] unityVector=null; public int dirs=8; public DirInc(int width, int height){ // int [] dirs8={1,1+width,width,-1+width,-1,-1-width,-width,1-width}; int [][] incXY8={{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1}}; this.inc=new int [incXY8.length]; this.unityVector=new double [incXY8.length][2]; for (int i=0;i<incXY8.length;i++){ this.inc[i]=incXY8[i][0]+width*incXY8[i][1]; double len=Math.sqrt(incXY8[i][0]*incXY8[i][0]+incXY8[i][1]*incXY8[i][1]); this.unityVector[i][0]=incXY8[i][0]/len; this.unityVector[i][1]=incXY8[i][1]/len; } // this.inc=dirs8; this.validDirs=new int [width*height]; for (int i=0;i<this.validDirs.length;i++) this.validDirs[i]=0xff; for (int i=0;i<width;i++){ this.validDirs[ i]&=top; this.validDirs[(height-1)*width+i]&=bottom; } for (int i=0;i<height;i++){ this.validDirs[i*width]&=left; this.validDirs[i*width + width-1]&=right; } } public int newIndex(int oldIndex, int dir){ if ((validDirs[oldIndex] & (1<<dir))==0) return -1; // invalid dir for this location (border) return oldIndex+this.inc[dir]; } public double [] unity(int dir) { return this.unityVector[(dir+this.unityVector.length)%this.unityVector.length]; } } public class PixXYUV{ double [][]xy=null; int [][]uv=null; double [] alpha=null; double [][]dxy=null; public PixXYUV(){} public PixXYUV(int len){ this.uv=new int [len][2]; this.xy=new double [len][2]; this.alpha=new double [len]; this.dxy=new double [len][2]; } } /** * Interpolate (bi-linear) X/Y corrections and flat-field data for the sensor * @param chnNum - sensor (channel) number * @param px - pixel X coordinate (non-decimated) * @param py - pixel Y coordinate (non-decimated) * @return - vector of {corrX, corrY, alpha, flatfield_red, flatfield_green, flatfield_blue} */ public double [] interpolateCorrectionVector ( int chnNum, double px, double py){ if (this.pixelCorrection==null){ double [] vector={0.0,0.0,1.0,1.0,1.0,1.0}; return vector; } // this.pixelCorrectionDecimation=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks; // this.pixelCorrectionWidth= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth; // this.pixelCorrectionHeight= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight; int sensorCorrWidth= getSensorCorrWidth(chnNum); int sensorCorrHeight=this.pixelCorrection[chnNum][0].length/sensorCorrWidth; int [] ix={(int) Math.floor(px/getDecimateMasks(chnNum)), (int) Math.floor(px/getDecimateMasks(chnNum))+1}; int [] iy={(int) Math.floor(py/getDecimateMasks(chnNum)),(int) Math.floor(py/getDecimateMasks(chnNum))+1}; for (int i=0;i<2;i++){ if (ix[i]<0) ix[i]=0; else if (ix[i]>=sensorCorrWidth) ix[i]=sensorCorrWidth-1; if (iy[i]<0) iy[i]=0; else if (iy[i]>=sensorCorrHeight) iy[i]=sensorCorrHeight-1; } int index00=ix[0] + iy[0]*sensorCorrWidth; int indexX0=ix[1] + iy[0]*sensorCorrWidth; int index0Y=ix[0] + iy[1]*sensorCorrWidth; int indexXY=ix[1] + iy[1]*sensorCorrWidth; double corrDX=0,corrDY=0; if ((px>ix[0])&& (px<ix[1])) corrDX=px-ix[0]; if ((py>iy[0])&& (py<iy[1])) corrDY=py-iy[0]; double [] vector=new double [this.pixelCorrection[chnNum].length]; for (int n=0;n<vector.length;n++){ // bilinear interpolation vector[n]= (1-corrDX)* (1-corrDY)* this.pixelCorrection[chnNum][n][index00]+ corrDX * (1-corrDY)* this.pixelCorrection[chnNum][n][indexX0]+ (1-corrDX)* corrDY * this.pixelCorrection[chnNum][n][index0Y]+ corrDX * corrDY * this.pixelCorrection[chnNum][n][indexXY]; } return vector; } /** * Bilinear interpolate sensor mask array * @param mask decimated mask data * @param px - pixel X coordinate (non-decimated) * @param py - pixel Y coordinate (non-decimated) * @return interpolated mask data at specified fractional pixel */ public double interpolateMask ( int chnNum, double [] mask, double px, double py){ /// this.pixelCorrectionDecimation=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks; /// this.pixelCorrectionWidth= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth; /// this.pixelCorrectionHeight= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight; int sensorCorrWidth= getSensorCorrWidth(chnNum); // (this.pixelCorrectionWidth-1)/this.pixelCorrectionDecimation+1; int sensorCorrHeight=mask.length/sensorCorrWidth; int [] ix={(int) Math.floor(px/getDecimateMasks(chnNum)), (int) Math.floor(px/getDecimateMasks(chnNum))+1}; int [] iy={(int) Math.floor(py/getDecimateMasks(chnNum)), (int) Math.floor(py/getDecimateMasks(chnNum))+1}; for (int i=0;i<2;i++){ if (ix[i]<0) ix[i]=0; else if (ix[i]>=sensorCorrWidth) ix[i]=sensorCorrWidth-1; if (iy[i]<0) iy[i]=0; else if (iy[i]>=sensorCorrHeight) iy[i]=sensorCorrHeight-1; } int index00=ix[0] + iy[0]*sensorCorrWidth; int indexX0=ix[1] + iy[0]*sensorCorrWidth; int index0Y=ix[0] + iy[1]*sensorCorrWidth; int indexXY=ix[1] + iy[1]*sensorCorrWidth; double corrDX=0,corrDY=0; if ((px>ix[0])&& (px<ix[1])) corrDX=px-ix[0]; if ((py>iy[0])&& (py<iy[1])) corrDY=py-iy[0]; double result= (1-corrDX)* (1-corrDY)* mask[index00]+ corrDX * (1-corrDY)* mask[indexX0]+ (1-corrDX)* corrDY * mask[index0Y]+ corrDX * corrDY * mask[indexXY]; return result; } /** * after fitting finished and accepted - fittingStrategy.saveSeriesVector(double [] vector) */ public void saveFittingSeries() { fittingStrategy.saveSeriesVector(this.currentVector); } /* * For each image in the series: public double [] fittingStrategy.getImageParametersVector(int numImg, double [] parameterVector); * Calculates current values of all parameters for the particular sensor - some ("fixed") * are taken from the data stored for this individual image, others - from the parameter * vector (used in fitting) * @param numImg number of image * @param vector parameters vector * @return vector used for the current image (parameters influencing the acquired grid * on the sensor (common parameters and those of the sensor's subchannel) public void calcInterParamers( double [] parVect, boolean [] mask, // calculate only selected derivatives (all parVect values are still boolean calculateDerivatives // calculate this.interParameterDerivatives -derivatives array (false - just this.values) ){ * Calculate/set this.lensDistortionParameters and this.interParameterDerivatives * @param parVect 21-element vector for eyesis sub-camera, including common and individual parameters * @param mask -mask - which partial derivatives are needed to be calculated (others will be null) * @param calculateDerivatives calculate array of partial derivatives, if false - just the values For each point in the image public double [][] lensDistortionParameters.reorderPartialDerivatives (double [][] srcDerivatives){ double [][] lensDistortionParameters.calcPartialDerivatives( double xp, // target point horizontal, positive - right, mm double yp, // target point vertical, positive - down, mm double zp, // target point horizontal, positive - away from camera, mm boolean calculateAll){ // calculate derivatives, false - values only public double [][] interParameterDerivatives=null; //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) public double [] currentVector; // current variable parameter vector public double [] Y=null; // array of "y" - for each grid image, each defined grid node - 2 elements public double [][] targetXYZ=null; // array of target {x,y,z} matching each image each grid point public double [] fX=null; // array of "f(x)" - simulated data for all images, combining pixel-X and pixel-Y (odd/even) public double [][] jacobian=null; // partial derivatives of fX (above) by parameters to be adjusted (rows) */ public ImagePlus simulatePatternOnSensor( int stationNumber, int subCam, double goniometerTilt, double goniometerAxial, double goniometerInterAxis, SimulationPattern.SimulParameters simulParametersDefault, int threadsMax, boolean updateStatus, int mspDebugLevel, int global_debug_level, // DEBUG_LEVEL int debug_level // debug level used inside loops ){ MatchSimulatedPattern matchSimulatedPattern = new MatchSimulatedPattern(64); // new instance, all reset, FFTSize=64 will not be used matchSimulatedPattern.debugLevel = mspDebugLevel; // MatchSimulatedPattern.DistortionParameters distortionParameters = modifyDistortionParameters(); // SimulationPattern.SimulParameters simulParameters = modifySimulParameters(); int sensorWidth= getSensorWidth(subCam); int sensorHeight= getSensorHeight(subCam); double [][][] hintGrid=estimateGridOnSensor( stationNumber, subCam, goniometerTilt, // Tilt, goniometerHorizontal goniometerAxial, // Axial,goniometerAxial goniometerInterAxis, // inter-axis angle -1, // use camera parameters, not imageSet true // filter border ); if (hintGrid==null){ String msg="Grid is not visible for subcamera="+subCam+", tilt="+goniometerTilt+", axial="+goniometerAxial; IJ.showMessage("Error",msg); System.out.println("Error: "+msg); return null; } if (global_debug_level>1){ double [][] pixels=new double[4][hintGrid.length*hintGrid[0].length]; int index=0; String [] titles={"pixel-X","pixel-Y","grid-U","grid-V"}; for (int v=0; v<hintGrid.length;v++) for (int u=0;u<hintGrid[v].length;u++){ if (hintGrid[v][u]!=null){ for (int i=0; i<4;i++) pixels[i][index]=hintGrid[v][u][i]; } else { for (int i=0; i<4;i++) pixels[i][index]=-1; } index++; } (new ShowDoubleFloatArrays()).showArrays(pixels, hintGrid[0].length, hintGrid.length, true, "hintGrid", titles); } if (global_debug_level>0){ System.out.println("simulatePatternOnSensor(): subcamera="+subCam+", tilt="+goniometerTilt+", axial="+goniometerAxial); } int numCells=matchSimulatedPattern.restoreSimulatedPatternGridFromHint(hintGrid, sensorWidth, sensorHeight); matchSimulatedPattern.recalculateWaveVectors ( updateStatus, debug_level);// debug level used inside loops if (global_debug_level>0){ System.out.println("simulatePatternOnSensor(): "+numCells+" grid cells"); } SimulationPattern.SimulParameters simulParameters = simulParametersDefault.clone(); SimulationPattern simulationPattern=new SimulationPattern(simulParameters); double [][] xy0={{simulParameters.offsetX,simulParameters.offsetY},{simulParameters.offsetX-0.5,simulParameters.offsetY-0.5}} ; // TODO: add marks for the laser pointers when visible? float[] simPixels=simulationPattern.simulateGrid ( matchSimulatedPattern.getDArray(), 2, // gridFrac, // number of grid steps per pattern full period simulParameters, matchSimulatedPattern.getWOI(), 1, // simulParameters.subdiv/2, xy0[0], // add to patternGrid xy threadsMax, updateStatus, (debug_level>1)?1:0); //debug_level); // debug level if (global_debug_level>0){ System.out.println("simulatePatternOnSensor(): simPixels.length="+simPixels.length+" sensorWidth="+sensorWidth+" sensorHeight="+sensorHeight); } for (int i=0;i<simPixels.length;i++) simPixels[i]*=255.0; ImageProcessor ip_simGrid = new FloatProcessor(sensorWidth, sensorHeight); ip_simGrid.setPixels(simPixels); ip_simGrid.resetMinAndMax(); ImagePlus imp_simGrid= new ImagePlus("Simulated_Grid_CHN"+subCam+"_TILT"+goniometerTilt+"_AXIAL"+goniometerAxial, ip_simGrid); return imp_simGrid; } //TODO: add additional parameter - process all, but with matched pointers less than 2 public int applyHintedGrids( LaserPointer laserPointer, // LaserPointer object that specifies actual laser pointers on the target boolean removeOutOfGridPointers, double hintGridTolerance, // allowed mismatch (fraction of period) or 0 - orientation only boolean processAll, // if true - process all images, false - only disabled boolean ignoreLaserPointers, // ignore laser pointers, rely on hints only boolean processBlind, // try to match without known orientation and no laser pointers int imageNumber, // <0 - all, >=0 only this image boolean useSetData, int threadsMax, boolean updateStatus, int mspDebugLevel, int global_debug_level, // DEBUG_LEVEL int debug_level // debug level used inside loops ){ return applyHintedGrids( laserPointer, // LaserPointer object that specifies actual laser pointers on the target removeOutOfGridPointers, hintGridTolerance, // allowed mismatch (fraction of period) or 0 - orientation only processAll, // if true - process all images, false - only disabled ignoreLaserPointers, // ignore laser pointers, rely on hints only processBlind, // try to match without known orientation and no laser pointers imageNumber, // <0 - all, >=0 only this image 0, // int start_set, this.fittingStrategy.distortionCalibrationData.getNumSets()-1, // int end_set, useSetData, threadsMax, updateStatus, mspDebugLevel, global_debug_level, // DEBUG_LEVEL debug_level // debug level used inside loops ); } public int applyHintedGrids( LaserPointer laserPointer, // LaserPointer object that specifies actual laser pointers on the target boolean removeOutOfGridPointers, double hintGridTolerance, // allowed mismatch (fraction of period) or 0 - orientation only boolean processAll, // if true - process all images, false - only disabled boolean ignoreLaserPointers, // ignore laser pointers, rely on hints only boolean processBlind, // try to match without known orientation and no laser pointers int imageNumber, // <0 - all, >=0 only this image int start_set, int end_set, boolean useSetData, int threadsMax, boolean updateStatus, int mspDebugLevel, int global_debug_level, // DEBUG_LEVEL int debug_level // debug level used inside loops ){ int debugThreshold0=0; int debugThreshold=2; MatchSimulatedPattern matchSimulatedPattern = new MatchSimulatedPattern(64); // new instance, all reset, FFTSize=64 will not be used // next 2 lines are not needed for the new instance, but can be // used alternatively if keeping it // matchSimulatedPattern.invalidateFlatFieldForGrid(); // Reset Flat Filed calibration - different image. // matchSimulatedPattern.invalidateFocusMask(); matchSimulatedPattern.debugLevel = mspDebugLevel; // ImagePlus imp_eq = matchSimulatedPattern.applyFlatField(images[nImg]); // current image with grid flat-field correction // if (debug_level > 0){ // System.out.println("\n ======= Looking for grid, matching pointers in image " +images[nImg].getTitle()+ // ", initial number of pointers was "+numPointers); // } //matchSimulatedPatterns[numSensor].getChannel(images[numSensor])+" "); // MatchSimulatedPattern.DistortionParameters distortionParameters = modifyDistortionParameters(); // SimulationPattern.SimulParameters simulParameters = modifySimulParameters(); boolean noMessageBoxes=true; double [] xy0={0.0,0.0} ; //(old) debug only int numSuccess=0; DistortionCalibrationData dcd=fittingStrategy.distortionCalibrationData; for (int numGridImage=0;numGridImage<dcd.gIP.length;numGridImage++) { int set_number = dcd.gIP[numGridImage].getSetNumber(); if ((set_number >= start_set) && (set_number <= end_set) && (((imageNumber<0) || ((imageNumber==numGridImage)) &&(processAll) || (!dcd.gIP[numGridImage].enabled && ((hintGridTolerance>0.0) || ((dcd.gIP[numGridImage].matchedPointers>0)) && !ignoreLaserPointers))))){ // skip no-pointers if only orientation is hinted if (((dcd.gIP[numGridImage].matchedPointers==0) || ignoreLaserPointers)&& (dcd.gIS[dcd.get_gIS_index(numGridImage)].orientationEstimated)) { if ( !processBlind) { if (this.debugLevel>0) { System.out.println("\n**** Orientation is not known exactly for image # "+numGridImage+" - "+dcd.gIP[numGridImage].path+ ", and there are no laser pointer references (processBlind==false) - skipping"); } continue; } else { if (this.debugLevel>0) { System.out.println("\n**** Orientation is not known exactly for image # "+numGridImage+" - "+dcd.gIP[numGridImage].path+ ", and there are no laser pointer references, but processBlind is enabled, proceeding"); } } } if (this.debugLevel>debugThreshold0) { System.out.println("\n---- applyHintedGrids() image #"+numGridImage+" (imageNumber="+imageNumber+") "+ " dcd.gIP["+numGridImage+"].pixelsXY.length="+dcd.gIP[numGridImage].pixelsXY.length+ " dcd.gIP["+numGridImage+"].pixelsXY_extra.length="+dcd.gIP[numGridImage].pixelsXY_extra.length+ " grid period="+dcd.gIP[numGridImage].getGridPeriod()+ " enabled="+dcd.gIP[numGridImage].enabled+ " hintedMatch="+dcd.gIP[numGridImage].hintedMatch ); if (this.debugLevel>(debugThreshold)){ for (int i=0;i<dcd.gIP[numGridImage].pixelsXY.length;i++){ System.out.println(i+": dcd.gIP["+numGridImage+"].pixelsXY={"+dcd.gIP[numGridImage].pixelsXY[i][0]+ ","+dcd.gIP[numGridImage].pixelsXY[i][1]+"}"+ " uv={"+dcd.gIP[numGridImage].pixelsUV[i][0]+ ","+dcd.gIP[numGridImage].pixelsUV[i][1]+"}"); } for (int i=0;i<dcd.gIP[numGridImage].pixelsXY_extra.length;i++){ System.out.println(i+": dcd.gIP["+numGridImage+"].pixelsXY_extra={"+dcd.gIP[numGridImage].pixelsXY_extra[i][0]+ ","+dcd.gIP[numGridImage].pixelsXY_extra[i][1]+"}"+ " uv={"+dcd.gIP[numGridImage].pixelsUV_extra[i][0]+ ","+dcd.gIP[numGridImage].pixelsUV_extra[i][1]+"}"); } } } double [][][] pixelsXYSet={ dcd.gIP[numGridImage].pixelsXY, dcd.gIP[numGridImage].pixelsXY_extra}; int [][][] pixelsUVSet={ dcd.gIP[numGridImage].pixelsUV, dcd.gIP[numGridImage].pixelsUV_extra}; // shifts pixelsUV to have minimal u,v of 0 (stores shift in this.minUV), sets PATTERN_GRID matchSimulatedPattern.restorePatternGridFromGridList( pixelsXYSet, //double [][] pixelsXY, pixelsUVSet, // int [][] pixelsUV, dcd.gIP[numGridImage].intensityRange ); // width and height will be calculated from maximal of pixelsXY boolean OK=matchSimulatedPattern.createUV_INDEX( /// **** fails here null, //imp, // or null - just to determine WOI (when getWOI matches image size) xy0, // add to patterGrid xy, null OK threadsMax, updateStatus, global_debug_level, // DEBUG_LEVEL debug_level); // debug level used inside loops if (!OK) { System.out.println("++++++ BUG: in applyHintedGrids() failed in createUV_INDEX()"); continue; } double [] goniometerTiltAxial=dcd.getImagesetTiltAxial(numGridImage); if ((goniometerTiltAxial==null) || Double.isNaN(goniometerTiltAxial[0]) || Double.isNaN(goniometerTiltAxial[1])){ if (this.debugLevel>0) { System.out.println("No goniometer orientation is available for image # "+numGridImage+" - "+dcd.gIP[numGridImage].path); } } else { int station=dcd.getImageStation(numGridImage); int setNumber=dcd.gIP[numGridImage].getSetNumber(); double [][][] hintGrid=estimateGridOnSensor( station, // station number dcd.gIP[numGridImage].channel, goniometerTiltAxial[0], // Tilt, goniometerHorizontal goniometerTiltAxial[1], // Axial,goniometerAxial goniometerTiltAxial[2], // inter-axis angle setNumber, // -1 or specific image set true // filter border ); if (global_debug_level>0){ System.out.println("\n**** applyHintedGrids(): processing grid image # "+numGridImage+", path="+dcd.gIP[numGridImage].path); } if (hintGrid==null){ if (global_debug_level>0){ System.out.println("estimateGridOnSensor() failed - skipping"); } dcd.gIP[numGridImage].hintedMatch =0; continue; } int rslt= matchSimulatedPattern.combineGridCalibration( laserPointer, // LaserPointer object or null ignoreLaserPointers?null:dcd.gIP[numGridImage].laserPixelCoordinates, //pointersXY, removeOutOfGridPointers, // hintGrid, // predicted grid array (or null) hintGridTolerance, // allowed mismatch (fraction of period) or 0 - orientation only global_debug_level, // DEBUG_LEVEL noMessageBoxes ); if (global_debug_level>0){ System.out.println("applyHintedGrids(): rslt="+rslt); } if (rslt<0) { // failed hinting dcd.gIP[numGridImage].hintedMatch =0; } else { // re-create pixelsXY, pixelsXY_extra, pixelsUV, pixelsUV_extra int size=0; int size_extra=0; /* System.out.println("numGridImage="+numGridImage+" matchSimulatedPattern.getHeight()="+matchSimulatedPattern.getHeight()+ " matchSimulatedPattern.getWidth()="+matchSimulatedPattern.getWidth()+ " matchSimulatedPattern.targetUV is "+((matchSimulatedPattern.targetUV==null)?"null":"not null")+ " matchSimulatedPattern.pixelsUV is "+((matchSimulatedPattern.pixelsUV==null)?"null":"not null") ); System.out.println( " matchSimulatedPattern.targetUV[0] is "+((matchSimulatedPattern.targetUV[0]==null)?"null":"not null")+ " matchSimulatedPattern.pixelsUV[0] is "+((matchSimulatedPattern.pixelsUV[0]==null)?"null":"not null") );*/ for (int v=0;v<matchSimulatedPattern.getHeight();v++) for (int u=0;u<matchSimulatedPattern.getWidth();u++) { /* System.out.println("v="+v+", u="+u); System.out.println(" matchSimulatedPattern.targetUV[v][u] is "+((matchSimulatedPattern.targetUV[v][u]==null)?"null":"not null")); System.out.println(" matchSimulatedPattern.pixelsUV[v][u] is "+((matchSimulatedPattern.pixelsUV[v][u]==null)?"null":"not null"));*/ if ((matchSimulatedPattern.targetUV[v][u]!=null) && (matchSimulatedPattern.pXYUV [v][u]!=null)){ if ((matchSimulatedPattern.targetUV[v][u]!=null) && (matchSimulatedPattern.pXYUV [v][u]!=null) && (matchSimulatedPattern.pXYUV[v][u][0]>=0.0) || (matchSimulatedPattern.pXYUV[v][u][1]>=0.0)) { // disregard negative sensor pixels // System.out.println(" matchSimulatedPattern.targetUV[v][u] is "+((matchSimulatedPattern.targetUV[v][u]==null)?"null":"not null")); // System.out.println(" matchSimulatedPattern.targetUV[v][u][0]= "+matchSimulatedPattern.targetUV[v][u][0]); // System.out.println(" matchSimulatedPattern.targetUV[v][u][1]= "+matchSimulatedPattern.targetUV[v][u][1]); //******** // System.out.println(" patternParameters is "+((patternParameters==null)?"null":"not null")); // int tu=matchSimulatedPattern.targetUV[v][u][0]; // int tv=matchSimulatedPattern.targetUV[v][u][1]; // if (patternParameters.getXYZM(matchSimulatedPattern.targetUV[v][u][0],matchSimulatedPattern.targetUV[v][u][1],false,station)!=null) { size++; } else { size_extra++; } } } } // Move to DCD? dcd.gIP[numGridImage].resetMask(); dcd.gIP[numGridImage].pixelsXY=new double [size][6]; dcd.gIP[numGridImage].pixelsUV=new int [size][2]; dcd.gIP[numGridImage].pixelsXY_extra=new double [size_extra][6]; dcd.gIP[numGridImage].pixelsUV_extra=new int [size_extra][2]; int index=0; int index_extra=0; for (int v=0;v<matchSimulatedPattern.getHeight();v++) for (int u=0;u<matchSimulatedPattern.getWidth();u++) { /* System.out.println("+ v="+v+", u="+u); System.out.println(" + matchSimulatedPattern.targetUV[v][u] is "+((matchSimulatedPattern.targetUV[v][u]==null)?"null":"not null")); System.out.println(" + matchSimulatedPattern.pixelsUV[v][u] is "+((matchSimulatedPattern.pixelsUV[v][u]==null)?"null":"not null"));*/ if ((matchSimulatedPattern.targetUV[v][u]!=null) &&(matchSimulatedPattern.pXYUV[v][u]!=null) ) { // System.out.println("++ v="+v+", u="+u+" index="+index+" ("+size+"), index_extra="+index_extra+" ("+size_extra+")"); if ((matchSimulatedPattern.targetUV[v][u]!=null) &&(matchSimulatedPattern.pXYUV[v][u]!=null) && (matchSimulatedPattern.pXYUV[v][u][0]>=0.0) || (matchSimulatedPattern.pXYUV[v][u][1]>=0.0)) { // disregard negative sensor pixels if ( (v>=matchSimulatedPattern.gridContrastBrightness[0].length) || (u>=matchSimulatedPattern.gridContrastBrightness[0][0].length)){ System.out.println( " matchSimulatedPattern.gridContrastBrightness[0].length="+matchSimulatedPattern.gridContrastBrightness[0].length+ " matchSimulatedPattern.gridContrastBrightness[0][0].length="+matchSimulatedPattern.gridContrastBrightness[0][0].length+ " v="+v+" u="+u); } } // setting dcd.gIP[numGridImage].pixelsUV[index] with rotated/shifted if (patternParameters.getXYZM(matchSimulatedPattern.targetUV[v][u][0],matchSimulatedPattern.targetUV[v][u][1],false,station)!=null) { dcd.gIP[numGridImage].pixelsXY[index][0]=matchSimulatedPattern.pXYUV[v][u][0]; dcd.gIP[numGridImage].pixelsXY[index][1]=matchSimulatedPattern.pXYUV[v][u][1]; dcd.gIP[numGridImage].pixelsUV[index][0]=matchSimulatedPattern.targetUV[v][u][0]; dcd.gIP[numGridImage].pixelsUV[index][1]=matchSimulatedPattern.targetUV[v][u][1]; dcd.gIP[numGridImage].pixelsXY[index][2]=matchSimulatedPattern.gridContrastBrightness[0][v][u]; // grid contrast dcd.gIP[numGridImage].pixelsXY[index][3]=matchSimulatedPattern.gridContrastBrightness[1][v][u]/dcd.gIP[numGridImage].intensityRange[0]; // red dcd.gIP[numGridImage].pixelsXY[index][4]=matchSimulatedPattern.gridContrastBrightness[2][v][u]/dcd.gIP[numGridImage].intensityRange[1]; // green dcd.gIP[numGridImage].pixelsXY[index][5]=matchSimulatedPattern.gridContrastBrightness[3][v][u]/dcd.gIP[numGridImage].intensityRange[2]; // blue index++; } else { dcd.gIP[numGridImage].pixelsXY_extra[index_extra][0]=matchSimulatedPattern.pXYUV[v][u][0]; dcd.gIP[numGridImage].pixelsXY_extra[index_extra][1]=matchSimulatedPattern.pXYUV[v][u][1]; dcd.gIP[numGridImage].pixelsUV_extra[index_extra][0]=matchSimulatedPattern.targetUV[v][u][0]; dcd.gIP[numGridImage].pixelsUV_extra[index_extra][1]=matchSimulatedPattern.targetUV[v][u][1]; dcd.gIP[numGridImage].pixelsXY_extra[index_extra][2]=matchSimulatedPattern.gridContrastBrightness[0][v][u]; // grid contrast dcd.gIP[numGridImage].pixelsXY_extra[index_extra][3]=matchSimulatedPattern.gridContrastBrightness[1][v][u]/dcd.gIP[numGridImage].intensityRange[0]; // red dcd.gIP[numGridImage].pixelsXY_extra[index_extra][4]=matchSimulatedPattern.gridContrastBrightness[2][v][u]/dcd.gIP[numGridImage].intensityRange[1]; // green dcd.gIP[numGridImage].pixelsXY_extra[index_extra][5]=matchSimulatedPattern.gridContrastBrightness[3][v][u]/dcd.gIP[numGridImage].intensityRange[2]; // blue index_extra++; } } } dcd.gIP[numGridImage].hintedMatch =(hintGridTolerance>0.0)?2:1; // orientation or both orientation and translation dcd.gIP[numGridImage].matchedPointers=rslt; // update number of matched pointers if ((dcd.gIP[numGridImage].hintedMatch>1) || (dcd.gIP[numGridImage].matchedPointers>0)) numSuccess++; // Update rotation/shift //matchSimulatedPattern int [] fileUVShiftRot=dcd.gIP[numGridImage].getUVShiftRot(); int [] extraUVShiftRot=matchSimulatedPattern.getUVShiftRot(true); // last shift/rotation during matching pattern, correct for zero shift // int [] extraDbg=matchSimulatedPattern.getUVShiftRot(false); int [] combinedUVShiftRot=MatchSimulatedPattern.combineUVShiftRot(fileUVShiftRot,extraUVShiftRot); dcd.gIP[numGridImage].setUVShiftRot(combinedUVShiftRot); System.out.println("applyHintedGrids(): dcd.gIP["+numGridImage+"].hintedMatch="+dcd.gIP[numGridImage].hintedMatch+ " dcd.gIP["+numGridImage+"].matchedPointers="+dcd.gIP[numGridImage].matchedPointers+ " points:"+index+" extra points:"+index_extra); // testing rot/shift: String nonzero=((extraUVShiftRot[0]==0)&&(extraUVShiftRot[1]==0)&&(extraUVShiftRot[2]==0))?" ":"*"; System.out.println("applyHintedGrids(): fileUVShiftRot= "+fileUVShiftRot[0]+"/"+fileUVShiftRot[1]+":"+fileUVShiftRot[2]); System.out.println(" "+nonzero+"extraUVShiftRot= "+extraUVShiftRot[0]+"/"+extraUVShiftRot[1]+":"+extraUVShiftRot[2]); System.out.println(" combinedUVShiftRot="+combinedUVShiftRot[0]+"/"+combinedUVShiftRot[1]+":"+combinedUVShiftRot[2]); // System.out.println(" extraDbg="+extraDbg[0]+"/"+extraDbg[1]+":"+extraDbg[2]); } } } } return numSuccess; } public void showSourceImage(int numGridImage){ String source_path=fittingStrategy.distortionCalibrationData.gIP[numGridImage].source_path; if (source_path != null) { ImagePlus imp = new ImagePlus(source_path); imp.show(); } } public int [][] getImageMarkers(int numGridImage){ String source_path=fittingStrategy.distortionCalibrationData.gIP[numGridImage].source_path; if (source_path != null) { final ImagePlus imp = new ImagePlus(source_path); imp.show(); /* Thread msg_box_thread = new Thread() { @Override public void run() { IJ.showMessage("Please place point markers on the "+imp.getTitle()); } }; msg_box_thread.setPriority(Thread.MIN_PRIORITY); msg_box_thread.start(); try { msg_box_thread.join(); } catch (InterruptedException ie) { throw new RuntimeException(ie); } */ // IJ.showMessage("Please place point markers on the "+imp.getTitle()); System.out.println("got it"); PointRoi pointRoi = null; if (imp.getRoi() instanceof PointRoi) { pointRoi = (PointRoi) imp.getRoi(); } else { System.out.println("This image does not have point marks - please mark it in "+source_path); IJ.showMessage("This image does not have point marks - please mark it in "+source_path); return null; } Point [] points = pointRoi.getContainedPoints(); int [][] ipoints = new int [points.length][2]; for (int n = 0; n < ipoints.length; n++) { ipoints[n][0] = points[n].x; ipoints[n][1] = points[n].y; } return ipoints; } return null; } public void showGridImage(int numGridImage){ DistortionCalibrationData.GridImageParameters grid=fittingStrategy.distortionCalibrationData.gIP[numGridImage]; boolean valid=false; int minU=0,maxU=0,minV=0,maxV=0; for (int i=0;i<grid.pixelsUV.length;i++){ if (!valid){ minU=grid.pixelsUV[i][0]; minV=grid.pixelsUV[i][1]; maxU=minU; maxV=minV; valid=true; } else { if (minU>grid.pixelsUV[i][0]) minU=grid.pixelsUV[i][0]; if (minV>grid.pixelsUV[i][1]) minV=grid.pixelsUV[i][1]; if (maxU<grid.pixelsUV[i][0]) maxU=grid.pixelsUV[i][0]; if (maxV<grid.pixelsUV[i][1]) maxV=grid.pixelsUV[i][1]; } } for (int i=0;i<grid.pixelsUV_extra.length;i++){ if (!valid){ minU=grid.pixelsUV_extra[i][0]; minV=grid.pixelsUV_extra[i][1]; maxU=minU; maxV=minV; valid=true; } else { if (minU>grid.pixelsUV_extra[i][0]) minU=grid.pixelsUV_extra[i][0]; if (minV>grid.pixelsUV_extra[i][1]) minV=grid.pixelsUV_extra[i][1]; if (maxU<grid.pixelsUV_extra[i][0]) maxU=grid.pixelsUV_extra[i][0]; if (maxV<grid.pixelsUV_extra[i][1]) maxV=grid.pixelsUV_extra[i][1]; } } String [] titles={"X","Y","U","V","valid","extra"}; int height=maxV-minV+1; int width= maxU-minU+1; // System.out.println("showGridImage(): minU="+minU+" maxU="+maxU+" minV="+minV+" maxV="+maxV+" width="+width+" height="+height); // System.out.println("showGridImage(): grid.pixelsXY.length="+grid.pixelsXY.length+" grid.pixelsXY.length="+grid.pixelsXY.length); double [][] pixels=new double [titles.length][width*height]; for (int i=0;i<pixels[0].length;i++) { pixels[0][i]=-1.0; // x pixels[1][i]=-1.0; // y pixels[2][i]= 0.0; // u pixels[3][i]= 0.0; // v pixels[4][i]=-1000.0; // valid pixels[5][i]=-1000.0; // extra } for (int i=0;i<grid.pixelsUV.length;i++){ int u=grid.pixelsUV[i][0]-minU; int v=grid.pixelsUV[i][1]-minV; int index=u+width*v; pixels[0][index]=grid.pixelsXY[i][0]; pixels[1][index]=grid.pixelsXY[i][1]; pixels[2][index]=grid.pixelsUV[i][0]; pixels[3][index]=grid.pixelsUV[i][1]; pixels[4][index]=1000.0; } for (int i=0;i<grid.pixelsUV_extra.length;i++){ int u=grid.pixelsUV_extra[i][0]-minU; int v=grid.pixelsUV_extra[i][1]-minV; int index=u+width*v; pixels[0][index]=grid.pixelsXY_extra[i][0]; pixels[1][index]=grid.pixelsXY_extra[i][1]; pixels[2][index]=grid.pixelsUV_extra[i][0]; pixels[3][index]=grid.pixelsUV_extra[i][1]; pixels[4][index]=1000.0; } (new ShowDoubleFloatArrays()).showArrays(pixels, width, height, true, "grid-"+numGridImage, titles); } public void manualGridHint(int imgNumber) { int [][] markers = getImageMarkers(imgNumber); if ((markers != null) && (markers.length > 0)) { double [][] xyuv = new double [markers.length][4]; for (int i =0; i < markers.length; i++) { xyuv[i][0]=markers[i][0]; xyuv[i][1]=markers[i][1]; xyuv[i][2]=0.5; xyuv[i][3]=0.5; } GenericDialog gd=new GenericDialog("Specify U,V coordinates of the marker(s)"); gd.addMessage("Center white (LWIR black) cell U=0.5, V=0.5"); for (int n = 0; n < markers.length; n++) { String label = "Marker "+(n+1)+" (x="+markers[n][0]+", y="+markers[n][1]; gd.addNumericField(label+" U", xyuv[n][2], 1, 5, ""); gd.addNumericField(label+" V", xyuv[n][3], 1, 5, ""); } gd.showDialog(); if (gd.wasCanceled()) return; for (int i =0; i < markers.length; i++) { xyuv[i][2] = gd.getNextNumber(); xyuv[i][3] = gd.getNextNumber(); } // read grid image String grid_path=fittingStrategy.distortionCalibrationData.gIP[imgNumber].path; if (grid_path != null) { ImagePlus imp = new ImagePlus(grid_path); JP46_Reader_camera jp4_instance= new JP46_Reader_camera(false); jp4_instance.decodeProperiesFromInfo(imp); MatchSimulatedPattern.setPointersXYUV(imp, xyuv); updateGridToPointer(imp, xyuv); jp4_instance.encodeProperiesToInfo(imp); System.out.println("Updated "+grid_path); (new FileSaver(imp)).saveAsTiff(grid_path); // imp.show(); } return; } } public void updateGridToPointer(ImagePlus imp_grid, double[][] xyuv) { ImageStack stack=imp_grid.getStack(); if ((stack==null) || (stack.getSize()<4)) { String msg="Expected a 8-slice stack in "+imp_grid.getTitle(); IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } float [][] pixels=new float[stack.getSize()][]; // now - 8 (x,y,u,v,contrast, vignR,vignG,vignB for (int i=0;i<pixels.length;i++) pixels[i]= (float[]) stack.getPixels(i+1); // pixel X : negative - no grid here int width = imp_grid.getWidth(); int height = imp_grid.getHeight(); // start with translation only using xyuv[0][], may use full matching - same as laser pointers later int indx_best = -1; double d2_best = Double.NaN; for (int indx = 0; indx < pixels[0].length; indx++) { double dx = pixels[0][indx] - xyuv[0][0]; double dy = pixels[1][indx] - xyuv[0][1] ; double d2 = dx*dx + dy*dy; if (Double.isNaN(d2_best) || (d2 < d2_best)) { indx_best = indx; d2_best = d2; } } int ix0 = indx_best % width; int iy0 = indx_best / width; PolynomialApproximation polynomialApproximation =new PolynomialApproximation(0);// no debug double [][][] data = new double[9][3][]; int indx = 0; for (int idy = -1; idy <=1; idy++) { int iy = iy0+idy; for (int idx = -1; idx <=1; idx++) { int ix = ix0 + idx; data[indx][0] = new double[2]; data[indx][1] = new double[2]; data[indx][2] = new double[1]; data[indx][0][0] = idx; data[indx][0][1] = idy; if ((ix >= 0) && (ix < width) && (iy >= 0) && (iy < height)) { int offs = iy * width + ix; data[indx][1][0] = pixels[0][offs] - xyuv[0][0]; data[indx][1][1] = pixels[1][offs] - xyuv[0][1]; data[indx][2][0] = 1.0; } else { data[indx][2][0] = 0.0; } indx++; } } double [][] coeff = polynomialApproximation.quadraticApproximation( data, true); // force linear double [][] aA = {{coeff[0][0],coeff[0][1]},{coeff[1][0],coeff[1][1]}}; double [][] aB = {{-coeff[0][2]},{-coeff[1][2]}}; Matrix A = new Matrix(aA); Matrix B = new Matrix(aB); Matrix V = A.solve(B); double [] av = V.getColumnPackedCopy(); double u, v; // = xyuv[0][2]-() if (av[0] < 0) { av[0] += 1.0; ix0 -= 1; } if (av[1] < 0) { av[1] += 1.0; iy0 -= 1; } u = xyuv[0][2] - ( (1-av[0])*(1-av[1]) * pixels[2][(iy0 + 0) * width + ix0 + 0]+ ( av[0])*(1-av[1]) * pixels[2][(iy0 + 0) * width + ix0 + 1]+ (1-av[0])*( av[1]) * pixels[2][(iy0 + 1) * width + ix0 + 0]+ ( av[0])*( av[1]) * pixels[2][(iy0 + 1) * width + ix0 + 1]); v = xyuv[0][3] - ( (1-av[0])*(1-av[1]) * pixels[3][(iy0 + 0) * width + ix0 + 0]+ ( av[0])*(1-av[1]) * pixels[3][(iy0 + 0) * width + ix0 + 1]+ (1-av[0])*( av[1]) * pixels[3][(iy0 + 1) * width + ix0 + 0]+ ( av[0])*( av[1]) * pixels[3][(iy0 + 1) * width + ix0 + 1]); int idu = (int)Math.round(u); int idv = (int)Math.round(v); // Verify that idy+idv - even number if (((idu + idv) & 1) != 0) { String msg = "Incorrect shift - u="+u+", v="+v+", idu="+idu+", idv="+idv+", idu+idv="+(idu+idv)+" SHOULD BE EVEN!"; System.out.println(msg); IJ.showMessage(msg); } for (int i = 0; i < pixels[2].length; i++) { pixels[2][i] += idu; pixels[3][i] += idv; } } public void showGridAndHint(){ GenericDialog gd=new GenericDialog("Show selected grid and/or hint grid"); gd.addNumericField("Grid Image index", 0,0); gd.addCheckbox("Show source image (if available)", true); gd.addCheckbox("Show grid image", true); gd.addCheckbox("Show hint grid", true); gd.addCheckbox("Use imageSet data if available (unchecked - camera data)", true); gd.showDialog(); if (gd.wasCanceled()) return; int numGridImage= (int) gd.getNextNumber(); boolean showGrid=gd.getNextBoolean(); boolean showSource=gd.getNextBoolean(); boolean showHint=gd.getNextBoolean(); boolean useSetData=gd.getNextBoolean(); IJ.showStatus("grid: "+((fittingStrategy.distortionCalibrationData.gIP[numGridImage].path==null)?"":fittingStrategy.distortionCalibrationData.gIP[numGridImage].path)); // showStatus("grid: "+((fittingStrategy.distortionCalibrationData.gIP[numGridImage].path==null)?"":fittingStrategy.distortionCalibrationData.gIP[numGridImage].path),0); if (showGrid) showGridImage(numGridImage); if (showSource) showSourceImage(numGridImage); // if (showSource) getImageMarkers(numGridImage); if (showHint) calcAndShowHintGrid(numGridImage,useSetData); } public void calcAndShowHintGrid(int numGridImage, boolean useSetData){ double [] goniometerTiltAxial=fittingStrategy.distortionCalibrationData.getImagesetTiltAxial(numGridImage); if ((goniometerTiltAxial==null) || Double.isNaN(goniometerTiltAxial[0]) || Double.isNaN(goniometerTiltAxial[1])){ if (this.debugLevel>0)System.out.println("No goniometer orientation is available for image # "+numGridImage+" - "+fittingStrategy.distortionCalibrationData.gIP[numGridImage].path); GenericDialog gd=new GenericDialog("Specify camera orientation (channel"+fittingStrategy.distortionCalibrationData.gIP[numGridImage].channel+")"); gd.addMessage("No goniometer orientation is available for image # "+numGridImage+" - "+fittingStrategy.distortionCalibrationData.gIP[numGridImage].path+ ", please specify orientation manually"); gd.addNumericField("Camera tilt (0 - vertical, >0 looking above horizon on the target)", 0.0, 1,6,"degrees"); gd.addNumericField("Camera axial (0 - subcamera 0 looking to the target, >0 - rotated clockwise)", 0.0, 1,6,"degrees"); gd.addNumericField("Camera inter-axis angle (from 90) ", 0.0, 1,6,"degrees"); gd.showDialog(); if (gd.wasCanceled()) return; goniometerTiltAxial=new double[3]; goniometerTiltAxial[0]= gd.getNextNumber(); goniometerTiltAxial[1]= gd.getNextNumber(); goniometerTiltAxial[2]= gd.getNextNumber(); } double [][][] hintGrid=estimateGridOnSensor( fittingStrategy.distortionCalibrationData.getImageStation(numGridImage), // station number fittingStrategy.distortionCalibrationData.gIP[numGridImage].channel, goniometerTiltAxial[0], // Tilt, goniometerHorizontal goniometerTiltAxial[1], // Axial,goniometerAxial goniometerTiltAxial[2], // inter-axis angle (useSetData?fittingStrategy.distortionCalibrationData.gIP[numGridImage].getSetNumber():-1), true // filter border ); if (hintGrid == null) { String msg = "hintGrid is null"; IJ.showMessage("Error",msg); System.out.println(msg); return; } showHintGrid(hintGrid,"hint-"+numGridImage); } public void showHintGrid(double [][][] hintGrid){ showHintGrid(hintGrid,"hintGrid"); } public void showHintGrid(double [][][] hintGrid, String title){ double [][] pixels=new double[4][hintGrid.length*hintGrid[0].length]; int index=0; String [] titles={"pixel-X","pixel-Y","grid-U","grid-V"}; for (int v=0; v<hintGrid.length;v++) for (int u=0;u<hintGrid[v].length;u++){ if (hintGrid[v][u]!=null){ for (int i=0; i<4;i++) pixels[i][index]=hintGrid[v][u][i]; } else { for (int i=0; i<4;i++) pixels[i][index]=0; } index++; } (new ShowDoubleFloatArrays()).showArrays(pixels, hintGrid[0].length, hintGrid.length, true, title, titles); } /** * Calculate grid on sensor using current camera parameters (including goniometer angles), sub-camera number * @param subCamera * @return grid array [v][u][0- x, 1 - y, 2 - u, 3 - v] */ /* // wrong, orientation depends on timestamp public double [][][] estimateGridOnSensor( int subCamera){ double [] parVector=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getParametersVector(subCamera); return estimateGridOnSensor( subCamera, parVector[fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getGoniometerHorizontalIndex()], parVector[fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getGoniometerAxialIndex()]); } */ public LensDistortionParameters setupLensDistortionParameters( int numImg, int debugLevel){ // Axial - may be Double.NaN LensDistortionParameters lensDistortionParameters = new LensDistortionParameters ( this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(numImg), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(numImg), null, //double [][] interParameterDerivatives, //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) if null - just values, no derivatives this.fittingStrategy.distortionCalibrationData.getParameters(numImg), //parVector, null, //boolean [] mask, // calculate only selected derivatives (all parVect values are still debugLevel ); return lensDistortionParameters; } public LensDistortionParameters setupLensDistortionParameters( int stationNumber, int subCamera, double goniometerHorizontal, // Tilt - may be Double.NaN double goniometerAxial, int debugLevel){ // Axial - may be Double.NaN double [] parVector=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getParametersVector(stationNumber,subCamera); int goniometerHorizontalIndex=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getGoniometerHorizontalIndex(); int goniometerAxialIndex=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getGoniometerAxialIndex(); if (!Double.isNaN(goniometerHorizontal))parVector[goniometerHorizontalIndex]=goniometerHorizontal; if (!Double.isNaN(goniometerAxial))parVector[goniometerAxialIndex]=goniometerAxial; LensDistortionParameters lensDistortionParameters = new LensDistortionParameters ( this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(stationNumber, subCamera), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(stationNumber, subCamera), null, //double [][] interParameterDerivatives, //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) if null - just values, no derivatives parVector, null, //boolean [] mask, // calculate only selected derivatives (all parVect values are still debugLevel ); return lensDistortionParameters; } /** * Calculate grid projection to pixel X, Y (not counting sensor correction (add?) and grid photometrics * @param lensDistortionParameters LensDistortionParameters instance created for particular image with setupLensDistortionParameters() * @param numImg image number * @param u grid U (signed, 0 in the center) * @param v grid V (signed, 0 in the center) * @return [7] {pX,pY,grid mask (binary), grid R, grid G, grid B, alpha} */ public double [] reprojectGridNode( LensDistortionParameters lensDistortionParameters, int numImg, int u, // grid signed u,v int v){ double maxRelativeRadius=this.hintedMaxRelativeRadius; // make adjustable return reprojectGridNode( lensDistortionParameters, numImg, u, // grid signed u,v v, maxRelativeRadius); } public double [] reprojectGridNode( LensDistortionParameters lensDistortionParameters, int numImg, int u, // grid signed u,v int v, double maxRelativeRadius //=2.0; ){ int debugThreshold=1; int nChn= this.fittingStrategy.distortionCalibrationData.gIP[numImg].channel; int station=this.fittingStrategy.distortionCalibrationData.gIP[numImg].getStationNumber(); // if (!lensDistortionParameters.isTargetVisible(false)) return null; // camera is looking away from the target (does not mean target is in FOV) // double [][][] patternGeometry=this.patternParameters.getGeometry(); // [v][u]{x,y,z,alpha} - no photometric double [] result= new double[7]; double [] XYZMP=this.patternParameters.getXYZMP( // null pointer u, v, station, nChn, false); if (XYZMP==null) return null; // project the target point to this sensor double [][]pXY= lensDistortionParameters.calcPartialDerivatives( XYZMP[0], // target point horizontal, positive - right, mm XYZMP[1], // target point vertical, positive - down, mm XYZMP[2], // target point horizontal, positive - away from camera, mm maxRelativeRadius, // false); // calculate derivatives, false - values only (NaN for behind points - only when false here) if (Double.isNaN(pXY[0][0])) { if (this.debugLevel>debugThreshold){ System.out.println("reprojectGridNode(...,"+numImg+","+u+","+"v"+") - point behind the sensor"); } return null; // point behind camera } result[0]=pXY[0][0]; result[1]=pXY[0][1]; result[2]=XYZMP[3]; // binary mask result[3]=XYZMP[4]; // R result[4]=XYZMP[5]; // G result[5]=XYZMP[6]; // B result[6]=XYZMP[7]; // alpha // get photometrics here return result; } /** * Apply sensor correction to the projected grid (generated by estimateGridOnSensor()) * @param gridOnSensor array [v][u][0- x, 1 - y, 2 - targetAbsolute-u, 3 - targetAbsolute-v] * @param subCamera channel number * @return true if the correction was applied (in-place) false if no correction is available */ public boolean correctGridOnSensor( double [][][] gridOnSensor, int subCamera){ if (this.pixelCorrection==null) return false; for (double [][] row:gridOnSensor) for (double [] cell:row) if ((cell!=null) && (cell.length>1)){ double [] corrXYARGB=interpolateCorrectionVector ( // vector of {corrX, corrY, alpha, flatfield_red, flatfield_green, flatfield_blue} subCamera, //int chnNum, cell[0], //double px, cell[1]); //double py) cell[0]+=corrXYARGB[0]; // measured-> corrected : subtract, projected->simulated:add; cell[1]+=corrXYARGB[1]+0.0; // Debugging by adding +1.0!! } // System.out.println("================== Added +0.0 to pixel y for debugging purposes! ====================="); return true; } /** * Calculate grid on sensor using current Camera parameters, sub-camera number and the two goniometer angles * @param stationNumber * @param subCamera * @param goniometerHorizontal * @param goniometerAxial * @param imageSet - if >=0 - use this set number data instead of the camera data) * @return grid array [v][u][0- x, 1 - y, 2 - u, 3 - v] */ // TODO:calcInterParamers() -> lensDistortionParameters.lensCalcInterParamers public double [][][] estimateGridOnSensor( // not yet thread safe int stationNumber, int subCamera, double goniometerHorizontal, // Tilt double goniometerAxial, // Axial double goniometerInterAxis, // interAxisAngle int imageSet, boolean filterBorder){ double maxRelativeRadius=this.hintedMaxRelativeRadius; // make adjustable int debugThreshold=2; // Get parameter vector (22) for the selected sensor, current Eyesisparameters and specified orientation angles double [] parVector=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getParametersVector(stationNumber,subCamera); if ((imageSet>=0) && (this.fittingStrategy.distortionCalibrationData.gIS!=null) && (this.fittingStrategy.distortionCalibrationData.gIS[imageSet]!=null)){ this.fittingStrategy.distortionCalibrationData.gIS[imageSet].updateParameterVectorFromSet(parVector); } if (!Double.isNaN(goniometerHorizontal)) { int goniometerHorizontalIndex=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getGoniometerHorizontalIndex(); parVector[goniometerHorizontalIndex]=goniometerHorizontal; } if (!Double.isNaN(goniometerAxial)) { int goniometerAxialIndex=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getGoniometerAxialIndex(); parVector[goniometerAxialIndex]= goniometerAxial; } if (!Double.isNaN(goniometerInterAxis)) { int goniometerInterAxisAngleIndex=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getInterAxisAngleIndex(); parVector[goniometerInterAxisAngleIndex]= goniometerInterAxis; } // /interAxis int sensorWidth=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth(subCamera); int sensorHeight=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight(subCamera); System.out.println("estimateGridOnSensor(): subCamera="+subCamera+", goniometerHorizontal="+goniometerHorizontal+", goniometerAxial="+goniometerAxial); this.lensDistortionParameters.lensCalcInterParamers( this.lensDistortionParameters, // 22-long parameter vector for the image this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(stationNumber, subCamera), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(stationNumber, subCamera), null, // this.interParameterDerivatives, // [22][] parVector, null); // if no derivatives, null is OK if (!lensDistortionParameters.isTargetVisible(this.debugLevel>0)) { if (this.debugLevel>debugThreshold) System.out.println("Camera is looking away from the target"); // return null; // camera is looking away from the target (does not mean target is in FOV) } double [][][] patternGeometry=this.patternParameters.getGeometry(); // [v][u]{x,y,z,alpha} - no photometric double [][][] result= new double[patternGeometry.length][patternGeometry[0].length][4]; int visibleCells=0; double [][] debugPixels=null; String [] debugTitles={"pX","pY","X","Y","Z","mask"}; if (this.debugLevel>debugThreshold){ debugPixels=new double [6][patternGeometry.length*patternGeometry[0].length]; for (int c=0;c<debugPixels.length;c++) for (int i=0;i<debugPixels[c].length;i++) debugPixels[c][i]=Double.NaN; } // was bug cased by +/- infinity (and sometimes numbers falling into the sensor range) when the image plane intersected target // simple fix - remove pixels with too few neighbors (maybe just all border pixels? for (int v=0;v<result.length;v++) for (int u=0;u<result[v].length;u++){ int [] iUV=this.patternParameters.uvIndicesToUV (u, v); if (iUV==null) { result[v][u]=null; } else { double [] XYZM=this.patternParameters.getXYZM(iUV[0],iUV[1],stationNumber); // project the target point to this sensor double [][]pXY= this.lensDistortionParameters.calcPartialDerivatives( XYZM[0], // target point horizontal, positive - right, mm XYZM[1], // target point vertical, positive - down, mm XYZM[2], // target point horizontal, positive - away from camera, mm maxRelativeRadius, false); // calculate derivatives, false - values only (NaN for behind points - only when false here) // verify the grid is inside the sensor area (may use sensor mask later too? probably not needed) // Now NaN if point is behind the sensor if (Double.isNaN(pXY[0][0]) || (pXY[0][0]<0) || (pXY[0][0]>=sensorWidth) || (pXY[0][1]<0) || (pXY[0][1]>=sensorHeight)){ if (this.debugLevel>debugThreshold){ System.out.println("--- estimateGridOnSensor():v="+v+" u="+u+" X="+XYZM[0]+" Y="+XYZM[1]+" Z="+XYZM[2]+" M="+XYZM[3]+ " pXY[0][0]="+pXY[0][0]+", pXY[0][1]="+pXY[0][1]+", iUV[0]="+iUV[0]+", iUV[1]="+iUV[1]); } result[v][u]=null; } else { double [] resultCell={pXY[0][0],pXY[0][1],iUV[0],iUV[1]}; result[v][u]=resultCell; if (this.debugLevel>debugThreshold){ System.out.println("+++ estimateGridOnSensor():v="+v+" u="+u+" X="+XYZM[0]+" Y="+XYZM[1]+" Z="+XYZM[2]+" M="+XYZM[3]+ " pXY[0][0]="+pXY[0][0]+", pXY[0][1]="+pXY[0][1]+", iUV[0]="+iUV[0]+", iUV[1]="+iUV[1]); } visibleCells++; } if (this.debugLevel>debugThreshold){ int uv=u+v*result[v].length; debugPixels[0][uv]=pXY[0][0]; debugPixels[1][uv]=pXY[0][1]; debugPixels[2][uv]=XYZM[0]; debugPixels[3][uv]=XYZM[1]; debugPixels[4][uv]=XYZM[2]; } } } if (filterBorder){ // now filter border nodes boolean [] mask= new boolean [patternGeometry.length*patternGeometry[0].length]; int index=0; for (int v=0;v<result.length;v++) for (int u=0;u<result[v].length;u++){ mask [index++]=(result[v][u]!=null) && ((v==0) || (result[v-1][u]!=null)) && ((v==(result.length-1)) || (result[v+1][u]!=null)) && ((u==0) || (result[v][u-1]!=null))&& ((u==(result[v].length-1)) || (result[v][u+1]!=null)); } index=0; for (int v=0;v<result.length;v++) for (int u=0;u<result[v].length;u++){ if (!mask[index++]) result[v][u]=null; } } if (this.debugLevel>debugThreshold){ for (int v=0;v<result.length;v++) for (int u=0;u<result[v].length;u++){ int uv=u+v*result[v].length; debugPixels[5][uv]=(result[v][u]!=null)?3000:-3000; // masked } (new ShowDoubleFloatArrays()).showArrays( debugPixels, result[0].length, result.length, true, "Hinted-All", debugTitles); } if (this.debugLevel>1) { System.out.println("Grid in the FOV of the subcamera "+subCamera+ " tilt="+goniometerHorizontal+" axial="+goniometerAxial+" has "+visibleCells+" cells"); } if (visibleCells==0) return null; // no grid cells in FOV return result; } public void debugCompareInterparameterDerivatives( double [] vector, int imgNum, double delta){ if (this.debugLevel>1) { System.out.println("debugCompareInterparameterDerivatives(vector, imgNum="+imgNum+", delta="+delta+")"); for (int ii=0;ii<vector.length;ii++) System.out.println(ii+": "+vector[ii]); } int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); if (imgNum<0){ // find first selected image boolean [] selectedImages=fittingStrategy.selectedImages(); imgNum=0; while ((imgNum<numImg) && (!selectedImages[imgNum])) imgNum++; } if (imgNum>=numImg){ IJ.showMessage("No images found for this fitting strategy"); return; // no images found } double [] imgVector=fittingStrategy.getImageParametersVector(imgNum, vector); //this.currentVector); boolean [] imgMask= new boolean[imgVector.length]; for (int i=0;i<imgMask.length;i++) imgMask[i]=true; this.lensDistortionParameters.lensCalcInterParamers( this.lensDistortionParameters, this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(imgNum), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(imgNum), this.interParameterDerivatives, // [22][] imgVector, imgMask); // calculate only selected derivatives (all parVect values are still // true); // calculate this.interParameterDerivatives -derivatives array (false - just this.values) // reorder derivatives to match lensDistortionParameters.getExtrinsicVector(); (dist,x0,y0,yaw,pitch,roll) // double [] parameterVector0=lensDistortionParameters.getAllVector(); double [] values=lensDistortionParameters.getExtrinsicVector(); double [][] derivatives_true = new double [this.lensDistortionParameters.getNumInputs()][6]; for (int i=0;i<this.lensDistortionParameters.getNumInputs();i++){ derivatives_true[i][0]=this.interParameterDerivatives[i][2]; // d distance /d vector[i] derivatives_true[i][1]=this.interParameterDerivatives[i][0]; // d x0 /d vector[i] derivatives_true[i][2]=this.interParameterDerivatives[i][1]; // d y0 /d vector[i] derivatives_true[i][3]=this.interParameterDerivatives[i][3]; // d jaw /d vector[i] derivatives_true[i][4]=this.interParameterDerivatives[i][4]; // d pitch /d vector[i] derivatives_true[i][5]=this.interParameterDerivatives[i][5]; // d roll /d vector[i] } double [][] derivatives_delta = new double [this.lensDistortionParameters.getNumInputs()][values.length]; for (int i=0;i<this.lensDistortionParameters.getNumInputs();i++){ double [] vector_delta=imgVector.clone(); vector_delta[i]+=delta; this.lensDistortionParameters.lensCalcInterParamers( this.lensDistortionParameters, this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(imgNum), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(imgNum), null, // this.interParameterDerivatives, // just values, no derivatives vector_delta, imgMask); // false); // just values, no derivatives double [] values_delta=lensDistortionParameters.getExtrinsicVector(); for (int j=0;j<derivatives_delta[i].length;j++) derivatives_delta[i][j]=(values_delta[j]-values[j])/delta; } String [] lensParNames = lensDistortionParameters.getExtrinsicNames(); String header="#\tphysical/lens\t "; for (int i=0;i<lensParNames.length;i++)header+="\t"+lensParNames[i]; StringBuffer sb = new StringBuffer(); for (int parNum=0;parNum<this.lensDistortionParameters.getNumInputs();parNum++){ sb.append(parNum+"\t"+fittingStrategy.distortionCalibrationData.descrField(parNum,0)+"\tderivative"); for (int i=0;i<lensParNames.length;i++) sb.append("\t"+derivatives_true[parNum][i]); sb.append("\n"); sb.append("\t \tdelta"); for (int i=0;i<lensParNames.length;i++) sb.append("\t"+derivatives_delta[parNum][i]); sb.append("\n"); sb.append("\t \tdifference"); for (int i=0;i<lensParNames.length;i++) sb.append("\t"+(derivatives_true[parNum][i]-derivatives_delta[parNum][i])); sb.append("\n"); sb.append("---\t---\t---"); for (int i=0;i<lensParNames.length;i++) sb.append("\t---"); sb.append("\n"); } new TextWindow("Comparisison of the interparameter dcerivatives (true and compared as deltas)", header, sb.toString(), 500,900); } // after stepping back - no need to rerun calculateFxAndJacobian(false), just keep /** * Calculates f(X) and optionally Jacobian for the current parameters * @parameter vector - parameter vector to be used * @parameter calcJacobian if true, calculates Jacobian as this.jacobian * @return f(X) - pixel coordinates for each (visible) grid pattern node for current parameters this.currentVector * as a 1-d array that alternates pixel-X and pixel-Y for all images * NOTE: this one is not thread safe */ public double [] calculateFxAndJacobian( double [] vector, boolean calcJacobian){ // when false, modifies only this.lensDistortionParameters.* if (vector==null) { calcJacobian=false; // vector = new double[0]; } // TODO: verify classes/arrays exist? int doubleNumAllPoints=this.Y.length; // all points in all images multiplied by 2 (x and y error are separate) int fittedParNumber=(vector==null)?0:vector.length; //this.currentVector.length; double [] vectorFX=new double[doubleNumAllPoints]; // this.fX=new double[doubleNumAllPoints]; if (this.debugLevel>2) { System.out.println("calculateFxAndJacobian(), calcJacobian="+calcJacobian+" D3304 + this.debugLevel="+this.debugLevel); if (vector!=null) { for (int ii=0;ii<vector.length;ii++) System.out.println(ii+": "+vector[ii]); } else { System.out.println("calculateFxAndJacobian() : vector==null"); } } if (calcJacobian) { this.jacobian=new double[fittedParNumber][doubleNumAllPoints]; for (int i=0;i<fittedParNumber;i++) for (int j=0;j<doubleNumAllPoints;j++) this.jacobian[i][j]=0.0; } int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); boolean [] selectedImages=fittingStrategy.selectedImages(); int index=0; IJ.showProgress(0); for (int imgNum=0;imgNum<numImg;imgNum++) if (selectedImages[imgNum]) { // initialize arrays for parameters and derivatives conversion double [] imgVector=fittingStrategy.getImageParametersVector(imgNum, vector); // null is OK now boolean [] imgMask=null; int [] imgMap= null; if (calcJacobian) { imgMask= fittingStrategy.getImageParametersVectorMask(imgNum); int [] imgRMap= fittingStrategy.getImageParametersVectorReverseMap(imgNum); imgMap=new int[vector.length]; for (int i=0;i<imgMap.length;i++) imgMap[i]=-1; for (int i=0;i<imgRMap.length;i++) if (imgRMap[i]>=0)imgMap[imgRMap[i]]=i; } // Calculate/set this.lensDistortionParameters class, so it will calculate values/derivatives correctly) // and this.interParameterDerivatives // if (this.debugLevel>1) { if (this.debugLevel>2) { System.out.println("calculateFxAndJacobian(), imgNum="+imgNum+" calcInterParamers(): (D3336)"); } this.lensDistortionParameters.debugLevel=this.debugLevel; this.lensDistortionParameters.lensCalcInterParamers( this.lensDistortionParameters, this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(imgNum), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(imgNum), calcJacobian?this.interParameterDerivatives:null, // [22][] imgVector, imgMask); // imgMask may be null if no derivativescalculate only selected derivatives (all parVect values are still int numPoints=fittingStrategy.distortionCalibrationData.getImageNumPoints(imgNum); if (this.debugLevel>2) { System.out.println("calculateFxAndJacobian(), numPoints="+numPoints+" (imgNum="+imgNum+")"); } // iterate through points, for each calculate pixelx, pixely and derivatives for (int pointNum=0;pointNum<numPoints;pointNum++){ int fullIndex=index+pointNum; if (fullIndex>=this.targetXYZ.length){ System.out.println("BUG: calculateFxAndJacobian() imgNum="+imgNum+" pointNum="+pointNum+" fullIndex="+fullIndex+" this.targetXYZ.length="+this.targetXYZ.length); } double [][]derivatives15= lensDistortionParameters.calcPartialDerivatives( // [NaN, NaN] this.targetXYZ[fullIndex][0], // target point horizontal, positive - right, mm this.targetXYZ[fullIndex][1], // target point vertical, positive - down, mm this.targetXYZ[fullIndex][2], // target point horizontal, positive - away from camera, mm calcJacobian); // calculate derivatives, false - values only if (this.debugLevel>3) { System.out.println(fullIndex+": calculateFxAndJacobian->calcPartialDerivatives("+IJ.d2s(targetXYZ[fullIndex][0],2)+","+ IJ.d2s(targetXYZ[fullIndex][1],2)+","+ IJ.d2s(targetXYZ[fullIndex][2],2)+" ("+calcJacobian+") -> "+ IJ.d2s(derivatives15[0][0],2)+"/"+IJ.d2s(derivatives15[0][1],2)); String all="derivatives15: D3365"; for (int ii=0;ii<derivatives15.length;ii++) all+=" "+ii+":"+IJ.d2s(derivatives15[ii][0],3)+"/"+IJ.d2s(derivatives15[ii][1],3); System.out.println(all); } vectorFX[2*fullIndex]= derivatives15[0][0]; vectorFX[2*fullIndex+1]=derivatives15[0][1]; if (calcJacobian) { double [][]derivatives = lensDistortionParameters.reorderPartialDerivatives(derivatives15); if (this.debugLevel>3) { String all="derivatives:"; for (int ii=0;ii<derivatives.length;ii++) all+=" "+ii+":"+IJ.d2s(derivatives[ii][0],3)+"/"+IJ.d2s(derivatives[ii][1],3); System.out.println(all); } for (int i=0;i<this.jacobian.length;i++) if (imgMap[i]>=0){ double sX=0,sY=0; for (int k=0;k<derivatives.length;k++){ sX+=this.interParameterDerivatives[imgMap[i]][k]*derivatives[k][0]; sY+=this.interParameterDerivatives[imgMap[i]][k]*derivatives[k][1]; } this.jacobian[i][2*fullIndex]= sX; this.jacobian[i][2*fullIndex+1]=sY; } } } index+=numPoints; IJ.showProgress(imgNum, numImg-1); } // IJ.showProgress(0); not needed, will turn off automatically return vectorFX; } /** * Calculate FX and (optionally) Jacobian for one image. FX is a single vector for all images, jacobian - only for one (to save on memory usage) * @param numImage number of image being processed * @param vector parameters vector * @param patternXYZ X,Y,Z of the physical target for each node of each image (TODO: memory may be reduced) * @param vectorFX Vector to be filled here , twice length as patternXYZ (x and y alternating) * @param imageStartIndex start index in patternXYZ array (length - difference to the next, includes extra last element) * @param lensDistortionParameters LensDistortionParameters class instance (may be reused between calls) * @param calcJacobian calculate Jacobian matrix (if false - only FX) * @return partial Jacobian matrix, number of rows= vector.length, number of columns - 2*indexCount * NOTE: this one is thread safe */ public double [][] calculatePartialFxAndJacobian( final int numImage, // number of grid image final double [] vector, // parameters vector final double [][] patternXYZ, // this.targetXYZ final double [] vectorFX, // non-overlapping segments will be filled final int [] imageStartIndex, // start index in patternXYZ array (length - difference to the next, includes extra last element) final LensDistortionParameters lensDistortionParameters, // initialize one per each thread? Or for each call? boolean calcJacobian){ // when false, modifies only this.lensDistortionParameters.* final int indexStart=imageStartIndex[numImage]; // start index in patternXYZ array final int indexCount=imageStartIndex[numImage+1]-imageStartIndex[numImage]; // number of nodes in the current grid image int fittedParNumber=vector.length; //this.currentVector.length; if (this.debugLevel>3) { System.out.println("calculatePartialFxAndJacobian(), calcJacobian="+calcJacobian+" indexStart="+indexStart+" indexCount="+indexCount); for (int ii=0;ii<vector.length;ii++) System.out.println("vector["+ii+"]: "+vector[ii]); } boolean [] imgMask= fittingStrategy.getImageParametersVectorMask(numImage); // thread safe int [] imgRMap= fittingStrategy.getImageParametersVectorReverseMap(numImage); // thread safe int [] imgMap=new int[vector.length]; for (int i=0;i<imgMap.length;i++) imgMap[i]=-1; for (int i=0;i<imgRMap.length;i++) if (imgRMap[i]>=0)imgMap[imgRMap[i]]=i; double [][] jacobian=null; if (calcJacobian) { // jacobian=new double[fittedParNumber][indexCount*2]; // for (int i=0;i<fittedParNumber;i++) for (int j=0;j<jacobian[0].length;j++) jacobian[i][j]=0.0; jacobian=new double[fittedParNumber][]; // TODO: verify that only small number of rows is calculated for (int i=0;i<fittedParNumber;i++) { if (imgMap[i]>=0) { jacobian[i]=new double [indexCount*2]; for (int j=0;j<jacobian[i].length;j++) jacobian[i][j]=0.0; } else { jacobian[i]=null; } } } double [][] interParameterDerivatives=new double [this.lensDistortionParameters.getNumInputs()][]; // initialize arrays for parameters and derivatives conversion double [] imgVector=fittingStrategy.getImageParametersVector(numImage, vector); // thread safe if (this.debugLevel>3) { String all="imgVector: "; for (int jj=0;jj<imgVector.length;jj++) all+=" "+imgVector[jj]; System.out.println(all); } // Calculate/set this.lensDistortionParameters class, so it will calculate values/derivatives correctly) // and this.interParameterDerivatives if (this.debugLevel>3) System.out.println("calculatePartialFxAndJacobian(), numImage="+numImage+" calcInterParamers():"); lensDistortionParameters.lensCalcInterParamers( lensDistortionParameters, this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(numImage), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(numImage), calcJacobian?interParameterDerivatives:null, // [22][] imgVector, imgMask); // calculate only selected derivatives (all parVect values are still // iterate through points, for each calculate pixelx, pixely and derivatives for (int pointNum=0;pointNum<indexCount;pointNum++){ int fullIndex=indexStart+pointNum; double [][]derivatives15= lensDistortionParameters.calcPartialDerivatives( patternXYZ[fullIndex][0], // target point horizontal, positive - right, mm patternXYZ[fullIndex][1], // target point vertical, positive - down, mm patternXYZ[fullIndex][2], // target point horizontal, positive - away from camera, mm calcJacobian); // calculate derivatives, false - values only if (this.debugLevel>3) { System.out.println(fullIndex+": calculateFxAndJacobian->calcPartialDerivatives("+IJ.d2s(patternXYZ[fullIndex][0],2)+","+ IJ.d2s(patternXYZ[fullIndex][1],2)+","+ IJ.d2s(patternXYZ[fullIndex][2],2)+" ("+calcJacobian+") -> "+ IJ.d2s(derivatives15[0][0],2)+"/"+IJ.d2s(derivatives15[0][1],2)); String all="derivatives15: D3476"; for (int ii=0;ii<derivatives15.length;ii++) all+=" "+ii+":"+IJ.d2s(derivatives15[ii][0],3)+"/"+IJ.d2s(derivatives15[ii][1],3); System.out.println(all); } vectorFX[2*fullIndex]= derivatives15[0][0]; vectorFX[2*fullIndex+1]=derivatives15[0][1]; if (calcJacobian) { double [][]derivatives = lensDistortionParameters.reorderPartialDerivatives(derivatives15); if (this.debugLevel>3) { String all="derivatives:"; for (int ii=0;ii<derivatives.length;ii++) all+=" "+ii+":"+IJ.d2s(derivatives[ii][0],3)+"/"+IJ.d2s(derivatives[ii][1],3); System.out.println(all); } for (int i=0;i<jacobian.length;i++) if (imgMap[i]>=0){ double sX=0,sY=0; for (int k=0;k<derivatives.length;k++){ sX+=interParameterDerivatives[imgMap[i]][k]*derivatives[k][0]; sY+=interParameterDerivatives[imgMap[i]][k]*derivatives[k][1]; } jacobian[i][2*pointNum]= sX; jacobian[i][2*pointNum+1]=sY; } } } return jacobian; } /** * * @param vector - parameter vector to be used * @param imgNumber - number of image to process or -1 - use the first of selected in this strategy * @return return Jacobian matrix for the selected image and individual parameters * NOTE: this one is not thread safe (used this.lensDistortionParameters) */ // used only to debug derivatives (delta==0 - real derivatives, delta>0 - difference) public double [][] calculateJacobian16( double [] vector, int imgNumber, double delta){ // these parameters can work for one image only int doubleNumAllPoints=this.Y.length; // all points in all images multiplied by 2 (x and y error are separate) double [][] jacobian16=new double[lensDistortionParameters.getNumOutputs()][doubleNumAllPoints]; double [] values= new double[doubleNumAllPoints]; for (int i=0;i<jacobian16.length;i++) for (int j=0;j<doubleNumAllPoints;j++) jacobian16[i][j]=0.0; int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); boolean [] selectedImages=fittingStrategy.selectedImages(); int index=0; for (int imgNum=0;imgNum<numImg;imgNum++) if (selectedImages[imgNum]) { int numPoints=fittingStrategy.distortionCalibrationData.getImageNumPoints(imgNum); if (imgNumber<0) imgNumber=imgNum; // -1 - use the first image in the list if (imgNum==imgNumber) { double [] imgVector=fittingStrategy.getImageParametersVector(imgNum, vector); //this.currentVector); boolean [] imgMask= fittingStrategy.getImageParametersVectorMask(imgNum); int [] imgRMap= fittingStrategy.getImageParametersVectorReverseMap(imgNum); int [] imgMap=new int[vector.length]; for (int i=0;i<imgMap.length;i++) imgMap[i]=-1; for (int i=0;i<imgRMap.length;i++) if (imgRMap[i]>=0)imgMap[imgRMap[i]]=i; // Calculate/set this.lensDistortionParameters class, so it will calculate values/derivatives correctly) // and this.interParameterDerivatives if (this.debugLevel>2) { System.out.println("calculateJacobian15(), imgNum="+imgNum+" calcInterParamers():"); } this.lensDistortionParameters.lensCalcInterParamers( this.lensDistortionParameters, this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(imgNum), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(imgNum), null, //this.interParameterDerivatives, // [22][] imgVector, imgMask); // calculate only selected derivatives (all parVect values are still // false); // probably can use false if (this.debugLevel>2) { System.out.println("calculateJacobian16(), numPoints="+numPoints+" (imgNum="+imgNum+")"); } if (delta<=0) { // iterate through points, for each calculate pixelx, pixely and derivatives for (int pointNum=0;pointNum<numPoints;pointNum++){ int fullIndex=index+pointNum; double [][]derivatives15= lensDistortionParameters.calcPartialDerivatives( targetXYZ[fullIndex][0], // target point horizontal, positive - right, mm targetXYZ[fullIndex][1], // target point vertical, positive - down, mm targetXYZ[fullIndex][2], // target point horizontal, positive - away from camera, mm true); // calculate derivatives, false - values only if (this.debugLevel>3) { System.out.println(fullIndex+": calculateFxAndJacobian->calcPartialDerivatives("+IJ.d2s(targetXYZ[fullIndex][0],2)+","+ IJ.d2s(targetXYZ[fullIndex][1],2)+","+ IJ.d2s(targetXYZ[fullIndex][2],2)+" -> "+ IJ.d2s(derivatives15[0][0],2)+"/"+IJ.d2s(derivatives15[0][1],2)); String all="derivatives15: D3563"; for (int ii=0;ii<derivatives15.length;ii++) all+=" "+ii+":"+IJ.d2s(derivatives15[ii][0],3)+"/"+IJ.d2s(derivatives15[ii][1],3); System.out.println(all); } double [][]derivatives = lensDistortionParameters.reorderPartialDerivativesAsNames(derivatives15); if (this.debugLevel>3) { String all="derivatives:"; for (int ii=0;ii<derivatives.length;ii++) all+=" "+ii+":"+IJ.d2s(derivatives[ii][0],3)+"/"+IJ.d2s(derivatives[ii][1],3); System.out.println(all); } for (int i=0;i<derivatives.length;i++){ jacobian16[i][2*fullIndex]= derivatives[i][0]; // oob 16 jacobian16[i][2*fullIndex+1]=derivatives[i][1]; } } } else { double [] parameterVector0=lensDistortionParameters.getAllVector(); for (int pointNum=0;pointNum<numPoints;pointNum++){ int fullIndex=index+pointNum; double [][]values2= lensDistortionParameters.calcPartialDerivatives( targetXYZ[fullIndex][0], // target point horizontal, positive - right, mm targetXYZ[fullIndex][1], // target point vertical, positive - down, mm targetXYZ[fullIndex][2], // target point horizontal, positive - away from camera, mm false); // calculate derivatives, false - values only values[2*fullIndex]= values2[0][0]; values[2*fullIndex+1]=values2[0][1]; } for (int nPar=0;nPar<jacobian16.length;nPar++){ double [] parameterVector=parameterVector0.clone(); parameterVector[nPar]+=delta; lensDistortionParameters.setAllVector(parameterVector); for (int pointNum=0;pointNum<numPoints;pointNum++){ int fullIndex=index+pointNum; double [][] values2=lensDistortionParameters.calcPartialDerivatives( targetXYZ[fullIndex][0], // target point horizontal, positive - right, mm targetXYZ[fullIndex][1], // target point vertical, positive - down, mm targetXYZ[fullIndex][2], // target point horizontal, positive - away from camera, mm false); // calculate derivatives, false - values only jacobian16[nPar][2*fullIndex]= (values2[0][0]- values[2*fullIndex])/delta; jacobian16[nPar][2*fullIndex+1]=(values2[0][1]- values[2*fullIndex+1])/delta; } } } return jacobian16; } index+=numPoints; } return null; // should normally return from inside the for loop } /* List calibration */ public boolean listImageParameters(boolean silent){ if (this.fittingStrategy==null) { String msg="Fitting strategy does not exist, exiting"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } int numSeries=fittingStrategy.getNumSeries(); if (silent) { this.seriesNumber=0; } else { GenericDialog gd = new GenericDialog("Settings for the parameter list"); gd.addNumericField("Iteration number to start (0.."+(numSeries-1)+")", this.seriesNumber, 0); gd.addCheckbox("Show image number (from 0)", this.showIndex); gd.addCheckbox("Show per-image RMS", this.showRMS); gd.addCheckbox("Show number of grid points", this.showPoints); gd.addCheckbox("Show lens coordinates (relative to target)", this.showLensLocation); gd.addCheckbox("Show physical camera parameters", this.showEyesisParameters); gd.addCheckbox("Show intrinsic lens/sensor parameters ", this.showIntrinsicParameters); gd.addCheckbox("Show extrinsic lens/sensor parameters", this.showExtrinsicParameters); gd.addNumericField("Extra decimal places (precision) in the list", this.extraDecimals, 0); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber= (int) gd.getNextNumber(); this.showIndex= gd.getNextBoolean(); this.showRMS= gd.getNextBoolean(); this.showPoints= gd.getNextBoolean(); this.showLensLocation= gd.getNextBoolean(); this.showEyesisParameters= gd.getNextBoolean(); this.showIntrinsicParameters= gd.getNextBoolean(); this.showExtrinsicParameters= gd.getNextBoolean(); this.extraDecimals= (int) gd.getNextNumber(); } // need to select strategy initFittingSeries(true,this.filterForAll,this.seriesNumber); // will set this.currentVector this.currentfX=calculateFxAndJacobian(this.currentVector, false); // is it always true here (this.jacobian==null) double [] errors=calcErrors(calcYminusFx(this.currentfX)); double rms= calcError (calcYminusFx(this.currentfX)); int [] numPairs=calcNumPairs(); boolean [] selectedImages=fittingStrategy.selectedImages(); //TODO: add display of per-image RMS listImageParameters ( selectedImages, rms, errors, numPairs, this.showIndex, true, // grid match this.showRMS, this.showPoints, this.showLensLocation, this.showEyesisParameters, this.showIntrinsicParameters, this.showExtrinsicParameters, this.extraDecimals); return true; } public void markBadNodces(int seriesNumber, int debugLevel){ int oldSeries=this.seriesNumber; this.seriesNumber=seriesNumber; int totalBadNodes=markBadNodes( fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMS, fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMSNonweighted, false, debugLevel ); if (debugLevel>0) { System.out.println("Marked "+totalBadNodes+" nodes as bad (excessive errors, used fitting series #"+this.seriesNumber+")"); } this.seriesNumber=oldSeries; } public boolean dialogMarkBadNodes(int debugLevel){ int numSeries=fittingStrategy.getNumSeries(); boolean verbose=false; GenericDialog gd = new GenericDialog("Select parameters for marking bad nodes"); gd.addNumericField("Series number to use for selection (0.."+(numSeries-1)+")", this.seriesNumber, 0); gd.addNumericField("Remove nodes with error greater than scaled RMS in that image, weighted", fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMS, 2,6,"xRMS"); gd.addNumericField("Same, not weghted (not more permissive near the borders with low weight)", fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMSNonweighted, 2,6,"xRMS"); gd.addCheckbox ("Verbose (report number of bad nodes per image)",verbose); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber= (int) gd.getNextNumber(); fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMS= gd.getNextNumber(); fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMSNonweighted= gd.getNextNumber(); verbose= gd.getNextBoolean(); int totalBadNodes=markBadNodes( fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMS, fittingStrategy.distortionCalibrationData.eyesisCameraParameters.removeOverRMSNonweighted, verbose, debugLevel ); if (debugLevel>0) { System.out.println("Marked "+totalBadNodes+" nodes as bad (excessive errors"); } return true; } public boolean removeOutLiers( int series, int numOutLiers, boolean [] selectedChannels){ int numSeries=fittingStrategy.getNumSeries(); boolean removeEmpty=false; boolean recalculate=false; boolean applyChannelFilter=false; int filter=filterForAll; if ((series<0) || (numOutLiers<0)) { GenericDialog gd = new GenericDialog("Select series to process"); gd.addNumericField("Iteration number to start (0.."+(numSeries-1)+")", this.seriesNumber, 0); if (selectedChannels != null) { String s=""; for (boolean b:selectedChannels)s+=b?"+":"-"; gd.addCheckbox("Filter by channel selection ("+s+")", applyChannelFilter); } gd.addCheckbox("Recalculate parameters vector from selected strategy",recalculate); gd.addNumericField("Number of outliers to show", 10, 0); gd.addCheckbox("Remove empty (rms==NaN) images", removeEmpty); gd.addCheckbox("Ask filter (current filter="+filter+")", this.askFilter); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber= (int) gd.getNextNumber(); if (selectedChannels != null) applyChannelFilter= gd.getNextBoolean(); recalculate= gd.getNextBoolean(); numOutLiers= (int) gd.getNextNumber(); removeEmpty= gd.getNextBoolean(); this.askFilter= gd.getNextBoolean(); if (this.askFilter) filter= selectFilter(filter); filter=0; } else { this.seriesNumber=series; } if (!applyChannelFilter) selectedChannels=null; // initFittingSeries(!recalculate,this.filterForAll,this.seriesNumber); // will set this.currentVector initFittingSeries(!recalculate,this.filterForAll,this.seriesNumber); // will set this.currentVector this.currentfX=calculateFxAndJacobian(this.currentVector, false); // is it always true here (this.jacobian==null) double [] errors=calcErrors(calcYminusFx(this.currentfX)); // seem to have errors? - now may return NaN! double rms= calcError (calcYminusFx(this.currentfX)); boolean [] selectedImages=fittingStrategy.selectedImages(); if (selectedChannels!=null){ selectedImages=selectedImages.clone(); // disconnect from original for modification for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]){ int chn=this.fittingStrategy.distortionCalibrationData.gIP[i].channel; if ((chn<0) || (chn>=selectedChannels.length) || !selectedChannels[chn]){ selectedImages[i]=false; } } } int numSelectedNotNaNImages=0; int numNaN=0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]) { if (!Double.isNaN(errors[i])) numSelectedNotNaNImages++; else numNaN++; } int [] imgIndices=new int[numSelectedNotNaNImages]; int index=0; for (int i=0;i<selectedImages.length;i++) if ( selectedImages[i] && !Double.isNaN(errors[i])) imgIndices[index++]=i; // OOB 2389 if (numOutLiers>numSelectedNotNaNImages) numOutLiers=numSelectedNotNaNImages; int [] indices=new int [numOutLiers]; boolean [] availableImages=selectedImages.clone(); for (int i=0;i<selectedImages.length;i++) if (selectedImages[i] && Double.isNaN(errors[i])) availableImages[i]=false; if ((this.debugLevel>0) && (numNaN>0)){ System.out.println("removeOutLiers(): Number of empty (rms=NaN) images="+numNaN+":"); int n=0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i] && Double.isNaN(errors[i])){ n++; System.out.println(n+": "+i+": "+this.fittingStrategy.distortionCalibrationData.gIP[i].path); } } if (removeEmpty){ int n=0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i] && Double.isNaN(errors[i])){ n++; if (this.debugLevel>0) System.out.println(n+"removing empty image #"+i+": "+this.fittingStrategy.distortionCalibrationData.gIP[i].path); this.fittingStrategy.distortionCalibrationData.gIP[i].enabled=false; this.fittingStrategy.distortionCalibrationData.gIP[i].hintedMatch=-1; // so can be re-calibrated again w/o others } } System.out.println("removeOutLiers(): availableImages.length="+availableImages.length+" numSelectedNotNaNImages="+numSelectedNotNaNImages); for (int n=0;n<numOutLiers;n++){ double maxRMS=-1.0; indices[n]=-1; for (int i=0;i<availableImages.length;i++)if (availableImages[i] && (Double.isNaN(errors[i]) || (errors[i]>maxRMS))){ // Double.NaN will be greater maxRMS=errors[i]; indices[n]=i; } if (indices[n]<0){ System.out.println("removeOutLiers(): indices["+n+"]="+indices[n]); continue; } availableImages[indices[n]]=false; // java.lang.ArrayIndexOutOfBoundsException: -1 } GenericDialog gd = new GenericDialog("Select images to remove (RMS="+IJ.d2s(rms,3)+")"); if (this.debugLevel>0) System.out.println("Listing "+numOutLiers+" worst images:"); for (int n=0;n<indices.length;n++){ String msg=n+" ("+indices[n]+" / "+ this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].getSetNumber()+"): "+ IJ.d2s(errors[indices[n]],3)+" "+ this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].path+ " ("+this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].pixelsXY.length+ " points) "+selectedImages[indices[n]]; if (this.debugLevel>0) System.out.println( msg); gd.addCheckbox(msg, true); } WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return false; if (this.debugLevel>0) System.out.println("Removing outliers:"); for (int n=0;n<indices.length;n++){ if (gd.getNextBoolean()) { if (this.debugLevel>0) System.out.println(n+" :"+IJ.d2s(errors[indices[n]],3)+" "+this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].path); this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].enabled=false; this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].hintedMatch=-1; // so can be re-calibrated again w/o others } } fittingStrategy.distortionCalibrationData.updateSetOrientation(null); // remove orientation information from the image set if none is enabled return true; } public boolean removeOutLierSets(int numOutLiers){ boolean removeEmptySets=true; // false; if (numOutLiers<0) { GenericDialog gd = new GenericDialog("Select sets to process"); gd.addNumericField("Series number (<0 - all images)", -1, 0); gd.addNumericField("Number of outliers to show", 5, 0); gd.addCheckbox("Remove empty sets", removeEmptySets); gd.addCheckbox("Ask for weight function filter", this.askFilter); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber= (int) gd.getNextNumber(); numOutLiers= (int) gd.getNextNumber(); removeEmptySets= gd.getNextBoolean(); this.askFilter= gd.getNextBoolean(); } // boolean [] oldSelection=this.fittingStrategy.selectAllImages(0); // enable all images in series 0 int filter=this.filterForAll; if (this.askFilter) filter=selectFilter(filter); initFittingSeries(true,filter,this.seriesNumber); // will set this.currentVector // initFittingSeries(true,this.filterForAll,this.seriesNumber); // will set this.currentVector this.currentfX=calculateFxAndJacobian(this.currentVector, false); // is it always true here (this.jacobian==null) double [] errors=calcErrors(calcYminusFx(this.currentfX)); double rms= calcError (calcYminusFx(this.currentfX)); // boolean [] selectedImages=fittingStrategy.selectedImages(); int [] numPairs=calcNumPairs(); int [][] imageSets=this.fittingStrategy.distortionCalibrationData.listImages(false); // true - only enabled images int [] numSetPoints=new int [imageSets.length]; double [] rmsPerSet=new double[imageSets.length]; boolean [] hasNaNInSet=new boolean[imageSets.length]; boolean [] allNaNInSet=new boolean[imageSets.length]; for (int setNum=0;setNum<imageSets.length;setNum++){ double error2=0.0; int numInSet=0; hasNaNInSet[setNum]=false; for (int imgInSet=0;imgInSet<imageSets[setNum].length;imgInSet++){ int imgNum=imageSets[setNum][imgInSet]; int num=numPairs[imgNum]; if (Double.isNaN(errors[imgNum])){ hasNaNInSet[setNum]=true; } else { error2+=errors[imgNum]*errors[imgNum]*num; numInSet+=num; } } allNaNInSet[setNum]=(numInSet==0); numSetPoints[setNum]=numInSet; rmsPerSet[setNum]=(numInSet>0)?Math.sqrt(error2/numInSet):Double.NaN; } // int numSelectedNotNaNSets=0; int numSelectedSets=0; int numNaN=0; for (int i=0;i<imageSets.length;i++) { // if (!Double.isNaN(rmsPerSet[i])) numSelectedSets++; if (!allNaNInSet[i]) numSelectedSets++; else numNaN++; } // int [] imgIndices=new int[numSelectedNotNaNSets]; // int index=0; // for (int i=0;i<imageSets.length;i++) if ( selectedImages[i]) imgIndices[index++]=i; if (numOutLiers>numSelectedSets) numOutLiers=numSelectedSets; int [] indices=new int [numOutLiers]; boolean [] availableSets= new boolean [imageSets.length]; for (int i=0;i<imageSets.length;i++) availableSets[i]= !allNaNInSet[i]; //!Double.isNaN(rmsPerSet[i]); if (removeEmptySets && (numNaN>0)){ //(this.debugLevel>0) if (this.debugLevel>0) System.out.println("removeOutLierSets(): Number of empty (rms=NaN) sets="+numNaN+":"); // int n=0; for (int setNum=0;setNum<imageSets.length;setNum++) if (!availableSets[setNum]){ // n++; if (this.debugLevel>0) System.out.println("Set "+setNum); for (int imgInSet=0;imgInSet<imageSets[setNum].length;imgInSet++){ int numImg=imageSets[setNum][imgInSet]; if (this.debugLevel>0) System.out.println(setNum+":"+imgInSet+" #"+ numImg+" "+IJ.d2s(errors[numImg],3)+" "+ this.fittingStrategy.distortionCalibrationData.gIP[numImg].path); this.fittingStrategy.distortionCalibrationData.gIP[numImg].enabled=false; this.fittingStrategy.distortionCalibrationData.gIP[numImg].hintedMatch=-1; // so can be re-calibrated again w/o others } } } System.out.println("removeOutLierSets(): availableSets.length="+availableSets.length+" numSelectedSets="+numSelectedSets); for (int n=0;n<numOutLiers;n++){ double maxRMS=-1.0; indices[n]=-1; for (int i=0;i<availableSets.length;i++)if (availableSets[i] && (rmsPerSet[i]>maxRMS)){ // NaN are already skipped maxRMS=rmsPerSet[i]; indices[n]=i; } if (indices[n]<0){ System.out.println("removeOutLierSets(): indices["+n+"]="+indices[n]); continue; } availableSets[indices[n]]=false; // java.lang.ArrayIndexOutOfBoundsException: -1 } GenericDialog gd = new GenericDialog("Select image Sets to remove (RMS="+IJ.d2s(rms,3)+")"); if (this.debugLevel>0) System.out.println("Listing "+numOutLiers+" worst image sets"); for (int n=0;n<indices.length;n++){ int numSet=indices[n]; double setWeight=this.fittingStrategy.distortionCalibrationData.gIS[numSet].setWeight; if (this.debugLevel>0) System.out.println(n+" ("+numSet+"): "+(hasNaNInSet[numSet]?"* ":"")+IJ.d2s(rmsPerSet[numSet],3)+ " points: "+numSetPoints[numSet]+" weight:"+setWeight); gd.addCheckbox(n+": "+numSet+": "+(hasNaNInSet[numSet]?"* ":"")+IJ.d2s(rmsPerSet[numSet],3)+" weight:"+setWeight, true); for (int i=0;i<imageSets[numSet].length;i++){ int numImg=imageSets[numSet][i]; double diameter=this.fittingStrategy.distortionCalibrationData.gIP[numImg].getGridDiameter(); gd.addMessage(i+":"+numImg+": "+IJ.d2s(errors[numImg],3)+" "+ " ("+this.fittingStrategy.distortionCalibrationData.gIP[numImg].pixelsXY.length+" points, diameter="+diameter+") "+ this.fittingStrategy.distortionCalibrationData.gIP[numImg].path); if (this.debugLevel>0) System.out.println(" --- "+numImg+": "+IJ.d2s(errors[numImg],3)+" "+ " ("+this.fittingStrategy.distortionCalibrationData.gIP[numImg].pixelsXY.length+" points, diameter="+diameter+") "+ this.fittingStrategy.distortionCalibrationData.gIP[numImg].path); } } WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()){ // this.fittingStrategy.setImageSelection(0, oldSelection); // restore original selection in series 0 return false; } if (this.debugLevel>0) System.out.println("Removing outliers:"); for (int n=0;n<indices.length;n++){ if (gd.getNextBoolean()) { int numSet=indices[n]; if (this.debugLevel>0) System.out.println(" Removing imgages in image set "+numSet); for (int i=0;i<imageSets[numSet].length;i++){ int numImg=imageSets[numSet][i]; if (this.debugLevel>0) System.out.println(n+":"+i+"("+numImg+")"+IJ.d2s(errors[numImg],3)+" "+ this.fittingStrategy.distortionCalibrationData.gIP[numImg].path); this.fittingStrategy.distortionCalibrationData.gIP[numImg].enabled=false; this.fittingStrategy.distortionCalibrationData.gIP[numImg].hintedMatch=-1; // so can be re-calibrated again w/o others } } } // next is not needed fittingStrategy.distortionCalibrationData.updateSetOrientation(null); // remove orientation information from the image set if none is enabled // this.fittingStrategy.setImageSelection(0, oldSelection); // restore original selection in series 0 return true; } public boolean removeOutLiersJunk(int series, int numOutLiers){ int numSeries=fittingStrategy.getNumSeries(); if ((series<0) || (numOutLiers<0)) { GenericDialog gd = new GenericDialog("Select series to process"); gd.addNumericField("Iteration number to start (0.."+(numSeries-1)+")", this.seriesNumber, 0); gd.addNumericField("Number of outliers to show", 10, 0); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber= (int) gd.getNextNumber(); numOutLiers= (int) gd.getNextNumber(); } else { this.seriesNumber=series; } initFittingSeries(true,this.filterForAll,this.seriesNumber); // will set this.currentVector this.currentfX=calculateFxAndJacobian(this.currentVector, false); // is it always true here (this.jacobian==null) double [] errors=calcErrors(calcYminusFx(this.currentfX)); double rms= calcError (calcYminusFx(this.currentfX)); boolean [] selectedImages=fittingStrategy.selectedImages(); int numSelectedImages=0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]) numSelectedImages++; int [] imgIndices=new int[numSelectedImages]; int index=0; for (int i=0;i<selectedImages.length;i++) if ( selectedImages[i]) imgIndices[index++]=i; if (numOutLiers>numSelectedImages) numOutLiers=numSelectedImages; int [] indices=new int [numOutLiers]; int [] indicesSelected=new int [numOutLiers]; boolean [] availableImages=new boolean[numSelectedImages]; for (int i=0;i<availableImages.length;i++)availableImages[i]=true; for (int n=0;n<numOutLiers;n++){ double maxRMS=0; indices[n]=-1; indicesSelected[n]=-1; int imgIndex=0; for (int i=0;i<selectedImages.length;i++)if (selectedImages[i]){ if (availableImages[imgIndex] && (errors[imgIndex]>maxRMS)){ maxRMS=errors[imgIndex]; indicesSelected[n]=imgIndex; indices[n]=i; } imgIndex++; } availableImages[indicesSelected[n]]=false; } GenericDialog gd = new GenericDialog("Select images to remove (RMS="+IJ.d2s(rms,3)+")"); for (int n=0;n<indices.length;n++){ gd.addCheckbox(indices[n]+": "+IJ.d2s(errors[indicesSelected[n]],3)+" "+this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].path, true); } WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return false; for (int n=0;n<indices.length;n++){ if (gd.getNextBoolean()) this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].enabled=false; } return true; } /** * Opens a text window with the parameter table * @param imageSelection which images include in the output * @param showEyesisParameters show physical location/attitude based on Eyesis * @param showIntrinsicParameters show lens distortion/alignment parameters) * @param showExtrinsicParameters show position/attitude of the individual cameras * @param extraDecimals add this many decimals to data */ public void listImageParameters (boolean [] imageSelection, double rms, double [] errors, int [] numPairs, boolean showIndex, boolean showGridMatch, boolean showErrors, boolean showPoints, boolean showLensCoordinates, boolean showEyesisParameters, boolean showIntrinsicParameters, boolean showExtrinsicParameters, int extraDecimals){ int numImages=0; for (int i=0;i<fittingStrategy.distortionCalibrationData.getNumImages();i++) { if((imageSelection==null) || ((i<imageSelection.length) && imageSelection[i])) numImages++; } double [][] intrinsic=new double [numImages][]; double [][] extrinsic=new double [numImages][]; double [][] lensCoordinates=new double [numImages][]; int [] imgIndices=new int[numImages]; int index=0; for (int i=0;i<fittingStrategy.distortionCalibrationData.getNumImages();i++) { if((imageSelection==null) || ((i<imageSelection.length) && imageSelection[i])) imgIndices[index++]=i; } for (int imgIndex=0;imgIndex<numImages;imgIndex++) { int imgNum=imgIndices[imgIndex]; // image number if (this.debugLevel>2) { System.out.println("listImageParameters(), imgNum="+imgNum+" calcInterParamers():"); } this.lensDistortionParameters.lensCalcInterParamers( this.lensDistortionParameters, this.fittingStrategy.distortionCalibrationData.isTripod(), this.fittingStrategy.distortionCalibrationData.isCartesian(), this.fittingStrategy.distortionCalibrationData.getPixelSize(imgNum), this.fittingStrategy.distortionCalibrationData.getDistortionRadius(imgNum), null, //this.interParameterDerivatives, // [22][] // fittingStrategy.distortionCalibrationData.pars[imgNum], // 22-long parameter vector for the image fittingStrategy.distortionCalibrationData.getParameters(imgNum), // 22-long parameter vector for the image null); // if no derivatives, null is OK // false); // calculate this.interParameterDerivatives -derivatives array (false - just this.values) intrinsic[imgIndex]= lensDistortionParameters.getIntrinsicVector().clone(); extrinsic[imgIndex]= lensDistortionParameters.getExtrinsicVector().clone(); lensCoordinates[imgIndex]=lensDistortionParameters.getLensCenterCoordinates(); } String header="Name\tUnits"; for (int imgIndex=0;imgIndex<numImages;imgIndex++) header+="\t"+IJ.d2s(fittingStrategy.distortionCalibrationData.getImageTimestamp(imgIndices[imgIndex]),6); StringBuffer sb = new StringBuffer(); if (showIndex) { sb.append("index \t"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ sb.append("\t"+imgIndices[imgIndex]); } sb.append("\n"); } if (showGridMatch){ sb.append("Grid Match"+"\tX/Y:ROT"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ int imgNum=imgIndices[imgIndex]; // image number int [] shiftRot=fittingStrategy.distortionCalibrationData.getUVShiftRot(imgNum); sb.append("\t"+shiftRot[0]+"/"+shiftRot[1]+":"+shiftRot[2]); } sb.append("\n"); sb.append("Lasers(matched)"+"\t"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ int imgNum=imgIndices[imgIndex]; // image number int numPointers=0; // count number of laser pointers DistortionCalibrationData.GridImageParameters gip=fittingStrategy.distortionCalibrationData.getGridImageParameters(imgNum); if (gip.laserPixelCoordinates!=null){ for (int j=0;j<gip.laserPixelCoordinates.length;j++) if (gip.laserPixelCoordinates[j]!=null) numPointers++; } sb.append("\t"); if (!gip.enabled) sb.append("("); sb.append(numPointers+"("+gip.matchedPointers+"):"+gip.hintedMatch + " "+IJ.d2s(gip.getGridPeriod(),1)); if (!gip.enabled) sb.append(")"); } sb.append("\n"); } if (showErrors) { sb.append("--- RMS "+IJ.d2s(rms,3+extraDecimals)+"\tpix"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ int imgNum=imgIndices[imgIndex]; // image number sb.append("\t"+IJ.d2s(errors[imgNum],3+extraDecimals)); } sb.append("\n"); } if (showPoints) { int totalPoints=0; for (int imgIndex=0;imgIndex<numImages;imgIndex++){ totalPoints+=numPairs[imgIndices[imgIndex]]; } sb.append(" points "+totalPoints+"\t"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ sb.append("\t"+numPairs[imgIndices[imgIndex]]); } sb.append("\n"); sb.append(" Diameter\trel"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ int imgNum=imgIndices[imgIndex]; // image number sb.append("\t"+IJ.d2s(this.fittingStrategy.distortionCalibrationData.gIP[imgNum].getGridDiameter(),2)); } sb.append("\n"); } if (showEyesisParameters) { // getImageSubcamera sb.append("Sub-camera\t"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ int imgNum=imgIndices[imgIndex]; // image number sb.append("\t"+fittingStrategy.distortionCalibrationData.getImageSubcamera(imgNum)); } sb.append("\n"); for (int parNumber=0;parNumber<fittingStrategy.distortionCalibrationData.getNumDescriptions();parNumber++){ sb.append( fittingStrategy.distortionCalibrationData.descrField(parNumber,0)+"\t"+ fittingStrategy.distortionCalibrationData.descrField(parNumber,2)); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ int imgNum=imgIndices[imgIndex]; // image number // sb.append("\t"+IJ.d2s(fittingStrategy.distortionCalibrationData.pars[imgNum][parNumber],3+extraDecimals)); // TODO: make an array of decimals per parameter sb.append("\t"+IJ.d2s(fittingStrategy.distortionCalibrationData.getParameterValue(imgNum,parNumber),3+extraDecimals)); // TODO: make an array of decimals per parameter } sb.append("\n"); } } if (showIntrinsicParameters) { sb.append("--- Intrinsic\t"); for (int imgIndex=0;imgIndex<numImages;imgIndex++) sb.append("\t---"); sb.append("\n"); for (int parNumber=0;parNumber<lensDistortionParameters.getIntrinsicNames().length;parNumber++){ sb.append( lensDistortionParameters.getIntrinsicNames()[parNumber]+"\t"+ lensDistortionParameters.getIntrinsicUnits()[parNumber]); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ sb.append("\t"+IJ.d2s(intrinsic[imgIndex][parNumber],3+extraDecimals)); // TODO: make an array of decimals per parameter } sb.append("\n"); } } if (showExtrinsicParameters || showLensCoordinates) { sb.append("--- Extrinsic\t"); for (int imgIndex=0;imgIndex<numImages;imgIndex++) sb.append("\t---"); sb.append("\n"); if (showLensCoordinates){ sb.append("Lens X(right)\tmm"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ // int imgNum=imgIndices[imgIndex]; // image number sb.append("\t"+IJ.d2s(lensCoordinates[imgIndex][0],3+extraDecimals)); } sb.append("\n"); sb.append("Lens Y(down)\tmm"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ // int imgNum=imgIndices[imgIndex]; // image number sb.append("\t"+IJ.d2s(lensCoordinates[imgIndex][1],3+extraDecimals)); } sb.append("\n"); sb.append("Lens Z(into)\tmm"); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ // int imgNum=imgIndices[imgIndex]; // image number sb.append("\t"+IJ.d2s(lensCoordinates[imgIndex][2],3+extraDecimals)); } sb.append("\n"); } if (showExtrinsicParameters){ for (int parNumber=0;parNumber<lensDistortionParameters.getExtrinsicNames().length;parNumber++){ sb.append( lensDistortionParameters.getExtrinsicNames()[parNumber]+"\t"+ lensDistortionParameters.getExtrinsicUnits()[parNumber]); for (int imgIndex=0;imgIndex<numImages;imgIndex++){ sb.append("\t"+IJ.d2s(extrinsic[imgIndex][parNumber],3+extraDecimals)); // TODO: make an array of decimals per parameter } sb.append("\n"); } } } new TextWindow("Camera/lens parameters", header, sb.toString(), 500,900); } /** * Calculate differences vector * @param fX vector of calculated pixelX,pixelY on the sensors * @return same dimension vector of differences from this.Y (measured grid pixelxX, pixelY) */ public double [] calcYminusFx(double [] fX){ double [] result=this.Y.clone(); for (int i=0;i<result.length;i++) result[i]-=fX[i]; return result; } /** * Calcualte partial differences vector * @param fX vector of reprojected pixelX,pixelY on the sensors (number of elements - double number of points * @param startIndex start index to extract (even number, twice point index) * @param endIndex end index (1 greater than the last to extract) * @return partial differences (measured/corrected -reprojected), twice number of points long */ public double [] calcYminusFx(double [] fX, int startIndex, int endIndex){ double [] result=new double [endIndex-startIndex]; for (int i=0;i<result.length;i++) { int index=startIndex+i; result[i]=this.Y[index]-fX[index]; } return result; } /** * Calculate the RMS from the differences vector * @param diff - differences vector * @return RMS for the mean error (in sensor pixels) */ public double calcError(double [] diff){ double result=0; if (this.weightFunction!=null) { for (int i=0;i<diff.length;i++) result+=diff[i]*diff[i]*this.weightFunction[i]; result/=this.sumWeights; } else { for (int i=0;i<diff.length;i++) result+=diff[i]*diff[i]; result/=diff.length; } return Math.sqrt(result)*this.RMSscale; } public double calcErrorDiffY(double [] fX){ double result=0; if (this.weightFunction!=null) { for (int i=0;i<fX.length;i++){ double diff=this.Y[i]-fX[i]; result+=diff*diff*this.weightFunction[i]; } result/=this.sumWeights; } else { for (int i=0;i<fX.length;i++){ double diff=this.Y[i]-fX[i]; result+=diff*diff; } result/=fX.length; } return Math.sqrt(result)*this.RMSscale; } public double calcErrorDiffY( double [] fX, double [] extraWeightedErrors, double [] extraWeights){ double result=0; double effectiveWeight; if (this.weightFunction!=null) { effectiveWeight=this.sumWeights; for (int i=0;i<fX.length;i++){ double diff=this.Y[i]-fX[i]; result+=diff*diff*this.weightFunction[i]; } } else { effectiveWeight=fX.length; for (int i=0;i<fX.length;i++){ double diff=this.Y[i]-fX[i]; result+=diff*diff; } } if ((extraWeightedErrors!=null) && (extraWeights!=null)) { for (int i=0;i<extraWeightedErrors.length;i++){ result+=extraWeightedErrors[i]; effectiveWeight+=extraWeights[i]; } } result/=effectiveWeight; return Math.sqrt(result)*this.RMSscale; } public void resetBadNodes(){ for (int imgNum=0;imgNum<fittingStrategy.distortionCalibrationData.gIP.length;imgNum++) if (fittingStrategy.distortionCalibrationData.gIP[imgNum]!=null){ fittingStrategy.distortionCalibrationData.gIP[imgNum].resetBadNodes(); } } public int markBadNodes( // int numSeries, double removeOverRMS, double removeOverRMSNonweighted, boolean verbose, int debugLevel){ int debugThreshold=2; // this.seriesNumber=series; resetBadNodes(); // before calculating weight function initFittingSeries(false,this.filterForAll,this.seriesNumber); // recalculate this.currentfX=calculateFxAndJacobian(this.currentVector, false); // is it always true here (this.jacobian==null) int totalBadNodes=0; boolean [] selectedImages=fittingStrategy.selectedImages(); double [] diff=calcYminusFx(this.currentfX); int index=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) { double e2 =0.0; // errors[imgNum]=0.0; if (selectedImages[imgNum]) { int numThisRemoved=0; int len=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length*2; double w=0; int nw=0; if (this.weightFunction!=null) { for (int i=index;i<index+len;i++) { e2+=diff[i]*diff[i]*this.weightFunction[i]; w+=this.weightFunction[i]; if (this.weightFunction[i]>0.0) nw++; } } else { for (int i=index;i<index+len;i++) { e2+=diff[i]*diff[i]; w+=1.0; nw++; } } if (w>0.0) { // e2/=w; double threshold2Weighted= 2.0*removeOverRMS*removeOverRMS*e2/nw; // 2.0 because x^2+y^2 double threshold2NonWeighted=2.0*removeOverRMSNonweighted*removeOverRMSNonweighted*e2/w; // 2.0 because x^2+y^2 if (debugLevel>debugThreshold){ boolean someRemoved=false; for (int i=index;i<index+len;i+=2) { e2=(diff[i]*diff[i]+ diff[i+1]*diff[i+1]); if ((e2>threshold2NonWeighted) || ((this.weightFunction!=null) && ((e2*this.weightFunction[i]) > threshold2Weighted )) ) { double ww=(this.weightFunction==null)?1.0:(this.weightFunction[i]); if (ww>0.0) someRemoved=true; } } if (someRemoved || (debugLevel>2)) System.out.println("imgNum="+imgNum+" len="+len+" e2/w="+(e2/w)+" w="+w+" e2/nw="+(e2/nw)+ " threshold2Weighted="+threshold2Weighted+" threshold2NonWeighted="+threshold2NonWeighted); } for (int i=index;i<index+len;i+=2) { e2=(diff[i]*diff[i]+ diff[i+1]*diff[i+1]); if ((e2>threshold2NonWeighted) || ((this.weightFunction!=null) && ((e2*this.weightFunction[i]) > threshold2Weighted )) ) { double ww=(this.weightFunction==null)?1.0:(this.weightFunction[i]); int pointIndex=(i-index)/2; if (ww>0.0) { fittingStrategy.distortionCalibrationData.gIP[imgNum].setBadNode(pointIndex); numThisRemoved++; if (debugLevel>debugThreshold){ int iu=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[pointIndex][0]; int iv=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[pointIndex][1]; System.out.println(numThisRemoved+": "+pointIndex + " uv="+iu+":"+iv+ " e2="+e2+" ww="+ww+" e2w="+ (e2*ww)+" ["+diff[i]+":"+diff[i+1]+"]"); } } } } if (verbose && (numThisRemoved>0)) { System.out.println("Image "+imgNum+": removed "+numThisRemoved+" nodes over threshold"); } totalBadNodes+=numThisRemoved; } index+=len; } } return totalBadNodes; } public boolean showImageReprojectionErrorsDialog( int debugLevel){ boolean eachImageInSet=false; GenericDialog gd = new GenericDialog("Show Reprojection errors for image/image set/image selection"); gd.addNumericField("Series number for image selection (-1 - all enabled images)", -1, 0); gd.addNumericField("Single image number to show (<0 - do not select)", -1,0); gd.addNumericField("Image set number to show (<0 - do not select)", -1,0); gd.addCheckbox("Open each image in the set", eachImageInSet); gd.addCheckbox("Ask for weight function filter", this.askFilter); // gd.addNumericField("Weight function filter (-1 - use default for all )",-1,0); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber= (int) gd.getNextNumber(); int singleImageNumber= (int) gd.getNextNumber(); int imageSetNumber= (int) gd.getNextNumber(); eachImageInSet= gd.getNextBoolean(); this.askFilter= gd.getNextBoolean(); // int weightFunctionFilter= (int) gd.getNextNumber(); int filter=this.filterForAll; if (this.askFilter) filter=selectFilter(filter); int [] imageNumbers = null; if (singleImageNumber>=0){ imageNumbers=new int [1]; imageNumbers[0]=singleImageNumber; } else if (imageSetNumber>=0){ int numInSet=0; for (int nChn=0;nChn<this.fittingStrategy.distortionCalibrationData.gIS[imageSetNumber].imageSet.length;nChn++){ if (this.fittingStrategy.distortionCalibrationData.gIS[imageSetNumber].imageSet[nChn]!=null) numInSet++; } imageNumbers=new int [numInSet]; numInSet=0; for (int nChn=0;nChn<this.fittingStrategy.distortionCalibrationData.gIS[imageSetNumber].imageSet.length;nChn++){ if (this.fittingStrategy.distortionCalibrationData.gIS[imageSetNumber].imageSet[nChn]!=null) { imageNumbers[numInSet++]=this.fittingStrategy.distortionCalibrationData.gIS[imageSetNumber].imageSet[nChn].imgNumber; } } if (eachImageInSet){ for (int nChn=0;nChn<imageNumbers.length;nChn++){ int [] imageNumber={imageNumbers[nChn]}; showImageReprojectionErrors( imageNumber, // if null - use all images in a series filter, //weightFunctionFilter, debugLevel); } // Do not exit, continue and show combine reprojection errors for all set } } showImageReprojectionErrors( imageNumbers, // if null - use all images in a series filter, //weightFunctionFilter, debugLevel); return true; } public void showImageReprojectionErrors( int [] imageNumbers, // if null - use all images in a series int filter, int debugLevel){ if (filter<0) filter=this.filterForAll; if (debugLevel>1) { System.out.print("showImageReprojectionErrors: "); if (imageNumbers!=null){ for (int i=0;i<imageNumbers.length;i++) System.out.print(" "+imageNumbers[i]); } else { System.out.println("imageNumbers is NULL"); } } initFittingSeries(false, filter,this.seriesNumber); // recalculate this.currentfX=calculateFxAndJacobian(this.currentVector, false); // is it always true here (this.jacobian==null) boolean [] tmpSelectedImages=fittingStrategy.selectedImages(); boolean [] selectedImages; double [] diff=calcYminusFx(this.currentfX); if ((imageNumbers!=null) && (imageNumbers.length>0)){ selectedImages=new boolean[tmpSelectedImages.length]; for (int i=0;i<selectedImages.length;i++) selectedImages[i]=false; for (int i=0;i<imageNumbers.length;i++) if ((imageNumbers[i]>=0) && (imageNumbers[i]<=selectedImages.length)){ selectedImages[imageNumbers[i]]=tmpSelectedImages[imageNumbers[i]]; } } else { selectedImages=tmpSelectedImages; int numImg = 0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]) numImg++; imageNumbers = new int [numImg]; numImg = 0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]) { imageNumbers[numImg++] = i; } } int width= getGridWidth(); int height=getGridHeight(); double [][] imgData=new double[5][height * width]; // dPX, dPY, err String [] titles={"dX","dY", "Err","W_Err","Weight"}; for (int i=0;i<(width*height);i++){ for (int c=0;c<imgData.length;c++) imgData[c][i]=Double.NaN; } for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]){ int len=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length; int index=this.imageStartIndex[imgNum]; // pair index for (int i=0;i<len;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][0]+this.patternParameters.U0; int v=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][1]+this.patternParameters.V0; int vu=u+width*v; double w=this.weightFunction[2*(index+i)]; double dx=diff[2*(index+i)]; double dy=diff[2*(index+i)+1]; double e2=dx*dx+dy*dy; if (w>0.0){ if (Double.isNaN(imgData[0][vu])) for (int c=0;c<imgData.length;c++) imgData[c][vu]=0.0; imgData[0][vu]+=dx*w; imgData[1][vu]+=dy*w; imgData[2][vu]+=e2*w; imgData[4][vu]+=w; } } } int nonEmpty=0; double sumWeights=0.0; for (int vu=0;vu<(width*height);vu++) if (!Double.isNaN(imgData[0][vu])){ nonEmpty++; sumWeights+=imgData[4][vu]; } if ((nonEmpty==0) || (sumWeights==0.0)){ System.out.println("showImageReprojectionErrors(): No non-empty points"); return; } double averageWeight=sumWeights/nonEmpty; for (int vu=0;vu<(width*height);vu++) if (!Double.isNaN(imgData[0][vu])){ imgData[0][vu]/=imgData[4][vu]; imgData[1][vu]/=imgData[4][vu]; imgData[2][vu] =Math.sqrt(imgData[2][vu]/imgData[4][vu]); imgData[3][vu] =Math.sqrt(imgData[2][vu]/averageWeight); } String title="RPRJ"; int maxNumInTitle=10; for (int i=0;(i<imageNumbers.length) && (i<maxNumInTitle); i++) title+="-"+imageNumbers[i]; (new ShowDoubleFloatArrays()).showArrays( imgData, width, height, true, title, titles); } public double [] calcErrors(double [] diff){ boolean [] selectedImages=fittingStrategy.selectedImages(); double [] errors=new double [selectedImages.length]; int index=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) { errors[imgNum]=Double.NaN; //0.0; if (selectedImages[imgNum]) { errors[imgNum]=0.0; int len=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length*2; double w=0; if (this.weightFunction!=null) { for (int i=index;i<index+len;i++) { errors[imgNum]+=diff[i]*diff[i]*this.weightFunction[i]; w+=this.weightFunction[i]; } } else { for (int i=index;i<index+len;i++) { errors[imgNum]+=diff[i]*diff[i]; w+=1.0; } } if (w>0.0) { errors[imgNum]/=w; errors[imgNum]=Math.sqrt(errors[imgNum])*this.RMSscale; } else { errors[imgNum]=Double.NaN; } index+=len; } } return errors; } /** * Calculate number of used grid points (x/y pairs) for each image in the current fitting series * @return */ public int [] calcNumPairs(){ boolean [] selectedImages=fittingStrategy.selectedImages(); int [] numPairs=new int [selectedImages.length]; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) { numPairs[imgNum]=0; if (selectedImages[imgNum]) { numPairs[imgNum]=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length; } } return numPairs; } /** * Calculate corrections to the current parameter values * @param fX calculated grid pixelX, PixelY for current parameter values * @param lambda damping parameter * @return array of deltas to be applied to the coefficients */ public double [] solveLevenbergMarquardtOldNotUsed(double [] fX, double lambda){ // calculate JtJ double [] diff=calcYminusFx(fX); int numPars=this.jacobian.length; // number of parameters to be adjusted int length=diff.length; // should be the same as this.jacobian[0].length double [][] JtByJmod=new double [numPars][numPars]; //Transposed Jacobian multiplied by Jacobian double [] JtByDiff=new double [numPars]; for (int i=0;i<numPars;i++) for (int j=i;j<numPars;j++){ JtByJmod[i][j]=0.0; if (this.weightFunction!=null) for (int k=0;k<length;k++) JtByJmod[i][j]+=this.jacobian[i][k]*this.jacobian[j][k]*this.weightFunction[k]; else for (int k=0;k<length;k++) JtByJmod[i][j]+=this.jacobian[i][k]*this.jacobian[j][k]; } for (int i=0;i<numPars;i++) { // subtract lambda*diagonal , fill the symmetrical half below the diagonal JtByJmod[i][i]+=lambda*JtByJmod[i][i]; //Marquardt mod for (int j=0;j<i;j++) JtByJmod[i][j]= JtByJmod[j][i]; // it is symmetrical matrix, just copy } for (int i=0;i<numPars;i++) { JtByDiff[i]=0.0; if (this.weightFunction!=null) for (int k=0;k<length;k++) JtByDiff[i]+=this.jacobian[i][k]*diff[k]*this.weightFunction[k]; else for (int k=0;k<length;k++) JtByDiff[i]+=this.jacobian[i][k]*diff[k]; } // M*Ma=Mb Matrix M=new Matrix(JtByJmod); // public Matrix (double vals[], int m) { /* if (this.debugLevel>2) { for (int n=0;n<fittingStrategy.distortionCalibrationData.pixelsXY.length;n++) { for (int i=0;i<fittingStrategy.distortionCalibrationData.pixelsXY[n].length;i++){ System.out.println(n+":"+i+" "+ fittingStrategy.distortionCalibrationData.pixelsUV[n][i][0]+"/"+ fittingStrategy.distortionCalibrationData.pixelsUV[n][i][1]+" "+ IJ.d2s(fittingStrategy.distortionCalibrationData.pixelsXY[n][i][0], 2)+"/"+ IJ.d2s(fittingStrategy.distortionCalibrationData.pixelsXY[n][i][1], 2) ); } } } */ if (this.debugLevel>3) { // if (this.debugLevel>1) { System.out.println("Jt*J -lambda* diag(Jt*J), lambda="+lambda+":"); M.print(10, 5); } Matrix Mb=new Matrix(JtByDiff,numPars); // single column if (!(new LUDecomposition(M)).isNonsingular()){ double [][] arr=M.getArray(); System.out.println("Singular Matrix "+arr.length+"x"+arr[0].length); // any rowsx off all 0.0? for (int n=0;n<arr.length;n++){ boolean zeroRow=true; for (int i=0;i<arr[n].length;i++) if (arr[n][i]!=0.0){ zeroRow=false; break; } if (zeroRow){ System.out.println("Row of all zeros: "+n); } } // M.print(10, 5); return null; } // Matrix Ma=M.solve(Mb); // singular if (this.debugLevel>0) System.out.print("Running Cholesky decomposition..."); long decompositionTime=System.nanoTime(); Matrix Ma=M.chol().solve(Mb); // singular decompositionTime=System.nanoTime()-decompositionTime; if (this.debugLevel>0) System.out.println("done in "+(decompositionTime/1E9)+" sec"); return Ma.getColumnPackedCopy(); } public LMAArrays calculateJacobianArrays(double [] fX){ // calculate JtJ double [] diff=calcYminusFx(fX); int numPars=this.jacobian.length; // number of parameters to be adjusted int length=diff.length; // should be the same as this.jacobian[0].length double [][] JtByJmod=new double [numPars][numPars]; //Transposed Jacobian multiplied by Jacobian double [] JtByDiff=new double [numPars]; for (int i=0;i<numPars;i++) for (int j=i;j<numPars;j++){ JtByJmod[i][j]=0.0; if (this.weightFunction!=null) for (int k=0;k<length;k++) JtByJmod[i][j]+=this.jacobian[i][k]*this.jacobian[j][k]*this.weightFunction[k]; else for (int k=0;k<length;k++) JtByJmod[i][j]+=this.jacobian[i][k]*this.jacobian[j][k]; } for (int i=0;i<numPars;i++) { // subtract lambda*diagonal , fill the symmetrical half below the diagonal // JtByJmod[i][i]+=lambda*JtByJmod[i][i]; //Marquardt mod for (int j=0;j<i;j++) JtByJmod[i][j]= JtByJmod[j][i]; // it is symmetrical matrix, just copy } for (int i=0;i<numPars;i++) { JtByDiff[i]=0.0; if (this.weightFunction!=null) for (int k=0;k<length;k++) JtByDiff[i]+=this.jacobian[i][k]*diff[k]*this.weightFunction[k]; else for (int k=0;k<length;k++) JtByDiff[i]+=this.jacobian[i][k]*diff[k]; } LMAArrays lMAArrays = new LMAArrays(); lMAArrays.jTByJ=JtByJmod; lMAArrays.jTByDiff=JtByDiff; return lMAArrays; /* // M*Ma=Mb Matrix M=new Matrix(JtByJmod); // public Matrix (double vals[], int m) { if (this.debugLevel>2) { // if (this.debugLevel>1) { System.out.println("Jt*J -lambda* diag(Jt*J), lambda="+lambda+":"); M.print(10, 5); } Matrix Mb=new Matrix(JtByDiff,numPars); // single column if (!(new LUDecomposition(M)).isNonsingular()){ System.out.println("Singular Matrix"); M.print(10, 5); return null; } Matrix Ma=M.solve(Mb); // singular return Ma.getColumnPackedCopy(); */ } public LMAArrays calculateJacobianArrays ( final boolean [] selectedImages, // selected images to process final double [] Y, // should be initialized final double [] fX, // should be initialized to correct length, data is not needed final double [] vector, // parameters vector final int [] imageStartIndex, // index of the first point of each image (including extra element in the end so n+1 is always valid) final double [][] patternXYZ, // this.targetXYZ final double [] weightFunction, // may be null - make it twice smaller? - same for X and Y? final LensDistortionParameters lensDistortionParametersProto, // final double lambda, int threadsMax, boolean updateStatus){ // calculate JtJ // double [] diff=calcYminusFx(fX); // int numPars=this.jacobian.length; // number of parameters to be adjusted final int numPars=vector.length; // number of parameters to be adjusted // int length=diff.length; // should be the same as this.jacobian[0].length // final int length=fX.length; // should be the same as this.jacobian[0].length final double [][] JtByJmod=new double [numPars][numPars]; //Transposed Jacobian multiplied by Jacobian final double [] JtByDiff=new double [numPars]; for (int i=0;i<numPars;i++){ JtByDiff[i]=0.0; for (int j=0;j<numPars;j++) JtByJmod[i][j]=0.0; } final int debugLevel=this.debugLevel; final Thread[] threads = newThreadArray(threadsMax); final AtomicInteger imageNumberAtomic = new AtomicInteger(0); final AtomicInteger imageFinishedAtomic = new AtomicInteger(0); final double [] progressValues=new double [selectedImages.length]; int numSelectedImages=0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]) numSelectedImages++; int selectedIndex=0; for (int i=0;i<selectedImages.length;i++) { progressValues[i]=(selectedIndex+1.0)/numSelectedImages; if (selectedImages[i]) selectedIndex++; if (selectedIndex>=numSelectedImages) selectedIndex--; } final AtomicInteger stopRequested=this.stopRequested; final AtomicBoolean interruptedAtomic=new AtomicBoolean(); for (int ithread = 0; ithread < threads.length; ithread++) { threads[ithread] = new Thread() { @Override public void run() { LensDistortionParameters lensDistortionParameters=lensDistortionParametersProto.clone(); // see - if that is needed - maybe new is OK // LensDistortionParameters lensDistortionParameters= new LensDistortionParameters(); for (int numImage=imageNumberAtomic.getAndIncrement(); (numImage<selectedImages.length) && !interruptedAtomic.get();numImage=imageNumberAtomic.getAndIncrement()){ double [][] partialJacobian= calculatePartialFxAndJacobian( numImage, // number of grid image vector, // parameters vector patternXYZ, // this.targetXYZ fX, // non-overlapping segments will be filled imageStartIndex, // start index in patternXYZ array (length - difference to the next, includes extra last element) lensDistortionParameters, // initialize one per each tread? Or for each call? true); // when false, modifies only this.lensDistortionParameters.* int length=2*(imageStartIndex[numImage+1]-imageStartIndex[numImage]); int start= 2*imageStartIndex[numImage]; double [][] partialJtByJmod=new double [numPars][numPars]; // out of heap space double [] partialJtByDiff=new double [numPars]; for (int i=0;i<numPars;i++) if (partialJacobian[i]!=null) { for (int j=i;j<numPars;j++) if (partialJacobian[j]!=null) { partialJtByJmod[i][j]=0.0; if (weightFunction!=null) { for (int k=0;k<length;k++) partialJtByJmod[i][j]+=partialJacobian[i][k]*partialJacobian[j][k]*weightFunction[start+k]; } else { for (int k=0;k<length;k++) partialJtByJmod[i][j]+=partialJacobian[i][k]*partialJacobian[j][k]; } } } double [] partialDiff=new double[length]; for (int k=0;k<length;k++) partialDiff[k]=Y[start+k]-fX[start+k]; for (int i=0;i<numPars;i++) if (partialJacobian[i]!=null) { partialJtByDiff[i]=0.0; if (weightFunction!=null) for (int k=0;k<length;k++) partialJtByDiff[i]+=partialJacobian[i][k]*partialDiff[k]*weightFunction[start+k]; else for (int k=0;k<length;k++) partialJtByDiff[i]+=partialJacobian[i][k]*partialDiff[k]; } // wrong! fix it /* synchronized(this){ for (int i=0;i<numPars;i++) if (partialJacobian[i]!=null){ JtByDiff[i]+=partialJtByDiff[i]; for (int j=i;j<numPars;j++) JtByJmod[i][j]+=partialJtByJmod[i][j]; } } */ synchronizedCombinePartialJacobians( JtByJmod, //Transposed Jacobian multiplied by Jacobian JtByDiff, partialJacobian, partialJtByDiff, partialJtByJmod, numPars ); final int numFinished=imageFinishedAtomic.getAndIncrement(); // IJ.showProgress(progressValues[numFinished]); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // Here, we can safely update the GUI // because we'll be called from the // event dispatch thread IJ.showProgress(progressValues[numFinished]); } }); if (stopRequested.get()==1){ // ASAP interruptedAtomic.set(true); } // if (debugLevel>1) System.out.println("IJ.showProgress("+progressValues[numImage]+")"); // if (debugLevel>1) IJ.showStatus("Progress "+IJ.d2s(100*progressValues[numImage],2)+"%"); } } }; } startAndJoin(threads); if (interruptedAtomic.get()) { System.out.println("calculateJacobianArrays() aborted by user request"); return null; } if (debugLevel>3){ String msg="calculateJacobianArrays() ALL_trace="; for (int ii=0;ii<numPars;ii++) msg+=IJ.d2s(JtByJmod[ii][ii],5); System.out.println(msg); } for (int i=0;i<numPars;i++) { // subtract lambda*diagonal , fill the symmetrical half below the diagonal for (int j=0;j<i;j++) JtByJmod[i][j]= JtByJmod[j][i]; // it is symmetrical matrix, just copy } LMAArrays lMAArrays = new LMAArrays(); lMAArrays.jTByJ=JtByJmod; lMAArrays.jTByDiff=JtByDiff; if (debugLevel>3){ String msg="calculateJacobianArrays() lMAArrays.jTByJ trace="; for (int ii=0;ii<numPars;ii++) msg+=IJ.d2s(lMAArrays.jTByJ[ii][ii],5); System.out.println(msg); } return lMAArrays; } public synchronized void synchronizedCombinePartialJacobians( double [][] JtByJmod, //Transposed Jacobian multiplied by Jacobian double [] JtByDiff, double [][] partialJacobian, double [] partialJtByDiff, double [][] partialJtByJmod, int numPars ){ for (int i=0;i<numPars;i++) if (partialJacobian[i]!=null){ JtByDiff[i]+=partialJtByDiff[i]; for (int j=i;j<numPars;j++) JtByJmod[i][j]+=partialJtByJmod[i][j]; } } public double [] solveLMA( LMAArrays lMAArrays, double lambda){ double [][] JtByJmod= lMAArrays.jTByJ.clone(); int numPars=JtByJmod.length; for (int i=0;i<numPars;i++){ JtByJmod[i]=lMAArrays.jTByJ[i].clone(); JtByJmod[i][i]+=lambda*JtByJmod[i][i]; //Marquardt mod } // M*Ma=Mb Matrix M=new Matrix(JtByJmod); // public Matrix (double vals[], int m) { if (this.debugLevel>2) { // if (this.debugLevel>1) { System.out.println("Jt*J -lambda* diag(Jt*J), lambda="+lambda+":"); M.print(10, 5); } Matrix Mb=new Matrix(lMAArrays.jTByDiff,numPars); // single column if (!(new LUDecomposition(M)).isNonsingular()){ double [][] arr=M.getArray(); System.out.println("Singular Matrix "+arr.length+"x"+arr[0].length); // any rowsx off all 0.0? for (int n=0;n<arr.length;n++){ boolean zeroRow=true; for (int i=0;i<arr[n].length;i++) if (arr[n][i]!=0.0){ zeroRow=false; break; } if (zeroRow){ System.out.println("Row of all zeros: "+n); } } // M.print(10, 5); return null; } Matrix Ma=M.solve(Mb); // singular return Ma.getColumnPackedCopy(); } /** * Calculates next parameters vector, holds some arrays * @param numSeries * @return array of two booleans: { improved, finished} */ public boolean [] stepLevenbergMarquardtFirst(int numSeries){ double [] deltas=null; if (this.currentVector==null) { int filter=this.filterForAll; if (this.askFilter) filter=selectFilter(filter); initFittingSeries(false,filter,numSeries); // first step in series this.currentRMS=-1; this.currentRMSPure=-1; this.currentfX=null; // invalidate this.jacobian=null; // invalidate this.lMAArrays=null; lastImprovements[0]=-1.0; lastImprovements[1]=-1.0; } // calculate this.currentfX, this.jacobian if needed if (this.debugLevel>2) { System.out.println("this.currentVector"); for (int i=0;i<this.currentVector.length;i++){ System.out.println(i+": "+ this.currentVector[i]); } } // if ((this.currentfX==null)|| ((this.jacobian==null) && !this.threadedLMA )) { if ((this.currentfX==null)|| (this.lMAArrays==null)) { if (this.updateStatus){ // IJ.showStatus(this.seriesNumber+": "+"Step #"+this.iterationStepNumber+" RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.firstRMS,8)+")"); IJ.showStatus(this.seriesNumber+": initial Jacobian matrix calculation. Points:"+this.Y.length+" Parameters:"+this.currentVector.length); } if (this.debugLevel>1) { System.out.println(this.seriesNumber+": initial Jacobian matrix calculation. Points:"+this.Y.length+" Parameters:"+this.currentVector.length); } if (this.threadedLMA) { this.currentfX=new double[this.Y.length]; // deltas=solveLevenbergMarquardtThreaded( this.lMAArrays=calculateJacobianArrays( this.fittingStrategy.selectedImages(), // selected images to process this.Y, // should be initialized this.currentfX, // should be initialized to correct length, data is not needed this.currentVector, // parameters vector this.imageStartIndex, // index of the first point of each image (including extra element in the end so n+1 is always valid) this.targetXYZ, // this.targetXYZ this.weightFunction, // may be null - make it twice smaller? - same for X and Y? this.lensDistortionParameters, // this.lambda, this.threadsMax, this.updateStatus); if (this.lMAArrays == null) { return null ; // aborted } } else { this.currentfX=calculateFxAndJacobian(this.currentVector, true); // is it always true here (this.jacobian==null) this.lMAArrays=calculateJacobianArrays(this.currentfX); // deltas=solveLevenbergMarquardt(this.currentfX,this.lambda); } // add termes that push selected extrinsic parameters towards average (global, per station, per tilt-station) this.currentRMSPure= calcErrorDiffY(this.currentfX); if ((this.fittingStrategy.varianceModes!=null) && (this.fittingStrategy.varianceModes[numSeries]!=this.fittingStrategy.varianceModeDisabled)) { this.fittingStrategy.addVarianceToLMA( numSeries, this.currentVector, this.lMAArrays.jTByJ, // jacobian multiplied by Jacobian transposed (or null) this.lMAArrays.jTByDiff); this.currentRMS= calcErrorDiffY( this.currentfX, this.fittingStrategy.getVarianceError2(), //double [] extraWeightedErrors, this.fittingStrategy.getWeights()); //double [] extraWeights); if (this.updateStatus){ IJ.showStatus(this.seriesNumber+": initial RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.currentRMSPure,8)+")"+ ". Calculating next Jacobian. Points:"+this.Y.length+" Parameters:"+this.currentVector.length); } if ((this.debugLevel>0) && ((this.debugLevel>1) || ((System.nanoTime()-this.startTime)>10000000000.0))) { System.out.println(this.seriesNumber+": initial RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.currentRMSPure,8)+")"+ ". Calculating next Jacobian. Points:"+this.Y.length+" Parameters:"+this.currentVector.length); } } else { this.currentRMS= this.currentRMSPure; if (this.updateStatus){ IJ.showStatus(this.seriesNumber+": initial RMS="+IJ.d2s(this.currentRMS,8)+ ". Calculating next Jacobian. Points:"+this.Y.length+" Parameters:"+this.currentVector.length); } if (this.debugLevel>1) { System.out.println(this.seriesNumber+": initial RMS="+IJ.d2s(this.currentRMS,8)+ ". Calculating next Jacobian. Points:"+this.Y.length+" Parameters:"+this.currentVector.length); } } } else { this.currentRMSPure= calcErrorDiffY(this.currentfX); if ((this.fittingStrategy.varianceModes!=null) && (this.fittingStrategy.varianceModes[numSeries]!=this.fittingStrategy.varianceModeDisabled)) { this.fittingStrategy.addVarianceToLMA(// recalculating as this may keep from nextVector (or just being restored) numSeries, this.currentVector, null, //this.lMAArrays.jTByJ, // jacobian multiplied by Jacobian transposed (or null) null); //this.lMAArrays.jTByDiff); this.currentRMS= calcErrorDiffY( this.currentfX, this.fittingStrategy.getVarianceError2(), //double [] extraWeightedErrors, this.fittingStrategy.getWeights()); //double [] extraWeights); } else { this.currentRMS= this.currentRMSPure; } } // this.currentRMS= calcError(calcYminusFx(this.currentfX)); if (this.firstRMS<0) { this.firstRMS=this.currentRMS; this.firstRMSPure=this.currentRMSPure; } // calculate deltas // double [] deltas=solveLevenbergMarquardt(this.currentfX,fittingStrategy.getLambda()); deltas=solveLMA(this.lMAArrays, this.lambda ); boolean matrixNonSingular=true; if (deltas==null) { deltas=new double[this.currentVector.length]; for (int i=0;i<deltas.length;i++) deltas[i]=0.0; matrixNonSingular=false; } if (this.debugLevel>2) { System.out.println("deltas"); for (int i=0;i<deltas.length;i++){ System.out.println(i+": "+ deltas[i]); } } // apply deltas this.nextVector=this.currentVector.clone(); for (int i=0;i<this.nextVector.length;i++) this.nextVector[i]+=deltas[i]; // another option - do not calculate J now, just fX. and late - calculate both if it was improvement // save current Jacobian if (this.debugLevel>2) { System.out.println("this.nextVector"); for (int i=0;i<this.nextVector.length;i++){ System.out.println(i+": "+ this.nextVector[i]); } } // this.savedJacobian=this.jacobian; this.savedLMAArrays=lMAArrays.clone(); this.jacobian=null; // not needed, just to catch bugs // calculate next vector and Jacobian (this.jacobian) // this.nextfX=calculateFxAndJacobian(this.nextVector,true); //=========== OLD if (this.threadedLMA) { this.nextfX=new double[this.Y.length]; // deltas=solveLevenbergMarquardtThreaded( this.lMAArrays=calculateJacobianArrays( this.fittingStrategy.selectedImages(), // selected images to process this.Y, // should be initialized this.nextfX, // should be initialized to correct length, data is not needed this.nextVector, // parameters vector this.imageStartIndex, // index of the first point of each image (including extra element in the end so n+1 is always valid) this.targetXYZ, // this.targetXYZ this.weightFunction, // may be null - make it twice smaller? - same for X and Y? this.lensDistortionParameters, // this.lambda, this.threadsMax, this.updateStatus); if (this.lMAArrays == null) { return null ; // aborted } } else { this.nextfX=calculateFxAndJacobian(this.nextVector,true); this.lMAArrays=calculateJacobianArrays(this.nextfX); } // this.nextRMS=calcErrorDiffY(this.nextfX); this.nextRMSPure= calcErrorDiffY(this.nextfX); if ((this.fittingStrategy.varianceModes!=null) && (this.fittingStrategy.varianceModes[numSeries]!=this.fittingStrategy.varianceModeDisabled)) { this.fittingStrategy.addVarianceToLMA( numSeries, this.nextVector, this.lMAArrays.jTByJ, // jacobian multiplied by Jacobian transposed (or null) this.lMAArrays.jTByDiff); this.nextRMS= calcErrorDiffY( this.nextfX, this.fittingStrategy.getVarianceError2(), //double [] extraWeightedErrors, this.fittingStrategy.getWeights()); //double [] extraWeights); } else { this.nextRMS= this.nextRMSPure; } this.lastImprovements[1]=this.lastImprovements[0]; this.lastImprovements[0]=this.currentRMS-this.nextRMS; if (this.debugLevel>2) { System.out.println("stepLMA this.currentRMS="+this.currentRMS+ ", this.currentRMSPure="+this.currentRMSPure+ ", this.nextRMS="+this.nextRMS+ ", this.nextRMSPure="+this.nextRMSPure+ ", delta="+(this.currentRMS-this.nextRMS)+ ", deltaPure="+(this.currentRMSPure-this.nextRMSPure)); } boolean [] status={matrixNonSingular && (this.nextRMS<=this.currentRMS),!matrixNonSingular}; // additional test if "worse" but the difference is too small, it was be caused by computation error, like here: //stepLevenbergMarquardtAction() step=27, this.currentRMS=0.17068403807026408, this.nextRMS=0.1706840380702647 if (!status[0] && matrixNonSingular) { if (this.nextRMS<(this.currentRMS+this.currentRMS*this.thresholdFinish*0.01)) { this.nextRMS=this.currentRMS; this.nextRMSPure=this.currentRMSPure; status[0]=true; status[1]=true; this.lastImprovements[0]=0.0; if (this.debugLevel>1) { System.out.println("New RMS error is larger than the old one, but the difference is too small to be trusted "); System.out.println( "stepLMA this.currentRMS="+this.currentRMS+ ", this.currentRMSPure="+this.currentRMSPure+ ", this.nextRMS="+this.nextRMS+ ", this.nextRMSPure="+this.nextRMSPure+ ", delta="+(this.currentRMS-this.nextRMS)+ ", deltaPure="+(this.currentRMSPure-this.nextRMSPure)); } } } if (status[0] && matrixNonSingular) { //improved status[1]=(this.iterationStepNumber>this.numIterations) || ( // done (this.lastImprovements[0]>=0.0) && (this.lastImprovements[0]<this.thresholdFinish*this.currentRMS) && (this.lastImprovements[1]>=0.0) && (this.lastImprovements[1]<this.thresholdFinish*this.currentRMS)); } else if (matrixNonSingular){ // this.jacobian=this.savedJacobian;// restore saved Jacobian this.lMAArrays=this.savedLMAArrays; // restore Jt*J and Jt*diff status[1]=(this.iterationStepNumber>this.numIterations) || // failed ((this.lambda*this.lambdaStepUp)>this.maxLambda); } ///this.currentRMS //TODO: add other failures leading to result failure? if (this.debugLevel>2) { System.out.println("stepLevenbergMarquardtFirst("+numSeries+")=>"+status[0]+","+status[1]); } return status; } /** * Apply fitting step */ public void stepLevenbergMarquardtAction(){// this.iterationStepNumber++; // apply/revert,modify lambda if (this.debugLevel>1) { System.out.println( "stepLevenbergMarquardtAction() step="+this.iterationStepNumber+ ", this.currentRMS="+this.currentRMS+ ", this.currentRMSPure="+this.currentRMSPure+ ", this.nextRMS="+this.nextRMS+ ", this.nextRMSPure="+this.nextRMSPure+ " lambda="+this.lambda+" at "+IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec"); } if (this.nextRMS<this.currentRMS) { //improved this.lambda*=this.lambdaStepDown; this.currentRMS=this.nextRMS; this.currentRMSPure=this.nextRMSPure; this.currentfX=this.nextfX; this.currentVector=this.nextVector; } else { this.lambda*=this.lambdaStepUp; // this.jacobian=this.savedJacobian;// restore saved Jacobian this.lMAArrays=this.savedLMAArrays; // restore Jt*J and Jt*diff } } /** * Dialog to select Levenberg-Marquardt algorithm and related parameters * @return true if OK, false if canceled */ public boolean selectLMAParameters(){ int numSeries=fittingStrategy.getNumSeries(); GenericDialog gd = new GenericDialog("Levenberg-Marquardt algorithm parameters for cameras distortions/locations"); gd.addNumericField("Iteration number to start (0.."+(numSeries-1)+")", this.seriesNumber, 0); gd.addNumericField("Initial LMA Lambda ", this.lambda, 5); gd.addNumericField("Multiply lambda on success", this.lambdaStepDown, 5); gd.addNumericField("Threshold RMS to exit LMA", this.thresholdFinish, 7,9,"pix"); gd.addNumericField("Multiply lambda on failure", this.lambdaStepUp, 5); gd.addNumericField("Threshold lambda to fail", this.maxLambda, 5); gd.addNumericField("Maximal number of iterations", this.numIterations, 0); gd.addCheckbox("Dialog after each iteration step", this.stopEachStep); gd.addCheckbox("Dialog after each iteration series", this.stopEachSeries); gd.addCheckbox("Dialog after each failure", this.stopOnFailure); gd.addCheckbox("Ask for weight function filter", this.askFilter); gd.addCheckbox("Show modified parameters", this.showParams); gd.addCheckbox("Show debug images before correction",this.showThisImages); gd.addCheckbox("Show debug images after correction", this.showNextImages); gd.addNumericField("Maximal number of threads", this.threadsMax, 0); gd.addCheckbox("Use memory-saving/multithreaded version", this.threadedLMA); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber= (int) gd.getNextNumber(); this.lambda= gd.getNextNumber(); this.lambdaStepDown= gd.getNextNumber(); this.thresholdFinish= gd.getNextNumber(); this.lambdaStepUp= gd.getNextNumber(); this.maxLambda= gd.getNextNumber(); this.numIterations= (int) gd.getNextNumber(); this.stopEachStep= gd.getNextBoolean(); this.stopEachSeries= gd.getNextBoolean(); this.stopOnFailure= gd.getNextBoolean(); this.askFilter= gd.getNextBoolean(); this.showParams= gd.getNextBoolean(); this.showThisImages= gd.getNextBoolean(); this.showNextImages= gd.getNextBoolean(); this.threadsMax= (int) gd.getNextNumber(); this.threadedLMA= gd.getNextBoolean(); return true; } public boolean dialogLMAStep(boolean [] state){ String [] states={ "Worse, increase lambda", "Better, decrease lambda", "Failed to fit", "Fitting Successful"}; int iState=(state[0]?1:0)+(state[1]?2:0); GenericDialog gd = new GenericDialog("Levenberg-Marquardt algorithm step"); // String [][] parameterDescriptions=fittingStrategy.distortionCalibrationData.parameterDescriptions; gd.addMessage("Current state="+states[iState]); gd.addMessage("Iteration step="+this.iterationStepNumber); gd.addMessage("Initial RMS="+IJ.d2s(this.firstRMS,6)+", Current RMS="+IJ.d2s(this.currentRMS,6)+", new RMS="+IJ.d2s(this.nextRMS,6)); gd.addMessage("Pure initial RMS="+IJ.d2s(this.firstRMSPure,6)+", Current RMS="+IJ.d2s(this.currentRMSPure,6)+", new RMS="+IJ.d2s(this.nextRMSPure,6)); if (this.showParams) { for (int i=0;i<this.currentVector.length;i++){ int parNum=fittingStrategy.parameterMap[i][1]; int imgNum=fittingStrategy.parameterMap[i][0]; double delta= this.nextVector[i] - this.currentVector[i]; // gd.addMessage(i+": "+parameterDescriptions[parNum][0]+ // "["+imgNum+"]("+parameterDescriptions[parNum][2]+") "+IJ.d2s(this.currentVector[i],3)+ // " + "+IJ.d2s(delta,3)+" = "+IJ.d2s(this.nextVector[i],3)); gd.addMessage(i+": "+fittingStrategy.distortionCalibrationData.descrField(parNum,0)+ "["+imgNum+"]("+fittingStrategy.distortionCalibrationData.descrField(parNum,2)+") "+IJ.d2s(this.currentVector[i],3)+ " + "+IJ.d2s(delta,3)+" = "+IJ.d2s(this.nextVector[i],3)); } } gd.addNumericField("Lambda ", this.lambda, 5); gd.addNumericField("Multiply lambda on success", this.lambdaStepDown, 5); gd.addNumericField("Threshold RMS to exit LMA", this.thresholdFinish, 7,9,"pix"); gd.addNumericField("Multiply lambda on failure", this.lambdaStepUp, 5); gd.addNumericField("Threshold lambda to fail", this.maxLambda, 5); gd.addNumericField("Maximal number of iterations", this.numIterations, 0); gd.addCheckbox("Dialog after each iteration step", this.stopEachStep); gd.addCheckbox("Dialog after each iteration series", this.stopEachSeries); gd.addCheckbox("Dialog after each failure", this.stopOnFailure); gd.addCheckbox("Show modified parameters", this.showParams); gd.addCheckbox("Show debug images before correction",this.showThisImages); gd.addCheckbox("Show debug images after correction", this.showNextImages); gd.addMessage("Done will save the current (not new!) state and exit, Continue will proceed according to LMA"); gd.enableYesNoCancel("Continue", "Done"); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) { this.saveSeries=false; return false; } this.lambda= gd.getNextNumber(); this.lambdaStepDown= gd.getNextNumber(); this.thresholdFinish= gd.getNextNumber(); this.lambdaStepUp= gd.getNextNumber(); this.maxLambda= gd.getNextNumber(); this.numIterations= (int) gd.getNextNumber(); this.stopEachStep= gd.getNextBoolean(); this.stopEachSeries= gd.getNextBoolean(); this.stopOnFailure= gd.getNextBoolean(); this.showParams= gd.getNextBoolean(); this.showThisImages= gd.getNextBoolean(); this.showNextImages= gd.getNextBoolean(); this.saveSeries=true; return gd.wasOKed(); } public boolean modifyGrid( DistortionCalibrationData distortionCalibrationData, int threadsMax, boolean updateStatus){ if (fittingStrategy==null) { String msg="Fitting strategy does not exist, exiting"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if (distortionCalibrationData.sensorMasks==null){ String msg="Sensor mask(s) are not defined"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if (distortionCalibrationData.eyesisCameraParameters==null){ String msg="Eyesis camera parameters (and sensor dimensions) are not defined"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } // if (! selectGridEnhanceParameters()) return false; // int series=refineParameters.showDialog("Select Grid Tuning Parameters", 0xdc0, fittingStrategy.getNumSeries()); // int series=refineParameters.showDialog("Select Grid Tuning Parameters", 0x31cc0, (this.seriesNumber>=0)?this.seriesNumber:0); // 0x1dco with show result, but we can not show it easily // todo - add and implement 0x10000 to show just one individual image // int series=refineParameters.showDialog("Select Grid Tuning Parameters", 0x21cc0, (this.seriesNumber>=0)?this.seriesNumber:0); // 0x1dco with show result, but we can not show it easily int series=refineParameters.showDialog( "Select Grid Tuning Parameters", 0x61000, ((this.seriesNumber>=0)?this.seriesNumber:0), null); // averageRGB - only for target flat-field correction if (series<0) return false; this.seriesNumber=series; int filter=this.filterForTargetGeometry; if (this.askFilter) filter=selectFilter(filter); initFittingSeries(true,filter,this.seriesNumber); // first step in series // initFittingSeries(true,this.filterForTargetGeometry, this.seriesNumber); // first step in series this.currentfX=calculateFxAndJacobian(this.currentVector, false); // this.currentRMS= calcError(calcYminusFx(this.currentfX)); if (this.debugLevel>2) { System.out.println("this.currentVector"); for (int i=0;i<this.currentVector.length;i++){ System.out.println(i+": "+ this.currentVector[i]); } } if (this.showThisImages) showDiff (this.currentfX, "residual-series-"+this.seriesNumber); if (this.refineParameters.resetVariations) { this.patternParameters.resetStationZCorr(); } double [][][] correctionCombo= calculateGridXYZCorr3D( this.refineParameters.variationPenalty, this.refineParameters.fixXY, this.refineParameters.useVariations?(this.fittingStrategy.zGroups[this.seriesNumber]):null, //stationGroups, this.refineParameters.grid3DCorrection, this.refineParameters.rotateCorrection, this.refineParameters.grid3DMaximalZCorr, //20.0, this.refineParameters.noFallBack, this.refineParameters.targetShowPerImage, threadsMax, updateStatus); double [][] gridXYZCorr=correctionCombo[0]; double [][] gridZCorr3d =correctionCombo[1]; double [][] gridZCorr3dWeight =correctionCombo[2]; String [] titles={"X-correction(mm)","Y-correction(mm)","Z-correction","Weight"}; String [] titlesStations=new String [2*gridZCorr3d.length]; for (int i=0;i<gridZCorr3d.length;i++){ titlesStations[i]="Z_"+i; titlesStations[i+gridZCorr3d.length]="W_"+i; } if (this.refineParameters.targetShowThisCorrection) { if (this.debugLevel>1){ double [][] debugData=new double [2*gridZCorr3d.length][]; for (int i=0;i<gridZCorr3d.length;i++){ debugData[i]=gridZCorr3d[i]; debugData[i+gridZCorr3d.length]=gridZCorr3dWeight[i]; } this.SDFA_INSTANCE.showArrays(debugData, getGridWidth(), getGridHeight(), true, "Z corrections", titlesStations); } } // TODO: make configurable and optional shrinkExtrapolateGridCorrection( gridXYZCorr, // dx,dy,dz, mask >0 gridZCorr3d, getGridWidth(), 1, //preShrink, 5, // expand, 3.0, // sigma, 2.0); //double ksigma if (this.refineParameters.targetShowThisCorrection) { this.SDFA_INSTANCE.showArrays(gridXYZCorr, getGridWidth(), getGridHeight(), true, "Grid corrections", titles); if (this.debugLevel>1){ } } if (!this.refineParameters.targetApplyCorrection) return false; patternParameters.applyGridCorrection(gridXYZCorr, this.refineParameters.targetCorrectionScale); patternParameters.applyZGridCorrection(gridZCorr3d, this.refineParameters.targetCorrectionScale); return true; } public boolean modifyGrid0(DistortionCalibrationData distortionCalibrationData){ if (fittingStrategy==null) { String msg="Fitting strategy does not exist, exiting"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if (distortionCalibrationData.sensorMasks==null){ String msg="Sensor mask(s) are not defined"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if (distortionCalibrationData.eyesisCameraParameters==null){ String msg="Eyesis camera parameters (and sensor dimensions) are not defined"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } // if (! selectGridEnhanceParameters()) return false; // int series=refineParameters.showDialog("Select Grid Tuning Parameters", 0xdc0, fittingStrategy.getNumSeries()); // int series=refineParameters.showDialog("Select Grid Tuning Parameters", 0x31cc0, (this.seriesNumber>=0)?this.seriesNumber:0); // 0x1dco with show result, but we can not show it easily // todo - add and implement 0x10000 to show just one individual image // int series=refineParameters.showDialog("Select Grid Tuning Parameters", 0x21cc0, (this.seriesNumber>=0)?this.seriesNumber:0); // 0x1dco with show result, but we can not show it easily int series=refineParameters.showDialog( "Select Grid Tuning Parameters", 0x61000, ((this.seriesNumber>=0)?this.seriesNumber:0), null); // averageRGB - only for target flat-field correction if (series<0) return false; this.seriesNumber=series; int filter=this.filterForTargetGeometry; if (this.askFilter) filter=selectFilter(filter); initFittingSeries(true,filter,this.seriesNumber); // first step in series // initFittingSeries(true,this.filterForTargetGeometry, this.seriesNumber); // first step in series this.currentfX=calculateFxAndJacobian(this.currentVector, false); // this.currentRMS= calcError(calcYminusFx(this.currentfX)); if (this.debugLevel>2) { System.out.println("this.currentVector"); for (int i=0;i<this.currentVector.length;i++){ System.out.println(i+": "+ this.currentVector[i]); } } if (this.showThisImages) showDiff (this.currentfX, "residual-series-"+this.seriesNumber); double [][] gridXYZCorr=null; gridXYZCorr= calculateGridXYZCorr3D( // distortionCalibrationData, this.refineParameters.grid3DCorrection, this.refineParameters.rotateCorrection, this.refineParameters.grid3DMaximalZCorr, //20.0, this.refineParameters.targetShowPerImage); // TODO: make configurable and optional shrinkExtrapolateGridCorrection( gridXYZCorr, // dx,dy,dz, mask >0 null, getGridWidth(), 1, //preShrink, 5, // expand, 3.0, // sigma, 2.0); //double ksigma String [] titles={"X-correction(mm)","Y-correction(mm)","Z-correction","Weight"}; if (this.refineParameters.targetShowThisCorrection) { this.SDFA_INSTANCE.showArrays(gridXYZCorr, getGridWidth(), getGridHeight(), true, "Grid corrections", titles); } if (!this.refineParameters.targetApplyCorrection) return false; patternParameters.applyGridCorrection(gridXYZCorr, this.refineParameters.targetCorrectionScale); return true; } public boolean modifyPixelCorrection(DistortionCalibrationData distortionCalibrationData){ // old if (fittingStrategy==null) { String msg="Fitting strategy does not exist, exiting"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if (distortionCalibrationData.eyesisCameraParameters==null){ String msg="Eyesis camera parameters (and sensor dimensions) are not defined"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } // fittingStrategy.distortionCalibrationData.readAllGrids(); // if (! selectGridEnhanceParameters()) return false; int series=refineParameters.showDialog( "Select Lens Distrortion Residual Compensation Parameters", 0x1efff, ((this.seriesNumber>=0)?this.seriesNumber:0), null); // averageRGB - only for target flat-field correction if (series<0) return false; this.seriesNumber=series; int filter=this.filterForSensor; if (this.askFilter) filter=selectFilter(filter); initFittingSeries(true,filter,this.seriesNumber); // first step in series now uses pattern alpha // initFittingSeries(true,this.filterForSensor,this.seriesNumber); // first step in series now uses pattern alpha this.currentfX=calculateFxAndJacobian(this.currentVector, false); // this.currentRMS= calcError(calcYminusFx(this.currentfX)); if (this.debugLevel>2) { System.out.println("this.currentVector"); for (int i=0;i<this.currentVector.length;i++){ System.out.println(i+": "+ this.currentVector[i]); } } // if (this.showThisImages) showDiff (this.currentfX, "residual-series-"+this.seriesNumber); double [][][] sensorXYCorr= calculateSensorXYCorr( distortionCalibrationData, this.refineParameters.showPerImage, this.refineParameters.showIndividualNumber, this.refineParameters.usePatternAlpha); String [] titles={"X-corr(pix)","Y-corr(pix)","alpha","weight","Red","Green","Blue"}; if (this.refineParameters.extrapolate) { boolean [] whichExtrapolate={true, true,false,false,true,true,true}; boolean [] whichPositive= {false,false,false,false,true,true,true}; IJ.showStatus("Extrapolating sensor corrections..."); for (int numChn=0;numChn<sensorXYCorr.length;numChn++) if (sensorXYCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; int sHeight=(getSensorHeight(numChn)-1)/decimate+1; if (this.refineParameters.showUnfilteredCorrection) { this.SDFA_INSTANCE.showArrays(sensorXYCorr[numChn], sWidth, sHeight, true, "chn_"+numChn+"_extra_correction", titles); } for (int i=0;i<whichPositive.length;i++) if (whichPositive[i]){ logScale(sensorXYCorr[numChn][i],this.refineParameters.fatZero); } boolean extrapolateOK=extrapolateSensorCorrection( //ava.lang.NullPointerException at Distortions.modifyPixelCorrection(Distortions.java:2595) numChn, whichExtrapolate, sensorXYCorr[numChn], sensorXYCorr[numChn][2],// alpha - it is more pessimistic than fittingStrategy.distortionCalibrationData.sensorMasks[numChn] // fittingStrategy.distortionCalibrationData.sensorMasks[numChn], this.refineParameters.alphaThreshold, this.refineParameters.extrapolationSigma, this.refineParameters.extrapolationKSigma); IJ.showProgress(numChn+1, sensorXYCorr.length); for (int i=0;i<whichPositive.length;i++) if (whichPositive[i]){ unLogScale(sensorXYCorr[numChn][i],this.refineParameters.fatZero); } if (!extrapolateOK) sensorXYCorr[numChn]=null; // no correction for too small areas } IJ.showProgress(1.0); } if (this.refineParameters.showExtrapolationCorrection && this.refineParameters.extrapolate && this.refineParameters.smoothCorrection) { for (int numChn=0;numChn<sensorXYCorr.length;numChn++) if (sensorXYCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; int sHeight=(getSensorHeight(numChn)-1)/decimate+1; this.SDFA_INSTANCE.showArrays(sensorXYCorr[numChn], sWidth, sHeight, true, "chn_"+numChn+"_extrapolated", titles); } } if (this.refineParameters.smoothCorrection) { boolean [] whichBlur={true,true,false,false,true,true,true}; IJ.showStatus("Bluring sensor corrections..."); for (int numChn=0;numChn<sensorXYCorr.length;numChn++) if (sensorXYCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; int sHeight=(getSensorHeight(numChn)-1)/decimate+1; DoubleGaussianBlur gb=new DoubleGaussianBlur(); for (int m=0;m<whichBlur.length;m++) if (whichBlur[m]){ gb.blurDouble(sensorXYCorr[numChn][m], sWidth, sHeight, this.refineParameters.smoothSigma/decimate, this.refineParameters.smoothSigma/decimate, 0.01); } IJ.showProgress(numChn+1, sensorXYCorr.length); } IJ.showProgress(1.0); } if (this.refineParameters.showThisCorrection ) { for (int numChn=0;numChn<sensorXYCorr.length;numChn++) if (sensorXYCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; int sHeight=(getSensorHeight(numChn)-1)/decimate+1; this.SDFA_INSTANCE.showArrays(sensorXYCorr[numChn], sWidth, sHeight, true, "chn_"+numChn+"_filtered", titles); } } // if (!selectCorrectionScale()) return false; IJ.showStatus("Applying corrections:"+((!this.refineParameters.applyCorrection && !this.refineParameters.applyFlatField)? "none ":((this.refineParameters.applyCorrection?"geometry ":"")+(this.refineParameters.applyFlatField?"flat field":"")))); addOldXYCorrectionToCurrent( this.refineParameters.correctionScale, sensorXYCorr); boolean result=applySensorCorrection( this.refineParameters.applyCorrection, this.refineParameters.applyFlatField, this.refineParameters.correctionScale, sensorXYCorr, distortionCalibrationData); if (this.refineParameters.showCumulativeCorrection) { for (int numChn=0;numChn<sensorXYCorr.length;numChn++) if (sensorXYCorr[numChn]!=null){ int decimate=getDecimateMasks(numChn); int sWidth= (getSensorWidth(numChn)-1)/decimate+1; int sHeight=(getSensorHeight(numChn)-1)/decimate+1; this.SDFA_INSTANCE.showArrays(sensorXYCorr[numChn], sWidth, sHeight, true, "Cumulative_chn_"+numChn+"_corrections", titles); } } if (result) { // NEED to update from all? // updateCameraParametersFromCalculated(true); // update camera parameters from all (even disabled) images updateCameraParametersFromCalculated(false); // update camera parameters from enabled only images (may overwrite some of the above) } IJ.showStatus(""); return result; } public void resetSensorCorrection(){ this.pixelCorrection=null; this.pathNames=null; } public void resetSensorCorrection(int sensorNum){ if ((this.pixelCorrection!=null) && (sensorNum<this.pixelCorrection.length) && (sensorNum>=0)) { this.pixelCorrection[sensorNum]=null; this.pathNames[sensorNum]=null; } } public void initSensorCorrection(){ int numLayers=7; int numChannels=this.fittingStrategy.distortionCalibrationData.getNumChannels(); // number of used channels this.pixelCorrection=new double [numChannels][][]; this.pathNames=new String[numChannels]; double [][] masks=this.fittingStrategy.distortionCalibrationData.calculateSensorMasks(); for (int i=0;i<this.pixelCorrection.length;i++){ this.pixelCorrection[i]=new double [numLayers][]; this.pathNames[i]=null; for (int n=0;n<numLayers;n++) this.pixelCorrection[i][n]=new double [masks[i].length]; for (int j=0;j<masks[i].length;j++) { this.pixelCorrection[i][0][j]=0.0; this.pixelCorrection[i][1][j]=0.0; this.pixelCorrection[i][2][j]=masks[i][j]; this.pixelCorrection[i][3][j]=1.0; this.pixelCorrection[i][4][j]=1.0; this.pixelCorrection[i][5][j]=1.0; } } } /* * Adds new correction to the current one with the result to the new one. If update, the old arrays are also modified/created */ public boolean applySensorCorrection( boolean update, boolean updateFlatField, double scale, double [][][] sensorXYCorr, DistortionCalibrationData distortionCalibrationData){ int numLayers=6; /// int decimate=distortionCalibrationData.eyesisCameraParameters.decimateMasks; /// int width= distortionCalibrationData.eyesisCameraParameters.sensorWidth; /// int height=distortionCalibrationData.eyesisCameraParameters.sensorHeight; /// if ((this.pixelCorrection!=null) && (this.pixelCorrectionDecimation!=decimate)){ /// IJ.showMessage("Error","Can not apply correction as the current correction and the new one have different decimations"); /// return false; /// } if ((this.pixelCorrection==null) && !update && !updateFlatField) return true; if (update){ /// this.pixelCorrectionDecimation=decimate; /// this.pixelCorrectionWidth=width; /// this.pixelCorrectionHeight=height; } if (this.pixelCorrection==null) { if (this.debugLevel>1) System.out.println("Initializing pixelCorrection array"); this.pixelCorrection=new double [sensorXYCorr.length][][]; this.pathNames=new String[sensorXYCorr.length]; for (int i=0;i<this.pixelCorrection.length;i++){ this.pixelCorrection[i]=null; this.pathNames[i]=null; } } if (this.pixelCorrection.length<sensorXYCorr.length){ // OK to update even if !update if (this.debugLevel>1) System.out.println("Increasing number of sensors in pixelCorrection array"); double [][][] tmp=new double[sensorXYCorr.length][][]; String [] tmpPaths=new String[sensorXYCorr.length]; for (int i=0;i<tmp.length;i++){ if (i<this.pixelCorrection.length){ tmp[i]=this.pixelCorrection[i]; tmpPaths[i]=this.pathNames[i]; } else { tmp[i]=null; tmpPaths[i]=null; } } this.pixelCorrection=tmp; this.pathNames=tmpPaths; } for (int i=0;i<sensorXYCorr.length;i++) if (sensorXYCorr[i]!=null){ boolean in6=sensorXYCorr[i].length==6; // was - 7 int indxR=in6?3:4; int indxG=in6?4:5; int indxB=in6?5:6; double [] sensorMask=in6?((fittingStrategy.distortionCalibrationData.sensorMasks==null)?null:fittingStrategy.distortionCalibrationData.sensorMasks[i]):sensorXYCorr[i][2]; if (this.pixelCorrection[i]==null) { if (update || updateFlatField) { this.pixelCorrection[i]=new double [numLayers][]; this.pixelCorrection[i][0]=sensorXYCorr[i][0]; this.pixelCorrection[i][1]=sensorXYCorr[i][1]; if (sensorMask!=null){ this.pixelCorrection[i][2]=sensorMask; } else { this.pixelCorrection[i][2]= new double[this.pixelCorrection[i][0].length]; for (int j=0;j<this.pixelCorrection[i][2].length;j++) this.pixelCorrection[i][2][j]=1.0; } if (sensorXYCorr[i].length>=7){ this.pixelCorrection[i][3]=sensorXYCorr[i][indxR]; this.pixelCorrection[i][4]=sensorXYCorr[i][indxG]; this.pixelCorrection[i][5]=sensorXYCorr[i][indxB]; } else { for (int n=3;n<numLayers;n++){ this.pixelCorrection[i][n]=new double[this.pixelCorrection[0].length]; for (int j=0;j<this.pixelCorrection[i][0].length;j++) this.pixelCorrection[i][n][j]=1.0; } } } } else { for (int j=0;j<sensorXYCorr[i][0].length;j++){ // removed - now it is already done /// sensorXYCorr[i][0][j]=this.pixelCorrection[i][0][j]+scale*sensorXYCorr[i][0][j]; /// sensorXYCorr[i][1][j]=this.pixelCorrection[i][1][j]+scale*sensorXYCorr[i][1][j]; if (scale==1.0) { // recovering from Double.NaN in old values - still do not know where it came from in the first place } else { if (!in6){ sensorXYCorr[i][2][j]=this.pixelCorrection[i][2][j]+scale*(sensorXYCorr[i][2][j]-this.pixelCorrection[i][2][j]); } sensorXYCorr[i][indxR][j]=this.pixelCorrection[i][3][j]+scale*(sensorXYCorr[i][indxR][j]-this.pixelCorrection[i][3][j]); sensorXYCorr[i][indxG][j]=this.pixelCorrection[i][4][j]+scale*(sensorXYCorr[i][indxG][j]-this.pixelCorrection[i][4][j]); sensorXYCorr[i][indxB][j]=this.pixelCorrection[i][5][j]+scale*(sensorXYCorr[i][indxB][j]-this.pixelCorrection[i][5][j]); } } if (update){ this.pixelCorrection[i][0]=sensorXYCorr[i][0]; this.pixelCorrection[i][1]=sensorXYCorr[i][1]; } if (updateFlatField){ if (!in6){ this.pixelCorrection[i][2]=sensorXYCorr[i][2]; } this.pixelCorrection[i][3]=sensorXYCorr[i][indxR]; this.pixelCorrection[i][4]=sensorXYCorr[i][indxG]; this.pixelCorrection[i][5]=sensorXYCorr[i][indxB]; } } } return true; } public String getSensorPath(int numSensor){ //<0 - first available; if ((this.pathNames == null) || (numSensor>=this.pathNames.length)) return null; if (numSensor>=0) return this.pathNames[numSensor]; for (int i=0;i<this.pathNames.length;i++) if ((this.pathNames[i]!=null) && (this.pathNames[i].length()>0)) return this.pathNames[i]; return null; } public void saveDistortionAsImageStack( DistortionCalibrationData distortionCalibrationData, // null OK CamerasInterface camerasInterface, // to save channel map String title, String path, boolean emptyOK){ int indexPeriod=path.indexOf('.',path.lastIndexOf(Prefs.getFileSeparator())); int indexSuffix=indexPeriod; String digits="0123456789"; for (int i=1;i<=2;i++) if (digits.indexOf(path.charAt(indexSuffix-1))>=0) indexSuffix--; // remove 1 or 2 digits before period boolean hadSuffix= (path.charAt(indexSuffix-1)=='-'); int numSubCameras=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0].length; for (int chNum=0;chNum<numSubCameras;chNum++) if (emptyOK || ((this.pixelCorrection!=null) && (chNum<this.pixelCorrection.length) && (this.pixelCorrection[chNum]!=null))) { String channelPath= (hadSuffix?path.substring(0,indexSuffix):(path.substring(0,indexPeriod)+"-"))+ String.format("%02d",chNum)+path.substring(indexPeriod); saveDistortionAsImageStack( distortionCalibrationData, camerasInterface, // to save channel map title, channelPath, chNum, emptyOK); } } public ImagePlus saveDistortionAsImageStack( DistortionCalibrationData distortionCalibrationData, // null OK CamerasInterface camerasInterface, // to save channel map String title, String path, int numSensor, boolean emptyOK){ ImagePlus imp=getDistortionAsImageStack( distortionCalibrationData, camerasInterface, // to save channel map title, numSensor, emptyOK); if (imp==null) return null; boolean realData= (this.pixelCorrection!=null) && (this.pixelCorrection[numSensor]!=null); FileSaver fs=new FileSaver(imp); String msg="Saving "+(realData?"":"EMPTY")+" sensor distortions to "+path; if (updateStatus) IJ.showStatus(msg); if (this.debugLevel>0) System.out.println(msg); fs.saveAsTiffStack(path); if (this.pathNames==null){ this.pathNames=new String[this.fittingStrategy.distortionCalibrationData.getNumChannels()]; for (int i=0;i<this.pathNames.length;i++) this.pathNames[i]=null; } this.pathNames[numSensor]=path; return imp; } // / int numChannels=this.fittingStrategy.distortionCalibrationData.getNumChannels(); // number of used channels // TODO: Currently saves data from Station 0 public ImagePlus getDistortionAsImageStack( DistortionCalibrationData distortionCalibrationData, // null OK - will use old way from fittingStrategy CamerasInterface camerasInterface, // to save channel map String title, int numSensor, boolean emptyOK){ if (distortionCalibrationData == null) { distortionCalibrationData = this.fittingStrategy.distortionCalibrationData; } int stationNumber=0; String [] titles={"X-corr","Y-corr","mask","R-vign","G-vign","B-vign"}; double [][] pixelCorr=null; if (!emptyOK &&((this.pixelCorrection==null) || (numSensor<0) || (numSensor>=this.pixelCorrection.length) || (this.pixelCorrection[numSensor]==null))) { String msg="Sensor correction data is not available"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if ((this.pixelCorrection!=null) && (numSensor>=0) && (numSensor<this.pixelCorrection.length)) pixelCorr=this.pixelCorrection[numSensor]; int width = distortionCalibrationData.eyesisCameraParameters.getSensorWidth(numSensor) / distortionCalibrationData.eyesisCameraParameters.getDecimateMasks(numSensor); int height = distortionCalibrationData.eyesisCameraParameters.getSensorHeight(numSensor) / distortionCalibrationData.eyesisCameraParameters.getDecimateMasks(numSensor); // int length=this.pixelCorrection[numSensor][0].length; // should be == width*height int length=width*height; float [][]pixels=new float [titles.length][length]; // dx, dy, sensor mask,v-r,v-g,v-b // assuming all sensors have the same dimension double [] mask=null; if (distortionCalibrationData.sensorMasks.length<=numSensor) return null; // no data if ((distortionCalibrationData.sensorMasks!=null) && (distortionCalibrationData.sensorMasks[numSensor]!=null)){ mask=distortionCalibrationData.sensorMasks[numSensor]; } for (int index=0;index<length;index++){ if (pixelCorr==null){ pixels[0][index]= 0.0f; pixels[1][index]= 0.0f; for (int n=3;n<pixels.length;n++) pixels[n][index]= 1.0f; // normalize? } else { pixels[0][index]= (float) pixelCorr[0][index]; pixels[1][index]= (float) pixelCorr[1][index]; for (int n=3;n<pixels.length;n++) pixels[n][index]= (float) pixelCorr[n][index]; } // get sensor mask here pixels[2][index]= (mask==null)? 1.0f:((float) mask[index]); } ImagePlus imp=null; ImageStack stack=new ImageStack(width,height); for (int n=0;n<pixels.length;n++) stack.addSlice(titles[n], pixels[n]); imp = new ImagePlus(title, stack); // set properties sufficient to un-apply distortions to the image // First - corrections EyesisSubCameraParameters subCam=distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[stationNumber][numSensor]; subCam.updateCartesian(); // recalculate other parameters double entrancePupilForward=distortionCalibrationData.eyesisCameraParameters.entrancePupilForward[stationNumber]; imp.setProperty("VERSION", "1.0"); imp.setProperty("comment_arrays", "Array corrections from acquired image to radially distorted, in pixels"); imp.setProperty("arraysSet", ""+(pixelCorr!=null)); // per-pixel arrays are not set, using 0.0 imp.setProperty("maskSet", ""+(mask!=null)); // per-pixel masks is not set, using 1.0 imp.setProperty("pixelCorrectionWidth", ""+distortionCalibrationData.eyesisCameraParameters.getSensorWidth(numSensor)); // this.pixelCorrectionWidth); imp.setProperty("pixelCorrectionHeight", ""+distortionCalibrationData.eyesisCameraParameters.getSensorHeight(numSensor)); imp.setProperty("pixelCorrectionDecimation", ""+distortionCalibrationData.eyesisCameraParameters.getDecimateMasks(numSensor)); imp.setProperty("comment_decimation", "when decimation use integer divide to find the index, corection values are in non-decimated pixels"); imp.setProperty("distortion_formula", "(normalized by distortionRadius in mm) Rdist/R=A8*R^7+A7*R^6+A6*R^5+A5*R^4+A*R^3+B*R^2+C*R+(1-A6-A7-A6-A5-A-B-C)"); imp.setProperty("distortionRadius", ""+subCam.distortionRadius); imp.setProperty("distortionRadius_unuts", "mm"); imp.setProperty("focalLength", ""+subCam.focalLength); imp.setProperty("focalLength_units", "mm"); imp.setProperty("pixelSize", ""+subCam.pixelSize); imp.setProperty("pixelSize_units", "um"); imp.setProperty("distortionA8", ""+subCam.distortionA8); imp.setProperty("distortionA7", ""+subCam.distortionA7); imp.setProperty("distortionA6", ""+subCam.distortionA6); imp.setProperty("distortionA5", ""+subCam.distortionA5); imp.setProperty("distortionA", ""+subCam.distortionA); imp.setProperty("distortionB", ""+subCam.distortionB); imp.setProperty("distortionC", ""+subCam.distortionC); imp.setProperty("comment_px0_py0", "lens center on the sensor, in pixels"); imp.setProperty("px0", ""+subCam.px0); imp.setProperty("py0", ""+subCam.py0); imp.setProperty("comment_azimuth", "lens center azimuth, CW from top, degrees"); imp.setProperty("height", ""+subCam.height); imp.setProperty("comment_elevation", "lens elevation from horizontal, positive - above horizon, degrees"); imp.setProperty("elevation", ""+subCam.theta); imp.setProperty("comment_roll", "lens rotation around the lens axis. Positive - CW looking to the target, degrees"); imp.setProperty("roll", ""+subCam.psi); imp.setProperty("comment_cartesian", "Use cartesian coordinates for the sensor in the camera CS (forward, right,aheading), instead of (radius, azimuth, heading)"); imp.setProperty("cartesian", ""+subCam.cartesian); // cartesian parameters imp.setProperty("comment_forward", "lens forward (towards target) displacement in the camera CS"); imp.setProperty("forward", ""+subCam.forward); imp.setProperty("comment_right", "lens right (looking towards target) displacement in the camera CS"); imp.setProperty("right", ""+subCam.right); imp.setProperty("comment_aheading", "lens axis horizontal direction, degrees. Positive - CW from the target (looking from top)"); imp.setProperty("aheading", ""+subCam.heading); // cylindrical parameters imp.setProperty("azimuth", ""+subCam.azimuth); imp.setProperty("comment_radius", "lens center distance from the camera vertical axis, mm"); imp.setProperty("radius", ""+subCam.radius); imp.setProperty("comment_height", "lens center vertical position from the head center, mm"); imp.setProperty("comment_heading", "lens heading - added to azimuth"); imp.setProperty("heading", ""+subCam.phi); imp.setProperty("comment_channel", "number of the sensor (channel) in the camera"); imp.setProperty("channel", ""+numSensor); imp.setProperty("comment_subcamera", "number of the subcamera with individual IP, starting with 0"); if (camerasInterface != null) { subCam.subcamera = camerasInterface.getSubCamera(numSensor); subCam.sensor_port = camerasInterface.getSensorPort(numSensor); subCam.subchannel = camerasInterface.getSubChannel(numSensor); } imp.setProperty("subcamera", ""+subCam.subcamera); imp.setProperty("sensor_port",""+subCam.sensor_port); imp.setProperty("comment_subchannel", "number of the sensor port on a subcamera (0..2)"); imp.setProperty("subchannel", ""+subCam.subchannel); imp.setProperty("comment_entrancePupilForward", "entrance pupil distance from the azimuth/radius/height, outwards in mm"); imp.setProperty("entrancePupilForward", ""+entrancePupilForward); // currently global, decoders will use per-sensor imp.setProperty("comment_defects", "Sensor hot/cold pixels list as x:y:difference"); imp.setProperty("comment_lensDistortionModel", "Integer specifying lens distrotion model (0 - radial)"); imp.setProperty("lensDistortionModel", ""+subCam.lensDistortionModel); for (int i=0;i<subCam.r_xy.length;i++){ imp.setProperty("r_xy_"+i+"_x",subCam.r_xy[i][0]+""); imp.setProperty("r_xy_"+i+"_y",subCam.r_xy[i][1]+""); } for (int i=0;i<subCam.r_od.length;i++){ imp.setProperty("r_od_"+i+"_o",subCam.r_od[i][0]+""); imp.setProperty("r_od_"+i+"_d",subCam.r_od[i][1]+""); } if (subCam.defectsXY!=null){ StringBuffer sb = new StringBuffer(); for (int i=0;i<subCam.defectsXY.length;i++){ if (sb.length()>0) sb.append(" "); sb.append(subCam.defectsXY[i][0]+":"+subCam.defectsXY[i][1]+":"+subCam.defectsDiff[i]); } imp.setProperty("defects", sb.toString()); // } else { // imp.setProperty("defects", null); } //camerasInterface, numSensor (new JP46_Reader_camera(false)).encodeProperiesToInfo(imp); imp.getProcessor().resetMinAndMax(); return imp; } public void setDistortionFromImageStack( DistortionCalibrationData distortionCalibrationData, // null OK EyesisCameraParameters eyesisCameraParameters, // null OK String path, boolean overwriteExtrinsic, boolean overwriteDistortion){ int indexPeriod=path.indexOf('.',path.lastIndexOf(Prefs.getFileSeparator())); if (eyesisCameraParameters == null) { eyesisCameraParameters = fittingStrategy.distortionCalibrationData.eyesisCameraParameters; } if (distortionCalibrationData == null) { distortionCalibrationData = fittingStrategy.distortionCalibrationData; } int numSubCameras=distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0].length; for (int chNum=0;chNum<numSubCameras;chNum++){ String channelPath=path.substring(0,indexPeriod-2)+String.format("%02d",chNum)+path.substring(indexPeriod); try { // disable here for now setDistortionFromImageStack( distortionCalibrationData, channelPath, chNum, false, overwriteExtrinsic, overwriteDistortion); } catch (Exception e) { System.out.println("setDistortionFromImageStack(): " + e.toString()); e.printStackTrace(); } } } public void setDistortionFromImageStack( DistortionCalibrationData distortionCalibrationData, String path, int numSensor, boolean reportProblems, boolean overwriteExtrinsic, boolean overwriteDistortion){ Opener opener=new Opener(); ImagePlus imp=opener.openImage("", path); if (imp==null) { if (!reportProblems) return; String msg="Failed to read sensor calibration data file "+path; IJ.showMessage("Error",msg); System.out.println(msg); throw new IllegalArgumentException (msg); } if (this.debugLevel>0) System.out.println("Read "+path+" as a sensor calibration data"); (new JP46_Reader_camera(false)).decodeProperiesFromInfo(imp); setDistortionFromImageStack(distortionCalibrationData, imp, numSensor, overwriteExtrinsic, overwriteDistortion); this.pathNames[numSensor]=path; } //TODO: look more after testing. Currently all station parameters are set from the sensor images, may be minor differences public void setDistortionFromImageStack( DistortionCalibrationData distortionCalibrationData, ImagePlus imp, int numSensor, boolean overwriteExtrinsic, boolean overwriteDistortion){ if (distortionCalibrationData == null) { distortionCalibrationData = this.fittingStrategy.distortionCalibrationData; } // int corrX=0,corrY=1, int corrMask=2; if (numSensor<0) { System.out.println("setDistortionFromImageStack(): Tried to read negative channel"); return; } // System.out.println("setDistortionFromImageStack(): processing channel channel "+numSensor); if (imp == null){ String msg="Distortions image is null"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } String [] requiredProperties={ "pixelCorrectionWidth", "pixelCorrectionHeight", "pixelCorrectionDecimation", "distortionRadius", "focalLength", "pixelSize", // "distortionA8", // "distortionA7", // "distortionA6", "distortionA5", "distortionA", "distortionB", "distortionC", "px0", "py0"}; for (int i=0; i<requiredProperties.length;i++) if (imp.getProperty(requiredProperties[i])==null){ String msg="Required property "+requiredProperties[i]+" is not defined in "+imp.getTitle(); IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } if (imp.getStackSize()<3){ String msg="Expecting >=3 slices, got "+imp.getStackSize(); IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } ImageStack stack = imp.getStack(); float [][] pixels =new float[stack.getSize()][]; for (int i=0;i<pixels.length;i++) pixels[i]= (float[]) stack.getPixels(i+1); int numSubCameras=distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0].length; if (numSensor>=numSubCameras){ String msg="Loaded calibration channel number "+numSensor+"is higher than maximal in the system "+(numSubCameras-1); IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } // System.out.println("setDistortionFromImageStack(): processing channel channel "+numSensor); EyesisSubCameraParameters subCam; EyesisCameraParameters cam= distortionCalibrationData.eyesisCameraParameters; if ((distortionCalibrationData!=null) && (distortionCalibrationData.eyesisCameraParameters!=null)){ // Now it is the same /// distortionCalibrationData.eyesisCameraParameters.decimateMasks=this.pixelCorrectionDecimation; /// distortionCalibrationData.eyesisCameraParameters.sensorWidth= this.pixelCorrectionWidth; /// distortionCalibrationData.eyesisCameraParameters.sensorHeight=this.pixelCorrectionHeight; } for (int stationNumber=0; stationNumber < distortionCalibrationData.eyesisCameraParameters.numStations; stationNumber++){ subCam=distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[stationNumber][numSensor]; distortionCalibrationData.eyesisCameraParameters.setSensorWidth( numSensor, Integer.parseInt ((String) imp.getProperty("pixelCorrectionWidth"))); distortionCalibrationData.eyesisCameraParameters.setSensorHeight( numSensor, Integer.parseInt ((String) imp.getProperty("pixelCorrectionHeight"))); distortionCalibrationData.eyesisCameraParameters.setDecimateMasks(numSensor, Integer.parseInt ((String) imp.getProperty("pixelCorrectionDecimation"))); subCam.distortionRadius= Double.parseDouble((String) imp.getProperty("distortionRadius")); subCam.focalLength= Double.parseDouble((String) imp.getProperty("focalLength")); subCam.pixelSize= Double.parseDouble((String) imp.getProperty("pixelSize")); if (imp.getProperty("distortionA8")!=null) { subCam.distortionA8= Double.parseDouble((String) imp.getProperty("distortionA8")); } else subCam.distortionA8=0.0; if (imp.getProperty("distortionA7")!=null) { subCam.distortionA7= Double.parseDouble((String) imp.getProperty("distortionA7")); } else subCam.distortionA7=0.0; if (imp.getProperty("distortionA6")!=null) { subCam.distortionA6= Double.parseDouble((String) imp.getProperty("distortionA6")); } else subCam.distortionA6=0.0; subCam.distortionA5= Double.parseDouble((String) imp.getProperty("distortionA5")); subCam.distortionA= Double.parseDouble((String) imp.getProperty("distortionA")); subCam.distortionB= Double.parseDouble((String) imp.getProperty("distortionB")); subCam.distortionC= Double.parseDouble((String) imp.getProperty("distortionC")); subCam.px0= Double.parseDouble((String) imp.getProperty("px0")); subCam.py0= Double.parseDouble((String) imp.getProperty("py0")); if (imp.getProperty("azimuth") !=null) subCam.azimuth= Double.parseDouble((String) imp.getProperty("azimuth")); if (imp.getProperty("radius") !=null) subCam.radius= Double.parseDouble((String) imp.getProperty("radius")); if (imp.getProperty("height") !=null) subCam.height= Double.parseDouble((String) imp.getProperty("height")); if (imp.getProperty("entrancePupilForward")!=null) cam.entrancePupilForward[stationNumber]= Double.parseDouble((String) imp.getProperty("entrancePupilForward")); if (imp.getProperty("heading") !=null) subCam.phi= Double.parseDouble((String) imp.getProperty("heading")); if (imp.getProperty("elevation")!=null) subCam.theta= Double.parseDouble((String) imp.getProperty("elevation")); if (imp.getProperty("roll")!=null) subCam.psi= Double.parseDouble((String) imp.getProperty("roll")); if (imp.getProperty("forward") !=null) subCam.forward= Double.parseDouble((String) imp.getProperty("forward")); if (imp.getProperty("right") !=null) subCam.right= Double.parseDouble((String) imp.getProperty("right")); if (imp.getProperty("aheading") !=null) subCam.heading= Double.parseDouble((String) imp.getProperty("aheading")); if (imp.getProperty("cartesian") !=null) { subCam.cartesian= Boolean.parseBoolean((String) imp.getProperty("cartesian")); subCam.updateCartesian(); // recalculate other parameters (they may or may nort be provided } else { subCam.cartesian = false; } // Update intrinsic image parameters this.lensDistortionParameters.pixelSize=subCam.pixelSize; this.lensDistortionParameters.distortionRadius=subCam.distortionRadius; if (imp.getProperty("defects")!=null) { String sDefects=(String) imp.getProperty("defects"); String [] asDefects=sDefects.trim().split(" "); subCam.defectsXY=new int [asDefects.length][2]; subCam.defectsDiff=new double [asDefects.length]; for (int i=0;i<asDefects.length;i++) { String [] stDefect=asDefects[i].split(":"); subCam.defectsXY[i][0]=Integer.parseInt(stDefect[0]); subCam.defectsXY[i][1]=Integer.parseInt(stDefect[1]); subCam.defectsDiff[i]=Double.parseDouble(stDefect[2]); } } else { subCam.defectsXY=null; subCam.defectsDiff=null; } // non-radial if (imp.getProperty("lensDistortionModel") !=null) subCam.lensDistortionModel= Integer.parseInt((String) imp.getProperty("lensDistortionModel")); subCam.setDefaultNonRadial(); for (int i=0;i<subCam.r_xy.length;i++) { if (imp.getProperty("r_xy_"+i+"_x") !=null) subCam.r_xy[i][0]= Double.parseDouble((String) imp.getProperty("r_xy_"+i+"_x")); if (imp.getProperty("r_xy_"+i+"_y") !=null) subCam.r_xy[i][1]= Double.parseDouble((String) imp.getProperty("r_xy_"+i+"_y")); } for (int i=0;i<subCam.r_od.length;i++) { if (imp.getProperty("r_od_"+i+"_o") !=null) subCam.r_od[i][0]= Double.parseDouble((String) imp.getProperty("r_od_"+i+"_o")); if (imp.getProperty("r_od_"+i+"_d") !=null) subCam.r_od[i][1]= Double.parseDouble((String) imp.getProperty("r_od_"+i+"_d")); } if (imp.getProperty("subcamera") !=null) subCam.subcamera= Integer.parseInt((String) imp.getProperty("subcamera")); if (imp.getProperty("sensor_port") !=null) subCam.sensor_port= Integer.parseInt((String) imp.getProperty("sensor_port")); if (imp.getProperty("subchannel") !=null) subCam.subchannel= Integer.parseInt((String) imp.getProperty("subchannel")); } for (int imgNum=0;imgNum < distortionCalibrationData.getNumImages();imgNum++){ int imageSubCam= distortionCalibrationData.getImageSubcamera(imgNum); int stationNumber=distortionCalibrationData.getImageStation(imgNum); if (imageSubCam==numSensor){ // vector from the data we just set double [] parVector= distortionCalibrationData.eyesisCameraParameters.getParametersVector(stationNumber,imageSubCam); if (overwriteExtrinsic) distortionCalibrationData.setSubcameraParameters(parVector,imgNum); else if (overwriteDistortion) distortionCalibrationData.setIntrinsicParameters(parVector,imgNum); } } // now read the calibration data and mask if (this.pixelCorrection==null) { this.pixelCorrection=new double [numSubCameras][][]; this.pathNames=new String [numSubCameras]; for (int i=0;i<this.pixelCorrection.length;i++){ this.pixelCorrection[i]=null; this.pathNames[i]=null; } } if (numSensor>=this.pixelCorrection.length){ // increase number of elements double [][][] tmp=this.pixelCorrection.clone(); String [] tmpPaths=this.pathNames.clone(); this.pixelCorrection=new double[numSensor+1][][]; this.pathNames=new String[numSensor+1]; for (int i=0;i<this.pixelCorrection.length;i++) if (i<tmp.length){ this.pixelCorrection[i]=tmp[i]; this.pathNames[i]=tmpPaths[i]; }else { this.pixelCorrection[i]=null; this.pathNames[i]=null; } } int numLayers=6; //corr-x, corr-y,mask, ff-R, ff-G, ff-b if (numLayers<pixels.length) numLayers=pixels.length; // for the future? // this.pixelCorrection[numSensor]=new double [pixels.length] [pixels[0].length]; this.pixelCorrection[numSensor]=new double [numLayers][pixels[0].length]; for (int i= 0;i<this.pixelCorrection[numSensor][0].length;i++){ for (int n=0;n<pixels.length;n++) this.pixelCorrection[numSensor][n][i]=pixels[n][i]; // mask will go to two places } if (pixels.length<numLayers){ for (int i= 0;i<this.pixelCorrection[numSensor][0].length;i++){ for (int n=pixels.length;n<numLayers;n++) this.pixelCorrection[numSensor][n][i]=1.0; // default ff if no data is available } } // now mask boolean defined=false; for (int i=0;i<pixels[2].length;i++) if ((pixels[2][i]!=0.0) && (pixels[2][i]!=1.0)){ defined=true; break; } // System.out.println("setDistortionFromImageStack(): defined="+defined ); if (defined) { if (distortionCalibrationData.sensorMasks==null) { distortionCalibrationData.sensorMasks=new double [numSubCameras][]; for (int i=0;i<distortionCalibrationData.sensorMasks.length;i++) distortionCalibrationData.sensorMasks[i]=null; // System.out.println("setDistortionFromImageStack(): created distortionCalibrationData.sensorMasks["+numSubCameras+"] of null-s" ); } if (numSensor>=distortionCalibrationData.sensorMasks.length){ // increase number of elements double [][] tmp=distortionCalibrationData.sensorMasks; distortionCalibrationData.sensorMasks=new double[numSensor+1][]; for (int i=0;i<distortionCalibrationData.sensorMasks.length;i++) if (i<tmp.length)distortionCalibrationData.sensorMasks[i]=tmp[i]; else distortionCalibrationData.sensorMasks[i]=null; } if (distortionCalibrationData.sensorMasks[numSensor]==null){ distortionCalibrationData.sensorMasks[numSensor]=new double[pixels[corrMask].length]; // System.out.println("setDistortionFromImageStack(): created distortionCalibrationData.sensorMasks["+numSensor+"] of ["+pixels[corrMask].length+"]" ); } for (int i= 0;i<distortionCalibrationData.sensorMasks[numSensor].length;i++) // null pointer distortionCalibrationData.sensorMasks[numSensor][i]=pixels[corrMask][i]; } } /** * Accumulate per-sensor grid R,G,B intensities using current sensor flat-field values * @param serNumber - fitting series number to select images (-1 - all enabled) * @param sensorMasks "pessimistic" masks to use only center (low-vignetting) part of each sensor (at least on the first runs?) * @param minContrast - minimal contrast to consider a node * @param threshold - not yet used - disregard grid nodes with low data - in the end * @param interpolate - interpolate sensor data * @param maskThresholdOcclusion suspect occlusion only if grid is missing in the area where sensor mask is above this threshold * @param expandOcclusion - shrink defined grid on image by this steps - to handle occlusion by rollers * @param fadeOcclusion - fade shrank occlusion border * @param ignoreSensorFlatField - ignorfe previously calculated sensors flat-field calibration * @return */ public double [][][][] calculateGridFlatField( int serNumber, double [][] sensorMasks, double minContrast, double threshold, boolean interpolate, double maskThresholdOcclusion, int expandOcclusion, double fadeOcclusion, boolean ignoreSensorFlatField){ // TODO: add standard weight function used elsethere. int indexContrast=2; boolean [] selectedImages=fittingStrategy.selectedImages(serNumber); // negative series number OK - will select all enabled int gridHeight=this.patternParameters.gridGeometry.length; int gridWidth=this.patternParameters.gridGeometry[0].length; // was not here /// this.pixelCorrectionDecimation=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks; /// this.pixelCorrectionWidth= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth; /// this.pixelCorrectionHeight= fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight; int maxChannel=0; int numStations=this.patternParameters.getNumStations(); for (int numImg=0;numImg<fittingStrategy.distortionCalibrationData.gIP.length;numImg++) if (selectedImages[numImg]){ if (fittingStrategy.distortionCalibrationData.gIP[numImg].channel>maxChannel) maxChannel=fittingStrategy.distortionCalibrationData.gIP[numImg].channel; } double [][][][] sensorGrids=new double [numStations][maxChannel+1][][]; //{alpha, red,green, blue} for (int ns=0;ns<sensorGrids.length;ns++) for (int n=0;n<sensorGrids[ns].length;n++) sensorGrids[ns][n]=null; // For each sensor separately accumulate grid intensity using current sensor flat field calibration for (int numImg=0;numImg<fittingStrategy.distortionCalibrationData.gIP.length;numImg++) if (selectedImages[numImg]) { int channel=fittingStrategy.distortionCalibrationData.gIP[numImg].channel; int station=fittingStrategy.distortionCalibrationData.gIP[numImg].getStationNumber(); if (sensorMasks[channel]==null) continue; if (sensorGrids[station][channel]==null){ // null pointer sensorGrids[station][channel]=new double [4][gridHeight*gridWidth]; //{alpha, red,green, blue} for (int c=0;c<sensorGrids[station][channel].length;c++){ for (int i=0;i<sensorGrids[station][channel][0].length;i++) sensorGrids[station][channel][c][i]=0.0; } } double [][] pixelsXY=fittingStrategy.distortionCalibrationData.gIP[numImg].pixelsXY; if ((pixelsXY.length<1) || (pixelsXY[0].length<6)){ if (this.debugLevel>0) System.out.println("No flat-field data in image #"+numImg+ " - "+fittingStrategy.distortionCalibrationData.gIP[numImg].path+ " pixelsXY.length="+pixelsXY.length+ " pixelsXY[0].length="+((pixelsXY.length==0)?"nan": pixelsXY[0].length)); continue; } int [][] pixelsUV=fittingStrategy.distortionCalibrationData.gIP[numImg].pixelsUV; double [] defaultVector={0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; // double [] sensorMask=sensorMasks[channel]; // detect if there is any occlusion (i.e. by goniometer rollers) // double [] green=new double [pixelsXY.length]; boolean [] bMask=new boolean [gridHeight*gridWidth]; double [] mask=new double[bMask.length]; for (int i=0;i<bMask.length;i++){ bMask[i]=false; mask[i]=0.0; } for (int i=0;i<pixelsXY.length;i++){ double [] xyzmrgb=patternParameters.getXYZM( pixelsUV[i][0], pixelsUV[i][1], false, station); if (xyzmrgb!=null){ int index=patternParameters.getGridIndex(pixelsUV[i][0], pixelsUV[i][1]); bMask[index]=(pixelsXY[i][indexContrast]>=minContrast); mask[index]=interpolateMask ( channel, sensorMasks[channel], pixelsXY[i][0], pixelsXY[i][1]); } } boolean [] occlusionMask=new boolean[bMask.length]; for (int i=0;i<occlusionMask.length;i++){ occlusionMask[i]=false; } boolean occlusion=false; for (int i=1;i<(gridHeight-1);i++){ for (int j=1;j<(gridWidth-1);j++){ int index=i*gridWidth+j; if (bMask[index]){ if (( !bMask[(i-1)*gridWidth+j] || !bMask[(i+1)*gridWidth+j] || !bMask[i* gridWidth+j-1] || !bMask[i* gridWidth+j+1]) && (mask[index]>=maskThresholdOcclusion) ){ occlusionMask[index]=true; occlusion=true; } } } } if (occlusion){ for (int n=0;n<expandOcclusion;n++){ // expand boolean [] bMaskPrevious=occlusionMask.clone(); for (int i=1;i<(gridHeight-1);i++){ for (int j=1;j<(gridWidth-1);j++){ if (!occlusionMask[i*gridHeight+j]){ if ( bMaskPrevious[(i-1)*gridWidth+j] || bMaskPrevious[(i+1)*gridWidth+j] || bMaskPrevious[i* gridWidth+j-1] || bMaskPrevious[i* gridWidth+j+1]){ occlusionMask[i*gridWidth+j]=true; } } } } } double [] maskNonOccluded=new double [occlusionMask.length]; for (int i=0;i<maskNonOccluded.length;i++) maskNonOccluded[i]=occlusionMask[i]?0.0:1.0; if (fadeOcclusion>0.0){ (new DoubleGaussianBlur() ).blurDouble( maskNonOccluded, gridWidth, gridHeight, fadeOcclusion, fadeOcclusion, 0.01); } if (fadeOcclusion>=0.0 )for (int i=0;i<mask.length;i++){ double d=2.0*(maskNonOccluded[i]-0.5); mask[i]*=(!occlusionMask[i] && (d>0))?(d*d):0.0; } } for (int i=0;i<pixelsXY.length;i++){ double [] xyzmrgb=patternParameters.getXYZM( pixelsUV[i][0], pixelsUV[i][1], false, station); if (xyzmrgb==null) continue; // out of grid double [] vector=ignoreSensorFlatField?defaultVector: ((interpolate)? interpolateCorrectionVector ( channel, pixelsXY[i][0], pixelsXY[i][1]): getCorrectionVector ( channel, pixelsXY[i][0], pixelsXY[i][1])) ; int index=patternParameters.getGridIndex(pixelsUV[i][0], pixelsUV[i][1]); double weight=mask[index]; sensorGrids[station][channel][0][index]+=weight; for (int c=0;c<3;c++)if (vector[c+3]>0.0){ sensorGrids[station][channel][c+1][index]+=weight*pixelsXY[i][c+3]/vector[c+3]; } } } for (int station=0;station<sensorGrids.length;station++){ for (int channel=0;channel<sensorGrids[station].length; channel++) if (sensorGrids[station][channel]!=null){ if (this.pixelCorrection[channel]==null) { sensorGrids[station][channel]=null; } else { for (int i=0;i<sensorGrids[station][channel][0].length;i++){ if (sensorGrids[station][channel][0][i]<threshold) { for (int j=0;j<sensorGrids[station][channel].length;j++) sensorGrids[station][channel][j][i]=0.0; } else { for (int j=1;j<sensorGrids[station][channel].length;j++) sensorGrids[station][channel][j][i]/=sensorGrids[station][channel][0][i]; } } } } } return sensorGrids; } public double [] getCorrectionVector( int chnNum, double px, double py){ int sensorCorrWidth= getSensorCorrWidth(chnNum); int indexXY=((int) Math.floor(px/getDecimateMasks(chnNum))) + ((int) Math.floor(py/getDecimateMasks(chnNum)))*sensorCorrWidth; double []vector=new double[this.pixelCorrection[chnNum].length]; for (int i=0;i<vector.length;i++) vector[i]=this.pixelCorrection[chnNum][i][indexXY]; return vector; } /** * Calculate color flat-field data for the pattern grid, calculate pattern grid mask (alpha) * @param referenceStation - station number for unity target brightness * @param flatFields partial, per-station, per-sensor pattern flat-field data * @param shrinkForMatching shrink pattern mask for calculating pattern average (removing unreliable borders) * @param resetMask reset pattern mask to default before (re)-calculating mask * @param maxDiffNeighb maximal relative difference between neghbor nodes (ignoring off-grid) * @param shrinkMask shrink result mask * @param fadeMask smooth fade the alpha on the pattern edge, keep zeros zeros * @return {alpha, r,g,b,number of images used} for each view group separately */ public double [][][][] combineGridFlatField( int referenceStation, double [][][][] flatFields, double shrinkForMatching, boolean resetMask, double maxDiffNeighb, // maximal allowed relative difference between neighbour nodes (relative), 0 - do not filter any int shrinkMask, // shrink result mask double fadeMask ){ int maskIndex=3; // if (resetMask) patternParameters.calculateGridGeometry(false); if (resetMask) patternParameters.calculateGridGeometryAndPhotometric(false); double [][][] gridGeometry= patternParameters.getGeometry(); int [] viewMap= patternParameters.getViewMap(); int gridHeight=gridGeometry.length; int gridWidth=gridGeometry[0].length; int numStations=patternParameters.getNumStations(); int numViews=patternParameters.getNumViews(); double [][][][] viewPatterns=new double [numStations][numViews][][]; double [][][] gridMask= new double[numStations][numViews][gridWidth*gridHeight]; double [][] scaleIndividual=new double[flatFields[referenceStation].length][3]; // scale individual sensor patters before averaging for (int station=0;station<numStations;station++){ for (int numView=0;numView<numViews;numView++){ viewPatterns[station][numView]=null; // double [] gridMask= new double[gridWidth*gridHeight]; for (int v=0;v<gridHeight;v++) for (int u=0;u<gridWidth;u++) gridMask[station][numView][u+v*gridWidth]=(gridGeometry[v][u]!=null)?gridGeometry[v][u][maskIndex]:0.0; if (shrinkForMatching>0){ (new DoubleGaussianBlur() ).blurDouble(gridMask[station][numView], gridWidth, gridHeight, shrinkForMatching, shrinkForMatching, 0.01); for (int i=0;i<gridMask[station][numView].length;i++){ double d=2.0*(gridMask[station][numView][i]-0.5); gridMask[station][numView][i]=(d>0)?(d*d):(0.0); } } for (int v=0;v<gridHeight;v++) for (int u=0;u<gridWidth;u++){ if ((gridGeometry[v][u]==null) || (gridGeometry[v][u][maskIndex]<=0.0)) gridMask[station][numView][u+v*gridWidth]=0.0; } if (this.debugLevel>2){ this.SDFA_INSTANCE.showArrays(gridMask[station][numView], gridWidth, gridHeight, "MATCH_MASK"+numView); } // double [][] scaleIndividual=new double[flatFields[station].length][3]; // scale individual sensor patters before averaging // for (int numSensor=0;numSensor<flatFields.length; numSensor++ ) if (flatFields[numSensor]!=null){ // process only sensors from the same view of the target (i.e. 0 - eyesis head, 1 - eyesis bottom) if (station==referenceStation) { int numUsedSensors=0; for (int numSensor=0;numSensor<flatFields[station].length; numSensor++ ) if ((flatFields[station][numSensor]!=null) && (viewMap[numSensor]==numView)){ numUsedSensors++; double [] weightedSums={0.0,0.0,0.0}; double sumWeights=0; for (int i=0;i<flatFields[station][numSensor][0].length;i++){ if ((gridMask[station][numView][i]>0.0) && (flatFields[station][numSensor][0][i]>1.0)){ // more than one overlapping image double weight=flatFields[station][numSensor][0][i]*gridMask[station][numView][i]; sumWeights+=weight; for (int c=0;c<weightedSums.length;c++) weightedSums[c]+=weight*flatFields[station][numSensor][c+1][i]; } } for (int c=0;c<weightedSums.length;c++){ scaleIndividual[numSensor][c]=patternParameters.averageRGB[c]*sumWeights/weightedSums[c]; if (this.debugLevel>2){ System.out.println("combineGridFlatField(): scaleIndividual["+numSensor+"]["+c+"]="+scaleIndividual[numSensor][c]); } } } if (numUsedSensors==0){ System.out.println("No data for target view #"+numView+" reference station ="+referenceStation); continue; } } } } for (int station=0;station<numStations;station++){ for (int numView=0;numView<numViews;numView++){ // double [][] combinedPattern=new double [5][gridWidth*gridHeight]; viewPatterns[station][numView]=new double [5][gridWidth*gridHeight]; double [][] combinedPattern=viewPatterns[station][numView]; for (int i=0;i<combinedPattern[0].length;i++){ double sumWeights=0; double [] weightedSums={0.0,0.0,0.0}; for (int numSensor=0;numSensor<flatFields[station].length; numSensor++ ) if ((flatFields[station][numSensor]!=null) && (viewMap[numSensor]==numView)){ double weight=flatFields[station][numSensor][0][i]; sumWeights+=weight; for (int c=0;c<weightedSums.length;c++) weightedSums[c]+=weight*flatFields[station][numSensor][c+1][i]*scaleIndividual[numSensor][c]; } combinedPattern[4][i]=sumWeights; // just for debugging - no, actually used? - number of images used for this grid node for (int c=0;c<weightedSums.length;c++){ combinedPattern[c+1][i]=(sumWeights>0.0)?(weightedSums[c]/sumWeights):0.0; } } /* } } for (int station=0;station<viewPatterns.length;station++){ for (int numView=0;numView<viewPatterns[station].length;numView++){ double [][] combinedPattern=viewPatterns[station][numView]; */ // double [] gridMask[station][numView]= new double[gridWidth*gridHeight]; // calculate final mask for (int v=0;v<gridHeight;v++) for (int u=0;u<gridWidth;u++) gridMask[station][numView][u+v*gridWidth]=(gridGeometry[v][u]!=null)?gridGeometry[v][u][maskIndex]:0.0; if (maxDiffNeighb>0.0) { // throw away bad (having sharp gradients) nodes int expWidth=gridWidth+2; int expHeight=gridHeight+2; double [] expandedGrid=new double [expWidth*expHeight]; boolean [] enabled=new boolean[expandedGrid.length]; for (int v=0;v<expHeight;v++) for (int u=0;u<expWidth;u++){ int index=u+expWidth*v; if ((u==0) || (v==0) || (u==(expWidth-1)) || (v==(expHeight-1))){ expandedGrid[index]=0.0; enabled[index]=false; } else { int indexSrc=(u-1)+gridWidth*(v-1); expandedGrid[index]=(combinedPattern[1][indexSrc]+combinedPattern[2][indexSrc]+combinedPattern[3][indexSrc])/3.0; // average value; enabled[index]=gridMask[station][numView][indexSrc]>0.0; } } boolean [] badNodes=enabled.clone(); int [] dirs={ -expWidth-1,-expWidth,-expWidth+1, 1, expWidth+1, expWidth, expWidth-1, -1}; int numBadOnTheBorder=1; // just to make while(true) happy int minNeighb=3; // remove nodes with less than 3 neighbors while (numBadOnTheBorder>0){ // build/update badNodes array numBadOnTheBorder=0; int numBad=0; double [] diffs = new double [8]; int [] indices=new int [8]; for (int i=0;i<8;i++) { diffs[i]= -1.0; // diff==0 on isolated pair? indices[i]=-1; } for (int index=0;index<badNodes.length;index++) if (badNodes[index]){ int numNeighb=0; double maxDiff=0.0; for (int dir=0;dir<dirs.length;dir++) { int index1=index+dirs[dir]; if (enabled[index1]) { numNeighb++; double d=2.0*Math.abs((expandedGrid[index1]-expandedGrid[index])/(expandedGrid[index]+expandedGrid[index1])); if (maxDiff<d) maxDiff=d; } } if ((maxDiff<((maxDiffNeighb*numNeighb)/minNeighb)) && (numNeighb>=minNeighb)){ //more neighbors - more likely to keep badNodes[index]=false; // rehabilitate node } else { numBad++; if (numNeighb<8) { // do nothing if bad node is inside - it may be removed in the next passes numBadOnTheBorder++; if (maxDiff>diffs[numNeighb]){ diffs[numNeighb]=maxDiff; indices[numNeighb]=index; } } } } if (this.debugLevel>1) System.out.println("combineGridFlatField(): "+numBad+" bad nodes, "+numBadOnTheBorder+" of them on the border"); if (numBadOnTheBorder==0) break; // nothing to remove - break from the while(true) loop // find bad node with least enabled neighbors - there will be one at least for (int n=0;n<8;n++){ if (indices[n]>=0){ enabled[indices[n]]=false; // disable this node badNodes[indices[n]]=false; // and remove from bad nodes (it is dead now) // Any orphans around (were not bad, but now have to few neighbors) for (int dir=0;dir<dirs.length;dir++) { int index1=indices[n]+dirs[dir]; if (enabled[index1]) { badNodes[index1]=true; } } break; } } } // shrink enabled cells by shrinkMask for (int n=0;n<shrinkMask;n++){ for (int i=0;i<badNodes.length;i++) badNodes[i]=false; for (int v=1;v<(expHeight-1);v++) for (int u=1;u<(expWidth-1);u++){ int index=u+expWidth*v; badNodes[index]=!enabled[index+1] || !enabled[index-1] || !enabled[index+expWidth] || !enabled[index-expWidth]; } for (int i=0;i<badNodes.length;i++) if (badNodes[i]) enabled[i]=false; } // copy back to the gridMask[station][numView] for (int v=1;v<(expHeight-1);v++) for (int u=1;u<(expWidth-1);u++){ int index=u+expWidth*v; int indexSrc=(u-1)+gridWidth*(v-1); if (!enabled[index]) gridMask[station][numView][indexSrc]=0.0; } for (int i=0;i<gridMask[station][numView].length;i++) if (gridMask[station][numView][i]==0.0){ combinedPattern[1][i]=0.0; combinedPattern[2][i]=0.0; combinedPattern[3][i]=0.0; } } // fade mask on the borders, keep zeros - zeros if (fadeMask>0.0){ double [] gridMask1=gridMask[station][numView].clone(); (new DoubleGaussianBlur() ).blurDouble(gridMask[station][numView], gridWidth, gridHeight, fadeMask, fadeMask, 0.01); for (int i=0;i<gridMask[station][numView].length;i++){ double d=2.0*(gridMask[station][numView][i]-0.5); gridMask[station][numView][i]=(gridMask1[i]>0)?((d>0)?(d*d):(0.0)):0.0; if (combinedPattern[4][i]==0.0) gridMask[station][numView][i]=0.0; // how can it be zero combinedPattern[4][i] with non-zero gridMask[i]? } } combinedPattern[0]=gridMask[station][numView]; } } return viewPatterns; } /** * Applies calculated [][] pattern alpha, r,g,b to the current grid geometry * @param patternFlatField */ public void applyGridFlatField( double [][][][] patternFlatField // {alpha, red,green,blue, number of images used}[pixel_index] for each view of the pattern ){ for (int station=0;station<patternParameters.getNumStations();station++){ for (int nView=0;nView<patternParameters.getNumViews();nView++) if ((patternFlatField[station]!=null) && (patternFlatField[station][nView]!=null)) { double [][] photometrics=patternParameters.getPhotometricByView(station,nView); photometrics[0]=patternFlatField[station][nView][1].clone(); // red photometrics[1]=patternFlatField[station][nView][2].clone(); // green photometrics[2]=patternFlatField[station][nView][3].clone(); // blue photometrics[3]=patternFlatField[station][nView][0].clone(); // alpha } } /* double [][][] gridGeometry= patternParameters.getGeometry(); int gridHeight=gridGeometry.length; int gridWidth=gridGeometry[0].length; for (int v=0;v<gridHeight;v++) for (int u=0;u<gridWidth;u++) { int index=u+v*gridWidth; gridGeometry[v][u][3]=patternFlatField[0][index]; gridGeometry[v][u][4]=patternFlatField[1][index]; gridGeometry[v][u][5]=patternFlatField[2][index]; gridGeometry[v][u][6]=patternFlatField[3][index]; } */ } /** * Remove areas on the target flat-field data with specular reflections of the lamps by matching different views * @param highPassSigma - subtract weighted average of the difference with this * @param thershold mismatch causing 50% drop of the weight function * @param numIterations number of iterations of comparing to the weighted/masked average * @param apply apply changes * @param debugShowMode 0 - do not show debug images, 1 show only during last iteration, 2 - show always */ public void removeSpecular ( boolean positiveDiffOnly, double highPassSigma, double lowPassSigma, double thershold, int numIterations, boolean apply, int debugShowMode){ // 0 - none, 1 - last iteration, 2 - all iterations int debugThreshold=1; int length=0; double [][][] weights=new double [patternParameters.getNumStations()][patternParameters.getNumViews()][]; double [][][][] photometrics=new double [patternParameters.getNumStations()][patternParameters.getNumViews()][][]; double [][][][] highPassDiff=new double [patternParameters.getNumStations()][patternParameters.getNumViews()][][]; double [][][][] lowPassDiff=new double [patternParameters.getNumStations()][patternParameters.getNumViews()][][]; int width= patternParameters.gridGeometry[0].length; int height= patternParameters.gridGeometry.length; for (int station=0;station<patternParameters.getNumStations();station++){ for (int nView=0;nView<patternParameters.getNumViews();nView++) { photometrics[station][nView]=patternParameters.getPhotometricByView(station,nView); if (photometrics[station][nView]!=null){ length=photometrics[0][0][3].length; // should all be the same length (or null) weights[station][nView]=new double [length]; for (int nPix=0;nPix<length;nPix++) weights[station][nView][nPix]=(photometrics[station][nView][3][nPix]>0.0)?1.0:0.0; } else { weights[station][nView]=null; } } } double threshold23=9.0*thershold*thershold; for (int nIter=0;nIter<numIterations;nIter++){ boolean showDebug=(debugShowMode>1) || ((debugShowMode>0) && (nIter== (numIterations-1))); // Calculate weighted average among different stations/views. double [][] average=new double [4][length]; for (int nPix=0;nPix<length;nPix++){ double w0=0.0; for (int station=0;station<patternParameters.getNumStations();station++){ for (int nView=0;nView<patternParameters.getNumViews();nView++) { if (photometrics[station][nView]!=null){ double w=weights[station][nView][nPix]*photometrics[station][nView][3][nPix]; average[0][nPix]+=w*photometrics[station][nView][0][nPix]; average[1][nPix]+=w*photometrics[station][nView][1][nPix]; average[2][nPix]+=w*photometrics[station][nView][2][nPix]; w0+=w; } } } double k= (w0>0.0)?(1.0/w0):0.0; average[0][nPix]*=k; average[1][nPix]*=k; average[2][nPix]*=k; average[3][nPix]=w0/(patternParameters.getNumStations()*patternParameters.getNumViews()); } double [][][][] diffFromAverage=new double [photometrics.length][photometrics[0].length][4][length]; // Scale each station/view for best fit for (int station=0;station<patternParameters.getNumStations();station++){ for (int nView=0;nView<patternParameters.getNumViews();nView++) { double scale=0.0; if (photometrics[station][nView]!=null){ double [] weightsHighLowPass=new double[length]; double sf2=0.0,sfg=0.0; for (int nPix=0;nPix<length;nPix++){ double w=weights[station][nView][nPix]*photometrics[station][nView][3][nPix]; weightsHighLowPass[nPix]=w; sf2+=w*(photometrics[station][nView][0][nPix]*photometrics[station][nView][0][nPix]+ photometrics[station][nView][1][nPix]*photometrics[station][nView][1][nPix]+ photometrics[station][nView][2][nPix]*photometrics[station][nView][2][nPix]); sfg+=w*(photometrics[station][nView][0][nPix]*average[0][nPix]+ photometrics[station][nView][1][nPix]*average[1][nPix]+ photometrics[station][nView][2][nPix]*average[2][nPix]); } scale=sfg/sf2; if ((this.debugLevel>=debugThreshold) && showDebug){ System.out.println("removeSpecular(), pass"+nIter+" scale["+station+"]["+nView+"]="+scale); } // Calculate difference from average for (int nPix=0;nPix<length;nPix++){ if (photometrics[station][nView][3][nPix]>0.0){ for (int c=0;c<3;c++){ double d=scale*photometrics[station][nView][c][nPix]-average[c][nPix]; diffFromAverage[station][nView][c][nPix]=d; } } } if (highPassSigma>0.0){ double [] weightsHighPass=weightsHighLowPass.clone(); (new DoubleGaussianBlur()).blurDouble( weightsHighPass, width, height, highPassSigma, highPassSigma, 0.01); highPassDiff[station][nView]=new double [3][]; for (int c=0;c<3;c++){ highPassDiff[station][nView][c]=diffFromAverage[station][nView][c].clone(); // deep for (int nPix=0;nPix<length;nPix++){ highPassDiff[station][nView][c][nPix]*=weightsHighLowPass[nPix]; } (new DoubleGaussianBlur()).blurDouble( highPassDiff[station][nView][c], width, height, highPassSigma, highPassSigma, 0.01); for (int nPix=0;nPix<length;nPix++){ highPassDiff[station][nView][c][nPix]= diffFromAverage[station][nView][c][nPix]- ((weightsHighPass[nPix]>0)?(highPassDiff[station][nView][c][nPix]/weightsHighPass[nPix]):0.0); } } } else { highPassDiff[station][nView]=diffFromAverage[station][nView].clone(); // shallow } if (lowPassSigma>0.0){ double [] weightsLowPass=weightsHighLowPass.clone(); (new DoubleGaussianBlur()).blurDouble( weightsLowPass, width, height, lowPassSigma, lowPassSigma, 0.01); lowPassDiff[station][nView]=new double [3][]; for (int c=0;c<3;c++){ lowPassDiff[station][nView][c]=highPassDiff[station][nView][c].clone(); for (int nPix=0;nPix<length;nPix++){ lowPassDiff[station][nView][c][nPix]*=weightsHighLowPass[nPix]; } (new DoubleGaussianBlur()).blurDouble( lowPassDiff[station][nView][c], width, height, lowPassSigma, lowPassSigma, 0.01); for (int nPix=0;nPix<length;nPix++){ lowPassDiff[station][nView][c][nPix]= (weightsLowPass[nPix]>0)?(lowPassDiff[station][nView][c][nPix]/weightsLowPass[nPix]):0.0; } } } else { lowPassDiff[station][nView]=highPassDiff[station][nView].clone(); // shallow } // TODO: display, calculate new weight from filtered difference. // Calculate new weight for (int nPix=0;nPix<length;nPix++){ if (photometrics[station][nView][3][nPix]>0.0){ double e2=0.0; for (int c=0;c<3;c++){ double d=lowPassDiff[station][nView][c][nPix]; // double d=scale*photometrics[station][nView][c][nPix]-average[c][nPix]; // diffFromAverage[station][nView][c][nPix]=d; if (!positiveDiffOnly || (d>0)) e2+=d*d; } weights[station][nView][nPix]=1.0/(e2/threshold23+1.0); } else { weights[station][nView][nPix]=0.0; } } } } } if ((this.debugLevel>=debugThreshold) && showDebug) { String [] titles = new String [weights.length*weights[0].length]; double [][] debugData= new double [weights.length*weights[0].length][]; for (int station=0;station<patternParameters.getNumStations();station++){ for (int nView=0;nView<patternParameters.getNumViews();nView++) { int n=station*weights[0].length+nView; titles[n]="S"+station+" V"+nView; if (photometrics[station][nView]!=null){ debugData[n]=weights[station][nView]; } } } (new ShowDoubleFloatArrays()).showArrays( debugData, width, height, true, "GridWeights"+nIter, titles); double [][] debugDiffGreen= new double [weights.length*weights[0].length][]; double [][] debugHighpassDiffGreen= new double [weights.length*weights[0].length][]; double [][] debugLowpassDiffGreen= new double [weights.length*weights[0].length][]; for (int station=0;station<patternParameters.getNumStations();station++){ for (int nView=0;nView<patternParameters.getNumViews();nView++) { int n=station*weights[0].length+nView; debugDiffGreen[n]=diffFromAverage[station][nView][1]; debugHighpassDiffGreen[n]=highPassDiff[station][nView][1]; debugLowpassDiffGreen[n]=lowPassDiff[station][nView][1]; } } if (this.debugLevel>=(debugThreshold+1)) (new ShowDoubleFloatArrays()).showArrays( debugDiffGreen, width, height, true, "DiffGreen"+nIter, titles); if (this.debugLevel>=(debugThreshold+1)) (new ShowDoubleFloatArrays()).showArrays( debugHighpassDiffGreen, width, height, true, "HighpassGreen"+nIter, titles); (new ShowDoubleFloatArrays()).showArrays( debugLowpassDiffGreen, width, height, true, "LowpassGreen"+nIter, titles); String [] averageTitles={"Red","Green","Blue","Weight"}; (new ShowDoubleFloatArrays()).showArrays( average, width, height, true, "Average-"+nIter, averageTitles); } } // for (int nIter=0;nIter<numIterations;nIter++){ // Apply new weights if (apply) { for (int station=0;station<patternParameters.getNumStations();station++){ for (int nView=0;nView<patternParameters.getNumViews();nView++) { if (photometrics[station][nView]!=null){ for (int nPix=0;nPix<length;nPix++) photometrics[station][nView][3][nPix]*=weights[station][nView][nPix]; } } } } } /** * * @param shrink sensor mask by this amount (sensor, non-decimated pixels) * @param radius radial mask - zero if farther than radius, 0.5*(cos(pi*r/radius)+1.0) if less * @param minimalAlpha - zero mask below this threshold * @return returns arrray with the same size as sensorMask that corresponds to low-vignetting areas of the sensor/lens */ // Using station 0 - should be not much difference public double [][] nonVignettedMasks( double shrink, double radius, double minimalAlpha){ if (this.pixelCorrection==null){ initSensorCorrection(); } double [][]masks=new double [this.pixelCorrection.length][]; int maskIndex=2; for (int numSensor=0;numSensor<masks.length;numSensor++){ if (this.pixelCorrection[numSensor]==null) masks[numSensor] = null; else { masks[numSensor] = fittingStrategy.distortionCalibrationData.nonVignettedMask( this.pixelCorrection[numSensor][maskIndex], getSensorWidth(numSensor), // this.pixelCorrectionWidth, getSensorHeight(numSensor), // this.pixelCorrectionHeight, fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numSensor].px0, // lens center X (sensor, non-decimated pix) fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[0][numSensor].py0, // lens center Y (sensor, non-decimated pix) shrink, radius, minimalAlpha); // System.out.println("nonVignettedMasks(), masks["+numSensor+"].length="+masks[numSensor].length); } } return masks; } // Use series0 to find grid mismatch (and set it correctly). Uses that pattern in the world coordinate system is // approximately in XY plane, so by freezing all other parameters but GXY0 and GXY1 it is possible to find // the pattern grid match. public int [] findImageGridOffset( int num_img, boolean even, PatternParameters patternParameters) { // set series 0 to this set images boolean [] selection = fittingStrategy.selectAllImages(0); // enable all images in series 0 for (int i=0;i<selection.length;i++) selection[i]=false; selection[num_img]=true; fittingStrategy.setImageSelection(0,selection); seriesNumber= 0; // start from 0; initFittingSeries(false, filterForAll,0); // will set this.currentVector //this.stopAfterThis[numSeries] fittingStrategy.stopAfterThis[0]=true; stopEachStep= false; stopEachSeries= false; // will not ask for confirmation after done lambda = fittingStrategy.lambdas[0]; boolean LMA_OK = false; try { LMA_OK = LevenbergMarquardt (false, true); // skip dialog, dry run (no updates) } catch (Exception e) { // LMA failed - e.g. not enough points (Singular Matrix) } if (!LMA_OK) { return null; // LMA did not converge } double [] new_XY = {this.currentVector[0],this.currentVector[1]}; DistortionCalibrationData dcd = this.fittingStrategy.distortionCalibrationData; // int num_set = dcd.gIP[num_img].getSetNumber(); double [] ref_XYZ = dcd.getXYZ(num_img); double [] diff_XY = { new_XY[0] -ref_XYZ[0], new_XY[1] -ref_XYZ[1]}; //save safe settings to run LMA manually (or save what was set) seriesNumber= 0; // start from 0; initFittingSeries (false,filterForAll,0); // will set this.currentVector stopEachSeries= true; // will not ask for confirmation after done stopOnFailure= true; lambda= fittingStrategy.lambdas[0]; int [] grid_offset = dcd.suggestOffset ( num_img, diff_XY, // This XYZ minus reference XYZ z is not used, may be just[2] even, patternParameters); return grid_offset; /* * this.currentVector[0] - GXYZ[0] this.currentVector[1] - GXYZ[1] dcd. public int [] suggestOffset ( int num_img, double [] diff_xyz, // This XYZ minus reference XYZ z is not used, may be just[2] boolean even, PatternParameters patternParameters) { */ } public boolean LevenbergMarquardt( boolean openDialog, boolean dry_run){ // do not save results if (this.fittingStrategy==null) { String msg="Fitting strategy does not exist, exiting"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } // fittingStrategy.distortionCalibrationData.readAllGrids(); if (openDialog && !selectLMAParameters()) return false; this.startTime=System.nanoTime(); // while (this.seriesNumber<fittingStrategy.getNumSeries()){ // TODO: Add "stop" tag to series this.firstRMS=-1; //undefined this.fittingStrategy.invalidateSelectedImages(this.seriesNumber); // undo any filters, only user selection of the images will be in effect while (this.fittingStrategy.isSeriesValid(this.seriesNumber)){ // TODO: Add "stop" tag to series this.currentVector=null; // invalidate for the new series boolean wasLastSeries=false; while (true) { // loop for the same series boolean [] state=stepLevenbergMarquardtFirst(this.seriesNumber); if (!this.fittingStrategy.isSeriesValid(this.seriesNumber)){ System.out.println("Series "+this.seriesNumber+" is invalid when weight function filters are applied (probably removed some images)"); return false; } if (state==null) { String msg="Calculation aborted by user request"; IJ.showMessage(msg); System.out.println(msg); return false; } if (this.debugLevel>1) System.out.println(this.seriesNumber+":"+this.iterationStepNumber+": stepLevenbergMarquardtFirst("+this.seriesNumber+")==>"+state[1]+":"+state[0]); boolean cont=true; // Make it success if this.currentRMS<this.firstRMS even if LMA failed to converge if (state[1] && !state[0] && (this.firstRMS>this.currentRMS)){ if (this.debugLevel>1) System.out.println("LMA failed to converge, but RMS improved from the initial value ("+this.currentRMS+" < "+this.firstRMS+")"); state[0]=true; } if ( (this.stopRequested.get()>0) || // graceful stop requested (this.stopEachStep) || (this.stopEachSeries && state[1]) || (this.stopOnFailure && state[1] && !state[0])){ if (this.debugLevel>0){ if (this.stopRequested.get()>0) System.out.println("User requested stop"); System.out.println("LevenbergMarquardt(): series:step ="+this.seriesNumber+":"+this.iterationStepNumber+ ", RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.firstRMS,8)+") "+ ", RMSPure="+IJ.d2s(this.currentRMSPure,8)+ " ("+IJ.d2s(this.firstRMSPure,8)+ ") at "+ IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)); } long startDialogTime=System.nanoTime(); cont=dialogLMAStep(state); this.stopRequested.set(0); // Will not stop each run this.startTime+=(System.nanoTime()-startDialogTime); // do not count time used by the User. if (this.showThisImages) showDiff (this.currentfX, "fit-"+this.iterationStepNumber); if (this.showNextImages) showDiff (this.nextfX, "fit-"+(this.iterationStepNumber+1)); } else if (this.debugLevel>1){ System.out.println("==> LevenbergMarquardt(): before action series:step ="+this.seriesNumber+":"+this.iterationStepNumber+ ", RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.firstRMS,8)+") "+ ", RMSPure="+IJ.d2s(this.currentRMSPure,8)+ " ("+IJ.d2s(this.firstRMSPure,8)+ ") at "+ IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)); } stepLevenbergMarquardtAction(); // apply step - in any case? if (this.updateStatus){ IJ.showStatus(this.seriesNumber+": "+"Step #"+this.iterationStepNumber+ " RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.firstRMS,8)+")"+ " RMSPure="+IJ.d2s(this.currentRMSPure,8)+ " ("+IJ.d2s(this.firstRMSPure,8)+")"+ " "); // showStatus(this.seriesNumber+": "+"Step #"+this.iterationStepNumber+" RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.firstRMS,8)+")",0); } if ((this.debugLevel>0) && ((this.debugLevel>1) || ((System.nanoTime()-this.startTime)>10000000000.0))){ // > 10 sec System.out.println("--> LevenbergMarquardt(): series:step ="+this.seriesNumber+":"+this.iterationStepNumber+ ", RMS="+IJ.d2s(this.currentRMS,8)+ " ("+IJ.d2s(this.firstRMS,8)+") "+ ", RMSPure="+IJ.d2s(this.currentRMSPure,8)+ " ("+IJ.d2s(this.firstRMSPure,8)+ ") at "+ IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)); } if (!cont){ if (this.saveSeries && !dry_run) { saveFittingSeries(); // will save series even if it ended in failure, vector will be only updated updateCameraParametersFromCalculated(true); // update camera parameters from all (even disabled) images updateCameraParametersFromCalculated(false); // update camera parameters from enabled only images (may overwrite some of the above) } // if RMS was decreased. this.saveSeries==false after dialogLMAStep(state) only if "cancel" was pressed return this.saveSeries; // TODO: Maybe change result? } //stepLevenbergMarquardtAction(); if (state[1]) { if (!state[0]) return false; // sequence failed if (dry_run) { wasLastSeries= true; // always just one series } else { saveFittingSeries(); updateCameraParametersFromCalculated(true); // update camera parameters from all (even disabled) images updateCameraParametersFromCalculated(false); // update camera parameters from enabled only images (may overwrite some of the above) wasLastSeries=this.fittingStrategy.isLastSeries(this.seriesNumber); this.seriesNumber++; } break; // while (true), proceed to the next series } } // if (this.fittingStrategy.isLastSeries(this.seriesNumber)) break; if (wasLastSeries) break; // this.seriesNumber++; } // while (this.fittingStrategy.isSeriesValid(this.seriesNumber)){ // TODO: Add "stop" tag to series if (this.debugLevel>0) System.out.println("LevenbergMarquardt(): series="+this.seriesNumber+ ", RMS="+this.currentRMS+ " ("+this.firstRMS+") "+ ", RMSPure="+this.currentRMSPure+ " ("+this.firstRMSPure+ ") at "+ IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)); return true; // all series done } /** * Show debug image (see showDiff (int imgNumber, double [] fX, String title ) above) * for each image used in the current fitting series * @param fX - calculated data for all images (use with this.Y) * @param title - Image title */ public void showDiff (double [] fX, String title ){ boolean [] selectedImages=fittingStrategy.selectedImages(); for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) { showDiff (imgNum, fX, title+"-"+imgNum); } } /** * Shows a 7-slice image for provided f(X) array (this.Y is also used): * 1 - distance - sqrt (dx^2+dy^2) * 2 - difference for pixel-X * 3 - difference for pixel-Y * 4 - calculated pixel-X * 5 - calculated pixel-Y * 6 - measured pixel-X * 7 - measured pixel-Y * @param imgNumber - number of image * @param fX - calculated data for all images (use with this.Y) * @param title - Image title */ public void showDiff (int imgNumber, double [] fX, String title ){ String [] titles={"distance","diff-X","diff-Y","f(x)-X","f(x)-Y","y-X","y-Y"}; double [] diff=calcYminusFx(fX); // find data range for the selected image int index=0; int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); boolean [] selectedImages=fittingStrategy.selectedImages(); for (int imgNum=0;(imgNum<imgNumber) && (imgNum<numImg) ;imgNum++) if (selectedImages[imgNum]) index+=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length; int width=getGridWidth(); if (this.debugLevel>1) { System.out.println("showDiff("+imgNumber+",...): fX.length="+fX.length+" this image index="+index); } double [][] imgData=new double[7][getGridHeight() * width]; for (int i=0;i<imgData.length;i++) for (int j=0;j<imgData[i].length;j++)imgData[i][j]=0.0; for (int i=0;i<fittingStrategy.distortionCalibrationData.gIP[imgNumber].pixelsUV.length;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNumber].pixelsUV[i][0]+patternParameters.U0; int v=fittingStrategy.distortionCalibrationData.gIP[imgNumber].pixelsUV[i][1]+patternParameters.V0; int vu=u+width*v; imgData[0][vu]= Math.sqrt(diff[2*(index+i)]*diff[2*(index+i)] + diff[2*(index+i)+1]*diff[2*(index+i)+1]); imgData[1][vu]= diff[2*(index+i)]; // out of bound 1410 imgData[2][vu]= diff[2*(index+i)+1]; imgData[3][vu]= fX[2*(index+i)]; imgData[4][vu]= fX[2*(index+i)+1]; imgData[5][vu]= this.Y[2*(index+i)]; imgData[6][vu]= this.Y[2*(index+i)+1]; } this.SDFA_INSTANCE.showArrays(imgData, width, getGridHeight(), true, title, titles); } /** * Calculates corrections to X and Y coordinates of the grid nodes * @param variationPenalty - cost of different Z for different stations * @param fixXY - if true, do not adjust X,Y - only Z * @param stationGroupsIn - consider some stations have the same pattern - assign them the same number. Negative - do not process the station * @param grid3DCorrection - if true - calculate 3d correction, false - slow 3d (2d perpendicular to view) * @param maxZCorr - maximal allowed correction in Z-direction (if wants more, will fall back to 2-d correction (perpendicular to the view) * @param showIndividual - show individual images * @return combination of 3 arrays: 1 (original) - first index - 0 - correction x (mm), 1 - correction y(mm), 2 - correction z(mm) 3 - weight (number of images used) * 2 - gridZCorr3d - per station differential Z correction * 3 - gridZCorr3dWeight - per station weight of Z-corrections */ public double [][][] calculateGridXYZCorr3D( double variationPenalty, boolean fixXY, int [] stationGroupsIn, boolean grid3DCorrection, boolean rotateCorrection, double maxZCorr, boolean noFallBack, boolean showIndividual, int threadsMax, boolean updateStatus ){ int debugThreshold=2; // Normalize stationGroups int numStations=fittingStrategy.distortionCalibrationData.getNumStations(); int [] stationGroups=new int [numStations]; int [] stationGroupsTmp=(stationGroupsIn==null)?(new int [0]):stationGroupsIn.clone(); for (int i=0;i<numStations;i++) stationGroups[i]=-1; int numberOfZGroups=0; for (int i=0;i<stationGroupsTmp.length;i++) if (stationGroupsTmp[i]>=0){ for (int j=i;j<stationGroupsTmp.length;j++) if (stationGroupsTmp[j]==stationGroupsTmp[i]){ stationGroups[j]=numberOfZGroups; if (j>i) stationGroupsTmp[j]=-1; } numberOfZGroups++; } if (numberOfZGroups==0) { System.out.println ("calculateGridXYZCorr3D(), no groups defined - using a single group for all stations"); numberOfZGroups=1; for (int i=0;i<numStations;i++) stationGroups[i]=0; } if (this.debugLevel>1) { System.out.println ("calculateGridXYZCorr3D(), groups: "+numberOfZGroups); for (int i=0;i<stationGroups.length;i++) if (stationGroups[i]>=0){ System.out.println (" station "+i+": group "+stationGroups[i]); } } int width=getGridWidth(); int height=getGridHeight(); boolean [] selectedImages=fittingStrategy.selectedImages(); double [][] cameraXYZ=new double [selectedImages.length][]; double [][][] gridCorr2d=calculateGridXYZCorr2D( width, height, stationGroups, selectedImages, cameraXYZ, this.lensDistortionParameters, showIndividual, threadsMax, updateStatus); IJ.showStatus("Calculating pattern 3d correction..."); // now using gridCorr2d[imgNum], cameraXYZ[imgNum] and patternParameters.gridGeometry[v][u] find the 3d correction public double [][][] gridGeometry=null; // [v][u]{x,y,z,"alpha"} alpha=0 - no ghrid, 1 - grid double [][] gridCorr3d= new double [4][width*height]; double [][] gridZCorr3d =new double [numStations][width*height]; double [][] gridZCorr3dWeight =new double [numStations][width*height]; for (int n=0;n<gridCorr3d.length;n++) for (int i=0;i<gridCorr3d[0].length;i++) gridCorr3d[n][i]=0.0; for (int n=0;n<gridZCorr3d.length;n++) for (int i=0;i<gridZCorr3d[0].length;i++){ gridZCorr3d[n][i]=0.0; gridZCorr3dWeight[n][i]=0.0; } double Cx,Cy,Cz,Cxy,Cxz,Cyz; double [] V= new double[3]; double [] V2= new double[3]; int debugIndex=(height/2)*width+ (width/2); int debugIndex1=(height/2)*width+ (width/4); double [] alphaStation=new double [numStations]; int zIndex=fixXY?0:2; int numVariables=numberOfZGroups+zIndex; double [][] aM=new double [numVariables][numVariables]; double [][] aB=new double [numVariables][1]; double [] zPerStation= new double [numStations]; for (int v=0;v<height;v++) for (int u=0;u<width; u++){ int index=u+v*width; boolean thisDebug=(this.debugLevel>debugThreshold) && ((index==debugIndex) || (index==debugIndex1)); if (thisDebug) System.out.println("calculateGridXYZCorr3D() debug("+this.debugLevel+"): index="+index+" v="+v+" u="+u); for (int i=0;i<numVariables;i++){ aB[i][0]=0.0; for (int j=0;j<numVariables;j++) aM[i][j]=0.0; } for (int i=0;i<numStations;i++) alphaStation[i]=0.0; double alpha=0.0; boolean fallBack2D=true; if (grid3DCorrection) { for (int imgNum=0;imgNum<selectedImages.length;imgNum++) { int station=fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera int zGroup=stationGroups[station]; if ((gridCorr2d[imgNum]!=null) && (gridCorr2d[imgNum][3][index]>0.0) && (zGroup>=0)) { zPerStation[station]=gridCorr2d[imgNum][2][index]; // should all be the same for the same station // calculate unity vector from the camera lens to the grid point double absV=0.0; for (int i=0;i<V.length;i++){ V[i]=patternParameters.gridGeometry[v][u][i]+gridCorr2d[imgNum][i][index]-cameraXYZ[imgNum][i]; // corrected value, including zCorr absV+=V[i]*V[i]; } absV=Math.sqrt(absV); if (absV>0) for (int i=0;i<V.length;i++) V[i]/=absV; for (int i=0;i<V.length;i++) V2[i]=V[i]*V[i]; if (thisDebug) System.out.println(" imgNum="+imgNum+" V[0]="+IJ.d2s(V[0],4)+" V[1]="+IJ.d2s(V[1],4)+" V[2]="+IJ.d2s(V[2],4)+ " V2[0]="+IJ.d2s(V2[0],4)+" V2[1]="+IJ.d2s(V2[1],4)+" V2[2]="+IJ.d2s(V2[2],4)); // When performin 3-d correction (X,Y,Z) the result point has to have minimal weighted sum of squared distances to all rays // when summing for different stations, multiply W by sign(image belongs to station) /* Px, Py - calculated correction for individual image V={Vx,Vy,Vz} unity vector from the camera lens center to the {Px,Py,0} A - vector from the {Px,Py,0} to {X,Y,Z} = {X-Px,Y-Py,Z} Projection of A on V will have length of A(.)V, Vector B=V*(A(.)V) Vector D=A-B = A - V*(A(.)V) D2=D(.)D= A(.)A - 2* (A(.)V ) * (A(.)V ) + (A(.)V ) * (A(.)V ) = A(.)A - (A(.)V ) * (A(.)V ) D2=A(.)A - (A(.)V )^2 A(.)A=(X-Px)^2 + (Y-Py)^2 + Z^2 =X^2 -2*X*Px +Px^2 +Y^2 -2*Y*Py +Py^2 +Z^2 A(.)A=X^2 -2*X*Px +Px^2 +Y^2 -2*Y*Py +Py^2 +Z^2 A(.)V= (X-Px)*Vx + (Y-Py)*Vy + Z*Vz (A(.)V)^2= ((X-Px)*Vx + (Y-Py)*Vy + Z*Vz)^2 = ((X-Px)*Vx)^2 + ((Y-Py)*Vy)^2 + (Z*Vz)^2 + 2*((X-Px)*Vx)*((Y-Py)*Vy)+ 2*((X-Px)*Vx)*(Z*Vz)+2*((Y-Py)*Vy)*(Z*Vz) (A(.)V)^2= X^2*Vx^2 +Px^2*Vx^2 - 2*X*Px*Vx^2 +Y^2*Vy^2+Py^2*Vy^2-2*Y*Py*Vy^2 +Z^2*Vz^2 +2*X*Y*Vx*Vy +2*Px*Py*Vx*Vy - 2*X*Py*Vx*Vy - 2*Y*Px*Vx*Vy +2*X*Z*Vx*Vz - 2*Z*Px*Vx*Vz +2*Y*Z*Vy*Vz -2*z*Py*Vy*Vz D2= +X^2 - X^2*Vx^2 +Y^2 - Y^2*Vy^2 +Z^2 - Z^2*Vz^2 -2*X*Y* Vx*Vy -2*X*Z* Vx*Vz -2*Y*Z* Vy*Vz -2*X*Px +2*X*Px*Vx^2+ 2*X*Py*Vx*Vy -2*Y*Py +2*Y*Py*Vy^2+ 2*Y*Px*Vx*Vy +2*Z*Px*Vx*Vz +2*Z*Py*Vy*Vz +Px^2 +Py^2 -Px^2*Vx^2 -Py^2*Vy^2 -2*Px*Py*Vx*Vy 0= dD2/dX/2= X*(1-Vx^2) - Y* Vx*Vy - Z* Vx*Vz -Px + Px*Vx^2 + Py*Vx*Vy 0= dD2/dY/2= Y*(1-Vy^2) - X* Vx*Vy - Z* Vy*Vz -Py + Py*Vy^2 + Px*Vx*Vy 0= dD2/dZ/2= Z*(1-Vz^2) - X* Vx*Vz - Y* Vy*Vz + Px*Vx*Vz + Py*Vy*Vz X*(Vx^2-1) + Y* (Vx*Vy) + Z* (Vx*Vz) = Px * (Vx^2-1) + Py* (Vx*Vy) X*(Vx*Vy) + Y* (Vy^2-1) + Z* (Vy*Vz) = Px * (Vx*Vy) + Py * (Vy^2-1) X*(Vx*Vz) + Y* (Vy*Vz) + Z* (Vz^2-1) = Px * (Vx*Vz) + Py* (Vy*Vz) */ // | sum(Wi*Cxi), sum(Wi*Cxyi), sum(Wi*Cxzi) | //M= | sum(Wi*Cxyi), sum(Wi*Cyi ), sum(Wi*Cyzi) | // | sum(Wi*Cxzi), sum(Wi*Cyzi), sum(Wi*Czi ) | // | sum(Wi*(P0xi*Cxi + P0yi*Cxyi + P0zi*Cxzi)) | //B= | sum(Wi*(P0yi*Cyi + P0xi*Cyxi + P0zi*Cyzi)) | // | sum(Wi*(P0zi*Czi + P0yi*Czyi + P0xi*Czxi)) | /* X*(Vxi^2-1) + Y*(Vxi*Vyi) + Z*(Vxi*Vzi) = P0xi*(Vxi^2-1) +P0yi*(Vxi*Vyi) + P0zi*(Vxi*Vzi) X*(Vxi*Vyi) + Y*(Vyi^2-1) + Z*(Vyi*Vzi) = P0xi*(Vxi*Vyi) +P0yi*(Vyi^2-1) + P0zi*(Vyi*Vzi) X*(Vxi*Vzi) + Y*(Vxi*Vyi) + Z*(Vzi^2-1) = P0xi*(Vxi*Vzi) +P0yi*(Vxi*Vyi) + P0zi*(Vzi^2-1) X*Cx + Y*Cxy + Z*Cxz = P0xi*Cx +P0yi*Cxy + P0zi*Cxz X*Cxy + Y*Cy + Z*Cyz = P0xi*Cxy +P0yi*Cy + P0zi*Cyz X*Cxz + Y*Cyz + Z*Cz = P0xi*Cxz +P0yi*Cyz + P0zi*Cz P0zi==0.0, so - now we'll use P0zi - difference from this station to average X*Cx + Y*Cxy + Z*Cxz = P0xi*Cx +P0yi*Cxy X*Cxy + Y*Cy + Z*Cyz = P0xi*Cxy +P0yi*Cy X*Cxz + Y*Cyz + Z*Cz = P0xi*Cxz +P0yi*Cyz */ Cx=V2[0]-1.0; Cy=V2[1]-1.0; Cz=V2[2]-1.0; Cxy= V[0]*V[1]; Cxz= V[0]*V[2]; Cyz= V[1]*V[2]; if (thisDebug) System.out.println(" Cx="+IJ.d2s(Cx,6)+" Cy="+IJ.d2s(Cy,6)+" Cz="+IJ.d2s(Cz,6)+ " Cxy="+IJ.d2s(Cxy,6)+" Cxz="+IJ.d2s(Cxz,6)+" Cyz="+IJ.d2s(Cyz,6)); double W=gridCorr2d[imgNum][3][index]; double Px=gridCorr2d[imgNum][0][index]; double Py=gridCorr2d[imgNum][1][index]; double Pz=gridCorr2d[imgNum][2][index]; alpha+=W; alphaStation[station]+=W; if (thisDebug) System.out.println(imgNum+": Px="+IJ.d2s(Px,6)+" Py="+IJ.d2s(Py,6)+" W="+IJ.d2s(W,6)); if (zIndex>0){ // X,Y correction is enabled, not only Z aM[0][0]+=W*Cx; aM[0][1]+=W*Cxy; aM[1][1]+=W*Cy; aM[0][2+zGroup]+=W*Cxz; aM[1][2+zGroup]+=W*Cyz; aB[0][0]+=W*(Px*Cx + Py*Cxy + Pz*Cxz); aB[1][0]+=W*(Px*Cxy + Py*Cy + Pz*Cyz); } aM[zIndex+zGroup][zIndex+zGroup]+=W*(Cz-variationPenalty); // -1>>Cz<0 aB[zIndex+zGroup][0]+=W*(Px*Cxz + Py*Cyz + Pz*Cz); } } if (zIndex>0){// X,Y correction is enabled, not only Z aM[1][0]+=aM[0][1]; // why "+=" - just "=" for (int zGroup=0;zGroup<numberOfZGroups;zGroup++){ aM[zIndex+zGroup][0]+=aM[0][zIndex+zGroup]; aM[zIndex+zGroup][1]+=aM[1][zIndex+zGroup]; } } Matrix M=new Matrix(aM); Matrix B=new Matrix(aB); if (thisDebug) { System.out.println(" M:"); M.print(8, 6); System.out.println(" B:"); B.print(8, 6); } // boolean fallBack2D=true; if ((new LUDecomposition(M)).isNonsingular()){ double [] dXYZ=M.solve(B).getRowPackedCopy(); //// Now save per station group (with weights) if (zIndex>0){// X,Y correction is enabled, not only Z for (int i=0;i<2;i++) gridCorr3d[i][index]=dXYZ[i]; } double zAverage=0.0; double sumW=0; for (int station=0;station<numStations;station++){ double w=alphaStation[station]; sumW+=w; gridZCorr3dWeight[station][index]=w; int zGroup=stationGroups[station]; zAverage+=w*dXYZ[zIndex+zGroup]; } if (sumW>0.0) { zAverage/=sumW; gridCorr3d[2][index]=zAverage; // weighted average of grid Z correction (from current pattern Z) gridCorr3d[3][index]=alpha; // same as sumW? // second pass - calculate per-station Z corrections - referenced to existent current values //zPerStation[station] for (int station=0;station<numStations;station++){ int zGroup=stationGroups[station]; // gridZCorr3d[station][index]=dXYZ[zIndex+zGroup]-zPerStation[station]; // differential from the current pattern geometry gridZCorr3d[station][index]=dXYZ[zIndex+zGroup]-zAverage; // differential from the current pattern geometry } } fallBack2D=false; //TODO: make sure delta Z (Math.abs(gridCorr3d[2][index])) is not too big!! if (Math.abs(gridCorr3d[2][index])>maxZCorr) { fallBack2D=true; // temporary limit } if (thisDebug) System.out.println(" dX="+IJ.d2s(gridCorr3d[0][index],6)+" dY="+IJ.d2s(gridCorr3d[1][index],6)+" dZ="+IJ.d2s(gridCorr3d[2][index],6)); } } if(fallBack2D && !(grid3DCorrection && noFallBack)) { // make a 2d averaging of weighted dx, dy correction - separately for each station group double [] gridZcorrPerGroup= new double [numberOfZGroups]; double [] gridZcorrAddPerGroup= new double [numberOfZGroups]; double [] gridZcorrWeightPerGroup=new double [numberOfZGroups]; for (int i=0;i<numberOfZGroups;i++){ gridZcorrPerGroup[i]=0.0; gridZcorrWeightPerGroup[i]=0.0; gridZcorrAddPerGroup[i]=0.0; } for (int imgNum=0;imgNum<selectedImages.length;imgNum++) { int station=fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera int zGroup=stationGroups[station]; if ((gridCorr2d[imgNum]!=null) && (gridCorr2d[imgNum][3][index]>0.0) && (zGroup>=0)) { double w=gridCorr2d[imgNum][3][index]; double z=gridCorr2d[imgNum][2][index]; // difference from average Z gridZcorrPerGroup[zGroup]+=w*z; gridZcorrWeightPerGroup[zGroup]+=w; } } for (int i=0;i<numberOfZGroups;i++) if (gridZcorrWeightPerGroup[i]>0.0) gridZcorrPerGroup[i]/=gridZcorrWeightPerGroup[i]; for (int i=0;i<gridCorr3d.length;i++) gridCorr3d[i][index]=0.0; double s=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) { int station=fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera int zGroup=stationGroups[station]; if ((gridCorr2d[imgNum]!=null) && (gridCorr2d[imgNum][3][index]>0.0) && (zGroup>=0)) { double z=patternParameters.gridGeometry[v][u][2]+gridZcorrPerGroup[zGroup]; double [] cv={ patternParameters.gridGeometry[v][u][0]-cameraXYZ[imgNum][0], patternParameters.gridGeometry[v][u][1]-cameraXYZ[imgNum][1], z-cameraXYZ[imgNum][2]}; double cv2=cv[0]*cv[0]+cv[1]*cv[1]+cv[2]*cv[2]; double acv=Math.sqrt(cv2); for (int i=0;i<3;i++)cv[i]/=acv; // make unity vector; // intersection of the corrected view ray with the average taget plane double [] dXYplane0={-gridZcorrPerGroup[zGroup]/cv[2]*cv[0],-gridZcorrPerGroup[zGroup]/cv[2]*cv[1]}; double [] modCorrXY={gridCorr2d[imgNum][0][index]+dXYplane0[0], gridCorr2d[imgNum][1][index]+dXYplane0[1]}; double kv=(modCorrXY[0]*cv[0]+modCorrXY[1]*cv[1])/cv2; double w=gridCorr2d[imgNum][3][index]; gridCorr3d[0][index]+=w*(gridCorr2d[imgNum][0][index]-cv[0]*kv); gridCorr3d[1][index]+=w*(gridCorr2d[imgNum][1][index]-cv[1]*kv); gridZcorrAddPerGroup[zGroup]+=w*( -cv[2]*kv); // not finished per station/per group 2d correction, will just use corerction average gridCorr3d[2][index]+=w*( -cv[2]*kv); s+=w; } } for (int i=0;i<numberOfZGroups;i++) if (gridZcorrWeightPerGroup[i]>0.0) gridZcorrAddPerGroup[i]/=gridZcorrWeightPerGroup[i]; for (int station=0;station<numStations;station++){ int zGroup=stationGroups[station]; gridZCorr3d[station][index]=gridZcorrAddPerGroup[zGroup]; // differential from the current pattern geometry } if (s>0){ gridCorr3d[0][index]/=s; gridCorr3d[1][index]/=s; gridCorr3d[2][index]/=s; } else { gridCorr3d[0][index]=0.0; gridCorr3d[1][index]=0.0; gridCorr3d[2][index]=0.0; } gridCorr3d[3][index]=s; if (thisDebug) System.out.println(" Using 2d averaging: dX="+IJ.d2s(gridCorr3d[0][index],6)+ " dY="+IJ.d2s(gridCorr3d[1][index],6)+" dZ="+IJ.d2s(gridCorr3d[2][index],6)); } } // Make average correction zero is it needed? // create "reliable" mask for averaging/tilting - disregard the outmost grid pixels boolean [] reliable=new boolean [width*height]; double wThreshold=0.0; for (int v=0;v<height;v++) for (int u=0;u<width;u++){ int index=u+v*width; reliable[index]=false; if ((v>0) && (u>0) && (v<(height-1)) && (u<(width-1)) && (gridCorr3d[3][index]>wThreshold) && (gridCorr3d[3][index-1]>wThreshold) && (gridCorr3d[3][index+1]>wThreshold) && (gridCorr3d[3][index-width]>wThreshold) && (gridCorr3d[3][index+width]>wThreshold) ){ reliable[index]=true; } } double corrAverage; for (int c=0;c<3;c++){ corrAverage=0.0; double s=0.0; for (int i=0;i<gridCorr3d[0].length;i++) if (reliable[i]) { corrAverage+=gridCorr3d[c][i]*gridCorr3d[3][i]; s+=gridCorr3d[3][i]; } corrAverage/=s; // System.out.println("zCorrAverage["+c+"="+corrAverage); for (int i=0;i<gridCorr3d[c].length;i++) gridCorr3d[c][i]-=corrAverage; } // for Z correction compensate for x/y tilts String [] titles={"X-correction(mm)","Y-correction(mm)","Z-correction","Weight"}; if (rotateCorrection) { double SX=0.0,SX2=0.0,SZ=0.0,SXY=0.0,SXZ=0.0,S0=0.0,SY=0.0,SY2=0.0,SYZ=0.0; double [][] gridGeom=new double [3][gridCorr3d[0].length]; for (int c=0;c<gridGeom.length;c++) for (int i=0;i<gridGeom[c].length;i++)gridGeom[c][i]=0.0; for (int v=0;v<height;v++) for (int u=0;u<width; u++){ int index=u+v*width; double W=gridCorr3d[3][index]; gridGeom[0][index]=patternParameters.gridGeometry[v][u][0]; gridGeom[1][index]=patternParameters.gridGeometry[v][u][1]; gridGeom[2][index]=W; if ((reliable[index]) && (W>0.0)){ S0+=W; SX+= W*patternParameters.gridGeometry[v][u][0]; SX2+= W*patternParameters.gridGeometry[v][u][0]*patternParameters.gridGeometry[v][u][0]; SY+= W*patternParameters.gridGeometry[v][u][1]; SY2+= W*patternParameters.gridGeometry[v][u][1]*patternParameters.gridGeometry[v][u][1]; SXY+= W*patternParameters.gridGeometry[v][u][0]*patternParameters.gridGeometry[v][u][1]; SZ+= W*gridCorr3d[2][index]; SXZ+= W*gridCorr3d[2][index]*patternParameters.gridGeometry[v][u][0]; SYZ+= W*gridCorr3d[2][index]*patternParameters.gridGeometry[v][u][1]; } } double [][] aM1= { {SX2, SXY, SX}, {SXY, SY2, SY}, {SX, SY, S0}}; double [][] aB1= {{SXZ},{SYZ},{SZ}}; Matrix M=new Matrix(aM1); Matrix B=new Matrix(aB1); if (this.debugLevel>2) { System.out.println(" M:"); M.print(8, 6); System.out.println(" B:"); B.print(8, 6); System.out.println(" Ax,Ay,B:"); M.solve(B).print(8, 6); } double [] tilts=M.solve(B).getRowPackedCopy(); // singular ??? if (this.debugLevel>2) { if (this.refineParameters.showThisCorrection) { this.SDFA_INSTANCE.showArrays(gridCorr3d, getGridWidth(), getGridHeight(), true, "before tilt:", titles); } } for (int v=0;v<height;v++) for (int u=0;u<width; u++){ int index=u+v*width; gridCorr3d[2][index]-=tilts[0]*patternParameters.gridGeometry[v][u][0]+tilts[1]*patternParameters.gridGeometry[v][u][1]+tilts[2]; } } if (this.debugLevel>2) { if (this.refineParameters.showThisCorrection) { double [][] gridCorr3dClone=new double [4][width*height]; for (int c=0;c<gridCorr3dClone.length;c++) for (int i=0;i<gridCorr3dClone[c].length;i++) gridCorr3dClone[c][i]=reliable[i]? gridCorr3d[c][i]:0.0; this.SDFA_INSTANCE.showArrays(gridCorr3dClone, getGridWidth(), getGridHeight(), true, "after tilt:", titles); } } IJ.showStatus(""); // combine in a single array? double [][][] result={gridCorr3d,gridZCorr3d,gridZCorr3dWeight}; return result; } public double [][][] calculateGridXYZCorr2D( final int width, final int height, final int [] stationGroups, final boolean [] selectedImages, final double [][] cameraXYZ, final LensDistortionParameters lensDistortionParametersProto, final boolean showIndividual, final int threadsMax, final boolean updateStatus ){ // final boolean isTripod=this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.is_tripod; // final boolean cartesian=this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.cartesian; final int [][] dirs= {{0,0},{-1,0},{1,0},{0,-1},{0,1}}; // possible to make 8 directions final double [][][] derivatives={ // for of /du, /dv 3 variants, depending on which neighbors are available { { 0.0,-0.5, 0.5, 0.0, 0.0}, { 1.0,-1.0, 0.0, 0.0, 0.0}, {-1.0, 0.0, 1.0, 0.0, 0.0} }, { { 0.0, 0.0, 0.0,-0.5, 0.5}, { 1.0, 0.0, 0.0,-1.0, 0.0}, {-1.0, 0.0, 0.0, 0.0, 1.0}}}; final double [][][] gridCorr2d=new double [selectedImages.length][][]; // per-image grid {dx,dy,weight} corrections for (int i=0;i<gridCorr2d.length;i++) { gridCorr2d[i]=null; cameraXYZ[i]=null; } // Should it be just once - common for all images? (removed from the "for" loop) final double [] diff=calcYminusFx(this.currentfX); final int debugLevel=this.debugLevel; final int [] imageStartIndex=this.imageStartIndex; final double [] Y=this.Y; final double [] weightFunction= this.weightFunction; final Thread[] threads = newThreadArray(threadsMax); final AtomicInteger imageNumberAtomic = new AtomicInteger(0); final AtomicInteger imageFinishedAtomic = new AtomicInteger(0); final double [] progressValues=new double [selectedImages.length]; int numSelectedImages=0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]) numSelectedImages++; int selectedIndex=0; for (int i=0;i<selectedImages.length;i++) { progressValues[i]=(selectedIndex+1.0)/numSelectedImages; if (selectedImages[i]) selectedIndex++; if (selectedIndex>=numSelectedImages) selectedIndex--; } IJ.showStatus("Calculating pattern geometry correction..."); for (int ithread = 0; ithread < threads.length; ithread++) { threads[ithread] = new Thread() { @Override public void run() { LensDistortionParameters lensDistortionParameters=lensDistortionParametersProto.clone(); // see - if that is needed - maybe new is OK // LensDistortionParameters lensDistortionParameters= new LensDistortionParameters(); for (int imgNum=imageNumberAtomic.getAndIncrement(); imgNum<selectedImages.length; imgNum=imageNumberAtomic.getAndIncrement()) if (selectedImages[imgNum]){ // for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) { int station=fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera if (stationGroups[station]<0) continue; // do not process images that do not belong to selected stations gridCorr2d[imgNum]=new double [4][width*height]; // dx, dy only - added zCorr per station for (int n=0;n<gridCorr2d[imgNum].length;n++) for (int i=0;i<gridCorr2d[imgNum][0].length;i++) gridCorr2d[imgNum][n][i]=0.0; // int chnNum=fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera cameraXYZ[imgNum]=new double[3]; // The following method sets this.lensDistortionParameters and invokes this.lensDistortionParameters.recalcCommons(); lensDistortionParameters.lensCalcInterParamers( lensDistortionParameters, fittingStrategy.distortionCalibrationData.isTripod(), fittingStrategy.distortionCalibrationData.isCartesian(), fittingStrategy.distortionCalibrationData.getPixelSize(imgNum), fittingStrategy.distortionCalibrationData.getDistortionRadius(imgNum), null, //this.interParameterDerivatives, // [22][] fittingStrategy.distortionCalibrationData.getParameters(imgNum), // 22-long parameter vector for the image null); // if no derivatives, null is OK // false); // calculate this.interParameterDerivatives -derivatives array (false - just this.values) cameraXYZ[imgNum]=lensDistortionParameters.getLensCenterCoordinates(); if (debugLevel>2) { System.out.println("calculateGridXYZCorr(): imgNum="+imgNum+" lens coordinates (mm)={"+ IJ.d2s(cameraXYZ[imgNum][0],3)+", "+IJ.d2s(cameraXYZ[imgNum][1],3)+", "+IJ.d2s(cameraXYZ[imgNum][2],3)+"}"); } // double [] diff=calcYminusFx(this.currentfX); // removed from the loop // find data range for the selected image int index=imageStartIndex[imgNum]; // set when fitting series is init double [][] imgData=new double[showIndividual?7:5][getGridHeight() * width]; // dPX, dPY, Px, Py, alpha for (int i=0;i<imgData.length;i++) for (int j=0;j<imgData[i].length;j++)imgData[i][j]=0.0; // first pass - prepare [v][u]arrays for (int i=0;i<fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][0]+patternParameters.U0; int v=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][1]+patternParameters.V0; int vu=u+width*v; imgData[0][vu]= diff[2*(index+i)]; // out of bound 1410 imgData[1][vu]= diff[2*(index+i)+1]; imgData[2][vu]= Y[2*(index+i)]; // measured pixel x imgData[3][vu]= Y[2*(index+i)+1];// measured pixel y // imgData[4][vu]= fittingStrategy.distortionCalibrationData.getMask(chnNum, imgData[2][vu], imgData[3][vu]); if (weightFunction!=null) { imgData[4][vu]= weightFunction[2*(index+i)]; } else { imgData[4][vu]= 1.0; } } // second pass - calculate derivatives, and residuals in mm for (int i=0;i<fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][0]+patternParameters.U0; int v=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][1]+patternParameters.V0; int vu=u+width*v; gridCorr2d[imgNum][0][vu]=0.0; gridCorr2d[imgNum][1][vu]=0.0; gridCorr2d[imgNum][2][vu]=patternParameters.getZCorr(vu,station); // per-station Z correction from average gridCorr2d[imgNum][3][vu]=0.0; // weight double [][] gXY =new double[dirs.length][3]; // double [][] gpXY=new double[dirs.length][2]; double [][] gpXY=new double[dirs.length][3]; boolean [] dirMask=new boolean [dirs.length]; for (int dir=0;dir<dirs.length;dir++){ int u1=u+dirs[dir][0]; int v1=v+dirs[dir][1]; int vu1=u1+width*v1; dirMask[dir] = (u1>=0) && (v1>=0) && (u1<width) && (v1<height) && (imgData[4][vu1]>0); if (dirMask[dir]){ gXY[dir][0]= patternParameters.gridGeometry[v1][u1][0]; gXY[dir][1]= patternParameters.gridGeometry[v1][u1][1]; gXY[dir][2]= patternParameters.gridGeometry[v1][u1][2]; // Here - average Z gpXY[dir][0]=imgData[2][vu1]; gpXY[dir][1]=imgData[3][vu1]; } else { gXY[dir][0]= 0.0; gXY[dir][1]= 0.0; gXY[dir][2]= 0.0; gpXY[dir][0]=0.0; gpXY[dir][1]=0.0; } } int [] variants={-1,-1}; // {horizontal, vertical} boolean variantsExist=true; for (int duv=0;duv<2;duv++){ // 0 - horizontal, 1 - vertical for (int variant=0;variant<derivatives[duv].length;variant++) { // variants: 0 half of right/left, 1 left deriv, 2 - right deriv boolean fit=true; for (int dir=0;dir<dirs.length;dir++) if ((derivatives[duv][variant][dir]!=0) && !dirMask[dir]){ fit=false; break; } if (fit) { variants[duv]=variant; break; } } if (variants[duv]<0) { // could not find any variant to calculate derivatives for this direction variantsExist=false; break; } } if (!variantsExist){ imgData[4][vu]=0.0; continue; } double [][] dXY_dUV= new double [2][2]; double [][] dpXY_dUV=new double [2][2]; for (int nom=0;nom<2;nom++) { // 0-x, 1 - y for (int denom=0;denom<2;denom++) { //0 - du, 1 - dv dXY_dUV [nom][denom]=0.0; dpXY_dUV[nom][denom]=0.0; for (int dir=0;dir<dirs.length;dir++){ dXY_dUV [nom][denom]+=gXY [dir][nom]*derivatives[denom][variants[denom]][dir]; dpXY_dUV[nom][denom]+=gpXY[dir][nom]*derivatives[denom][variants[denom]][dir]; } } } double [] dpXY={imgData[0][vu],imgData[1][vu]}; Matrix MdpXY= new Matrix(dpXY,2); // 2 rows Matrix MdXY_dUV= new Matrix(dXY_dUV); Matrix MdpXY_dUV=new Matrix(dpXY_dUV); if ((new LUDecomposition(MdpXY_dUV)).isNonsingular()){ /* * MdpXY= MdpXY_dUV* MdUV * MdXY= MdXY_dUV * MdUV * MdUV= MdpXY_dUV.solve(MdpXY); * MdXY= MdXY_dUV * MdpXY_dUV.solve(MdpXY); */ Matrix MdXY=MdXY_dUV.times(MdpXY_dUV.solve(MdpXY)); double [] dXY=MdXY.getRowPackedCopy(); gridCorr2d[imgNum][0][vu]=dXY[0]; gridCorr2d[imgNum][1][vu]=dXY[1]; gridCorr2d[imgNum][3][vu]=imgData[4][vu]; // weight } } // end scanning pixels if (showIndividual) { String [] titles={"diff-X","diff-Y","pX","pY","alpha","X-correction(mm)","Y-correction(mm)","Z-correction(mm)"}; (new ShowDoubleFloatArrays()).showArrays(imgData, width, height, true, "Grid"+imgNum, titles); } final int numFinished=imageFinishedAtomic.getAndIncrement(); // IJ.showProgress(progressValues[numFinished]); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // Here, we can safely update the GUI // because we'll be called from the // event dispatch thread IJ.showProgress(progressValues[numFinished]); } }); } } }; } startAndJoin(threads); IJ.showProgress(1.0); return gridCorr2d; } /** * Calculates corrections to X and Y coordinates of the grid nodes * //@param distortionCalibrationData - used to receive sensor mask(s) * @param grid3DCorrection - if true - calculate 3d correction, false - slow 3d (2d perpendicular to view) * @param maxZCorr - maximal allowed correction in Z-direction (if wants more, will fall back to 2-d correction (perpendicular to the view) * @param showIndividual - show individual images * @return first index - 0 - correction x (mm), 1 - correction y(mm), 2 - correction z(mm) 3 - weight (number of images used) */ public double [][] calculateGridXYZCorr3D( // old version // DistortionCalibrationData distortionCalibrationData, boolean grid3DCorrection, boolean rotateCorrection, double maxZCorr, boolean showIndividual){ int width=getGridWidth(); int height=getGridHeight(); int [][] dirs= {{0,0},{-1,0},{1,0},{0,-1},{0,1}}; // possible to make 8 directions double [][][] derivatives={ // for of /du, /dv 3 variants, depending on which neighbors are available { { 0.0,-0.5, 0.5, 0.0, 0.0}, { 1.0,-1.0, 0.0, 0.0, 0.0}, {-1.0, 0.0, 1.0, 0.0, 0.0} }, { { 0.0, 0.0, 0.0,-0.5, 0.5}, { 1.0, 0.0, 0.0,-1.0, 0.0}, {-1.0, 0.0, 0.0, 0.0, 1.0}}}; boolean [] selectedImages=fittingStrategy.selectedImages(); double [][][] gridCorr2d=new double [selectedImages.length][][]; // per-image grid {dx,dy,weight} corrections double [][] cameraXYZ=new double [selectedImages.length][]; for (int i=0;i<gridCorr2d.length;i++) { gridCorr2d[i]=null; cameraXYZ[i]=null; } int numSelected=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) numSelected++; int numProcessed=0; IJ.showStatus("Calculating pattern geometry correction..."); for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) { gridCorr2d[imgNum]=new double [3][width*height]; // dx, dy only for (int n=0;n<gridCorr2d[imgNum].length;n++) for (int i=0;i<gridCorr2d[imgNum][0].length;i++) gridCorr2d[imgNum][n][i]=0.0; // int chnNum=fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera cameraXYZ[imgNum]=new double[3]; // The following method sets this.lensDistortionParameters and invokes this.lensDistortionParameters.recalcCommons(); this.lensDistortionParameters.lensCalcInterParamers( this.lensDistortionParameters, fittingStrategy.distortionCalibrationData.isTripod(), fittingStrategy.distortionCalibrationData.isCartesian(), fittingStrategy.distortionCalibrationData.getPixelSize(imgNum), fittingStrategy.distortionCalibrationData.getDistortionRadius(imgNum), null, //this.interParameterDerivatives, // [22][] // fittingStrategy.distortionCalibrationData.pars[imgNum], // 22-long parameter vector for the image fittingStrategy.distortionCalibrationData.getParameters(imgNum), // 22-long parameter vector for the image null); // if no derivatives, null is OK // false); // calculate this.interParameterDerivatives -derivatives array (false - just this.values) cameraXYZ[imgNum]=lensDistortionParameters.getLensCenterCoordinates(); if (this.debugLevel>2) { System.out.println("calculateGridXYZCorr(): imgNum="+imgNum+" lens coordinates (mm)={"+ IJ.d2s(cameraXYZ[imgNum][0],3)+", "+IJ.d2s(cameraXYZ[imgNum][1],3)+", "+IJ.d2s(cameraXYZ[imgNum][2],3)+"}"); } double [] diff=calcYminusFx(this.currentfX); // find data range for the selected image int index=this.imageStartIndex[imgNum]; // set when fitting series is init /* int index=0; int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); for (int iNum=0;(iNum<imgNum) && (iNum<numImg) ;iNum++) if (selectedImages[iNum]) // index+=fittingStrategy.distortionCalibrationData.gIP[iNum].pixelsUV.length; //System.out.println ("+++++++++++++imgNum="+imgNum+" index="+index); */ if (this.debugLevel>2) { System.out.println("calculateGridXYZCorr(): fX.length="+this.currentfX.length+" this image index="+index); } double [][] imgData=new double[showIndividual?7:5][getGridHeight() * width]; // dPX, dPY, Px, Py, alpha for (int i=0;i<imgData.length;i++) for (int j=0;j<imgData[i].length;j++)imgData[i][j]=0.0; // first pass - prepare [v][u]arrays for (int i=0;i<fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][0]+patternParameters.U0; int v=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][1]+patternParameters.V0; int vu=u+width*v; imgData[0][vu]= diff[2*(index+i)]; // out of bound 1410 imgData[1][vu]= diff[2*(index+i)+1]; imgData[2][vu]= this.Y[2*(index+i)]; // measured pixel x imgData[3][vu]= this.Y[2*(index+i)+1];// measured pixel y // imgData[4][vu]= fittingStrategy.distortionCalibrationData.getMask(chnNum, imgData[2][vu], imgData[3][vu]); if (this.weightFunction!=null) { imgData[4][vu]= this.weightFunction[2*(index+i)]; } else { imgData[4][vu]= 1.0; } // if (imgNum==1) System.out.println ("---index="+index+" i="+i+" vu="+vu+ " v="+v+" u="+u+" x="+IJ.d2s(this.Y[2*(index+i)],1)+" y="+IJ.d2s(this.Y[2*(index+i)+1],1)); } // second pass - calculate derivatives, and residuals in mm for (int i=0;i<fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][0]+patternParameters.U0; int v=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][1]+patternParameters.V0; int vu=u+width*v; gridCorr2d[imgNum][0][vu]=0.0; gridCorr2d[imgNum][1][vu]=0.0; gridCorr2d[imgNum][2][vu]=0.0; // weight double [][] gXY =new double[dirs.length][3]; double [][] gpXY=new double[dirs.length][2]; boolean [] dirMask=new boolean [dirs.length]; for (int dir=0;dir<dirs.length;dir++){ int u1=u+dirs[dir][0]; int v1=v+dirs[dir][1]; int vu1=u1+width*v1; dirMask[dir] = (u1>=0) && (v1>=0) && (u1<width) && (v1<height) && (imgData[4][vu1]>0); gXY[dir][0]= dirMask[dir]?patternParameters.gridGeometry[v1][u1][0]:0.0; gXY[dir][1]= dirMask[dir]?patternParameters.gridGeometry[v1][u1][1]:0.0; gXY[dir][2]= dirMask[dir]?patternParameters.gridGeometry[v1][u1][2]:0.0; // Add per-station (optionally) gpXY[dir][0]=dirMask[dir]?imgData[2][vu1]:0.0; gpXY[dir][1]=dirMask[dir]?imgData[3][vu1]:0.0; } int [] variants={-1,-1}; // {horizontal, vertical} boolean variantsExist=true; for (int duv=0;duv<2;duv++){ // 0 - horizontal, 1 - vertical for (int variant=0;variant<derivatives[duv].length;variant++) { // variants: 0 half of right/left, 1 left deriv, 2 - right deriv boolean fit=true; for (int dir=0;dir<dirs.length;dir++) if ((derivatives[duv][variant][dir]!=0) && !dirMask[dir]){ fit=false; break; } if (fit) { variants[duv]=variant; break; } } if (variants[duv]<0) { // could not find any variant to calculate derivatives for this direction variantsExist=false; break; } } if (!variantsExist){ imgData[4][vu]=0.0; continue; } double [][] dXY_dUV= new double [2][2]; double [][] dpXY_dUV=new double [2][2]; for (int nom=0;nom<2;nom++) { // 0-x, 1 - y for (int denom=0;denom<2;denom++) { //0 - du, 1 - dv dXY_dUV [nom][denom]=0.0; dpXY_dUV[nom][denom]=0.0; for (int dir=0;dir<dirs.length;dir++){ dXY_dUV [nom][denom]+=gXY [dir][nom]*derivatives[denom][variants[denom]][dir]; dpXY_dUV[nom][denom]+=gpXY[dir][nom]*derivatives[denom][variants[denom]][dir]; } } } double [] dpXY={imgData[0][vu],imgData[1][vu]}; Matrix MdpXY= new Matrix(dpXY,2); // 2 rows Matrix MdXY_dUV= new Matrix(dXY_dUV); Matrix MdpXY_dUV=new Matrix(dpXY_dUV); if ((new LUDecomposition(MdpXY_dUV)).isNonsingular()){ /* * MdpXY= MdpXY_dUV* MdUV * MdXY= MdXY_dUV * MdUV * MdUV= MdpXY_dUV.solve(MdpXY); * MdXY= MdXY_dUV * MdpXY_dUV.solve(MdpXY); */ Matrix MdXY=MdXY_dUV.times(MdpXY_dUV.solve(MdpXY)); double [] dXY=MdXY.getRowPackedCopy(); gridCorr2d[imgNum][0][vu]=dXY[0]; gridCorr2d[imgNum][1][vu]=dXY[1]; gridCorr2d[imgNum][2][vu]=imgData[4][vu]; // weight } } // end scanning pixels if (showIndividual) { String [] titles={"diff-X","diff-Y","pX","pY","alpha","X-correction(mm)","Y-correction(mm)","Z-correction(mm)"}; this.SDFA_INSTANCE.showArrays(imgData, width, height, true, "Grid"+imgNum, titles); } IJ.showProgress(++numProcessed,numSelected); } IJ.showProgress(1.0); IJ.showStatus("Calculating pattern 3d correction..."); // now using gridCorr2d[imgNum], cameraXYZ[imgNum] and patternParameters.gridGeometry[v][u] find the 3d correction public double [][][] gridGeometry=null; // [v][u]{x,y,z,"alpha"} alpha=0 - no ghrid, 1 - grid double [][] gridCorr3d=new double [4][width*height]; for (int n=0;n<gridCorr3d.length;n++) for (int i=0;i<gridCorr3d[0].length;i++) gridCorr3d[n][i]=0.0; double Cx,Cy,Cz,Cxy,Cxz,Cyz; double [] V= new double[3]; double [] V2= new double[3]; int debugIndex=(height/2)*width+ (width/2); int debugIndex1=(height/2)*width+ (width/4); for (int v=0;v<height;v++) for (int u=0;u<width; u++){ int index=u+v*width; boolean thisDebug=(this.debugLevel>1) && ((index==debugIndex) || (index==debugIndex1)); if (thisDebug) System.out.println("calculateGridXYZCorr3D() debug("+this.debugLevel+"): index="+index+" v="+v+" u="+u); double [][] aM={{0.0,0.0,0.0},{0.0,0.0,0.0},{0.0,0.0,0.0}}; double [][] aB ={{0.0},{0.0},{0.0}}; double alpha=0.0; boolean fallBack2D=true; if (grid3DCorrection) { for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if ((gridCorr2d[imgNum]!=null) && (gridCorr2d[imgNum][2][index]>0.0)) { // calculate unity vector from the camera lens to the grid point double absV=0.0; for (int i=0;i<V.length;i++){ V[i]=patternParameters.gridGeometry[v][u][i]-cameraXYZ[imgNum][i]; absV+=V[i]*V[i]; } absV=Math.sqrt(absV); if (absV>0) for (int i=0;i<V.length;i++) V[i]/=absV; for (int i=0;i<V.length;i++) V2[i]=V[i]*V[i]; if (thisDebug) System.out.println(" imgNum="+imgNum+" V[0]="+IJ.d2s(V[0],4)+" V[1]="+IJ.d2s(V[1],4)+" V[2]="+IJ.d2s(V[2],4)+ " V2[0]="+IJ.d2s(V2[0],4)+" V2[1]="+IJ.d2s(V2[1],4)+" V2[2]="+IJ.d2s(V2[2],4)); // When performin 3-d correction (X,Y,Z) the result point has to have minimal weighted sum of squared distances to all rays // when summing for different stations, multiply W by sign(image belongs to station) /* Px, Py - calculated correction for individual image V={Vx,Vy,Vz} unity vector from the camera lens center to the {Px,Py,0} A - vector from the {Px,Py,0} to {X,Y,Z} = {X-Px,Y-Py,Z} Projection of A on V will have length of A(.)V, Vector B=V*(A(.)V) Vector D=A-B = A - V*(A(.)V) D2=D(.)D= A(.)A - 2* (A(.)V ) * (A(.)V ) + (A(.)V ) * (A(.)V ) = A(.)A - (A(.)V ) * (A(.)V ) D2=A(.)A - (A(.)V )^2 A(.)A=(X-Px)^2 + (Y-Py)^2 + Z^2 =X^2 -2*X*Px +Px^2 +Y^2 -2*Y*Py +Py^2 +Z^2 A(.)A=X^2 -2*X*Px +Px^2 +Y^2 -2*Y*Py +Py^2 +Z^2 A(.)V= (X-Px)*Vx + (Y-Py)*Vy + Z*Vz (A(.)V)^2= ((X-Px)*Vx + (Y-Py)*Vy + Z*Vz)^2 = ((X-Px)*Vx)^2 + ((Y-Py)*Vy)^2 + (Z*Vz)^2 + 2*((X-Px)*Vx)*((Y-Py)*Vy)+ 2*((X-Px)*Vx)*(Z*Vz)+2*((Y-Py)*Vy)*(Z*Vz) (A(.)V)^2= X^2*Vx^2 +Px^2*Vx^2 - 2*X*Px*Vx^2 +Y^2*Vy^2+Py^2*Vy^2-2*Y*Py*Vy^2 +Z^2*Vz^2 +2*X*Y*Vx*Vy +2*Px*Py*Vx*Vy - 2*X*Py*Vx*Vy - 2*Y*Px*Vx*Vy +2*X*Z*Vx*Vz - 2*Z*Px*Vx*Vz +2*Y*Z*Vy*Vz -2*z*Py*Vy*Vz D2= +X^2 - X^2*Vx^2 +Y^2 - Y^2*Vy^2 +Z^2 - Z^2*Vz^2 -2*X*Y* Vx*Vy -2*X*Z* Vx*Vz -2*Y*Z* Vy*Vz -2*X*Px +2*X*Px*Vx^2+ 2*X*Py*Vx*Vy -2*Y*Py +2*Y*Py*Vy^2+ 2*Y*Px*Vx*Vy +2*Z*Px*Vx*Vz +2*Z*Py*Vy*Vz +Px^2 +Py^2 -Px^2*Vx^2 -Py^2*Vy^2 -2*Px*Py*Vx*Vy 0= dD2/dX/2= X*(1-Vx^2) - Y* Vx*Vy - Z* Vx*Vz -Px + Px*Vx^2 + Py*Vx*Vy 0= dD2/dY/2= Y*(1-Vy^2) - X* Vx*Vy - Z* Vy*Vz -Py + Py*Vy^2 + Px*Vx*Vy 0= dD2/dZ/2= Z*(1-Vz^2) - X* Vx*Vz - Y* Vy*Vz + Px*Vx*Vz + Py*Vy*Vz X*(Vx^2-1) + Y* (Vx*Vy) + Z* (Vx*Vz) = Px * (Vx^2-1) + Py* (Vx*Vy) X*(Vx*Vy) + Y* (Vy^2-1) + Z* (Vy*Vz) = Px * (Vx*Vy) + Py * (Vy^2-1) X*(Vx*Vz) + Y* (Vy*Vz) + Z* (Vz^2-1) = Px * (Vx*Vz) + Py* (Vy*Vz) */ // | sum(Wi*Cxi), sum(Wi*Cxyi), sum(Wi*Cxzi) | //M= | sum(Wi*Cxyi), sum(Wi*Cyi ), sum(Wi*Cyzi) | // | sum(Wi*Cxzi), sum(Wi*Cyzi), sum(Wi*Czi ) | // | sum(Wi*(P0xi*Cxi + P0yi*Cxyi + P0zi*Cxzi)) | //B= | sum(Wi*(P0yi*Cyi + P0xi*Cyxi + P0zi*Cyzi)) | // | sum(Wi*(P0zi*Czi + P0yi*Czyi + P0xi*Czxi)) | /* X*(Vxi^2-1) + Y*(Vxi*Vyi) + Z*(Vxi*Vzi) = P0xi*(Vxi^2-1) +P0yi*(Vxi*Vyi) + P0zi*(Vxi*Vzi) X*(Vxi*Vyi) + Y*(Vyi^2-1) + Z*(Vyi*Vzi) = P0xi*(Vxi*Vyi) +P0yi*(Vyi^2-1) + P0zi*(Vyi*Vzi) X*(Vxi*Vzi) + Y*(Vxi*Vyi) + Z*(Vzi^2-1) = P0xi*(Vxi*Vzi) +P0yi*(Vxi*Vyi) + P0zi*(Vzi^2-1) X*Cx + Y*Cxy + Z*Cxz = P0xi*Cx +P0yi*Cxy + P0zi*Cxz X*Cxy + Y*Cy + Z*Cyz = P0xi*Cxy +P0yi*Cy + P0zi*Cyz X*Cxz + Y*Cyz + Z*Cz = P0xi*Cxz +P0yi*Cyz + P0zi*Cz P0zi==0.0, so - now we'll use P0zi - difference from this station to average X*Cx + Y*Cxy + Z*Cxz = P0xi*Cx +P0yi*Cxy X*Cxy + Y*Cy + Z*Cyz = P0xi*Cxy +P0yi*Cy X*Cxz + Y*Cyz + Z*Cz = P0xi*Cxz +P0yi*Cyz */ Cx=V2[0]-1.0; Cy=V2[1]-1.0; Cz=V2[2]-1.0; Cxy= V[0]*V[1]; Cxz= V[0]*V[2]; Cyz= V[1]*V[2]; if (thisDebug) System.out.println(" Cx="+IJ.d2s(Cx,6)+" Cy="+IJ.d2s(Cy,6)+" Cz="+IJ.d2s(Cz,6)+ " Cxy="+IJ.d2s(Cxy,6)+" Cxz="+IJ.d2s(Cxz,6)+" Cyz="+IJ.d2s(Cyz,6)); double W=gridCorr2d[imgNum][2][index]; double Px=gridCorr2d[imgNum][0][index]; double Py=gridCorr2d[imgNum][1][index]; alpha+=W; if (thisDebug) System.out.println(imgNum+": Px="+IJ.d2s(Px,6)+" Py="+IJ.d2s(Py,6)+" W="+IJ.d2s(W,6)); aM[0][0]+=W*Cx; aM[0][1]+=W*Cxy; aM[0][2]+=W*Cxz; aM[1][1]+=W*Cy; aM[1][2]+=W*Cyz; aM[2][2]+=W*Cz; aB[0][0]+=W*(Px*Cx + Py*Cxy);// Pz==0.0 aB[1][0]+=W*(Px*Cxy + Py*Cy);// Pz==0.0 aB[2][0]+=W*(Px*Cxz + Py*Cyz);// Pz==0.0 } aM[1][0]+=aM[0][1]; aM[2][0]+=aM[0][2]; aM[2][1]+=aM[1][2]; Matrix M=new Matrix(aM); Matrix B=new Matrix(aB); if (thisDebug) { System.out.println(" M:"); M.print(8, 6); System.out.println(" B:"); B.print(8, 6); } // boolean fallBack2D=true; if ((new LUDecomposition(M)).isNonsingular()){ double [] dXYZ=M.solve(B).getRowPackedCopy(); for (int i=0;i<3;i++) gridCorr3d[i][index]=dXYZ[i]; gridCorr3d[3][index]=alpha; fallBack2D=false; //TODO: make sure delta Z (Math.abs(gridCorr3d[2][index])) is not too big!! if (Math.abs(gridCorr3d[2][index])>maxZCorr) { fallBack2D=true; // temporary limit } if (thisDebug) System.out.println(" dX="+IJ.d2s(gridCorr3d[0][index],6)+" dY="+IJ.d2s(gridCorr3d[1][index],6)+" dZ="+IJ.d2s(gridCorr3d[2][index],6)); } } if(fallBack2D) { // make a 2d averaging of weighted dx, dy correction for (int i=0;i<gridCorr3d.length;i++) gridCorr3d[i][index]=0.0; double s=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if ((gridCorr2d[imgNum]!=null) &&(gridCorr2d[imgNum][2][index]>0.0)) { double W=gridCorr2d[imgNum][2][index]; // V[i]=patternParameters.gridGeometry[v][u][i]-cameraXYZ[imgNum][i]; double [] cv={ patternParameters.gridGeometry[v][u][0]-cameraXYZ[imgNum][0], patternParameters.gridGeometry[v][u][1]-cameraXYZ[imgNum][1], patternParameters.gridGeometry[v][u][2]-cameraXYZ[imgNum][2]}; double cv2=cv[0]*cv[0]+cv[1]*cv[1]+cv[2]*cv[2]; double kv=(gridCorr2d[imgNum][0][index]*cv[0]+gridCorr2d[imgNum][1][index]*cv[1])/cv2; gridCorr3d[0][index]+=W*(gridCorr2d[imgNum][0][index]-cv[0]*kv); gridCorr3d[1][index]+=W*(gridCorr2d[imgNum][1][index]-cv[1]*kv); gridCorr3d[2][index]+=W*( -cv[2]*kv); s+=W; } if (s>0){ gridCorr3d[0][index]/=s; gridCorr3d[1][index]/=s; gridCorr3d[2][index]/=s; } else { gridCorr3d[0][index]=0.0; gridCorr3d[1][index]=0.0; gridCorr3d[2][index]=0.0; } gridCorr3d[3][index]=s; if (thisDebug) System.out.println(" Using 2d averaging: dX="+IJ.d2s(gridCorr3d[0][index],6)+ " dY="+IJ.d2s(gridCorr3d[1][index],6)+" dZ="+IJ.d2s(gridCorr3d[2][index],6)); } } // Make average correction zero is it needed? // create "reliable" mask for averaging/tilting - disregard the outmost grid pixels boolean [] reliable=new boolean [width*height]; double wThreshold=0.0; for (int v=0;v<height;v++) for (int u=0;u<width;u++){ int index=u+v*width; reliable[index]=false; if ((v>0) && (u>0) && (v<(height-1)) && (u<(width-1)) && (gridCorr3d[3][index]>wThreshold) && (gridCorr3d[3][index-1]>wThreshold) && (gridCorr3d[3][index+1]>wThreshold) && (gridCorr3d[3][index-width]>wThreshold) && (gridCorr3d[3][index+width]>wThreshold) ){ reliable[index]=true; } } double corrAverage; for (int c=0;c<3;c++){ corrAverage=0.0; double s=0.0; for (int i=0;i<gridCorr3d[0].length;i++) if (reliable[i]) { corrAverage+=gridCorr3d[c][i]*gridCorr3d[3][i]; s+=gridCorr3d[3][i]; } corrAverage/=s; // System.out.println("zCorrAverage["+c+"="+corrAverage); for (int i=0;i<gridCorr3d[c].length;i++) gridCorr3d[c][i]-=corrAverage; } // for Z correction compensate for x/y tilts String [] titles={"X-correction(mm)","Y-correction(mm)","Z-correction","Weight"}; if (rotateCorrection) { double SX=0.0,SX2=0.0,SZ=0.0,SXY=0.0,SXZ=0.0,S0=0.0,SY=0.0,SY2=0.0,SYZ=0.0; double [][] gridGeom=new double [3][gridCorr3d[0].length]; for (int c=0;c<gridGeom.length;c++) for (int i=0;i<gridGeom[c].length;i++)gridGeom[c][i]=0.0; for (int v=0;v<height;v++) for (int u=0;u<width; u++){ int index=u+v*width; double W=gridCorr3d[3][index]; gridGeom[0][index]=patternParameters.gridGeometry[v][u][0]; gridGeom[1][index]=patternParameters.gridGeometry[v][u][1]; gridGeom[2][index]=W; if ((reliable[index]) && (W>0.0)){ S0+=W; SX+= W*patternParameters.gridGeometry[v][u][0]; SX2+= W*patternParameters.gridGeometry[v][u][0]*patternParameters.gridGeometry[v][u][0]; SY+= W*patternParameters.gridGeometry[v][u][1]; SY2+= W*patternParameters.gridGeometry[v][u][1]*patternParameters.gridGeometry[v][u][1]; SXY+= W*patternParameters.gridGeometry[v][u][0]*patternParameters.gridGeometry[v][u][1]; SZ+= W*gridCorr3d[2][index]; SXZ+= W*gridCorr3d[2][index]*patternParameters.gridGeometry[v][u][0]; SYZ+= W*gridCorr3d[2][index]*patternParameters.gridGeometry[v][u][1]; } } double [][] aM= { {SX2, SXY, SX}, {SXY, SY2, SY}, {SX, SY, S0}}; double [][] aB= {{SXZ},{SYZ},{SZ}}; Matrix M=new Matrix(aM); Matrix B=new Matrix(aB); if (this.debugLevel>2) { System.out.println(" M:"); M.print(8, 6); System.out.println(" B:"); B.print(8, 6); System.out.println(" Ax,Ay,B:"); M.solve(B).print(8, 6); } double [] tilts=M.solve(B).getRowPackedCopy(); if (this.debugLevel>2) { if (this.refineParameters.showThisCorrection) { this.SDFA_INSTANCE.showArrays(gridCorr3d, getGridWidth(), getGridHeight(), true, "before tilt:", titles); } } for (int v=0;v<height;v++) for (int u=0;u<width; u++){ int index=u+v*width; gridCorr3d[2][index]-=tilts[0]*patternParameters.gridGeometry[v][u][0]+tilts[1]*patternParameters.gridGeometry[v][u][1]+tilts[2]; } } if (this.debugLevel>2) { if (this.refineParameters.showThisCorrection) { double [][] gridCorr3dClone=new double [4][width*height]; for (int c=0;c<gridCorr3dClone.length;c++) for (int i=0;i<gridCorr3dClone[c].length;i++) gridCorr3dClone[c][i]=reliable[i]? gridCorr3d[c][i]:0.0; this.SDFA_INSTANCE.showArrays(gridCorr3dClone, getGridWidth(), getGridHeight(), true, "after tilt:", titles); } } IJ.showStatus(""); return gridCorr3d; } /** * * @param gridCorr3D Array of grid corrections (1-st index: dx, dy, dz, mask (>0 - valid point) * @param gridZCorr Optional per-station z-correction (or null) * @param width // width of the grid array * @param preShrink // shrink input array by this number of pixels (hor/vert) befere extrapolating (remove bad border nodes) * @param expand // expand/extrapolate this number of steps after shrinking (or until no pixels left * @param sigma // effective radius for fitting the extrapolation plane, in nodes * @param ksigma // size if square to consider (measured in ksigma-s). 2.0 means square is 4*sigma by 4*sigma * @return true if OK, false if error */ public boolean shrinkExtrapolateGridCorrection( double [][] gridCorr3D, // dx,dy,dz, mask >0 double [][] gridZCorr, // per-station additional Z-correction (or null) int width, int preShrink, int expand, double sigma, double ksigma){ int length=gridCorr3D[0].length; int height= length/width; // int decimate=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks; // int sWidth= (fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth-1)/decimate+1; // int sHeight=(fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight-1)/decimate+1; // double sigma=nsigma/decimate; boolean [] fMask=new boolean[length]; for (int i=0;i<fMask.length;i++) fMask[i]=gridCorr3D[3][i]>0; int len= (int) Math.ceil(sigma*ksigma); double [] gaussian=new double[len+1]; double k=0.5/sigma/sigma; for (int i=0;i<=len;i++) gaussian[i]=Math.exp(-i*i*k); int [][] dirs={{-1,0},{1,0},{0,-1},{0,1}}; List <Integer> extList=new ArrayList<Integer>(1000); Integer Index,Index2; extList.clear(); // create initial wave int debugThreshold=2; if (this.debugLevel>debugThreshold) System.out.println("shrinkExtrapolateGridCorrection width="+width+" height="+height); for (int iy=0;iy<height;iy++) for (int ix=0;ix<width;ix++) { Index=iy*width+ix; if (fMask[Index]) { int numNew=0; for (int dir=0;dir<dirs.length;dir++){ int ix1=ix+dirs[dir][0]; int iy1=iy+dirs[dir][1]; // Will not shrink from the array border! if ((ix1>=0) && (iy1>=0) && (ix1<width) && (iy1<height)) { if (!fMask[iy1*width+ix1]) numNew++; } if (numNew>0) extList.add(Index); // neighbor will have non-singular matrix } } } if (this.debugLevel>debugThreshold) System.out.println("Initial wave length="+extList.size()); // now shrink // unmask current wave for (int i=extList.size()-1; i>=0;i--) fMask[extList.get(i)]=false; if (extList.size()==0) return false; // no points for (int nShrink=0;nShrink<preShrink;nShrink++){ int size=extList.size(); if (this.debugLevel>debugThreshold) System.out.println("shrinking, size="+size); if (size==0) return false; // no points // wave step, unmasking for (int i=0; i<size;i++) { Index=extList.get(0); extList.remove(0); int iy=Index/width; int ix=Index%width; for (int dir=0;dir<dirs.length;dir++){ int ix1=ix+dirs[dir][0]; int iy1=iy+dirs[dir][1]; if ((ix1>=0) && (iy1>=0) && (ix1<width) && (iy1<height)){ Index=iy1*width+ix1; if (fMask[Index]){ extList.add(Index); fMask[Index]=false; // restore later? } } } } } // restore mask on the front for (int i=extList.size()-1; i>=0;i--) fMask[extList.get(i)]=true; // repeat with the wave until there is place to move, but not more than "expand" steps int [] dirs2=new int [2]; for (int n=0; (n<expand) && (extList.size()>0); n++ ){ if (this.updateStatus) IJ.showStatus("Expanding, step="+(n+1)+" (of "+expand+"), extList.size()="+extList.size()); // if (this.updateStatus) showStatus("Expanding, step="+(n+1)+" (of "+expand+"), extList.size()="+extList.size(),0); if (this.debugLevel>debugThreshold) System.out.println("Expanding, step="+n+", extList.size()="+extList.size()); // move wave front 1 pixel hor/vert for (int i=extList.size();i>0;i--){ // repeat current size times Index=extList.get(0); extList.remove(0); int iy=Index/width; int ix=Index%width; for (int dir=0;dir<dirs.length;dir++){ int ix1=ix+dirs[dir][0]; int iy1=iy+dirs[dir][1]; if ((ix1>=0) && (iy1>=0) && (ix1<width) && (iy1<height)){ Index=iy1*width+ix1; if (!fMask[Index]){ // verify it has neighbors in the perpendicular direction to dir dirs2[0]=(dir+2) & 3; dirs2[1]=dirs2[0] ^ 1; for (int dir2=0;dir2<dirs2.length;dir2++){ int ix2=ix+dirs[dirs2[dir2]][0]; // from the old, not the new point! int iy2=iy+dirs[dirs2[dir2]][1]; if ((ix2>=0) && (iy2>=0) && (ix2<width) && (iy2<height)){ Index2=iy2*width+ix2; if (fMask[Index2]){ // has orthogonal neighbor, OK to add extList.add(Index); fMask[Index]=true; // remove later break; } } } } } } } // now un-mask the pixels in new list new for (int i =0;i<extList.size();i++){ Index=extList.get(i); fMask[Index]=false; // now mask is only set for known pixels } // Calculate values (extrapolate) for the pixels in the list /* Err = sum (W(x,y)*(f(x,y)-F0-Ax*(x-X0)-Ay*(y-Y0))^2)= sum (Wxy*(Fxy^2+F0^2+Ax^2*(x-X0)^2+Ay^2*(y-Y0)^2 -2*Fxy*F0 -2*Fxy*Ax*(x-X0) - 2*Fxy*Ay*(y-Y0) +2*F0*Ax*(x-X0) + 2*F0*Ay*(y-Y0) +2*Ax*(x-X0)*Ay*(y-Y0)) (1)0=dErr/dF0= 2*sum (Wxy*(F0-Fxy+Ax*(x-X0)+Ay(y-Y0))) (2)0=dErr/dAx= 2*sum (Wxy*(Ax*(x-X0)^2-Fxy*(x-X0) +F0*(x-X0)+Ay*(x-x0)*(y-Y0))) (3)0=dErr/dAy= 2*sum (Wxy*(Ay*(y-y0)^2-Fxy*(y-Y0) +F0*(y-Y0)+Ax*(x-x0)*(y-Y0))) S0 = sum(Wxy) SF= sum(Wxy*Fxy) SX= sum(Wxy*(x-X0) SY= sum(Wxy*(y-Y0) SFX= sum(Wxy*Fxy*(x-X0) SFY= sum(Wxy*Fxy*(y-Y0) SX2= sum(Wxy*(x-X0)^2 SY2= sum(Wxy*(y-Y0)^2 SXY= sum(Wxy*(x-X0)*(y-Y0) (1) F0*S0 - SF + Ax*SX +Ay*Sy = 0 (2) Ax*SX2-SFX+F0*SX+Ay*SXY = 0 (3) Ay*Sy2 -SFY + F0*SY +Ax*SXY = 0 (1) F0*S0 + Ax*SX +Ay*SY = SF (2) Ax*SX2+F0*SX+Ay*SXY = SFX (3) Ay*Sy2 + F0*SY +Ax*SXY = SFY | F0 | V= | Ax | | Ay | | SF | B = | SFX | | SFY | | S0 SX SY | M = | SX SX2 SXY | | SY SXY SY2 | M * V = B */ int numOriginalComponents=3; boolean useExtra=gridZCorr!=null; for (int i =0;i<extList.size();i++){ Index=extList.get(i); int iy=Index/width; int ix=Index%width; double [] S0=new double [3+(useExtra?gridZCorr.length:0)]; for (int ii=0;ii<S0.length;ii++) S0[ii]=0.0; // double [] S0= {0.0,0.0,0.0}; double [] SF= S0.clone(); double [] SX= S0.clone(); double [] SY= S0.clone(); double [] SFX=S0.clone(); double [] SFY=S0.clone(); double [] SX2=S0.clone(); double [] SY2=S0.clone(); double [] SXY=S0.clone(); int iYmin=iy-len; if (iYmin<0) iYmin=0; int iYmax=iy+len; if (iYmax>=height) iYmax=height-1; int iXmin=ix-len; if (iXmin<0) iXmin=0; int iXmax=ix+len; if (iXmax>=width) iXmax=width-1; for (int iy1=iYmin;iy1<=iYmax;iy1++) for (int ix1=iXmin;ix1<=iXmax;ix1++) { int ind=ix1+iy1*width; if (fMask[ind]){ double w=gaussian[(iy1>=iy)?(iy1-iy):(iy-iy1)]*gaussian[(ix1>=ix)?(ix1-ix):(ix-ix1)]; for (int m=0;m<S0.length;m++){ double d=(m<numOriginalComponents)?gridCorr3D[m][ind]:gridZCorr[m-numOriginalComponents][ind]; S0[m]+= w; SF[m]+= w*d; SX[m]+= w*(ix1-ix); SY[m]+= w*(iy1-iy); SFX[m]+=w*d*(ix1-ix); SFY[m]+=w*d*(iy1-iy); SX2[m]+=w*(ix1-ix)*(ix1-ix); SY2[m]+=w*(iy1-iy)*(iy1-iy); SXY[m]+=w*(ix1-ix)*(iy1-iy); } } } for (int m=0;m<S0.length;m++){ double [][] aB={{SF[m]},{SFX[m]},{SFY[m]}}; double [][] aM={ {S0[m],SX[m], SY[m]}, {SX[m],SX2[m],SXY[m]}, {SY[m],SXY[m],SY2[m]} }; Matrix B=new Matrix(aB); Matrix M=new Matrix(aM); Matrix V=M.solve(B); if (m<numOriginalComponents) gridCorr3D[m][Index]=V.get(0,0); else gridZCorr[m-numOriginalComponents][Index]=V.get(0,0); } if (this.debugLevel>debugThreshold) System.out.println("updated v="+(Index/width)+" u="+(Index%width)+" {"+ IJ.d2s(gridCorr3D[0][Index],2)+","+IJ.d2s(gridCorr3D[1][Index],2)+","+IJ.d2s(gridCorr3D[2][Index],2)+"}"); } // set mask again for the new calculated layer of pixels for (int i =0;i<extList.size();i++){ Index=extList.get(i); fMask[Index]=true; } } return true; } public void logScale( double [] data, double fatZero){ for (int i=0;i<data.length;i++){ double d=((data[i]>=0)?data[i]:0.0); data[i]=(fatZero>0)?(Math.log(fatZero+d)):d; } } public void unLogScale( double [] data, double fatZero){ for (int i=0;i<data.length;i++){ if (fatZero>0.0) data[i]=Math.exp(data[i])-fatZero; if (data[i]<0.0) data[i]=0.0; } } /** * Extrapolates sensor correction beyond known data (in-place) * @param fieldXY [2][nPixels] vector field to extrapolate * @param sMask [nPixels] alpha (0.0 .. 1.0) "reliability" mask to apply to vector field * @param alphaThreshold start with pixels with alpha above this value (disregard border unreliable pixels) * @param nsigma when fitting plane through new point use Gaussian weight function for the neighbors * (normalized to non-decimated points) * @param ksigma Process pixels in a square with the side 2*sigma*ksigma * @return false if nothing to extrapolate (too small mask)? */ public boolean extrapolateSensorCorrection( int numChn, boolean [] whichExtrapolate, double [][] fieldXY, double []sMask, double alphaThreshold, double nsigma, double ksigma){ int decimate = getDecimateMasks(numChn); int sWidth = (getSensorWidth(numChn)-1)/decimate+1; int sHeight = (getSensorHeight(numChn)-1)/decimate+1; double sigma=nsigma/decimate; boolean [] fMask=new boolean[fieldXY[0].length]; for (int i=0;i<fMask.length;i++) fMask[i]=sMask[i]>=alphaThreshold; int len= (int) Math.ceil(sigma*ksigma); double [] gaussian=new double[len+1]; double k=0.5/sigma/sigma; for (int i=0;i<=len;i++) gaussian[i]=Math.exp(-i*i*k); int [][] dirs={{-1,0},{1,0},{0,-1},{0,1}}; List <Integer> extList=new ArrayList<Integer>(1000); Integer Index; extList.clear(); // create initial wave if (this.debugLevel>2) System.out.println("extrapolateSensorCorrection() decimate="+decimate+", sWidth="+sWidth+" sHeight="+sHeight); for (int iy=0;iy<sHeight;iy++) for (int ix=0;ix<sWidth;ix++) { Index=iy*sWidth+ix; if (fMask[Index]) { int numOld=0; int numNew=0; for (int dir=0;dir<dirs.length;dir++){ int ix1=ix+dirs[dir][0]; int iy1=iy+dirs[dir][1]; if ((ix1>=0) && (iy1>=0) && (ix1<sWidth) && (iy1<sHeight)) { if (fMask[iy1*sWidth+ix1]) numOld++; else numNew++; } if ((numNew>0) && (numOld>1)) extList.add(Index); // neighbor will have non-singular matrix } } } if (extList.size()==0) return false; while (extList.size()>0){ if (this.debugLevel>2) System.out.println("extList.size()="+extList.size()); // move wave front 1 pixel hor/vert for (int i=extList.size();i>0;i--){ // repeat current size times Index=extList.get(0); extList.remove(0); int iy=Index/sWidth; int ix=Index%sWidth; for (int dir=0;dir<dirs.length;dir++){ int ix1=ix+dirs[dir][0]; int iy1=iy+dirs[dir][1]; if ((ix1>=0) && (iy1>=0) && (ix1<sWidth) && (iy1<sHeight)){ Index=iy1*sWidth+ix1; if (!fMask[Index]){ extList.add(Index); fMask[Index]=true; // remove later } } } } // now un-mask the pixels in new list new for (int i =0;i<extList.size();i++){ Index=extList.get(i); fMask[Index]=false; // now mask is only set for known pixels } // Calculate values (extrapolate) for the pixels in the list /* Err = sum (W(x,y)*(f(x,y)-F0-Ax*(x-X0)-Ay*(y-Y0))^2)= sum (Wxy*(Fxy^2+F0^2+Ax^2*(x-X0)^2+Ay^2*(y-Y0)^2 -2*Fxy*F0 -2*Fxy*Ax*(x-X0) - 2*Fxy*Ay*(y-Y0) +2*F0*Ax*(x-X0) + 2*F0*Ay*(y-Y0) +2*Ax*(x-X0)*Ay*(y-Y0)) (1)0=dErr/dF0= 2*sum (Wxy*(F0-Fxy+Ax*(x-X0)+Ay(y-Y0))) (2)0=dErr/dAx= 2*sum (Wxy*(Ax*(x-X0)^2-Fxy*(x-X0) +F0*(x-X0)+Ay*(x-x0)*(y-Y0))) (3)0=dErr/dAy= 2*sum (Wxy*(Ay*(y-y0)^2-Fxy*(y-Y0) +F0*(y-Y0)+Ax*(x-x0)*(y-Y0))) S0 = sum(Wxy) SF= sum(Wxy*Fxy) SX= sum(Wxy*(x-X0) SY= sum(Wxy*(y-Y0) SFX= sum(Wxy*Fxy*(x-X0) SFY= sum(Wxy*Fxy*(y-Y0) SX2= sum(Wxy*(x-X0)^2 SY2= sum(Wxy*(y-Y0)^2 SXY= sum(Wxy*(x-X0)*(y-Y0) (1) F0*S0 - SF + Ax*SX +Ay*Sy = 0 (2) Ax*SX2-SFX+F0*SX+Ay*SXY = 0 (3) Ay*Sy2 -SFY + F0*SY +Ax*SXY = 0 (1) F0*S0 + Ax*SX +Ay*SY = SF (2) Ax*SX2+F0*SX+Ay*SXY = SFX (3) Ay*Sy2 + F0*SY +Ax*SXY = SFY | F0 | V= | Ax | | Ay | | SF | B = | SFX | | SFY | | S0 SX SY | M = | SX SX2 SXY | | SY SXY SY2 | M * V = B */ double [] zeros= new double[whichExtrapolate.length]; for (int i=0;i<zeros.length;i++)zeros[i]=0.0; for (int i =0;i<extList.size();i++){ Index=extList.get(i); int iy=Index/sWidth; int ix=Index%sWidth; double [] S0= zeros.clone(); double [] SF= zeros.clone(); double [] SX= zeros.clone(); double [] SY= zeros.clone(); double [] SFX=zeros.clone(); double [] SFY=zeros.clone(); double [] SX2=zeros.clone(); double [] SY2=zeros.clone(); double [] SXY=zeros.clone(); int iYmin=iy-len; if (iYmin<0) iYmin=0; int iYmax=iy+len; if (iYmax>=sHeight) iYmax=sHeight-1; int iXmin=ix-len; if (iXmin<0) iXmin=0; int iXmax=ix+len; if (iXmax>=sWidth) iXmax=sWidth-1; for (int iy1=iYmin;iy1<=iYmax;iy1++) for (int ix1=iXmin;ix1<=iXmax;ix1++) { int ind=ix1+iy1*sWidth; if (fMask[ind]){ double w=gaussian[(iy1>=iy)?(iy1-iy):(iy-iy1)]*gaussian[(ix1>=ix)?(ix1-ix):(ix-ix1)]; for (int m=0;m<whichExtrapolate.length;m++) if(whichExtrapolate[m]){ S0[m]+= w; SF[m]+= w*fieldXY[m][ind]; SX[m]+= w*(ix1-ix); SY[m]+= w*(iy1-iy); SFX[m]+=w*fieldXY[m][ind]*(ix1-ix); SFY[m]+=w*fieldXY[m][ind]*(iy1-iy); SX2[m]+=w*(ix1-ix)*(ix1-ix); SY2[m]+=w*(iy1-iy)*(iy1-iy); SXY[m]+=w*(ix1-ix)*(iy1-iy); } } } for (int m=0;m<whichExtrapolate.length;m++) if(whichExtrapolate[m]){ double [][] aB={{SF[m]},{SFX[m]},{SFY[m]}}; double [][] aM={ {S0[m],SX[m], SY[m]}, {SX[m],SX2[m],SXY[m]}, {SY[m],SXY[m],SY2[m]} }; Matrix B=new Matrix(aB); Matrix M=new Matrix(aM); Matrix V=M.solve(B); fieldXY[m][Index]=V.get(0,0); } } // set mask again for the new calculated layer of pixels for (int i =0;i<extList.size();i++){ Index=extList.get(i); fMask[Index]=true; } } return true; } /** * Calculates residual correction from the measured sensor pX, pY to the calculated {pixel X, pixel Y} * @param distortionCalibrationData * @param showIndividual - show individual images * @param showIndividualNumber - which image to show (-1 - all) * @return */ public double [][][] calculateSensorXYCorr( DistortionCalibrationData distortionCalibrationData, boolean showIndividual, int showIndividualNumber, // which image to show (-1 - all) boolean useGridAlpha // use grid alpha, false - use old calculations ){ int numChannels=distortionCalibrationData.getNumChannels(); // number of used channels int width=getGridWidth(); int height=getGridHeight(); int imgRGBIndex= 3; int [] uvInc={0,1,width,width+1}; // four corners as vu index int [][] cycles={ // counter-clockwise corners bounding the area (only orthogonal sides?) {1,0,2}, {2,3,1}, {0,2,3}, {3,1,0}}; double [][][] gridPCorr=new double [numChannels][][]; for (int chnNum=0;chnNum<gridPCorr.length;chnNum++) gridPCorr[chnNum]=null; boolean [] selectedImages=fittingStrategy.selectedImages(); boolean debugExit=false; int debugCntr=2; int numSelected=0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) numSelected++; int numProcessed=0; IJ.showStatus("Calculating sensor corrections..."); for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) { if (debugExit) break; int chnNum=fittingStrategy.distortionCalibrationData.gIP[imgNum].channel; // number of sub-camera int decimate=getDecimateMasks(chnNum); int sWidth= (getSensorWidth(chnNum)-1)/decimate+1; int sHeight=(getSensorHeight(chnNum)-1)/decimate+1; int station=fittingStrategy.distortionCalibrationData.gIP[imgNum].getStationNumber(); // number of sub-camera double [][] photometrics=patternParameters.getPhotometricBySensor(station,chnNum); // head/bottom grid intensity/alpha if (showIndividual && ((showIndividualNumber<0) || (showIndividualNumber==chnNum))) { String [] titles={"R","G","B","A"}; this.SDFA_INSTANCE.showArrays(photometrics, width, height, true, "Photometrics"+chnNum+"-"+imgNum, titles); } // initialize this array if it is needed, leave unused null if (gridPCorr[chnNum]==null){ gridPCorr[chnNum]=new double [7][sWidth*sHeight]; for (int n=0;n<gridPCorr[chnNum].length;n++) for (int i=0;i<gridPCorr[chnNum][0].length;i++) gridPCorr[chnNum][n][i]=0.0; } double [][] thisPCorr=null; thisPCorr=new double [7][sWidth*sHeight]; // calculate for a single (this) image, accumulate in the end for (int n=0;n<thisPCorr.length;n++) for (int i=0;i<thisPCorr[0].length;i++) thisPCorr[n][i]=0.0; double [] diff=calcYminusFx(this.currentfX); // find data range for the selected image int index=0; int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); for (int iNum=0;(iNum<imgNum) && (iNum<numImg) ;iNum++) if (selectedImages[iNum]) index+=fittingStrategy.distortionCalibrationData.gIP[iNum].pixelsUV.length; if (this.debugLevel>2) { System.out.println("calculateGridXYCorr(): fX.length="+this.currentfX.length+" this image index="+index); } double [][] imgData=new double[8][height * width]; // dPX, dPY, Px, Py, alpha,R,G,B for (int i=0;i<imgData.length;i++) for (int j=0;j<imgData[i].length;j++)imgData[i][j]=0.0; for (int i=0;i<fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][0]+patternParameters.U0; // starting from 0 int v=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV[i][1]+patternParameters.V0; // starting from 0 int vu=u+width*v; imgData[0][vu]= diff[2*(index+i)]; imgData[1][vu]= diff[2*(index+i)+1]; imgData[2][vu]= this.Y[2*(index+i)]; // measured pixel x imgData[3][vu]= this.Y[2*(index+i)+1];// measured pixel y imgData[4][vu]= this.weightFunction[2*(index+i)]; for (int c=0;c<3;c++){ // double g=gridGeometry[v][u][gridRGBIndex+c]; double g=photometrics[c][vu]; imgData[5+c][vu]=(g>0)?(fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsXY[i][imgRGBIndex+c]/g):g; } } if (showIndividual && ((showIndividualNumber<0) || (showIndividualNumber==chnNum))) { String [] titles={"dPx","dPy","Px","Py","A","R","G","B"};// dPX, dPY, Px, Py, alpha,R,G,B - rgb - full, not incremental this.SDFA_INSTANCE.showArrays(imgData, width, height, true, "imgData"+imgNum, titles); } // now use imgData array to fill thisPCorr by linear interpolation for (int v=0;v<(height-1); v++) for (int u=0; u<(width-1);u++){ if (debugExit) break; int vu=u+width*v; double [][] cornerXY =new double[4][]; for (int i=0;i<uvInc.length;i++){ int vu1=vu+uvInc[i]; if (imgData[4][vu1]>0.0){ cornerXY[i]=new double[2]; cornerXY[i][0]=imgData[2][vu1]; cornerXY[i][1]=imgData[3][vu1]; } else cornerXY[i]=null; } boolean [] cycleFits=new boolean[cycles.length]; boolean anyFits=false; for (int i=0;i<cycles.length;i++){ cycleFits[i]=true; for (int j=0;j<cycles[i].length;j++) if (cornerXY[cycles[i][j]]==null) { cycleFits[i]=false; break; } anyFits |=cycleFits[i]; } if (!anyFits) continue; // not a single cycle if ((this.debugLevel>3) && !debugExit) { String debugString="cycleFits "; for (int i =0;i<cycleFits.length; i++) debugString+=" "+cycleFits[i]; System.out.println(debugString); } if (cycleFits[0]&&cycleFits[1]){ // remove overlaps cycleFits[2]=false; cycleFits[3]=false; } boolean minMaxUndefined=true; double minX=0,maxX=0,minY=0,maxY=0; // find bounding rectangle; for (int nCycle=0;nCycle<cycles.length;nCycle++) if (cycleFits[nCycle]){ int [] cycle=cycles[nCycle]; for (int corner=0; corner<cycle.length;corner++){ if (minMaxUndefined || (minX>cornerXY[cycle[corner]][0])) minX=cornerXY[cycle[corner]][0]; if (minMaxUndefined || (maxX<cornerXY[cycle[corner]][0])) maxX=cornerXY[cycle[corner]][0]; if (minMaxUndefined || (minY>cornerXY[cycle[corner]][1])) minY=cornerXY[cycle[corner]][1]; if (minMaxUndefined || (maxY<cornerXY[cycle[corner]][1])) maxY=cornerXY[cycle[corner]][1]; minMaxUndefined=false; } } int iMinX=(int) Math.floor(minX/decimate); int iMinY=(int) Math.floor(minY/decimate); int iMaxX=(int) Math.ceil(maxX/decimate); int iMaxY=(int) Math.ceil(maxY/decimate); // not sure if these checks are needed, got out of bounds wheriDy was =484=sHeight if (iMinX<0) iMinX=0; if (iMaxX>=sWidth) iMaxX=sWidth-1; if (iMinY<0) iMinY=0; if (iMaxY>=sHeight) iMaxY=sHeight-1; double [] originXY=new double [2]; double [] endXY=new double [2]; boolean debugHadPixels=false; //TODO: scan X,Y in this rectangle, for points in defined squares/triangles find if the point is inside (accurate not to loose any). for (int idY=iMinY; idY<=iMaxY;idY++){ double pY=idY*decimate; // in sensor pixels for (int idX=iMinX; idX<=iMaxX;idX++){ double pX=idX*decimate; // in sensor pixels // scan allowed triangles, usually 2 for (int nCycle=0;nCycle<cycles.length;nCycle++) if (cycleFits[nCycle]){ int [] cycle=cycles[nCycle]; // is this point inside? if (debugExit) { for (int nEdge=0;nEdge<cycle.length;nEdge++){ int nextNEdge=(nEdge==(cycle.length-1))?0:(nEdge+1); System.out.println("nEdge="+nEdge+" nextNEdge"+nextNEdge); originXY[0]=imgData[2][vu+uvInc[cycle[nEdge]]]; originXY[1]=imgData[3][vu+uvInc[cycle[nEdge]]]; endXY[0]= imgData[2][vu+uvInc[cycle[nextNEdge]]]; endXY[1]= imgData[3][vu+uvInc[cycle[nextNEdge]]]; System.out.println("--- pX="+IJ.d2s(pX,1)+" originXY[0]="+IJ.d2s(originXY[0],1)+ " endXY[1]="+IJ.d2s(endXY[1],1)+" originXY[1]="+IJ.d2s(originXY[1],1)); System.out.println("--- pY="+IJ.d2s(pY,1)+" originXY[1]="+IJ.d2s(originXY[1],1)+ " endXY[0]="+IJ.d2s(endXY[0],1)+" originXY[0]="+IJ.d2s(originXY[0],1)); System.out.println("Cross-product="+IJ.d2s(((pX-originXY[0])*(endXY[1]-originXY[1]) - (pY-originXY[1])*(endXY[0]-originXY[0])),1)); } } boolean inside=true; for (int nEdge=0;nEdge<cycle.length;nEdge++){ int nextNEdge=(nEdge==(cycle.length-1))?0:(nEdge+1); originXY[0]=imgData[2][vu+uvInc[cycle[nEdge]]]; originXY[1]=imgData[3][vu+uvInc[cycle[nEdge]]]; endXY[0]= imgData[2][vu+uvInc[cycle[nextNEdge]]]; endXY[1]= imgData[3][vu+uvInc[cycle[nextNEdge]]]; if (((pX-originXY[0])*(endXY[1]-originXY[1]) - (pY-originXY[1])*(endXY[0]-originXY[0]))<0.0){ inside=false; break; } } if (!inside) continue; // point is outside of the interpolation area, try next triangle (if any) // if ((this.debugLevel>3) && !debugExit) { if (this.debugLevel>3) { System.out.println("idX="+idX+" idY="+idY+" nCycle="+nCycle); String debugString1="cycle:"; for (int i =0;i<cycle.length; i++) debugString1+=" "+cycle[i]; System.out.println(debugString1); } /* interpolate: 1. taking cycles[0] as origin and two (non co-linear) edge vectors - V1:from 0 to 1 and V2 from 1 to 2 find a1 and a2 so that vector V (from 0 to pXY) = a1*V1+ a2*V2 2. if F0 is the value of the interpolated function at cycles[0], F1 and F2 - at cycles[1] and cycles2 then F=F0+(F1-F0)*a1 +(F2-F1)*a2 */ double [] XY0={imgData[2][vu+uvInc[cycle[0]]],imgData[3][vu+uvInc[cycle[0]]]}; double [] XY1={imgData[2][vu+uvInc[cycle[1]]],imgData[3][vu+uvInc[cycle[1]]]}; double [] XY2={imgData[2][vu+uvInc[cycle[2]]],imgData[3][vu+uvInc[cycle[2]]]}; double [] V= {pX-XY0[0],pY-XY0[1]}; double [][] M={ {XY1[0]-XY0[0],XY2[0]-XY1[0]}, {XY1[1]-XY0[1],XY2[1]-XY1[1]}}; double det=M[0][0]*M[1][1]-M[1][0]*M[0][1]; double [][] MInverse={ { M[1][1]/det,-M[0][1]/det}, {-M[1][0]/det, M[0][0]/det}}; double [] a12={ MInverse[0][0]*V[0]+MInverse[0][1]*V[1], MInverse[1][0]*V[0]+MInverse[1][1]*V[1]}; int pCorrIndex=idY*sWidth+idX; // some points may be accumulated multiple times - thisPCorr[3] will take care of this if (this.debugLevel>3) { System.out.println("XY0="+IJ.d2s(XY0[0],3)+":"+IJ.d2s(XY0[1],3)); System.out.println("XY1="+IJ.d2s(XY1[0],3)+":"+IJ.d2s(XY1[1],3)); System.out.println("XY2="+IJ.d2s(XY2[0],3)+":"+IJ.d2s(XY2[1],3)); System.out.println("M00="+IJ.d2s(M[0][0],3)+" M01="+IJ.d2s(M[0][1],3)); System.out.println("M10="+IJ.d2s(M[1][0],3)+" M11="+IJ.d2s(M[1][1],3)); System.out.println("MInverse00="+IJ.d2s(MInverse[0][0],5)+" MInverse01="+IJ.d2s(MInverse[0][1],5)); System.out.println("MInverse10="+IJ.d2s(MInverse[1][0],5)+" MInverse11="+IJ.d2s(MInverse[1][1],5)); System.out.println("a12="+IJ.d2s(a12[0],3)+":"+IJ.d2s(a12[1],3)); System.out.println("imgData[0][vu+uvInc[cycle[0]]]="+IJ.d2s(imgData[0][vu+uvInc[cycle[0]]],3)+ "imgData[1][vu+uvInc[cycle[0]]]="+IJ.d2s(imgData[1][vu+uvInc[cycle[0]]],3)); System.out.println("imgData[0][vu+uvInc[cycle[1]]]="+IJ.d2s(imgData[0][vu+uvInc[cycle[1]]],3)+ "imgData[1][vu+uvInc[cycle[1]]]="+IJ.d2s(imgData[1][vu+uvInc[cycle[1]]],3)); System.out.println("imgData[0][vu+uvInc[cycle[2]]]="+IJ.d2s(imgData[0][vu+uvInc[cycle[2]]],3)+ "imgData[1][vu+uvInc[cycle[2]]]="+IJ.d2s(imgData[1][vu+uvInc[cycle[2]]],3)); } double [] corr={ imgData[0][vu+uvInc[cycle[0]]]+ // dPx (imgData[0][vu+uvInc[cycle[1]]]-imgData[0][vu+uvInc[cycle[0]]])*a12[0]+ (imgData[0][vu+uvInc[cycle[2]]]-imgData[0][vu+uvInc[cycle[1]]])*a12[1], imgData[1][vu+uvInc[cycle[0]]]+ // dPy (imgData[1][vu+uvInc[cycle[1]]]-imgData[1][vu+uvInc[cycle[0]]])*a12[0]+ (imgData[1][vu+uvInc[cycle[2]]]-imgData[1][vu+uvInc[cycle[1]]])*a12[1], imgData[4][vu+uvInc[cycle[0]]]+ // alpha (imgData[4][vu+uvInc[cycle[1]]]-imgData[4][vu+uvInc[cycle[0]]])*a12[0]+ (imgData[4][vu+uvInc[cycle[2]]]-imgData[4][vu+uvInc[cycle[1]]])*a12[1], imgData[5][vu+uvInc[cycle[0]]]+ // Red measured/pattern (imgData[5][vu+uvInc[cycle[1]]]-imgData[5][vu+uvInc[cycle[0]]])*a12[0]+ (imgData[5][vu+uvInc[cycle[2]]]-imgData[5][vu+uvInc[cycle[1]]])*a12[1], imgData[6][vu+uvInc[cycle[0]]]+ // Green measured/pattern (imgData[6][vu+uvInc[cycle[1]]]-imgData[6][vu+uvInc[cycle[0]]])*a12[0]+ (imgData[6][vu+uvInc[cycle[2]]]-imgData[6][vu+uvInc[cycle[1]]])*a12[1], imgData[7][vu+uvInc[cycle[0]]]+ // Blue measured/pattern (imgData[7][vu+uvInc[cycle[1]]]-imgData[7][vu+uvInc[cycle[0]]])*a12[0]+ (imgData[7][vu+uvInc[cycle[2]]]-imgData[7][vu+uvInc[cycle[1]]])*a12[1] }; if (this.debugLevel>3) { System.out.println("corr="+IJ.d2s(corr[0],3)+" "+IJ.d2s(corr[1],3)+" "+IJ.d2s(corr[2],3)); } if (pCorrIndex>thisPCorr[0].length) { System.out.println("imgNum=" + imgNum+": "+ fittingStrategy.distortionCalibrationData.gIP[imgNum].path); System.out.println("thisPCorr[0].length="+thisPCorr[0].length+" pCorrIndex="+pCorrIndex+" sWidth="+sWidth+" idY="+idY+" idX="+idX); } thisPCorr[0][pCorrIndex]+= corr[0];// dPx thisPCorr[1][pCorrIndex]+= corr[1];// dPy thisPCorr[2][pCorrIndex]+= corr[2];// alpha thisPCorr[3][pCorrIndex]+= 1.0; // number of times accumulated thisPCorr[4][pCorrIndex]+= corr[3];// red thisPCorr[5][pCorrIndex]+= corr[4];// green thisPCorr[6][pCorrIndex]+= corr[5];// blue if (this.debugLevel>3) { debugHadPixels=true; // if (!debugExit) debugCntr--; // if (debugCntr==0) debugExit=true; // exit after first non-empty tile } //gridPCorr[chnNum] } } // idX // use same order in calculations, make sure no gaps } // idY if ((this.debugLevel>3) && (debugHadPixels)){ if (!debugExit) { System.out.println( " minX="+IJ.d2s(minX,1)+ " maxX="+IJ.d2s(maxX,1)); System.out.println( " minY="+IJ.d2s(minY,1)+ " maxY="+IJ.d2s(maxY,1)); System.out.println( " iMinX="+iMinX+ " iMaxX="+iMaxX); System.out.println( " iMinY="+iMinY+ " iMaxY="+iMaxY); } if (!debugExit) debugCntr--; if (debugCntr==0) debugExit=true; // exit after first non-empty tile } } // finished image /* if (showIndividual) { String [] titles={"dPx","dPy","alpha","Multiple","Red","Green","Blue"}; this.SDFA_INSTANCE.showArrays(thisPCorr, sWidth, sHeight, true, "thisPCorr_pre"+imgNum, titles); } */ // some points may be calculated multiple times for (int i=0;i<gridPCorr[chnNum][0].length;i++) if (thisPCorr[3][i]>=1.0){ thisPCorr[0][i]/=thisPCorr[3][i]; // dPx thisPCorr[1][i]/=thisPCorr[3][i]; // dPy thisPCorr[2][i]/=thisPCorr[3][i]; // alpha thisPCorr[4][i]/=thisPCorr[3][i]; // r thisPCorr[5][i]/=thisPCorr[3][i]; // g thisPCorr[6][i]/=thisPCorr[3][i]; // b } if (showIndividual && ((showIndividualNumber<0) || (showIndividualNumber==chnNum))) { String [] titles={"dPx","dPy","alpha","Multiple","Red","Green","Blue"}; this.SDFA_INSTANCE.showArrays(thisPCorr, sWidth, sHeight, true, "thisPCorr"+imgNum, titles); } for (int i=0;i<gridPCorr[chnNum][0].length;i++) if (thisPCorr[2][i]>0){ gridPCorr[chnNum][0][i]+=thisPCorr[0][i]*thisPCorr[2][i]; gridPCorr[chnNum][1][i]+=thisPCorr[1][i]*thisPCorr[2][i]; /**TODO: not used anyway - just for debugging? see if just the sensor mask should go here? Or when saving?*/ if (gridPCorr[chnNum][2][i]<thisPCorr[2][i]) gridPCorr[chnNum][2][i]=thisPCorr[2][i]; // best alpha gridPCorr[chnNum][3][i]+= thisPCorr[2][i]; // sum of weights from all images gridPCorr[chnNum][4][i]+=thisPCorr[4][i]*thisPCorr[2][i]; gridPCorr[chnNum][5][i]+=thisPCorr[5][i]*thisPCorr[2][i]; gridPCorr[chnNum][6][i]+=thisPCorr[6][i]*thisPCorr[2][i]; } IJ.showProgress(++numProcessed, numSelected); } /* if (showIndividual) { String [] titles={"dPx","dPy","alpha","Multiple","Red","Green","Blue"}; for (int chnNum=0;chnNum<gridPCorr.length;chnNum++) if (gridPCorr[chnNum]!=null) this.SDFA_INSTANCE.showArrays(gridPCorr[chnNum], sWidth, sHeight, true, "gridPCorr1"+chnNum, titles); } */ for (int chnNum=0;chnNum<gridPCorr.length;chnNum++) if (gridPCorr[chnNum]!=null){ for (int i=0;i<gridPCorr[chnNum][0].length;i++) if (gridPCorr[chnNum][2][i]>0){ //null pointer gridPCorr[chnNum][0][i]/=gridPCorr[chnNum][3][i]; gridPCorr[chnNum][1][i]/=gridPCorr[chnNum][3][i]; gridPCorr[chnNum][4][i]/=gridPCorr[chnNum][3][i]; gridPCorr[chnNum][5][i]/=gridPCorr[chnNum][3][i]; gridPCorr[chnNum][6][i]/=gridPCorr[chnNum][3][i]; } } /* if (showIndividual) { String [] titles={"dPx","dPy","alpha","Multiple","Red","Green","Blue"}; for (int chnNum=0;chnNum<gridPCorr.length;chnNum++) if (gridPCorr[chnNum]!=null) this.SDFA_INSTANCE.showArrays(gridPCorr[chnNum], sWidth, sHeight, true, "gridPCorr2"+chnNum, titles); } */ return gridPCorr; } /** * Calculate partial derivative analytically (as the Jacobian calculation) and by difference divided by delta and compare * Done to debug derivatives calculation */ public void compareDerivatives(){ if (fittingStrategy==null) { String msg="Fitting strategy does not exist, exiting"; IJ.showMessage("Error",msg); throw new IllegalArgumentException (msg); } int numSeries=fittingStrategy.getNumSeries(); GenericDialog gd = new GenericDialog("Debug: verifying partial derivatives calculation, select series number"); gd.addNumericField("Series number to show (0.."+(numSeries-1), this.seriesNumber, 0); gd.addCheckbox("Show actual parameters (false: X0,Y0,distance, angles)", true); gd.addCheckbox("Apply sensor mask (fade near edges)", true); gd.addCheckbox("Debug derivatives (show analytic/difference match)",true); gd.showDialog(); if (gd.wasCanceled()) return; this.seriesNumber= (int) gd.getNextNumber(); boolean useActualParameters=gd.getNextBoolean(); boolean applySensorMask=gd.getNextBoolean(); boolean debugDerivatives=gd.getNextBoolean(); // currently not possible to debug "internal" parameters, so // debugDerivatives&=useActualParameters; //******************* initFittingSeries(false,filterForAll,this.seriesNumber); int numPars=this.currentVector.length; String [] parameterNames; String [] parameterUnits; if (useActualParameters) { parameterNames=new String[fittingStrategy.distortionCalibrationData.getNumDescriptions()]; parameterUnits=new String[fittingStrategy.distortionCalibrationData.getNumDescriptions()]; for (int i=0;i<parameterNames.length;i++){ // TODO: move to DdistortionCalibrationData methods() parameterNames[i]=fittingStrategy.distortionCalibrationData.descrField(i,0); parameterUnits[i]=fittingStrategy.distortionCalibrationData.descrField(i,2); } } else { parameterNames=lensDistortionParameters.getAllNames(); parameterUnits=lensDistortionParameters.getAllUnits(); } gd = new GenericDialog((debugDerivatives?"Debug: verifying partial derivatives calculation,":"Showing partial derivatives,") +" select parameter number"); if (useActualParameters) { for (int i=0;i<this.currentVector.length;i++){ int parNum=fittingStrategy.parameterMap[i][1]; int imgNum=fittingStrategy.parameterMap[i][0]; gd.addMessage(i+": "+parameterNames[parNum]+ "["+imgNum+"]("+parameterUnits[parNum]+") "+IJ.d2s(this.currentVector[i],3)); } gd.addNumericField("Select parameter number (0.."+(numPars-1)+") from above", 0, 0); } else { for (int i=0;i<parameterNames.length;i++){ gd.addMessage(i+": "+parameterNames[i]+"("+parameterUnits[i]+") "); } gd.addNumericField("Select parameter number (0.."+(parameterNames.length-1)+") from above", 0, 0); } if (debugDerivatives) gd.addNumericField("Select delta to increment selected parameter", .001, 5); if (debugDerivatives) gd.addCheckbox("Show inter-parameter derivatives matrix", true); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return; int selectedParameter= (int) gd.getNextNumber(); double delta=0; if (debugDerivatives) delta= gd.getNextNumber(); boolean showInterparameterDerivatives=false; if (debugDerivatives) showInterparameterDerivatives=gd.getNextBoolean(); double [] this_currentfX=null; double [] d_derivative; double [] d_delta=null; String title; if (useActualParameters) { this_currentfX=calculateFxAndJacobian(this.currentVector, true); // is it always true here (this.jacobian==null) d_derivative=this.jacobian[selectedParameter].clone(); // wrong? if (debugDerivatives) { double[] modVector=this.currentVector.clone(); modVector[selectedParameter]+=delta; d_delta=calculateFxAndJacobian(modVector, true); if (this.debugLevel>3) { for (int i=0;i<d_delta.length;i++) { System.out.println(i+": "+IJ.d2s(d_delta[i],3)+" - "+IJ.d2s(this_currentfX[i],3)+" = " + IJ.d2s(d_delta[i]-this_currentfX[i],3)); } } for (int i=0;i<d_delta.length;i++) d_delta[i]= (d_delta[i]-this_currentfX[i])/delta; } int parNum=fittingStrategy.parameterMap[selectedParameter][1]; int imgNum=fittingStrategy.parameterMap[selectedParameter][0]; title=parameterNames[parNum]+"_derivatives:"+imgNum; } else { d_derivative=calculateJacobian16(this.currentVector, -1,0.0)[selectedParameter].clone(); if (debugDerivatives) d_delta= calculateJacobian16(this.currentVector, -1,delta)[selectedParameter].clone(); title=parameterNames[selectedParameter]+"_derivatives"; } if (this.debugLevel>3) { for (int i=0;i<d_delta.length;i++) { System.out.println(i+":: "+IJ.d2s(d_delta[i],3)+" - "+IJ.d2s(d_derivative[i],3)); } } double [] sumWeight=showCompareDerivatives (d_derivative, d_delta, applySensorMask, !useActualParameters, title ); // d_delta==null - no debug if (showInterparameterDerivatives && (delta>0)) { debugCompareInterparameterDerivatives( this.currentVector.clone(), -1, //int imgNum, delta); } for (int i=0;i<sumWeight.length; i++) if (sumWeight[i]>0.0){ System.out.println("Image "+i+", "+title+"derivative RMS="+sumWeight[i]); } } /** * Show comparison of the calculated partial derivatives in Jacobian and approximated by difference * for incremented parameters * @param imgNumber - number of image in series to show * @param d_derivative vector array of "true" derivatives (from Jacobian) * @param d_delta approximated derivatives from varying parameter * @param title image title * @return rms */ public double showCompareDerivatives(int imgNumber, double [] d_derivative, double [] d_delta, boolean applySensorMask, String title ){ String [] titlesDebug={"dX-derivative","dY-derivative","abs-derivative","diff-X (should be 0)","diff-Y (should be 0)","dX-delta/delta","dY-delta/delta","dX-delta","dY-delta"}; String [] titlesNoDebug={"dX-derivative","dY-derivative","abs-derivative"}; String [] titles= (d_delta==null)? titlesNoDebug:titlesDebug; double [] d_diff=new double [d_derivative.length]; double [] r_diff=new double [d_derivative.length]; double [] aDeriv=new double [d_derivative.length/2]; if (d_delta!=null) for (int i=0;i<d_diff.length;i++){ d_diff[i]=d_derivative[i]-d_delta[i]; r_diff[i]=d_diff[i]/d_delta[i]; } // find data range for the selected image int index=0; int numImg=fittingStrategy.distortionCalibrationData.getNumImages(); boolean [] selectedImages=fittingStrategy.selectedImages(); for (int imgNum=0;(imgNum<imgNumber) && (imgNum<numImg) ;imgNum++) if (selectedImages[imgNum]) index+=fittingStrategy.distortionCalibrationData.gIP[imgNum].pixelsUV.length; double sumWeights=0.0; double sumDerivatives2=0.0; double w,sqrtW; for (int i=2*index;i<2*(2*index+fittingStrategy.distortionCalibrationData.gIP[imgNumber].pixelsUV.length);i++){ w=applySensorMask?this.weightFunction[i]:1.0; if (w<0.0) w=0.0; sumWeights+=w; sumDerivatives2+=d_derivative[i]*d_derivative[i]*w; sqrtW=Math.sqrt(w); d_derivative[i]*=sqrtW; // for display if (d_delta!=null) d_delta[i]*=sqrtW; if ((i&1)==0) aDeriv[i>>1]=Math.sqrt(d_derivative[i]*d_derivative[i]+d_derivative[i+1]*d_derivative[i+1]); } sumDerivatives2=Math.sqrt(sumDerivatives2/sumWeights*2.0); // 2.0 because x,y pair should not be averaged, just added titles[2]+=":rms="+sumDerivatives2; int width=getGridWidth(); double [][] imgData=new double[titles.length][getGridHeight() * width]; for (int i=0;i<imgData.length;i++) for (int j=0;j<imgData[i].length;j++)imgData[i][j]=0.0; for (int i=0;i<fittingStrategy.distortionCalibrationData.gIP[imgNumber].pixelsUV.length;i++){ int u=fittingStrategy.distortionCalibrationData.gIP[imgNumber].pixelsUV[index+i][0]+patternParameters.U0; int v=fittingStrategy.distortionCalibrationData.gIP[imgNumber].pixelsUV[index+i][1]+patternParameters.V0; int vu=u+width*v; imgData[0][vu]= d_derivative[2*(index+i)]; imgData[1][vu]= d_derivative[2*(index+i)+1]; imgData[2][vu]= aDeriv[index+i]; if (d_delta!=null) { imgData[3][vu]= d_diff[2*(index+i)]; imgData[4][vu]= d_diff[2*(index+i)+1]; imgData[5][vu]= r_diff[2*(index+i)]; imgData[6][vu]= r_diff[2*(index+i)+1]; imgData[7][vu]= d_delta[2*(index+i)]; imgData[8][vu]= d_delta[2*(index+i)+1]; } } this.SDFA_INSTANCE.showArrays(imgData, width, getGridHeight(), true, title, titles); return sumDerivatives2; } /** * Show comparison of the calculated partial derivatives in Jacobian and approximated by difference * for incremented parameters (for all selected images in the series) * @param d_derivative vector array of "true" derivatives (from Jacobian) * @param d_delta approximated derivatives form varying parameter * @param applySensorMask Multiply by sensor mask (fade near edges) * @param single calculate just the first selected image * @param title image title * @return array of rms */ public double[] showCompareDerivatives (double [] d_derivative, double [] d_delta, boolean applySensorMask, boolean single, String title ){ boolean [] selectedImages=fittingStrategy.selectedImages(); double [] diffs= new double [selectedImages.length]; for (int imgNum=0;imgNum<diffs.length;imgNum++) diffs[imgNum]=0.0; for (int imgNum=0;imgNum<selectedImages.length;imgNum++) if (selectedImages[imgNum]) { diffs[imgNum] =showCompareDerivatives(imgNum, d_derivative, d_delta, applySensorMask, title+"-"+imgNum); if (single) break; } return diffs; } /** * * @param delta if 0 - actual derivatives, >0 - approximate derivatives by deltas * @return for each v,u - values and derivatives */ public double [][][][] calcGridOnSensor( double delta) { int gridHeight=patternParameters.gridGeometry.length; int gridWidth=patternParameters.gridGeometry[0].length; this.gridOnSensor=new double[gridHeight][gridWidth][2][15]; double [][] node; // double [][][] nodes=new double [15][][]; boolean dMode=delta>0; if (this.debugLevel>2){ System.out.println("calcGridOnSensor()"); System.out.println("this.lensDistortionParameters.distance="+IJ.d2s(this.lensDistortionParameters.distance, 3)); System.out.println("this.lensDistortionParameters.x0="+ IJ.d2s(this.lensDistortionParameters.x0, 3)); System.out.println("this.lensDistortionParameters.y0="+ IJ.d2s(this.lensDistortionParameters.y0, 3)); System.out.println("this.lensDistortionParameters.z0="+ IJ.d2s(this.lensDistortionParameters.z0, 3)); System.out.println("this.lensDistortionParameters.pitch="+ IJ.d2s(this.lensDistortionParameters.pitch, 3)); System.out.println("this.lensDistortionParameters.yaw="+IJ.d2s(this.lensDistortionParameters.yaw, 3)); System.out.println("this.lensDistortionParameters.roll="+IJ.d2s(this.lensDistortionParameters.roll, 3)); System.out.println("this.lensDistortionParameters.focalLength="+IJ.d2s(this.lensDistortionParameters.focalLength, 3)); System.out.println("this.lensDistortionParameters.px0="+IJ.d2s(this.lensDistortionParameters.px0, 3)); System.out.println("this.lensDistortionParameters.py0="+IJ.d2s(this.lensDistortionParameters.py0, 3)); System.out.println("this.lensDistortionParameters.distortionA8="+IJ.d2s(this.lensDistortionParameters.distortionA8, 5)); System.out.println("this.lensDistortionParameters.distortionA7="+IJ.d2s(this.lensDistortionParameters.distortionA7, 5)); System.out.println("this.lensDistortionParameters.distortionA6="+IJ.d2s(this.lensDistortionParameters.distortionA6, 5)); System.out.println("this.lensDistortionParameters.distortionA5="+IJ.d2s(this.lensDistortionParameters.distortionA5, 5)); System.out.println("this.lensDistortionParameters.distortionA="+IJ.d2s(this.lensDistortionParameters.distortionA, 5)); System.out.println("this.lensDistortionParameters.distortionB="+IJ.d2s(this.lensDistortionParameters.distortionB, 5)); System.out.println("this.lensDistortionParameters.distortionC="+IJ.d2s(this.lensDistortionParameters.distortionC, 5)); System.out.println("this.lensDistortionParameters.lensDistortionModel="+this.lensDistortionParameters.lensDistortionModel); for (int i=0;i<this.lensDistortionParameters.r_xy.length;i++){ System.out.println("this.lensDistortionParameters.r_xy["+i+"][0]="+IJ.d2s(this.lensDistortionParameters.r_xy[i][0], 5)); System.out.println("this.lensDistortionParameters.r_xy["+i+"][1]="+IJ.d2s(this.lensDistortionParameters.r_xy[i][1], 5)); } for (int i=0;i<this.lensDistortionParameters.r_od.length;i++){ System.out.println("this.lensDistortionParameters.r_od["+i+"][0]="+IJ.d2s(this.lensDistortionParameters.r_od[i][0], 5)); System.out.println("this.lensDistortionParameters.r_od["+i+"][1]="+IJ.d2s(this.lensDistortionParameters.r_od[i][1], 5)); } } LensDistortionParameters ldp=this.lensDistortionParameters.clone(); // 06/2019 - need to update distortionRadius, pixelSize) // public void setLensDistortionParameters(LensDistortionParameters ldp for (int v=0; v<gridHeight; v++) for (int u=0; u<gridWidth; u++) if (patternParameters.gridGeometry[v][u][3]>0) { this.lensDistortionParameters.setLensDistortionParameters(ldp); // restore node=this.lensDistortionParameters.calcPartialDerivatives( patternParameters.gridGeometry[v][u][0],//double xp, // target point horizontal, positive - right, mm patternParameters.gridGeometry[v][u][1],//double yp, // target point vertical, positive - down, mm patternParameters.gridGeometry[v][u][2],//double zp, // target point horizontal, positive - away from camera, mm !dMode);//boolean calculateAll){ // calculate derivatives, false - values only if (this.debugLevel>3) { System.out.println("calcPartialDerivatives("+ IJ.d2s(patternParameters.gridGeometry[v][u][0],2)+","+ IJ.d2s(patternParameters.gridGeometry[v][u][1],2)+","+ IJ.d2s(patternParameters.gridGeometry[v][u][2],2)+" ("+true+") -> "+ IJ.d2s(node[0][0],2)+"/"+IJ.d2s(node[0][1],2)); } if (dMode) { // double []pXY=node[0]; // px,py values this.gridOnSensor[v][u][0][0]=node[0][0]; this.gridOnSensor[v][u][1][0]=node[0][1]; for (int j=1;j<15;j++) { // was 14 this.lensDistortionParameters.setLensDistortionParameters(ldp, j, delta); // set one of the parameters (j) with added delta to ldp node=this.lensDistortionParameters.calcPartialDerivatives( patternParameters.gridGeometry[v][u][0],//double xp, // target point horizontal, positive - right, mm patternParameters.gridGeometry[v][u][1],//double yp, // target point vertical, positive - down, mm patternParameters.gridGeometry[v][u][2],//double zp, // target point horizontal, positive - away from camera, mm false); this.gridOnSensor[v][u][0][j]=(node[0][0]-this.gridOnSensor[v][u][0][0])/delta; this.gridOnSensor[v][u][1][j]=(node[0][1]-this.gridOnSensor[v][u][1][0])/delta; } } else for (int i=0;i<2;i++) for (int j=0;j<15;j++){ // was 14 this.gridOnSensor[v][u][i][j]=node[j][i]; } } else { this.gridOnSensor[v][u]=null; } return this.gridOnSensor; } public int getGridWidth() { return patternParameters.gridGeometry[0].length; } public int getGridHeight() { return patternParameters.gridGeometry.length; } public double [][] prepareDisplayGrid(){ int gridHeight=this.patternParameters.gridGeometry.length; int gridWidth=this.patternParameters.gridGeometry[0].length; double [][] dgrid=new double[3][gridHeight*gridWidth]; double average; int num,index; for (int i=0;i<dgrid.length;i++){ average=0.0; num=0; for (int v=0; v<gridHeight; v++) for (int u=0; u<gridWidth; u++) if (this.patternParameters.gridGeometry[v][u][3]>0) { average+=this.patternParameters.gridGeometry[v][u][i]; num++; } average/=num; index=0; for (int v=0; v<gridHeight; v++) for (int u=0; u<gridWidth; u++) if (this.patternParameters.gridGeometry[v][u][3]>0) { dgrid[i][index++]=this.patternParameters.gridGeometry[v][u][i]; } else { dgrid[i][index++]=average; } } return dgrid; } public String [] displayGridTitles() { String [] titles={"Grid-X","Grid-Y","Grid-Z"}; return titles; } public String [] displayGridOnSensorTitles() { String [] titles={ "PX","PY", "dPX/dphi","dPY/dphi", "dPX/dtheta","dPY/dtheta", "dPX/dpsi","dPY/dpsi", "dPX/dX0","dPY/dX0", "dPX/dY0","dPY/dY0", "dPX/dZ0","dPY/dZ0", "dPX/df","dPY/df", "dPX/ddist","dPY/dist", "dPX/dDa","dPY/dDa", "dPX/dDb","dPY/dDb", "dPX/dDc","dPY/dDc", "dPX/dPX0","dPY/dPX0", "dPX/dPY0","dPY/dPY0" }; return titles; } public double [][] prepareDisplayGridOnSensor(boolean showAll){ int gridHeight=this.patternParameters.gridGeometry.length; int gridWidth=this.patternParameters.gridGeometry[0].length; // double [][] dgrid=new double[showAll?28:2][gridHeight*gridWidth]; double [][] dgrid=new double[showAll?(2*15):2][gridHeight*gridWidth]; double average; int num,index; for (int i=0;i<dgrid.length/2;i++) for (int j=0;j<2;j++){ int ii=i*2+j; average=0.0; num=0; for (int v=0; v<gridHeight; v++) for (int u=0; u<gridWidth; u++) if (this.patternParameters.gridGeometry[v][u][3]>0) { average+=this.gridOnSensor[v][u][j][i]; num++; } average/=num; index=0; for (int v=0; v<gridHeight; v++) for (int u=0; u<gridWidth; u++) if (this.patternParameters.gridGeometry[v][u][3]>0) { dgrid[ii][index++]=this.gridOnSensor[v][u][j][i]; } else { dgrid[ii][index++]=average; } } return dgrid; } /** * initialize image data with camera defaults * @param distortionCalibrationData grid distortionCalibrationData * @param eyesisCameraParameters deafault camera parameters */ // Used in Aberration_Calibration public void initImageSet( DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters) { // DistortionCalibrationData distortionCalibrationData= new DistortionCalibrationData(filenames); for (int i=0;i<distortionCalibrationData.getNumImages();i++){ int stationNumber=distortionCalibrationData.getImageStation(i); int subCam=distortionCalibrationData.getImageSubcamera(i); distortionCalibrationData.setParameters(eyesisCameraParameters.getParametersVector(stationNumber,subCam), i); this.lensDistortionParameters.pixelSize=eyesisCameraParameters.getPixelSize(subCam); this.lensDistortionParameters.distortionRadius=eyesisCameraParameters.getDistortionRadius(subCam); } } public void copySensorConstants(EyesisCameraParameters eyesisCameraParameters) { // copy from the first channel this.lensDistortionParameters.pixelSize=eyesisCameraParameters.getPixelSize(0); this.lensDistortionParameters.distortionRadius=eyesisCameraParameters.getDistortionRadius(0); } /** * Update per-image parameters from those of the camera and those that have the same timestamp. Usually needed after adding or * enabling new images. * @param distortionCalibrationData grid distortionCalibrationData * @param eyesisCameraParameters - camera parameters (common and per sub-camera) * @return true if dialog was not canceled and programs ran */ public boolean interactiveUpdateImageSet( DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters ){ boolean resetParametersToZero=false; boolean [] parameterMask= new boolean[distortionCalibrationData.getNumParameters()]; boolean [] channelMask= new boolean[distortionCalibrationData.getNumSubCameras()]; boolean [] stationMask= new boolean[distortionCalibrationData.getNumStations()]; for (int i=0;i<parameterMask.length;i++) parameterMask[i]=false; for (int i=0;i<channelMask.length;i++) channelMask[i]= true; for (int i=0;i<stationMask.length;i++) stationMask[i]= true; GenericDialog gd=new GenericDialog("Update (new) image settings from known data"); // gd.addCheckbox("Reset selected parameters to zero (false - update from camera parameters)", resetParametersToZero); gd.addMessage("Select which individual image parameters to be updated from the camera parameters (or reset to 0)"); for (int i=0;i<parameterMask.length;i++) gd.addCheckbox(i+": "+distortionCalibrationData.getParameterName(i), parameterMask[i]); gd.addMessage("----------"); gd.addMessage("Select which channels (sub-cameras) to update"); for (int i=0;i<channelMask.length;i++) gd.addCheckbox("Subcamera "+i, channelMask[i]); if (stationMask.length>1) { gd.addMessage("----------"); gd.addMessage("Select which stations (camera/goniometer locations) to update"); for (int i=0;i<stationMask.length;i++) gd.addCheckbox("Station "+i, stationMask[i]); } gd.addMessage("----------"); gd.addCheckbox("Applying known extrinsic parameters to the same timestamp images", true); gd.addCheckbox("Use closest (by motor steps) image if none for the same timestamp is enabled", true); gd.addMessage("==== Note: The following correction will be applied to all subcameras, use selection above to specify which heights should be averaged" ); gd.addCheckbox("Vertically center the camera head by calculateing center above horizontal", false); // gd.addCheckbox("Update currently disabled images", true); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return false; resetParametersToZero=gd.getNextBoolean(); for (int i=0;i<parameterMask.length;i++) parameterMask[i]= gd.getNextBoolean(); for (int i=0;i<channelMask.length;i++) channelMask[i]= gd.getNextBoolean(); if (stationMask.length>1) { for (int i=0;i<stationMask.length;i++) stationMask[i]= gd.getNextBoolean(); } boolean updateFromTimestamps= gd.getNextBoolean(); boolean allowClosest= gd.getNextBoolean(); boolean reCenterVertically= gd.getNextBoolean(); if (reCenterVertically){ eyesisCameraParameters.recenterVertically(channelMask, stationMask); for (int i=0;i<channelMask.length;i++) channelMask[i]= true; parameterMask[distortionCalibrationData.getParameterIndexByName("subcamHeight")] = true; } // boolean updateDisabled= gd.getNextBoolean(); updateImageSetFromCamera( resetParametersToZero, distortionCalibrationData, eyesisCameraParameters, parameterMask, //boolean [] parameterMask, channelMask, // copy X,Y,Z (usually true) stationMask // copy 2 goniometer angles (usually false) ); if (updateFromTimestamps) { updateImageSetFromSameTimestamps( distortionCalibrationData, eyesisCameraParameters, null, // boolean [] selectedImages, null, //boolean [] parameterMask, allowClosest //,updateDisabled ); distortionCalibrationData.updateSetOrientation(null); // update orientation of image sets } return true; } public boolean setSetFromClosestAndEstimateOrientation( int numSet, boolean [] selectedImages, boolean [] parameterMask, DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters){ if (selectedImages==null) { selectedImages= new boolean[distortionCalibrationData.getNumImages()]; for (int i=0;i<selectedImages.length;i++) selectedImages[i]=distortionCalibrationData.gIP[i].enabled; } if (parameterMask==null) { parameterMask= new boolean[distortionCalibrationData.getNumParameters()]; for (int i=0;i<parameterMask.length;i++) parameterMask[i]=true; } for (int i=0;i<parameterMask.length;i++) { if (distortionCalibrationData.isSubcameraParameter(i)) parameterMask[i]=false; } int enabledImage=getClosestImage( // {numEnabledSet,enabledChannel,enabledImage}; distortionCalibrationData, selectedImages, numSet); if (enabledImage<0) return false; // failed to find closest updateSetFromClosest( numSet, enabledImage, parameterMask, distortionCalibrationData); // invalidate current angles distortionCalibrationData.gIS[numSet].goniometerAxial=Double.NaN; distortionCalibrationData.gIS[numSet].goniometerTilt= Double.NaN; // re-estimate orientation double [] ta=distortionCalibrationData.getImagesetTiltAxial(distortionCalibrationData.gIS[numSet].timeStamp); // updates tilt/axial (now interAxis too!) if ((ta==null) || Double.isNaN(ta[0]) || Double.isNaN(ta[1])) return false; return true; } public boolean interactiveUpdateImageSetOld( DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters ){ GenericDialog gd=new GenericDialog("Update (new) image settings from known data"); gd.addCheckbox("Update per-image parameters from those of the camera", true); gd.addCheckbox("Copy location of the camera (X,Y,Z)", true); gd.addCheckbox("Copy orientation of the camera (tilt and axial)", false); gd.addMessage(""); gd.addCheckbox("Update per-image parameters from those with the same timestamp", true); gd.addCheckbox("Use closest (by motor steps) image if none for the same timestamp is enabled", true); // gd.addCheckbox("Update currently disabled images", true); gd.showDialog(); if (gd.wasCanceled()) return false; boolean updateFromCamera= gd.getNextBoolean(); boolean copyLocation= gd.getNextBoolean(); boolean copyOrientation= gd.getNextBoolean(); boolean updateFromTimestamps= gd.getNextBoolean(); boolean allowClosest= gd.getNextBoolean(); // boolean updateDisabled= gd.getNextBoolean(); boolean [] parameterMask= new boolean[distortionCalibrationData.getNumParameters()]; for (int i=0;i<parameterMask.length;i++) { parameterMask[i]=true; if (distortionCalibrationData.isLocationParameter(i) && !copyLocation) parameterMask[i]=false; if (distortionCalibrationData.isOrientationParameter(i) && !copyOrientation) parameterMask[i]=false; } if (updateFromCamera) updateImageSetFromCamera( false, //resetParametersToZero distortionCalibrationData, eyesisCameraParameters, parameterMask, //boolean [] parameterMask, null, null ); if (updateFromTimestamps) { updateImageSetFromSameTimestamps( distortionCalibrationData, eyesisCameraParameters, null, // boolean [] selectedImages, null, //boolean [] parameterMask, allowClosest // ,updateDisabled ); distortionCalibrationData.updateSetOrientation(null); // update orientation of image sets } return true; } /** * Copies selected parameters from the camera parameters to per-image parameters (i.e. for new/previously disabled images) * @param distortionCalibrationData grid distortionCalibrationData * @param eyesisCameraParameters - camera parameters (common and per sub-camera) * @param parameterMask when element is true - copy parameters, false - keep current value. Null - selects all (filtered by the next parameters) * @param copyLocation copy location (x,Y,Z) of the camera , normally should be true * @param copyOrientation copy 2 goniometer angles, normally should be false */ public void updateImageSetFromCamera( boolean resetParametersToZero, // reset to 0 instead of camera parameters DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters, boolean [] parameterMask, boolean [] channelMask, boolean [] stationMask ) { // DistortionCalibrationData distortionCalibrationData= new DistortionCalibrationData(filenames); for (int i=0;i<distortionCalibrationData.getNumImages();i++){ int stationNumber=distortionCalibrationData.getImageStation(i); int subCam=distortionCalibrationData.getImageSubcamera(i); if ((channelMask!=null) && !channelMask[subCam]) continue; if ((stationMask!=null) && !stationMask[stationNumber]) continue; double [] oldVector=distortionCalibrationData.getParameters(i); double [] newVector=eyesisCameraParameters.getParametersVector(stationNumber,subCam); for (int j=0;j<oldVector.length;j++) if (parameterMask[j]){ if (resetParametersToZero) newVector[j]=0.0; oldVector[j]=newVector[j]; } if (resetParametersToZero){ eyesisCameraParameters.setParametersVector( newVector, parameterMask, stationNumber, subCam); } distortionCalibrationData.setParameters(oldVector, i); this.lensDistortionParameters.pixelSize=eyesisCameraParameters.getPixelSize(subCam); this.lensDistortionParameters.distortionRadius=eyesisCameraParameters.getDistortionRadius(subCam); } } /** * Copies selected (normally all) parameters from the selected images with the same timestamp (i.e. for new/previously disabled images) * @param distortionCalibrationData grid distortionCalibrationData * @param eyesisCameraParameters - camera parameters (common and per sub-camera) * @param selectedImages Use only selected images (null - all enabled) * @param parameterMask when element is true - copy parameters, false - keep current value. Null - selects all (and should be normally null) * @param allowClosest If there is no enabled image for the current timestamp, find the closest selected using motor coordinates * @param updateDisabled update disable images also */ public void updateImageSetFromSameTimestamps( DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters, boolean [] selectedImages, boolean [] parameterMask, boolean allowClosest ){ System.out.println("updateImageSetFromSameTimestamps(), allowClosest="+allowClosest); //+" updateDisabled="+updateDisabled); if (selectedImages==null) { selectedImages= new boolean[distortionCalibrationData.getNumImages()]; for (int i=0;i<selectedImages.length;i++) selectedImages[i]=distortionCalibrationData.gIP[i].enabled; // for (int i=0;i<selectedImages.length;i++) selectedImages[i]=distortionCalibrationData.gIP[i].enabled || updateDisabled; } if (parameterMask==null) { parameterMask= new boolean[distortionCalibrationData.getNumParameters()]; for (int i=0;i<parameterMask.length;i++) parameterMask[i]=true; } for (int i=0;i<parameterMask.length;i++) { if (distortionCalibrationData.isSubcameraParameter(i)) parameterMask[i]=false; } for (int numSet=0; numSet<distortionCalibrationData.gIS.length;numSet++){ // find enabled image for this set int enabledImage=-1; // look for enabled image in the same imageSet for (int nChn=0;nChn<distortionCalibrationData.gIS[numSet].imageSet.length;nChn++) if (distortionCalibrationData.gIS[numSet].imageSet[nChn]!=null){ int img=distortionCalibrationData.gIS[numSet].imageSet[nChn].imgNumber; if (selectedImages[img]){ enabledImage=img; break; } } // look for closest in the other imageSet if ((enabledImage<0) && (allowClosest)){ enabledImage=getClosestImage( // {numEnabledSet,enabledChannel,enabledImage}; distortionCalibrationData, selectedImages, numSet); } if (enabledImage>=0){ updateSetFromClosest( numSet, enabledImage, parameterMask, distortionCalibrationData); } } } public int getClosestImage( DistortionCalibrationData distortionCalibrationData, boolean [] selectedImages, int numSet ){ int enabledChannel=-1; int enabledImage=-1; if (distortionCalibrationData.gIS[numSet].motors==null ){ if (this.debugLevel>0) System.out.println("getClosestSetChannelImage(): No motor data for timestamp "+distortionCalibrationData.gIS[numSet].timeStamp); return -1; } double d2Min=-1; for (int numOtherSet=0;numOtherSet<distortionCalibrationData.gIS.length;numOtherSet++) if ((numOtherSet!=numSet) && (distortionCalibrationData.gIS[numOtherSet].stationNumber==distortionCalibrationData.gIS[numSet].stationNumber) && (distortionCalibrationData.gIS[numOtherSet].motors!=null) && (distortionCalibrationData.gIS[numOtherSet].imageSet!=null) ) { enabledChannel=-1; int otherImage=-1; for (int nChn=0;nChn<distortionCalibrationData.gIS[numOtherSet].imageSet.length;nChn++) if (distortionCalibrationData.gIS[numOtherSet].imageSet[nChn]!=null){ otherImage=distortionCalibrationData.gIS[numOtherSet].imageSet[nChn].imgNumber; if (selectedImages[otherImage]){ enabledChannel=nChn; break; } } if (enabledChannel>=0){ double d2=0; for (int k=0;k<distortionCalibrationData.gIS[numOtherSet].motors.length;k++){ d2+=1.0*(distortionCalibrationData.gIS[numOtherSet].motors[k]-distortionCalibrationData.gIS[numSet].motors[k])* (distortionCalibrationData.gIS[numOtherSet].motors[k]-distortionCalibrationData.gIS[numSet].motors[k]); } if ((d2Min<0) || (d2Min>d2)) { d2Min=d2; enabledImage=otherImage; } } } return enabledImage; } public void updateSetFromClosest( int numSet, int enabledImage, boolean [] parameterMask, DistortionCalibrationData distortionCalibrationData ){ int numEnabledSet=distortionCalibrationData.gIP[enabledImage].getSetNumber(); distortionCalibrationData.gIS[numSet].setSetVector(distortionCalibrationData.gIS[numEnabledSet].getSetVector()); System.out.println("getClosestSetChannelImage(): imageSet "+numSet+" set orientationEstimated=true, updated from imageSet "+numEnabledSet); distortionCalibrationData.gIS[numSet].orientationEstimated=(numSet!=numEnabledSet); double [] newVector=distortionCalibrationData.getParameters(enabledImage); for (int nChn=0;nChn<distortionCalibrationData.gIS[numSet].imageSet.length;nChn++) if (distortionCalibrationData.gIS[numSet].imageSet[nChn]!=null){ // will copy back to itself, OK int targetImage=distortionCalibrationData.gIS[numSet].imageSet[nChn].imgNumber; double [] oldVector=distortionCalibrationData.getParameters(targetImage); for (int j=0;j<oldVector.length;j++) if (parameterMask[j]) oldVector[j]=newVector[j]; distortionCalibrationData.setParameters(oldVector, targetImage); } } // TODO: Add updating to all Stations depending on type of adjustment. Initially only teh same station as image will be updated // Not needed - for "super" unselected images are also updated /** * Update camera/subcamera parameters from the currently selected set of images * several images may have different values for the same parameter, in that case * these parameters will have the value of the last image */ public void updateCameraParametersFromCalculated( boolean allImages ){ int numSeries=allImages?(-1):this.fittingStrategy.currentSeriesNumber; boolean [] selectedImages=fittingStrategy.selectedImages(numSeries); // all enabled boolean [] selectedImagesDebug=null; boolean debugThis=false; int maxDebugImages=10; if (this.debugLevel>0) System.out.println("updateCameraParametersFromCalculated("+allImages+")"); if (this.debugLevel>2){ int numSel=0; for (int i=0;i<selectedImages.length;i++) if (selectedImages[i]) numSel++; if (numSel<=maxDebugImages) debugThis=true; else { System.out.println ("Too many images ("+numSel+">"+ maxDebugImages +") to debug, skipping console println."); selectedImagesDebug=fittingStrategy.selectedImages(this.fittingStrategy.currentSeriesNumber); // all enabled } } for (int numImg=0;numImg<selectedImages.length; numImg++) if (selectedImages[numImg]){ // here only adjusted images should participate int subCam=fittingStrategy.distortionCalibrationData.getImageSubcamera(numImg); // double [] par=fittingStrategy.distortionCalibrationData.pars[numImg]; double [] par=fittingStrategy.distortionCalibrationData.getParameters(numImg); boolean [] update=new boolean[par.length]; for (int i=0;i<update.length;i++) update[i]=true; int stationNumber=fittingStrategy.distortionCalibrationData.getImageStation(numImg); // TODO: maybe determine - which parameters to be updated, not all - i.e. "super-common", or having the same value, etc. // but all those intrinsic are required to match calibration files saved fittingStrategy.distortionCalibrationData.eyesisCameraParameters.setParametersVector(par, update, stationNumber, subCam); if (debugThis || ((selectedImagesDebug!=null) && selectedImagesDebug[numImg])){ System.out.println ("Updating from image #"+numImg+" (subCam="+subCam+" stationNumber="+stationNumber+"):"); //getParameterName for (int i=0;i<par.length;i++){ System.out.println(i+": "+fittingStrategy.distortionCalibrationData.getParameterName(i)+" = "+par[i]); } } } if (this.debugLevel>1) System.out.println("updateCameraParametersFromCalculated("+allImages+") for series="+numSeries); // Next line is not needed anymore (will harm as will set orientationEstimated for all unselected sets) // if (!allImages) fittingStrategy.distortionCalibrationData.updateSetOrientation(selectedImages); // only for selected images (not all enabled), OK } /* Create a Thread[] array as large as the number of processors available. * From Stephan Preibisch's Multithreading.java class. See: * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD */ private Thread[] newThreadArray(int maxCPUs) { int n_cpus = Runtime.getRuntime().availableProcessors(); if (n_cpus>maxCPUs)n_cpus=maxCPUs; return new Thread[n_cpus]; } /* Start all given threads and wait on each of them until all are done. * From Stephan Preibisch's Multithreading.java class. See: * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD */ private static void startAndJoin(Thread[] threads) { for (int ithread = 0; ithread < threads.length; ++ithread) { threads[ithread].setPriority(Thread.NORM_PRIORITY); threads[ithread].start(); } try { for (int ithread = 0; ithread < threads.length; ++ithread) threads[ithread].join(); } catch (InterruptedException ie) { throw new RuntimeException(ie); } } public static class RefineParameters{ public boolean extrapolate=true; // extrapolate sensor distortion correction public double alphaThreshold =0.8; // ignore sensor correction pixels with mask value below this public double fatZero=0.01; // when extrapolatging color transfer coefficients (flat field) use this for logariphm public double extrapolationSigma=30.0; // sigmna for Gaussian weight function when fittinga plane to known pixels // calculated for non-decimated pixels public double extrapolationKSigma=2.0; // consider pixels in 2*extrapolationSigma*extrapolationKSigma square when fitting public boolean smoothCorrection=true; // apply Gaussian blur to calculated pixel correction field public double smoothSigma=50.0; // sigma for Gaussian weight function when fittinga plane to known pixels public double correctionScale=1.0; // scale correction when accumulating; public boolean showCumulativeCorrection=false; // show correction afther this one is applied public boolean showUnfilteredCorrection=true; // show this (additional) correction before extrapolation and/or smoothing public boolean showExtrapolationCorrection=false; // show Extrapolation public boolean showThisCorrection=false; // show this (additional) correction separately public boolean showPerImage=false; // show residuals for each individual image public int showIndividualNumber=0; // which image to show (-1 - all) public boolean applyCorrection=true; // apply calculated corerction public boolean applyFlatField=true; // apply calculated flat-field public boolean grid3DCorrection=true; // Correct patetrn grid node locations in 3d (false - in 2d only) public boolean rotateCorrection=true; // old value - did not yet understand why is it needed public double grid3DMaximalZCorr=20.0; // Maximal Z-axis correc tion (if more will fall back to 2d correction algorithm) public boolean useVariations= false; // allow different Z for different stations (for not a wall/stable pattern) public double variationPenalty=0.001; // "stiffness" of individual (per-station) Z-values of the target pattern public boolean fixXY= false; // adjust only Z of the target pattern, keep X and Y public boolean resetVariations=false; public boolean noFallBack= true; // may have bugs - not tested yet public boolean usePatternAlpha= true; // use pattern grid alpha data, false - old calculation // New individual parameters for modify pattern grid public boolean targetShowPerImage=false; public boolean targetShowThisCorrection=false; public boolean targetApplyCorrection=true; public double targetCorrectionScale=1.0; // scale correction when accumulating; // New parameters for new sensor correction public boolean sensorExtrapolateDiff = false; // true - extrapolate correction, false - composite public double sensorShrinkBlurComboSigma = 50.0; public double sensorShrinkBlurComboLevel = 0.25; public double sensorAlphaThreshold = 0.1; public double sensorStep = 5; public double sensorInterpolationSigma= 100; public double sensorTangentialRadius= 0.5; public int sensorScanDistance= 200; public int sensorResultDistance= 500; public int sensorInterpolationDegree= 2; //New parameters for Flat field correction public int flatFieldSerNumber= -1; public int flatFieldReferenceStation= 0; public double flatFieldShrink= 100.0; public double flatFieldNonVignettedRadius = 1000.0; public double flatFieldMinimalAlpha = 0.01; // use % public double flatFieldMinimalContrast= 0.1; public double flatFieldMinimalAccumulate = 0.01; // use % public double flatFieldShrinkForMatching = 2.0; public double flatFieldMaxRelDiff = 0.1; // use % public int flatFieldShrinkMask= 2; public double flatFieldFadeBorder = 2.0; // gd.addMessage("Update pattern white balance (if the illumination is yellowish, increase red and green here)"); // LENS_DISTORTIONS.patternParameters.averageRGB[0]=gd.getNextNumber(); // LENS_DISTORTIONS.patternParameters.averageRGB[1]=gd.getNextNumber(); // LENS_DISTORTIONS.patternParameters.averageRGB[2]=gd.getNextNumber(); public boolean flatFieldResetMask= true; public boolean flatFieldShowSensorMasks=false; public boolean flatFieldShowIndividual= false; public boolean flatFieldShowResult= true; public boolean flatFieldApplyResult= true; public boolean flatFieldUseInterpolate= true; public double flatFieldMaskThresholdOcclusion=0.15; // use % public int flatFieldShrinkOcclusion= 2; public double flatFieldFadeOcclusion= 2.0; public boolean flatFieldIgnoreSensorFlatField= false; // public boolean flatFieldUseSelectedChannels= false; // Other public int repeatFlatFieldSensor=10; // TODO: add stop ! public double specularHighPassSigma= 10.0; public double specularLowPassSigma= 2.0; public double specularDiffFromAverageThreshold= 0.01; public int specularNumIter= 5; public boolean specularApplyNewWeights= true; public boolean specularPositiveDiffOnly= true; public int specularShowDebug= 1; // 0 - do not show, 1 - show on last iteration only, 2 - show always public RefineParameters(){} public RefineParameters( boolean extrapolate, double alphaThreshold, double fatZero, double extrapolationSigma, double extrapolationKSigma, boolean smoothCorrection, double smoothSigma, double correctionScale, boolean showCumulativeCorrection, boolean showUnfilteredCorrection, boolean showExtrapolationCorrection, boolean showThisCorrection, boolean showPerImage, int showIndividualNumber, // which image to show (-1 - all) boolean applyCorrection, boolean applyFlatField, // apply calculated flat-field boolean grid3DCorrection, // Correct patetrn grid node locations in 3d (false - in 2d only) boolean rotateCorrection, // not clear double grid3DMaximalZCorr, // Maximal Z-axis correc tion (if more will fall back to 2d correction algorithm) boolean useVariations, double variationPenalty, // "stiffness" of individual (per-station) Z-values of the target pattern boolean fixXY, boolean resetVariations, boolean noFallBack, // may have bugs - not tested yet boolean usePatternAlpha, boolean targetShowPerImage, boolean targetShowThisCorrection, boolean targetApplyCorrection, double targetCorrectionScale, boolean sensorExtrapolateDiff, double sensorShrinkBlurComboSigma, double sensorShrinkBlurComboLevel, double sensorAlphaThreshold, double sensorStep, double sensorInterpolationSigma, double sensorTangentialRadius, int sensorScanDistance, int sensorResultDistance, int sensorInterpolationDegree, int flatFieldSerNumber, int flatFieldReferenceStation, double flatFieldShrink, double flatFieldNonVignettedRadius, double flatFieldMinimalAlpha, double flatFieldMinimalContrast, double flatFieldMinimalAccumulate, double flatFieldShrinkForMatching, double flatFieldMaxRelDiff, int flatFieldShrinkMask, double flatFieldFadeBorder, boolean flatFieldResetMask, boolean flatFieldShowSensorMasks, boolean flatFieldShowIndividual, boolean flatFieldShowResult, boolean flatFieldApplyResult, boolean flatFieldUseInterpolate, double flatFieldMaskThresholdOcclusion, int flatFieldShrinkOcclusion, double flatFieldFadeOcclusion, boolean flatFieldIgnoreSensorFlatField, int repeatFlatFieldSensor, double specularHighPassSigma, double specularLowPassSigma, double specularDiffFromAverageThreshold, int specularNumIter, boolean specularApplyNewWeights, boolean specularPositiveDiffOnly, int specularShowDebug){ this.extrapolate=extrapolate; this.alphaThreshold=alphaThreshold; this.fatZero=fatZero; // when extrapolatging color transfer coefficients (flat field) use this for logariphm this.extrapolationSigma=extrapolationSigma; this.extrapolationKSigma=extrapolationKSigma; this.smoothCorrection=smoothCorrection; this.smoothSigma=smoothSigma; this.correctionScale=correctionScale; this.showCumulativeCorrection=showCumulativeCorrection; this.showUnfilteredCorrection=showUnfilteredCorrection; this.showExtrapolationCorrection=showExtrapolationCorrection; this.showThisCorrection=showThisCorrection; this.showPerImage=showPerImage; this.showIndividualNumber=showIndividualNumber; // which image to show (-1 - all) this.applyCorrection=applyCorrection; this.applyFlatField=applyFlatField; this.grid3DCorrection=grid3DCorrection; this.rotateCorrection=rotateCorrection; // not clear this.grid3DMaximalZCorr=grid3DMaximalZCorr; // Maximal Z-axis correc tion (if more will fall back to 2d correction algorithm) this.useVariations=useVariations; this.variationPenalty=variationPenalty; // "stiffness" of individual (per-station) Z-values of the target pattern this.fixXY=fixXY; this.resetVariations=resetVariations; this.noFallBack= noFallBack; // may have bugs - not tested yet this.usePatternAlpha=usePatternAlpha; this.targetShowPerImage= targetShowPerImage; this.targetShowThisCorrection= targetShowThisCorrection; this.targetApplyCorrection= targetApplyCorrection; this.targetCorrectionScale= targetCorrectionScale; this.sensorExtrapolateDiff= sensorExtrapolateDiff; this.sensorShrinkBlurComboSigma=sensorShrinkBlurComboSigma; this.sensorShrinkBlurComboLevel=sensorShrinkBlurComboLevel; this.sensorAlphaThreshold=sensorAlphaThreshold; this.sensorStep=sensorStep; this.sensorInterpolationSigma=sensorInterpolationSigma; this.sensorTangentialRadius=sensorTangentialRadius; this.sensorScanDistance=sensorScanDistance; this.sensorResultDistance=sensorResultDistance; this.sensorInterpolationDegree=sensorInterpolationDegree; this.flatFieldSerNumber=flatFieldSerNumber; this.flatFieldReferenceStation=flatFieldReferenceStation; this.flatFieldShrink=flatFieldShrink; this.flatFieldNonVignettedRadius=flatFieldNonVignettedRadius; this.flatFieldMinimalAlpha=flatFieldMinimalAlpha; this.flatFieldMinimalContrast=flatFieldMinimalContrast; this.flatFieldMinimalAccumulate=flatFieldMinimalAccumulate; this.flatFieldShrinkForMatching=flatFieldShrinkForMatching; this.flatFieldMaxRelDiff=flatFieldMaxRelDiff; this.flatFieldShrinkMask=flatFieldShrinkMask; this.flatFieldFadeBorder=flatFieldFadeBorder; this.flatFieldResetMask=flatFieldResetMask; this.flatFieldShowSensorMasks=flatFieldShowSensorMasks; this.flatFieldShowIndividual=flatFieldShowIndividual; this.flatFieldShowResult=flatFieldShowResult; this.flatFieldApplyResult=flatFieldApplyResult; this.flatFieldUseInterpolate=flatFieldUseInterpolate; this.flatFieldMaskThresholdOcclusion=flatFieldMaskThresholdOcclusion; this.flatFieldShrinkOcclusion=flatFieldShrinkOcclusion; this.flatFieldFadeOcclusion=flatFieldFadeOcclusion; this.flatFieldIgnoreSensorFlatField=flatFieldIgnoreSensorFlatField; // this.flatFieldUseSelectedChannels=flatFieldUseSelectedChannels; this.repeatFlatFieldSensor=repeatFlatFieldSensor; this.specularHighPassSigma=specularHighPassSigma; this.specularLowPassSigma=specularLowPassSigma; this.specularDiffFromAverageThreshold=specularDiffFromAverageThreshold; this.specularNumIter=specularNumIter; this.specularApplyNewWeights=specularApplyNewWeights; this.specularPositiveDiffOnly=specularPositiveDiffOnly; this.specularShowDebug=specularShowDebug; } @Override public RefineParameters clone(){ return new RefineParameters( this.extrapolate, this.alphaThreshold, this.fatZero, this.extrapolationSigma, this.extrapolationKSigma, this.smoothCorrection, this.smoothSigma, this.correctionScale, this.showCumulativeCorrection, this.showUnfilteredCorrection, this.showExtrapolationCorrection, this.showThisCorrection, this.showPerImage, this.showIndividualNumber, this.applyCorrection, this.applyFlatField, this.grid3DCorrection, this.rotateCorrection, // not clear this.grid3DMaximalZCorr, // Maximal Z-axis correc tion (if more will fall back to 2d correction algorithm) this.useVariations, this.variationPenalty, // "stiffness" of individual (per-station) Z-values of the target pattern this.fixXY, this.resetVariations, this.noFallBack, // may have bugs - not tested yet this.usePatternAlpha, this.targetShowPerImage, this.targetShowThisCorrection, this.targetApplyCorrection, this.targetCorrectionScale, this.sensorExtrapolateDiff, this.sensorShrinkBlurComboSigma, this.sensorShrinkBlurComboLevel, this.sensorAlphaThreshold, this.sensorStep, this.sensorInterpolationSigma, this.sensorTangentialRadius, this.sensorScanDistance, this.sensorResultDistance, this.sensorInterpolationDegree, this.flatFieldSerNumber, this.flatFieldReferenceStation, this.flatFieldShrink, this.flatFieldNonVignettedRadius, this.flatFieldMinimalAlpha, this.flatFieldMinimalContrast, this.flatFieldMinimalAccumulate, this.flatFieldShrinkForMatching, this.flatFieldMaxRelDiff, this.flatFieldShrinkMask, this.flatFieldFadeBorder, this.flatFieldResetMask, this.flatFieldShowSensorMasks, this.flatFieldShowIndividual, this.flatFieldShowResult, this.flatFieldApplyResult, this.flatFieldUseInterpolate, this.flatFieldMaskThresholdOcclusion, this.flatFieldShrinkOcclusion, this.flatFieldFadeOcclusion, this.flatFieldIgnoreSensorFlatField, this.repeatFlatFieldSensor, this.specularHighPassSigma, this.specularLowPassSigma, this.specularDiffFromAverageThreshold, this.specularNumIter, this.specularApplyNewWeights, this.specularPositiveDiffOnly, this.specularShowDebug); } public void setProperties(String prefix,Properties properties){ properties.setProperty(prefix+"extrapolate",this.extrapolate+""); properties.setProperty(prefix+"alphaThreshold",this.alphaThreshold+""); properties.setProperty(prefix+"fatZero",this.fatZero+""); properties.setProperty(prefix+"extrapolationSigma",this.extrapolationSigma+""); properties.setProperty(prefix+"extrapolationKSigma",this.extrapolationKSigma+""); properties.setProperty(prefix+"smoothCorrection",this.smoothCorrection+""); properties.setProperty(prefix+"smoothSigma",this.smoothSigma+""); properties.setProperty(prefix+"correctionScale",this.correctionScale+""); properties.setProperty(prefix+"showCumulativeCorrection",this.showCumulativeCorrection+""); properties.setProperty(prefix+"showUnfilteredCorrection",this.showUnfilteredCorrection+""); properties.setProperty(prefix+"showExtrapolationCorrection",this.showExtrapolationCorrection+""); properties.setProperty(prefix+"showThisCorrection",this.showThisCorrection+""); properties.setProperty(prefix+"showPerImage",this.showPerImage+""); properties.setProperty(prefix+"showIndividualNumber",this.showIndividualNumber+""); properties.setProperty(prefix+"applyCorrection",this.applyCorrection+""); properties.setProperty(prefix+"applyFlatField",this.applyFlatField+""); properties.setProperty(prefix+"grid3DCorrection",this.grid3DCorrection+""); properties.setProperty(prefix+"rotateCorrection",this.rotateCorrection+""); properties.setProperty(prefix+"grid3DMaximalZCorr",this.grid3DMaximalZCorr+""); properties.setProperty(prefix+"useVariations",this.useVariations+""); properties.setProperty(prefix+"variationPenalty",this.variationPenalty+""); properties.setProperty(prefix+"fixXY",this.fixXY+""); properties.setProperty(prefix+"resetVariations",this.resetVariations+""); properties.setProperty(prefix+"noFallBack",this.noFallBack+""); properties.setProperty(prefix+"usePatternAlpha",this.usePatternAlpha+""); properties.setProperty(prefix+"targetShowPerImage",this.targetShowPerImage+""); properties.setProperty(prefix+"targetShowThisCorrection",this.targetShowThisCorrection+""); properties.setProperty(prefix+"targetApplyCorrection",this.targetApplyCorrection+""); properties.setProperty(prefix+"targetCorrectionScale",this.targetCorrectionScale+""); properties.setProperty(prefix+"sensorExtrapolateDiff",this.sensorExtrapolateDiff+""); properties.setProperty(prefix+"sensorShrinkBlurComboSigma",this.sensorShrinkBlurComboSigma+""); properties.setProperty(prefix+"sensorShrinkBlurComboLevel",this.sensorShrinkBlurComboLevel+""); properties.setProperty(prefix+"sensorAlphaThreshold",this.sensorAlphaThreshold+""); properties.setProperty(prefix+"sensorStep",this.sensorStep+""); properties.setProperty(prefix+"sensorInterpolationSigma",this.sensorInterpolationSigma+""); properties.setProperty(prefix+"sensorTangentialRadius",this.sensorTangentialRadius+""); properties.setProperty(prefix+"sensorScanDistance",this.sensorScanDistance+""); properties.setProperty(prefix+"sensorResultDistance",this.sensorResultDistance+""); properties.setProperty(prefix+"sensorInterpolationDegree",this.sensorInterpolationDegree+""); properties.setProperty(prefix+"flatFieldSerNumber",this.flatFieldSerNumber+""); properties.setProperty(prefix+"flatFieldReferenceStation",this.flatFieldReferenceStation+""); properties.setProperty(prefix+"flatFieldShrink",this.flatFieldShrink+""); properties.setProperty(prefix+"flatFieldNonVignettedRadius",this.flatFieldNonVignettedRadius+""); properties.setProperty(prefix+"flatFieldMinimalAlpha",this.flatFieldMinimalAlpha+""); properties.setProperty(prefix+"flatFieldMinimalContrast",this.flatFieldMinimalContrast+""); properties.setProperty(prefix+"flatFieldMinimalAccumulate",this.flatFieldMinimalAccumulate+""); properties.setProperty(prefix+"flatFieldShrinkForMatching",this.flatFieldShrinkForMatching+""); properties.setProperty(prefix+"flatFieldMaxRelDiff",this.flatFieldMaxRelDiff+""); properties.setProperty(prefix+"flatFieldShrinkMask",this.flatFieldShrinkMask+""); properties.setProperty(prefix+"flatFieldFadeBorder",this.flatFieldFadeBorder+""); properties.setProperty(prefix+"flatFieldResetMask",this.flatFieldResetMask+""); properties.setProperty(prefix+"flatFieldShowSensorMasks",this.flatFieldShowSensorMasks+""); properties.setProperty(prefix+"flatFieldShowIndividual",this.flatFieldShowIndividual+""); properties.setProperty(prefix+"flatFieldShowResult",this.flatFieldShowResult+""); properties.setProperty(prefix+"flatFieldApplyResult",this.flatFieldApplyResult+""); properties.setProperty(prefix+"flatFieldUseInterpolate",this.flatFieldUseInterpolate+""); properties.setProperty(prefix+"flatFieldMaskThresholdOcclusion",this.flatFieldMaskThresholdOcclusion+""); properties.setProperty(prefix+"flatFieldShrinkOcclusion",this.flatFieldShrinkOcclusion+""); properties.setProperty(prefix+"flatFieldFadeOcclusion",this.flatFieldFadeOcclusion+""); properties.setProperty(prefix+"flatFieldIgnoreSensorFlatField",this.flatFieldIgnoreSensorFlatField+""); properties.setProperty(prefix+"repeatFlatFieldSensor",this.repeatFlatFieldSensor+""); properties.setProperty(prefix+"specularHighPassSigma",this.specularHighPassSigma+""); properties.setProperty(prefix+"specularLowPassSigma", this.specularLowPassSigma+""); properties.setProperty(prefix+"specularDiffFromAverageThreshold",this.specularDiffFromAverageThreshold+""); properties.setProperty(prefix+"specularNumIter",this.specularNumIter+""); properties.setProperty(prefix+"specularApplyNewWeights",this.specularApplyNewWeights+""); properties.setProperty(prefix+"specularPositiveDiffOnly",this.specularPositiveDiffOnly+""); properties.setProperty(prefix+"specularShowDebug",this.specularShowDebug+""); } public void getProperties(String prefix,Properties properties){ if (properties.getProperty(prefix+"extrapolate")!=null) this.extrapolate=Boolean.parseBoolean(properties.getProperty(prefix+"extrapolate")); if (properties.getProperty(prefix+"alphaThreshold")!=null) this.alphaThreshold=Double.parseDouble(properties.getProperty(prefix+"alphaThreshold")); if (properties.getProperty(prefix+"fatZero")!=null) this.fatZero=Double.parseDouble(properties.getProperty(prefix+"fatZero")); if (properties.getProperty(prefix+"extrapolationSigma")!=null) this.extrapolationSigma=Double.parseDouble(properties.getProperty(prefix+"extrapolationSigma")); if (properties.getProperty(prefix+"extrapolationKSigma")!=null) this.extrapolationKSigma=Double.parseDouble(properties.getProperty(prefix+"extrapolationKSigma")); if (properties.getProperty(prefix+"smoothCorrection")!=null) this.smoothCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"smoothCorrection")); if (properties.getProperty(prefix+"smoothSigma")!=null) this.smoothSigma=Double.parseDouble(properties.getProperty(prefix+"smoothSigma")); if (properties.getProperty(prefix+"correctionScale")!=null) this.correctionScale=Double.parseDouble(properties.getProperty(prefix+"correctionScale")); if (properties.getProperty(prefix+"showCumulativeCorrection")!=null) this.showCumulativeCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"showCumulativeCorrection")); if (properties.getProperty(prefix+"showUnfilteredCorrection")!=null) this.showUnfilteredCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"showUnfilteredCorrection")); if (properties.getProperty(prefix+"showExtrapolationCorrection")!=null) this.showExtrapolationCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"showExtrapolationCorrection")); if (properties.getProperty(prefix+"showThisCorrection")!=null) this.showThisCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"showThisCorrection")); if (properties.getProperty(prefix+"showPerImage")!=null) this.showPerImage=Boolean.parseBoolean(properties.getProperty(prefix+"showPerImage")); if (properties.getProperty(prefix+"showIndividualNumber")!=null) this.showIndividualNumber=Integer.parseInt(properties.getProperty(prefix+"showIndividualNumber")); if (properties.getProperty(prefix+"applyCorrection")!=null) this.applyCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"applyCorrection")); if (properties.getProperty(prefix+"applyFlatField")!=null) this.applyFlatField=Boolean.parseBoolean(properties.getProperty(prefix+"applyFlatField")); if (properties.getProperty(prefix+"grid3DCorrection")!=null) this.grid3DCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"grid3DCorrection")); if (properties.getProperty(prefix+"rotateCorrection")!=null) this.rotateCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"rotateCorrection")); if (properties.getProperty(prefix+"grid3DMaximalZCorr")!=null) this.grid3DMaximalZCorr=Double.parseDouble(properties.getProperty(prefix+"grid3DMaximalZCorr")); if (properties.getProperty(prefix+"useVariations")!=null) this.useVariations=Boolean.parseBoolean(properties.getProperty(prefix+"useVariations")); if (properties.getProperty(prefix+"variationPenalty")!=null) this.variationPenalty=Double.parseDouble(properties.getProperty(prefix+"variationPenalty")); if (properties.getProperty(prefix+"fixXY")!=null) this.fixXY=Boolean.parseBoolean(properties.getProperty(prefix+"fixXY")); if (properties.getProperty(prefix+"resetVariations")!=null) this.resetVariations=Boolean.parseBoolean(properties.getProperty(prefix+"resetVariations")); if (properties.getProperty(prefix+"noFallBack")!=null) this.noFallBack=Boolean.parseBoolean(properties.getProperty(prefix+"noFallBack")); if (properties.getProperty(prefix+"usePatternAlpha")!=null) this.usePatternAlpha=Boolean.parseBoolean(properties.getProperty(prefix+"usePatternAlpha")); if (properties.getProperty(prefix+"targetShowPerImage")!=null) this.targetShowPerImage=Boolean.parseBoolean(properties.getProperty(prefix+"targetShowPerImage")); if (properties.getProperty(prefix+"targetShowThisCorrection")!=null) this.targetShowThisCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"targetShowThisCorrection")); if (properties.getProperty(prefix+"targetApplyCorrection")!=null) this.targetApplyCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"targetApplyCorrection")); if (properties.getProperty(prefix+"targetCorrectionScale")!=null) this.targetCorrectionScale=Double.parseDouble(properties.getProperty(prefix+"targetCorrectionScale")); if (properties.getProperty(prefix+"sensorExtrapolateDiff")!=null) this.sensorExtrapolateDiff=Boolean.parseBoolean(properties.getProperty(prefix+"sensorExtrapolateDiff")); if (properties.getProperty(prefix+"sensorShrinkBlurComboSigma")!=null) this.sensorShrinkBlurComboSigma=Double.parseDouble(properties.getProperty(prefix+"sensorShrinkBlurComboSigma")); if (properties.getProperty(prefix+"sensorShrinkBlurComboLevel")!=null) this.sensorShrinkBlurComboLevel=Double.parseDouble(properties.getProperty(prefix+"sensorShrinkBlurComboLevel")); if (properties.getProperty(prefix+"sensorAlphaThreshold")!=null) this.sensorAlphaThreshold=Double.parseDouble(properties.getProperty(prefix+"sensorAlphaThreshold")); if (properties.getProperty(prefix+"sensorStep")!=null) this.sensorStep=Double.parseDouble(properties.getProperty(prefix+"sensorStep")); if (properties.getProperty(prefix+"sensorInterpolationSigma")!=null) this.sensorInterpolationSigma=Double.parseDouble(properties.getProperty(prefix+"sensorInterpolationSigma")); if (properties.getProperty(prefix+"sensorTangentialRadius")!=null) this.sensorTangentialRadius=Double.parseDouble(properties.getProperty(prefix+"sensorTangentialRadius")); if (properties.getProperty(prefix+"sensorScanDistance")!=null) this.sensorScanDistance=Integer.parseInt(properties.getProperty(prefix+"sensorScanDistance")); if (properties.getProperty(prefix+"sensorResultDistance")!=null) this.sensorResultDistance=Integer.parseInt(properties.getProperty(prefix+"sensorResultDistance")); if (properties.getProperty(prefix+"sensorInterpolationDegree")!=null) this.sensorInterpolationDegree=Integer.parseInt(properties.getProperty(prefix+"sensorInterpolationDegree")); if (properties.getProperty(prefix+"flatFieldSerNumber")!=null) this.flatFieldSerNumber=Integer.parseInt(properties.getProperty(prefix+"flatFieldSerNumber")); if (properties.getProperty(prefix+"flatFieldReferenceStation")!=null) this.flatFieldReferenceStation=Integer.parseInt(properties.getProperty(prefix+"flatFieldReferenceStation")); if (properties.getProperty(prefix+"flatFieldShrink")!=null) this.flatFieldShrink=Double.parseDouble(properties.getProperty(prefix+"flatFieldShrink")); if (properties.getProperty(prefix+"flatFieldNonVignettedRadius")!=null) this.flatFieldNonVignettedRadius=Double.parseDouble(properties.getProperty(prefix+"flatFieldNonVignettedRadius")); if (properties.getProperty(prefix+"flatFieldMinimalAlpha")!=null) this.flatFieldMinimalAlpha=Double.parseDouble(properties.getProperty(prefix+"flatFieldMinimalAlpha")); if (properties.getProperty(prefix+"flatFieldMinimalContrast")!=null) this.flatFieldMinimalContrast=Double.parseDouble(properties.getProperty(prefix+"flatFieldMinimalContrast")); if (properties.getProperty(prefix+"flatFieldMinimalAccumulate")!=null) this.flatFieldMinimalAccumulate=Double.parseDouble(properties.getProperty(prefix+"flatFieldMinimalAccumulate")); if (properties.getProperty(prefix+"flatFieldShrinkForMatching")!=null) this.flatFieldShrinkForMatching=Double.parseDouble(properties.getProperty(prefix+"flatFieldShrinkForMatching")); if (properties.getProperty(prefix+"flatFieldMaxRelDiff")!=null) this.flatFieldMaxRelDiff=Double.parseDouble(properties.getProperty(prefix+"flatFieldMaxRelDiff")); if (properties.getProperty(prefix+"flatFieldShrinkMask")!=null) this.flatFieldShrinkMask=Integer.parseInt(properties.getProperty(prefix+"flatFieldShrinkMask")); if (properties.getProperty(prefix+"flatFieldFadeBorder")!=null) this.flatFieldFadeBorder=Double.parseDouble(properties.getProperty(prefix+"flatFieldFadeBorder")); if (properties.getProperty(prefix+"flatFieldResetMask")!=null) this.flatFieldResetMask=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldResetMask")); if (properties.getProperty(prefix+"flatFieldShowSensorMasks")!=null) this.flatFieldShowSensorMasks=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldShowSensorMasks")); if (properties.getProperty(prefix+"flatFieldShowIndividual")!=null) this.flatFieldShowIndividual=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldShowIndividual")); if (properties.getProperty(prefix+"flatFieldShowResult")!=null) this.flatFieldShowResult=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldShowResult")); if (properties.getProperty(prefix+"flatFieldApplyResult")!=null) this.flatFieldApplyResult=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldApplyResult")); if (properties.getProperty(prefix+"flatFieldUseInterpolate")!=null) this.flatFieldUseInterpolate=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldUseInterpolate")); if (properties.getProperty(prefix+"flatFieldMaskThresholdOcclusion")!=null) this.flatFieldMaskThresholdOcclusion=Double.parseDouble(properties.getProperty(prefix+"flatFieldMaskThresholdOcclusion")); if (properties.getProperty(prefix+"flatFieldShrinkOcclusion")!=null) this.flatFieldShrinkOcclusion=Integer.parseInt(properties.getProperty(prefix+"flatFieldShrinkOcclusion")); if (properties.getProperty(prefix+"flatFieldFadeOcclusion")!=null) this.flatFieldFadeOcclusion=Double.parseDouble(properties.getProperty(prefix+"flatFieldFadeOcclusion")); if (properties.getProperty(prefix+"flatFieldIgnoreSensorFlatField")!=null) this.flatFieldIgnoreSensorFlatField=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldIgnoreSensorFlatField")); if (properties.getProperty(prefix+"repeatFlatFieldSensor")!=null) this.repeatFlatFieldSensor=Integer.parseInt(properties.getProperty(prefix+"repeatFlatFieldSensor")); if (properties.getProperty(prefix+"specularHighPassSigma")!=null) this.specularHighPassSigma=Double.parseDouble(properties.getProperty(prefix+"specularHighPassSigma")); if (properties.getProperty(prefix+"specularLowPassSigma")!=null) this.specularLowPassSigma=Double.parseDouble(properties.getProperty(prefix+"specularLowPassSigma")); if (properties.getProperty(prefix+"specularDiffFromAverageThreshold")!=null) this.specularDiffFromAverageThreshold=Double.parseDouble(properties.getProperty(prefix+"specularDiffFromAverageThreshold")); if (properties.getProperty(prefix+"specularNumIter")!=null) this.specularNumIter=Integer.parseInt(properties.getProperty(prefix+"specularNumIter")); if (properties.getProperty(prefix+"specularApplyNewWeights")!=null) this.specularApplyNewWeights=Boolean.parseBoolean(properties.getProperty(prefix+"specularApplyNewWeights")); if (properties.getProperty(prefix+"specularPositiveDiffOnly")!=null) this.specularPositiveDiffOnly=Boolean.parseBoolean(properties.getProperty(prefix+"specularPositiveDiffOnly")); if (properties.getProperty(prefix+"specularShowDebug")!=null) this.specularShowDebug=Integer.parseInt(properties.getProperty(prefix+"specularShowDebug")); } public int showDialog(String title, int parMask, int numSeries, double [] averageRGB) { // sensor 0xfff, grid - 0xcc0 // cannot show result (cumulative) grid correction GenericDialog gd = new GenericDialog(title); if (numSeries>=0) gd.addNumericField("Fitting strategy series number (selects images to process) ", numSeries,0); if ((parMask&0x200000)!=0) gd.addNumericField("Repeat target/sensor flat-field calculation", this.repeatFlatFieldSensor,0,3,"times"); //sensorExtrapolateDiff if ((parMask&0x80000)!=0) gd.addCheckbox("Extrapolate incremetal (not checked - cumulative) correction", this.sensorExtrapolateDiff); if ((parMask&0x80000) !=0) gd.addNumericField("Shrink-blur combined sigma", this.sensorShrinkBlurComboSigma, 2,6,"sensor pixels"); // 20 if ((parMask&0x80000) !=0) gd.addNumericField("Shrink-blur combined level (-1..+1)", this.sensorShrinkBlurComboLevel, 2,6,""); // 0 if ((parMask&0x80000) !=0) gd.addNumericField("Combined alpha extrapolation threshold", this.sensorAlphaThreshold, 2,6,""); // normalize later? if ((parMask&0x80000) !=0) gd.addNumericField("Extrapolation seed step",this.sensorStep, 1,4,"decimated pixels"); if ((parMask&0x80000) !=0) gd.addNumericField("Extrapolation gaussian sigma", this.sensorInterpolationSigma, 2,6,"sensor pixels"); // 50 if ((parMask&0x80000) !=0) gd.addNumericField("Extrapolation effective radius (doubling sigma in tangential direction)", this.sensorTangentialRadius, 2,6,"fraction of full image radius"); if ((parMask&0x80000) !=0) gd.addNumericField("Extrapolation half-square side for polynomial approximation", this.sensorScanDistance, 0,3,"sensor pixels"); if ((parMask&0x80000) !=0) gd.addNumericField("Extrapolation half-square side for extrapolation", this.sensorResultDistance, 0,3,"sensor pixels"); if ((parMask&0x80000) !=0) gd.addNumericField("Extrapolation polynomial degree", this.sensorInterpolationDegree, 0,1,""); if ((parMask&0x100000)!=0) gd.addNumericField("Fitting series number (to select images), negative - use all enabled images", this.flatFieldSerNumber,0); if ((parMask&0x100000)!=0) gd.addNumericField("Reference station number (unity target brightness)", this.flatFieldReferenceStation,0); if ((parMask&0x100000)!=0) gd.addNumericField("Shrink sensor mask", this.flatFieldShrink, 1,6,"sensor pix"); if ((parMask&0x100000)!=0) gd.addNumericField("Non-vignetted radius", this.flatFieldNonVignettedRadius, 1,6,"sensor pix"); if ((parMask&0x100000)!=0) gd.addNumericField("Minimal alpha", 100.0*this.flatFieldMinimalAlpha, 3,7,"%"); if ((parMask&0x100000)!=0) gd.addNumericField("Minimal contrast (occlusion detection)", this.flatFieldMinimalContrast, 3,7,"(0 .. ~0.8"); if ((parMask&0x100000)!=0) gd.addNumericField("Minimal alpha for accumulation", 100.0*this.flatFieldMinimalAccumulate, 3,7,"%"); if ((parMask&0x100000)!=0) gd.addNumericField("Shrink pattern for matching", this.flatFieldShrinkForMatching, 3,7,"grid nodes"); if ((parMask&0x100000)!=0) gd.addNumericField("Maximal relative difference between nodes", 100.0*this.flatFieldMaxRelDiff, 3,7,"%"); if ((parMask&0x100000)!=0) gd.addNumericField("Shrink pattern border", this.flatFieldShrinkMask, 0,3,"grid nodes"); if ((parMask&0x100000)!=0) gd.addNumericField("Fade pattern border", this.flatFieldFadeBorder, 3,7,"grid nodes"); if ((parMask&0x100000)!=0) gd.addMessage("Update pattern white balance (if the illumination is yellowish, increase red and green here)"); if ((parMask&0x100000)!=0) gd.addNumericField("Average grid RED (1.0 for white)", averageRGB[0], 3,5,"x"); // if ((parMask&0x100000)!=0) gd.addNumericField("Average grid GREEN (1.0 for white)", averageRGB[1], 3,5,"x"); // if ((parMask&0x100000)!=0) gd.addNumericField("Average grid BLUE (1.0 for white)", averageRGB[2], 3,5,"x"); // if ((parMask&0x100000)!=0) gd.addCheckbox("Reset pattern mask", this.flatFieldResetMask); if ((parMask&0x100000)!=0) gd.addCheckbox("Show non-vignetting sensor masks", this.flatFieldShowSensorMasks); if ((parMask&0x100000)!=0) gd.addCheckbox("Show per-sensor patterns", this.flatFieldShowIndividual); if ((parMask&0x100000)!=0) gd.addCheckbox("Show result mask", this.flatFieldShowResult); if ((parMask&0x100000)!=0) gd.addCheckbox("Apply pattern flat field and mask",this.flatFieldApplyResult); if ((parMask&0x100000)!=0) gd.addCheckbox("Use interpolation for sensor correction",this.flatFieldUseInterpolate); if ((parMask&0x100000)!=0) gd.addNumericField("Suspect occlusion only if grid is missing in the area where sensor mask is above this threshold",100.0* this.flatFieldMaskThresholdOcclusion, 3,7,"%"); if ((parMask&0x100000)!=0) gd.addNumericField("Expand suspected occlusion area", this.flatFieldShrinkOcclusion, 0,3,"grid nodes"); if ((parMask&0x100000)!=0) gd.addNumericField("Fade grid on image (occlusion handling)", this.flatFieldFadeOcclusion, 3,7,"grid nodes"); if ((parMask&0x100000)!=0) gd.addCheckbox("Ignore existent sensor flat-field calibration",this.flatFieldIgnoreSensorFlatField); if ((parMask&0x400000)!=0) gd.addMessage("Specular reflections removal parameters:"); if ((parMask&0x400000)!=0) gd.addCheckbox("Apply new (after removal of specular reflections) weights", this.specularApplyNewWeights); if ((parMask&0x400000)!=0) gd.addCheckbox("Process only positive difference from average", this.specularPositiveDiffOnly); if ((parMask&0x400000)!=0) gd.addNumericField("High-pass sigma for difference from average (to detect specular)",this.specularHighPassSigma, 3,7,"pix"); if ((parMask&0x400000)!=0) gd.addNumericField("Low-pass sigma for difference from average (to detect specular)",this.specularLowPassSigma, 3,7,"pix"); if ((parMask&0x400000)!=0) gd.addNumericField("Difference from average threshold", 100.0*this.specularDiffFromAverageThreshold, 3,7,"%"); if ((parMask&0x400000)!=0) gd.addNumericField("Number of iterations for calculating average", this.specularNumIter, 0); if ((parMask&0x400000)!=0) gd.addNumericField("Debug show mode (0 - off, 1 - last iteration only, 2 - all iterations)",this.specularShowDebug, 0); if ((parMask & 1) !=0) gd.addCheckbox ("Extrapolate correction results", this.extrapolate); if ((parMask & 2) !=0) gd.addNumericField("Threshold alpha (discard pixels with mask below that value)", this.alphaThreshold,3); if ((parMask &0x8000) !=0) gd.addNumericField("Fat zero for color trasfer functions", this.fatZero,3); if ((parMask & 4) !=0) gd.addNumericField("Fitting radius for extrapolation, Gaussian weight function sigma (in non-decimated pixels) ", this.extrapolationSigma,3); if ((parMask & 8) !=0) gd.addNumericField("Fitting scan half-size of the square, in multiples of Fitting Radius", this.extrapolationKSigma,3); if ((parMask & 0x10) !=0) gd.addCheckbox ("Apply smoothing to the correction results", this.smoothCorrection); if ((parMask & 0x20) !=0) gd.addNumericField("Smoothing sigma, in non-decimated pixels", this.smoothSigma,3); if ((parMask & 0x40) !=0) gd.addCheckbox ("Apply correction", this.applyCorrection); if ((parMask&0x40000) !=0) gd.addCheckbox ("Apply correction", this.targetApplyCorrection); if ((parMask &0x4000) !=0) gd.addCheckbox ("Apply flat-field correction", this.applyFlatField); if ((parMask & 0x80) !=0) gd.addNumericField("Scale correction before applying", this.correctionScale,3); if ((parMask&0x40000) !=0) gd.addNumericField("Scale correction before applying", this.targetCorrectionScale,3); if ((parMask & 0x100) !=0) gd.addCheckbox ("Show result (cumulative) correction", this.showCumulativeCorrection); if ((parMask & 0x200) !=0) gd.addCheckbox ("Show additional correction before blurring",this.showUnfilteredCorrection); if ((parMask & 0x200) !=0) gd.addCheckbox ("Show correction extrapolatiuon", this.showExtrapolationCorrection); if ((parMask & 0x400) !=0) gd.addCheckbox ("Show this (additional) correction", this.showThisCorrection); if ((parMask&0x40000) !=0) gd.addCheckbox ("Show this (additional) correction", this.targetShowThisCorrection); if ((parMask & 0x800) !=0) gd.addCheckbox ("Show individual, per-image residuals", this.showPerImage); if ((parMask&0x40000) !=0) gd.addCheckbox ("Show individual, per-image residuals", this.targetShowPerImage); if ((parMask&0x10000) !=0) gd.addNumericField("Show individual residuals for image number (<0 - all images)", this.showIndividualNumber,0); if ((parMask &0x1000) !=0) gd.addCheckbox ("Correct patetrn grid node locations in 3d (false - in 2d only)", this.grid3DCorrection); if ((parMask &0x1000) !=0) gd.addCheckbox ("Rotate final 3d pattern correction (?)", this.rotateCorrection); if ((parMask&0x20000) !=0) gd.addNumericField("Maximal Z-axis correction (if more will fall back to 2d correction algorithm)", this.grid3DMaximalZCorr,1,3,"mm"); if ((parMask&0x20000) !=0) gd.addCheckbox ("Use Z-variations of the pattern for different stations", this.useVariations); if ((parMask&0x20000) !=0) gd.addNumericField("Penalty for different Z for the same target nodes for different stations", 100.0*this.variationPenalty,3,7,"%"); if ((parMask&0x20000) !=0) gd.addCheckbox ("Keep X and Y pattern correction, adjust only Z",this.fixXY); if ((parMask&0x20000) !=0) gd.addCheckbox ("Reset previous Z variations before calculating the new one", this.resetVariations); if ((parMask&0x20000) !=0) gd.addCheckbox ("Do not fall back to 2-d calculation if 3d fails", this.noFallBack); if ((parMask &0x2000) !=0) gd.addCheckbox ("Use pattern grid alpha data", this.usePatternAlpha); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return -1; int selectedSeries=0; if (numSeries>=0) selectedSeries= (int) gd.getNextNumber(); if ((parMask&0x200000)!=0) this.repeatFlatFieldSensor= (int) gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorExtrapolateDiff= gd.getNextBoolean(); if ((parMask&0x80000) !=0) this.sensorShrinkBlurComboSigma= gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorShrinkBlurComboLevel= gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorAlphaThreshold= gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorStep= gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorInterpolationSigma= gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorTangentialRadius= gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorScanDistance= (int) gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorResultDistance= (int) gd.getNextNumber(); if ((parMask&0x80000) !=0) this.sensorInterpolationDegree=(int) gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldSerNumber= (int) gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldReferenceStation= (int) gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldShrink= gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldNonVignettedRadius= gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldMinimalAlpha= 0.01*gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldMinimalContrast= gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldMinimalAccumulate= 0.01*gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldShrinkForMatching= gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldMaxRelDiff= 0.01*gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldShrinkMask= (int) gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldFadeBorder= gd.getNextNumber(); if ((parMask&0x100000)!=0) averageRGB[0]= gd.getNextNumber(); if ((parMask&0x100000)!=0) averageRGB[1]= gd.getNextNumber(); if ((parMask&0x100000)!=0) averageRGB[2]= gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldResetMask= gd.getNextBoolean(); if ((parMask&0x100000)!=0) this.flatFieldShowSensorMasks= gd.getNextBoolean(); if ((parMask&0x100000)!=0) this.flatFieldShowIndividual= gd.getNextBoolean(); if ((parMask&0x100000)!=0) this.flatFieldShowResult= gd.getNextBoolean(); if ((parMask&0x100000)!=0) this.flatFieldApplyResult= gd.getNextBoolean(); if ((parMask&0x100000)!=0) this.flatFieldUseInterpolate= gd.getNextBoolean(); if ((parMask&0x100000)!=0) this.flatFieldMaskThresholdOcclusion=0.01*gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldShrinkOcclusion= (int) gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldFadeOcclusion= gd.getNextNumber(); if ((parMask&0x100000)!=0) this.flatFieldIgnoreSensorFlatField= gd.getNextBoolean(); // if ((parMask&0x100000)!=0) this.flatFieldUseSelectedChannels= gd.getNextBoolean(); if ((parMask&0x400000)!=0) this.specularApplyNewWeights= gd.getNextBoolean(); if ((parMask&0x400000)!=0) this.specularPositiveDiffOnly= gd.getNextBoolean(); if ((parMask&0x400000)!=0) this.specularHighPassSigma= gd.getNextNumber(); if ((parMask&0x400000)!=0) this.specularLowPassSigma= gd.getNextNumber(); if ((parMask&0x400000)!=0) this.specularDiffFromAverageThreshold=0.01*gd.getNextNumber();; if ((parMask&0x400000)!=0) this.specularNumIter= (int) gd.getNextNumber(); if ((parMask&0x400000)!=0) this.specularShowDebug= (int) gd.getNextNumber(); if ((parMask & 1) !=0) this.extrapolate= gd.getNextBoolean(); if ((parMask & 2) !=0) this.alphaThreshold= gd.getNextNumber(); if ((parMask &0x8000) !=0) this.fatZero= gd.getNextNumber(); if ((parMask & 4) !=0) this.extrapolationSigma= gd.getNextNumber(); if ((parMask & 8) !=0) this.extrapolationKSigma= gd.getNextNumber(); if ((parMask & 0x10) !=0) this.smoothCorrection= gd.getNextBoolean(); if ((parMask & 0x20) !=0) this.smoothSigma= gd.getNextNumber(); if ((parMask & 0x40) !=0) this.applyCorrection= gd.getNextBoolean(); if ((parMask&0x40000) !=0) this.targetApplyCorrection= gd.getNextBoolean(); if ((parMask &0x4000) !=0) this.applyFlatField= gd.getNextBoolean(); if ((parMask & 0x80) !=0) this.correctionScale= gd.getNextNumber(); if ((parMask&0x40000) !=0) this.targetCorrectionScale= gd.getNextNumber(); if ((parMask & 0x100) !=0) this.showCumulativeCorrection= gd.getNextBoolean(); if ((parMask & 0x200) !=0) this.showUnfilteredCorrection= gd.getNextBoolean(); if ((parMask & 0x200) !=0) this.showExtrapolationCorrection= gd.getNextBoolean(); if ((parMask & 0x400) !=0) this.showThisCorrection= gd.getNextBoolean(); if ((parMask&0x40000) !=0) this.targetShowThisCorrection= gd.getNextBoolean(); if ((parMask & 0x800) !=0) this.showPerImage= gd.getNextBoolean(); if ((parMask&0x40000) !=0) this.targetShowPerImage= gd.getNextBoolean(); if ((parMask&0x10000) !=0) this.showIndividualNumber=(int)gd.getNextNumber(); if ((parMask &0x1000) !=0) this.grid3DCorrection= gd.getNextBoolean(); if ((parMask &0x1000) !=0) this.rotateCorrection= gd.getNextBoolean(); if ((parMask&0x20000) !=0) this.grid3DMaximalZCorr= gd.getNextNumber(); if ((parMask&0x20000) !=0) this.useVariations= gd.getNextBoolean(); if ((parMask&0x20000) !=0) this.variationPenalty = 0.01*gd.getNextNumber(); if ((parMask&0x20000) !=0) this.fixXY= gd.getNextBoolean(); if ((parMask&0x20000) !=0) this.resetVariations= gd.getNextBoolean(); if ((parMask&0x20000) !=0) this.noFallBack= gd.getNextBoolean(); if ((parMask &0x2000) !=0) this.usePatternAlpha= gd.getNextBoolean(); return selectedSeries; } } }