Java tutorial
/* ** ** Distortions.java - Calculate lens distortion parameters from the pattern image ** ** Copyright (C) 2011-2013 Elphel, Inc. ** ** -----------------------------------------------------------------------------** ** ** MatchSimulatedPattern.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 ij.*; //import ij.process.*; import ij.gui.GenericDialog; import ij.io.FileSaver; import ij.io.Opener; import ij.process.FloatProcessor; import ij.process.ImageProcessor; import ij.text.TextWindow; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; //import java.io.StringWriter; import java.util.List; import java.util.ArrayList; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.SwingUtilities; import Jama.LUDecomposition; import Jama.Matrix; //import src.java.org.apache.commons.configuration.*; import 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 { 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 // {description, units, "S"/"C" (Subcamera/Eyesis)} // public double [][][] gridGeometry=null; // create initial uniform grid public PatternParameters patternParameters; public LensDistortionParameters lensDistortionParameters; public RefineParameters refineParameters = new RefineParameters(); //create with default values public FittingStrategy fittingStrategy = null; // public double patternWidth; // pattern full width in mm // public double patternHeight; // pattern full height in mm // public double patternHalfPeriod; // distance between opposite sign nodes // public double patternTilt; // pattern tilt (degrees) - U clockwise from X-right (V clockwise from Y-down) public double[][][][] gridOnSensor = null; // [v][u][px,py][0-value, 1..14 - derivative] public double[][] interParameterDerivatives = 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 [] imageSetWeight=null; 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 [][] savedJacobian=null; // Jacobian saved from previous calculation 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; public int pixelCorrectionDecimation = 1; public int pixelCorrectionWidth = 2592; 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 void showStatus (String msg, int slot){ String separator = " | "; if ((slot<0) || (slot>=status.length)) return; status[slot]=msg; String s=status[0]; for (int i=1;i<status.length;i++) if (status[i].length()>0) s+=separator+status[i]; IJ.showStatus(s); } public void showProgress(double frac){ if ((frac>=1.0) || (frac <=0)) showStatus("",1); showStatus(IJ.d2s(100*frac,2)+"%",1); } public void showProgress(int currentIndex, int finalIndex){ showProgress((currentIndex+1.0)/(double)finalIndex); } */ public class LMAArrays { public double[][] jTByJ = null; // jacobian multiplied by Jacobian transposed public double[] jTByDiff = null; // jacobian multiplied difference vector 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; } 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 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() { public void run() { for (int imgNum = imageNumberAtomic .getAndIncrement(); imgNum < numImg; imgNum = imageNumberAtomic.getAndIncrement()) { distortionCalibrationData[imgNum].calculateMask(minContrast, shrinkBlurSigma, shrinkBlurLevel); final int numFinished = imageFinishedAtomic.getAndIncrement(); SwingUtilities.invokeLater(new Runnable() { 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.decimateMasks; this.pixelCorrectionWidth = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth; this.pixelCorrectionHeight = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight; 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\ 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 ((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 ((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 ((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 ((filter & this.filterMulti) != 0) { weight *= multiWeight[imgNum]; if (Double.isNaN(multiWeight[imgNum]) && (this.debugLevel > 1)) System.out.println("multiWeight[" + imgNum + "]=NaN, imgNum=" + imgNum); } if ((filter & this.filterMaskBadNodes) != 0) { if (fittingStrategy.distortionCalibrationData.gIP[imgNum].isNodeBad(pointNumber)) weight = 0.0; } //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 (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] / this.pixelCorrectionDecimation)) + ((int) Math.floor(pXY[1] / this.pixelCorrectionDecimation)) * sensorCorrWidth; 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=" + sensorCorrWidth); } 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 uis 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() { // 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 + " 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 + " 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 int[] numSetPoints = new int[imageSets.length]; double[] rmsPerSet = new double[imageSets.length]; boolean[] hasNaNInSet = 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; } } numSetPoints[setNum] = numInSet; rmsPerSet[setNum] = Math.sqrt(error2 / numInSet); } this.fittingStrategy.distortionCalibrationData.listImageSet(numSetPoints, rmsPerSet, hasNaNInSet); // 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; } */ if (enableShow && this.refineParameters.flatFieldShowSensorMasks) (new showDoubleFloatArrays()).showArrays( //java.lang.ArrayIndexOutOfBoundsException: 313632 masks, this.pixelCorrectionWidth / this.pixelCorrectionDecimation, this.pixelCorrectionHeight / this.pixelCorrectionDecimation, true, "nonVinetting masks"); 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" }; int decimate = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks; int sWidth = (fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth - 1) / decimate + 1; int sHeight = (fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight - 1) / decimate + 1; if (enableShow && this.refineParameters.showUnfilteredCorrection) { for (int numChn = 0; numChn < sensorXYRGBCorr.length; numChn++) if (sensorXYRGBCorr[numChn] != null) { //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) { 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) { // 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) { // 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() { 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() { 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() { 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() { 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() { 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()) { 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() { 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 sensorWidth = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth; int sensorHeight = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight; int decimation = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks; 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 = (this.pixelCorrectionWidth - 1) / this.pixelCorrectionDecimation + 1; int sensorCorrHeight = this.pixelCorrection[chnNum][0].length / sensorCorrWidth; int[] ix = { (int) Math.floor(px / this.pixelCorrectionDecimation), (int) Math.floor(px / this.pixelCorrectionDecimation) + 1 }; int[] iy = { (int) Math.floor(py / this.pixelCorrectionDecimation), (int) Math.floor(py / this.pixelCorrectionDecimation) + 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(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 = (this.pixelCorrectionWidth - 1) / this.pixelCorrectionDecimation + 1; int sensorCorrHeight = mask.length / sensorCorrWidth; int[] ix = { (int) Math.floor(px / this.pixelCorrectionDecimation), (int) Math.floor(px / this.pixelCorrectionDecimation) + 1 }; int[] iy = { (int) Math.floor(py / this.pixelCorrectionDecimation), (int) Math.floor(py / this.pixelCorrectionDecimation) + 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, 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 = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth(subCam); int sensorHeight = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight(subCam); double[][][] hintGrid = estimateGridOnSensor(stationNumber, subCam, goniometerTilt, // Tilt, goniometerHorizontal goniometerAxial, // Axial,goniometerAxial -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(MatchSimulatedPattern.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 ) { 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++) if (((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].gridPeriod); 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 }; 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; } // int numPointers=(laserPointer!=null)?laserPointer.laserUVMap.length:0; // double [][] pointersXY=(numPointers>0)?getPointersXY(imp, numPointers):null; 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].setNumber; double[][][] hintGrid = estimateGridOnSensor(station, // station number dcd.gIP[numGridImage].channel, goniometerTiltAxial[0], // Tilt, goniometerHorizontal goniometerTiltAxial[1], // Axial,goniometerAxial 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.pixelsUV[v][u] != null)) { if ((matchSimulatedPattern.targetUV[v][u] != null) && (matchSimulatedPattern.pixelsUV[v][u] != null) && (matchSimulatedPattern.pixelsUV[v][u][0] >= 0.0) || (matchSimulatedPattern.pixelsUV[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++; } } } } 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.pixelsUV[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.pixelsUV[v][u] != null) && (matchSimulatedPattern.pixelsUV[v][u][0] >= 0.0) || (matchSimulatedPattern.pixelsUV[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); } } if (patternParameters.getXYZM(matchSimulatedPattern.targetUV[v][u][0], matchSimulatedPattern.targetUV[v][u][1], false, station) != null) { dcd.gIP[numGridImage].pixelsXY[index][0] = matchSimulatedPattern.pixelsUV[v][u][0]; dcd.gIP[numGridImage].pixelsXY[index][1] = matchSimulatedPattern.pixelsUV[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.pixelsUV[v][u][0]; dcd.gIP[numGridImage].pixelsXY_extra[index_extra][1] = matchSimulatedPattern.pixelsUV[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++; 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); } } } return numSuccess; } public void showGridImage(int numGridImage) { DistortionCalibrationData.GridImageParameters grid = fittingStrategy.distortionCalibrationData.gIP[numGridImage]; /* public double [][] pixelsXY= null; // for each image, each grid node - a pair of {px,py} public int [][] pixelsUV= null; // for each image, each grid node - a pair of {gridU, gridV} public double [][] pixelsXY_extra= null; // extra data, for nodes that are out of the physical grid (may be needed after re-calibration) public int [][] pixelsUV_extra= null; */ 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 showGridAndHint() { GenericDialog gd = new GenericDialog("Show selected grid and/or hint grid"); gd.addNumericField("Grid Image index", 0, 0); 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 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 (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.showDialog(); if (gd.wasCanceled()) return; goniometerTiltAxial = new double[2]; goniometerTiltAxial[0] = gd.getNextNumber(); goniometerTiltAxial[1] = gd.getNextNumber(); } double[][][] hintGrid = estimateGridOnSensor( fittingStrategy.distortionCalibrationData.getImageStation(numGridImage), // station number fittingStrategy.distortionCalibrationData.gIP[numGridImage].channel, goniometerTiltAxial[0], // Tilt, goniometerHorizontal goniometerTiltAxial[1], // Axial,goniometerAxial (useSetData ? fittingStrategy.distortionCalibrationData.gIP[numGridImage].setNumber : -1), true // filter border ); 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 stationNumber, // int subCamera, int debugLevel) { // Axial - may be Double.NaN // double [] parVector=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getParametersVector(stationNumber,subCamera); // System.out.println("setupLensDistortionParameters(): subCamera="+subCamera+", goniometerHorizontal="+goniometerHorizontal+", goniometerAxial="+goniometerAxial); boolean isTripod = this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.isTripod; LensDistortionParameters lensDistortionParameters = new LensDistortionParameters(isTripod, null, //double [][] interParameterDerivatives, //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) if null - just values, no derivatives // this.fittingStrategy.distortionCalibrationData.pars[numImg], //parVector, 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(); // int sensorWidth=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorWidth(subCamera); // int sensorHeight=fittingStrategy.distortionCalibrationData.eyesisCameraParameters.getSensorHeight(subCamera); if (!Double.isNaN(goniometerHorizontal)) parVector[goniometerHorizontalIndex] = goniometerHorizontal; if (!Double.isNaN(goniometerAxial)) parVector[goniometerAxialIndex] = goniometerAxial; // System.out.println("setupLensDistortionParameters(): subCamera="+subCamera+", goniometerHorizontal="+goniometerHorizontal+", goniometerAxial="+goniometerAxial); boolean isTripod = this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.isTripod; LensDistortionParameters lensDistortionParameters = new LensDistortionParameters(isTripod, 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) { 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 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 int imageSet, boolean filterBorder) { 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; } int sensorWidth = fittingStrategy.distortionCalibrationData.eyesisCameraParameters .getSensorWidth(subCamera); int sensorHeight = fittingStrategy.distortionCalibrationData.eyesisCameraParameters .getSensorHeight(subCamera); System.out.println("estimateGridOnSensor(): subCamera=" + subCamera + ", goniometerHorizontal=" + goniometerHorizontal + ", goniometerAxial=" + goniometerAxial); calcInterParamers(this.lensDistortionParameters, // 22-long parameter vector for the image null, // this.interParameterDerivatives, // [22][] parVector, null); // if no derivatives, null is OK // false); // calculate this.interParameterDerivatives -derivatives array (false - just this.values) 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 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= fittingStrategy.getImageParametersVectorMask(imgNum); boolean[] imgMask = new boolean[imgVector.length]; for (int i = 0; i < imgMask.length; i++) imgMask[i] = true; calcInterParamers(this.lensDistortionParameters, 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[getNumInputs()][6]; for (int i = 0; i < 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[getNumInputs()][values.length]; for (int i = 0; i < getNumInputs(); i++) { double[] vector_delta = imgVector.clone(); vector_delta[i] += delta; calcInterParamers(this.lensDistortionParameters, 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 < getNumInputs(); parNum++) { sb.append(parNum + "\t" + fittingStrategy.distortionCalibrationData.parameterDescriptions[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); 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():"); } calcInterParamers(this.lensDistortionParameters, 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( 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:"; 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[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():"); calcInterParamers(lensDistortionParameters, 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:"; 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[16][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():"); } calcInterParamers(this.lensDistortionParameters, 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:"; 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]; 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 intrinsic 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, 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 removeOutLayers(int series, int numOutLayers, boolean[] selectedChannels) { int numSeries = fittingStrategy.getNumSeries(); boolean removeEmpty = false; boolean recalculate = false; boolean applyChannelFilter = false; int filter = filterForAll; if ((series < 0) || (numOutLayers < 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 outlayers 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(); numOutLayers = (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 (numOutLayers > numSelectedNotNaNImages) numOutLayers = numSelectedNotNaNImages; int[] indices = new int[numOutLayers]; 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("removeOutLayers(): 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("removeOutLayers(): availableImages.length=" + availableImages.length + " numSelectedNotNaNImages=" + numSelectedNotNaNImages); for (int n = 0; n < numOutLayers; 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("removeOutLayers(): 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 " + numOutLayers + " worst images:"); for (int n = 0; n < indices.length; n++) { String msg = n + " (" + indices[n] + " / " + this.fittingStrategy.distortionCalibrationData.gIP[indices[n]].setNumber + "): " + 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 outlayers:"); 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 removeOutLayerSets(int numOutLayers) { boolean removeEmptySets = false; if (numOutLayers < 0) { GenericDialog gd = new GenericDialog("Select sets to process"); gd.addNumericField("Series number (<0 - all images)", -1, 0); gd.addNumericField("Number of outlayers 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(); numOutLayers = (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 (numOutLayers > numSelectedSets) numOutLayers = numSelectedSets; int[] indices = new int[numOutLayers]; 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("removeOutLayerSets(): 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("removeOutLayerSets(): availableSets.length=" + availableSets.length + " numSelectedSets=" + numSelectedSets); for (int n = 0; n < numOutLayers; 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("removeOutLayerSets(): 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 " + numOutLayers + " 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 outlayers:"); 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 + " " + 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 removeOutLayersJunk(int series, int numOutLayers) { int numSeries = fittingStrategy.getNumSeries(); if ((series < 0) || (numOutLayers < 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 outlayers to show", 10, 0); gd.showDialog(); if (gd.wasCanceled()) return false; this.seriesNumber = (int) gd.getNextNumber(); numOutLayers = (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 (numOutLayers > numSelectedImages) numOutLayers = numSelectedImages; int[] indices = new int[numOutLayers]; int[] indicesSelected = new int[numOutLayers]; boolean[] availableImages = new boolean[numSelectedImages]; for (int i = 0; i < availableImages.length; i++) availableImages[i] = true; for (int n = 0; n < numOutLayers; 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 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():"); } calcInterParamers(this.lensDistortionParameters, 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 (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.parameterDescriptions.length; parNumber++) { sb.append(fittingStrategy.distortionCalibrationData.parameterDescriptions[parNumber][0] + "\t" + fittingStrategy.distortionCalibrationData.parameterDescriptions[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 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() { 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() { 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 > 1) { 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.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" }; int decimate = distortionCalibrationData.eyesisCameraParameters.decimateMasks; int sWidth = (distortionCalibrationData.eyesisCameraParameters.sensorWidth - 1) / decimate + 1; int sHeight = (distortionCalibrationData.eyesisCameraParameters.sensorHeight - 1) / decimate + 1; if (this.refineParameters.showUnfilteredCorrection) { for (int numChn = 0; numChn < sensorXYCorr.length; numChn++) if (sensorXYCorr[numChn] != null) { this.SDFA_INSTANCE.showArrays(sensorXYCorr[numChn], sWidth, sHeight, true, "chn_" + numChn + "_extra_correction", titles); } } 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) { 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) 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) { 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) { 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) { 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) { this.SDFA_INSTANCE.showArrays(sensorXYCorr[numChn], sWidth, sHeight, true, "Cumulative_chn_" + numChn + "_corrections", titles); } } if (result) { // updateCameraParametersFromCalculated(); // 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; // System.out.println("applySensorCorrection(): in6="+in6+" indxR="+indxR+" indxG="+indxG+" indxB="+indxB); 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=2;n<numLayers;n++){ 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(CalibrationHardwareInterface.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(camerasInterface, // to save channel map title, channelPath, chNum, emptyOK); } } public ImagePlus saveDistortionAsImageStack(CalibrationHardwareInterface.CamerasInterface camerasInterface, // to save channel map String title, String path, int numSensor, boolean emptyOK) { ImagePlus imp = getDistortionAsImageStack(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(CalibrationHardwareInterface.CamerasInterface camerasInterface, // to save channel map String title, int numSensor, boolean emptyOK) { 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 = this.pixelCorrectionWidth / this.pixelCorrectionDecimation; int height = this.pixelCorrectionHeight / this.pixelCorrectionDecimation; // 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 (fittingStrategy.distortionCalibrationData.sensorMasks.length <= numSensor) return null; // no data if ((fittingStrategy.distortionCalibrationData.sensorMasks != null) && (fittingStrategy.distortionCalibrationData.sensorMasks[numSensor] != null)) { mask = fittingStrategy.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 = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[stationNumber][numSensor]; double entrancePupilForward = fittingStrategy.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", "" + this.pixelCorrectionWidth); imp.setProperty("pixelCorrectionHeight", "" + this.pixelCorrectionHeight); imp.setProperty("pixelCorrectionDecimation", "" + this.pixelCorrectionDecimation); 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("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("height", "" + subCam.height); imp.setProperty("comment_heading", "lens heading - added to azimuth"); imp.setProperty("heading", "" + subCam.phi); 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_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"); imp.setProperty("subcamera", "" + camerasInterface.getSubCamera(numSensor)); imp.setProperty("comment_subchannel", "number of the sensor port on a subcamera (0..2)"); imp.setProperty("subchannel", "" + camerasInterface.getSubChannel(numSensor)); 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"); 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(String path, boolean overwriteExtrinsic) { int indexPeriod = path.indexOf('.', path.lastIndexOf(Prefs.getFileSeparator())); int numSubCameras = fittingStrategy.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(channelPath, chNum, false, overwriteExtrinsic); } catch (Exception e) { System.out.println("setDistortionFromImageStack(): " + e.toString()); e.printStackTrace(); } } } public void setDistortionFromImageStack(String path, int numSensor, boolean reportProblems, boolean overwriteExtrinsic) { 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(imp, numSensor, overwriteExtrinsic); 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(ImagePlus imp, int numSensor, boolean overwriteExtrinsic) { // 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 = fittingStrategy.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 = fittingStrategy.distortionCalibrationData.eyesisCameraParameters; this.pixelCorrectionWidth = Integer.parseInt((String) imp.getProperty("pixelCorrectionWidth")); this.pixelCorrectionHeight = Integer.parseInt((String) imp.getProperty("pixelCorrectionHeight")); this.pixelCorrectionDecimation = Integer.parseInt((String) imp.getProperty("pixelCorrectionDecimation")); if ((this.fittingStrategy != null) && (this.fittingStrategy.distortionCalibrationData != null) && (this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters != null)) { this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.decimateMasks = this.pixelCorrectionDecimation; this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorWidth = this.pixelCorrectionWidth; this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.sensorHeight = this.pixelCorrectionHeight; } for (int stationNumber = 0; stationNumber < fittingStrategy.distortionCalibrationData.eyesisCameraParameters.numStations; stationNumber++) { subCam = fittingStrategy.distortionCalibrationData.eyesisCameraParameters.eyesisSubCameras[stationNumber][numSensor]; 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")); // 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; } } for (int imgNum = 0; imgNum < fittingStrategy.distortionCalibrationData.getNumImages(); imgNum++) { int imageSubCam = fittingStrategy.distortionCalibrationData.getImageSubcamera(imgNum); int stationNumber = fittingStrategy.distortionCalibrationData.getImageStation(imgNum); if (imageSubCam == numSensor) { // vector from the data we just set double[] parVector = fittingStrategy.distortionCalibrationData.eyesisCameraParameters .getParametersVector(stationNumber, imageSubCam); if (overwriteExtrinsic) fittingStrategy.distortionCalibrationData.setSubcameraParameters(parVector, imgNum); else fittingStrategy.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 (this.fittingStrategy.distortionCalibrationData.sensorMasks == null) { this.fittingStrategy.distortionCalibrationData.sensorMasks = new double[numSubCameras][]; for (int i = 0; i < this.fittingStrategy.distortionCalibrationData.sensorMasks.length; i++) this.fittingStrategy.distortionCalibrationData.sensorMasks[i] = null; // System.out.println("setDistortionFromImageStack(): created this.fittingStrategy.distortionCalibrationData.sensorMasks["+numSubCameras+"] of null-s" ); } if (numSensor >= this.fittingStrategy.distortionCalibrationData.sensorMasks.length) { // increase number of elements double[][] tmp = this.fittingStrategy.distortionCalibrationData.sensorMasks; this.fittingStrategy.distortionCalibrationData.sensorMasks = new double[numSensor + 1][]; for (int i = 0; i < this.fittingStrategy.distortionCalibrationData.sensorMasks.length; i++) if (i < tmp.length) this.fittingStrategy.distortionCalibrationData.sensorMasks[i] = tmp[i]; else this.fittingStrategy.distortionCalibrationData.sensorMasks[i] = null; } if (this.fittingStrategy.distortionCalibrationData.sensorMasks[numSensor] == null) { this.fittingStrategy.distortionCalibrationData.sensorMasks[numSensor] = new double[pixels[corrMask].length]; // System.out.println("setDistortionFromImageStack(): created this.fittingStrategy.distortionCalibrationData.sensorMasks["+numSensor+"] of ["+pixels[corrMask].length+"]" ); } for (int i = 0; i < this.fittingStrategy.distortionCalibrationData.sensorMasks[numSensor].length; i++) // null pointer this.fittingStrategy.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; // int indexRGB=3; 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 sensorCorrWidth= (this.pixelCorrectionWidth-1)/this.pixelCorrectionDecimation+1; int maxChannel = 0; // int numVierws= this.patternParameters.getNumViews(); 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(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 = (this.pixelCorrectionWidth - 1) / this.pixelCorrectionDecimation + 1; int indexXY = ((int) Math.floor(px / this.pixelCorrectionDecimation)) + ((int) Math.floor(py / this.pixelCorrectionDecimation)) * 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 */ 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], this.pixelCorrectionWidth, 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; } public boolean LevenbergMarquardt(boolean openDialog) { 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)); } 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 (!cont) { if (this.saveSeries) { 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 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 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() { 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(); calcInterParamers(lensDistortionParameters, 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 (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() { 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(); calcInterParamers(this.lensDistortionParameters, 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(boolean[] whichExtrapolate, double[][] fieldXY, double[] sMask, double alphaThreshold, double nsigma, double ksigma) { 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[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 decimate = distortionCalibrationData.eyesisCameraParameters.decimateMasks; int sWidth = (distortionCalibrationData.eyesisCameraParameters.sensorWidth - 1) / decimate + 1; int sHeight = (distortionCalibrationData.eyesisCameraParameters.sensorHeight - 1) / decimate + 1; 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 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; } public double[][][] calculateSensorXYCorrOld(DistortionCalibrationData distortionCalibrationData, boolean showIndividual) { int decimate = distortionCalibrationData.eyesisCameraParameters.decimateMasks; int sWidth = (distortionCalibrationData.eyesisCameraParameters.sensorWidth - 1) / decimate + 1; int sHeight = (distortionCalibrationData.eyesisCameraParameters.sensorHeight - 1) / decimate + 1; int numChannels = distortionCalibrationData.getNumChannels(); // number of used channels int width = getGridWidth(); int height = getGridHeight(); int[] uvInc = { 0, 1, width, width + 1 }; // four corners as vu index int[][] cycles = { // counter-clockwise corners bounding the area (only orthogonal sides?) // {0,2,3,1}, // { 1, 0, 2 }, { 2, 3, 1 }, { 0, 2, 3 }, { 3, 1, 0 } }; // prepare grid correction arrays // if ((decimate!=) // public double [][][] pixelCorrection=null; // public int pixelCorrectionDecimation=1; 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; 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 // initialize this array if it is needed, leave unused null if (gridPCorr[chnNum] == null) { gridPCorr[chnNum] = new double[4][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[4][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[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 double[] mask = fittingStrategy.distortionCalibrationData.calculateImageGridMask(imgNum); 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)]; 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[2][vu]= this.currentfX[2*(index+i)]; // calculated pixel x // imgData[3][vu]= this.currentfX[2*(index+i)+1];// calculated pixel y // next line not needed? - no, let it stay, 0.0 / >0 will be needed // TODO: Use grid alpha imgData[4][vu] = fittingStrategy.distortionCalibrationData.getMask(mask, imgData[2][vu], imgData[3][vu]); // will decimate } // 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; } } 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); 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] }; if (this.debugLevel > 3) { System.out.println("corr=" + IJ.d2s(corr[0], 3) + " " + IJ.d2s(corr[1], 3) + " " + IJ.d2s(corr[2], 3)); } 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 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 // 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]; thisPCorr[1][i] /= thisPCorr[3][i]; thisPCorr[2][i] /= thisPCorr[3][i]; } if (showIndividual) { String[] titles = { "dPx", "dPy", "alpha", "Multiple" }; 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 } } for (int chnNum = 0; chnNum < gridPCorr.length; chnNum++) { 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]; } } 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.parameterDescriptions.length]; parameterUnits = new String[fittingStrategy.distortionCalibrationData.parameterDescriptions.length]; for (int i = 0; i < parameterNames.length; i++) { // TODO: move to DdistortionCalibrationData methods() parameterNames[i] = fittingStrategy.distortionCalibrationData.parameterDescriptions[i][0]; parameterUnits[i] = fittingStrategy.distortionCalibrationData.parameterDescriptions[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", .01, 5); if (debugDerivatives) gd.addCheckbox("Show inter-parameter derivatives matrix", true); 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(); 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" }; String[] titlesNoDebug = { "dX-derivative", "dY-derivative", "abs-derivative" }; String[] titles = (d_delta == null) ? titlesNoDebug : titlesDebug; double[] d_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]; // 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] = d_delta[2 * (index + i)]; imgData[6][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)); } LensDistortionParameters ldp = this.lensDistortionParameters.clone(); // 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; } /** * Calculate/set this.lensDistortionParameters and this.interParameterDerivatives * UPDATE - Modifies lensDistortionParameters, not "this" formulti-threaded * @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 */ public void calcInterParamers(LensDistortionParameters lensDistortionParameters, double[][] interParameterDerivatives, //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) if null - just values, no derivatives double[] parVect, boolean[] mask // calculate only selected derivatives (all parVect values are still // boolean calculateDerivatives // calculate this.interParameterDerivatives -derivatives array (false - just this.values) ) { boolean calculateDerivatives = (interParameterDerivatives != null); // calculate this.interParameterDerivatives -derivatives array (false - just this.values) // change meaning of goniometerHorizontal (tripod vertical) and goniometerAxial (tripod horizontal) boolean isTripod = this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.isTripod; double azimuth = parVect[0]; double radius = parVect[1]; double height = parVect[2]; double phi = parVect[3]; double theta = parVect[4]; double psi = parVect[5]; double goniometerHorizontal = parVect[6]; double goniometerAxial = parVect[7]; double interAxisDistance = parVect[8]; double interAxisAngle = parVect[9]; double horAxisErrPhi = parVect[10]; double horAxisErrPsi = parVect[11]; double entrancePupilForward = parVect[12]; double centerAboveHorizontal = parVect[13]; double GXYZ0 = parVect[14]; double GXYZ1 = parVect[15]; double GXYZ2 = parVect[16]; double cPS = Math.cos(psi * Math.PI / 180); // subCam.psi double sPS = Math.sin(psi * Math.PI / 180); // subCam.psi double cTH = Math.cos(theta * Math.PI / 180); // subCam.theta double sTH = Math.sin(theta * Math.PI / 180); // subCam.theta double cAZP = Math.cos((azimuth + phi) * Math.PI / 180); //subCam.azimuth+subCam.phi double sAZP = Math.sin((azimuth + phi) * Math.PI / 180); //subCam.azimuth+subCam.phi double cAZ = Math.cos(azimuth * Math.PI / 180); //subCam.azimuth double sAZ = Math.sin(azimuth * Math.PI / 180); //subCam.azimuth double cGA = Math.cos(goniometerAxial * Math.PI / 180); //eyesisCameraParameters.goniometerAxial double sGA = Math.sin(goniometerAxial * Math.PI / 180); //eyesisCameraParameters.goniometerAxial double cGH = Math.cos(goniometerHorizontal * Math.PI / 180); //eyesisCameraParameters.goniometerHorizontal double sGH = Math.sin(goniometerHorizontal * Math.PI / 180); //eyesisCameraParameters.goniometerHorizontal double cGIAA = Math.cos(interAxisAngle * Math.PI / 180); // eyesisCameraParameters.interAxisAngle double sGIAA = Math.sin(interAxisAngle * Math.PI / 180); //eyesisCameraParameters.interAxisAngle double cHAEPH = Math.cos(horAxisErrPhi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPhi double sHAEPH = Math.sin(horAxisErrPhi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPhi double cHAEPS = Math.cos(horAxisErrPsi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPsi double sHAEPS = Math.sin(horAxisErrPsi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPsi\ /* 0) Translate by distance to entrance pupil (lens center) | Xc0 | | 0 | |Xc| | Yc0 | = | 0 | + |Yc| | Zc0 | | entrancePupilForward | |Zc| */ double[][] aT0 = { { 0.0 }, { 0.0 }, { entrancePupilForward } }; Matrix T0 = new Matrix(aT0); /* Converting from the sub-camera coordinates to the target coordinates 1) rotate by -psi around CZ: Vc1= R1*Vc | Xc1 | | cos(psi) sin(psi) 0 | |Xc0| | Yc1 | = |-sin(psi) cos(psi) 0 | * |Yc0| | Zc1 | | 0 0 1 | |Zc0| */ double[][] aR1 = { { cPS, sPS, 0.0 }, { -sPS, cPS, 0.0 }, { 0.0, 0.0, 1.0 } }; Matrix R1 = new Matrix(aR1); /* 2) rotate by - theta around C1X:Vc2= R2*Vc1 | Xc2 | | 1 0 0 | |Xc1| | Yc2 | = | 0 cos(theta) sin(theta) | * |Yc1| | Zc2 | | 0 -sin(theta) cos(theta) | |Zc1| */ double[][] aR2 = { { 1.0, 0.0, 0.0 }, { 0.0, cTH, sTH }, { 0.0, -sTH, cTH } }; Matrix R2 = new Matrix(aR2); /* 3) rotate by -(azimuth+phi) around C2Y:Vc3= R3*Vc2 | Xc3 | | cos(azimuth+phi) 0 sin(azimuth+phi) | |Xc2| | Yc3 | = | 0 1 0 | * |Yc2| | Zc3 | | -sin(azimuth+phi) 0 cos(azimuth+phi) | |Zc2| */ double[][] aR3 = { { cAZP, 0.0, sAZP }, { 0.0, 1.0, 0.0 }, { -sAZP, 0.0, cAZP } }; Matrix R3 = new Matrix(aR3); /* 4) Now axes are aligned, just translate to get to eyesis coordinates: Vey= T1+Vc3 | Xey | | r * sin (azimuth) | |Xc3| | Yey | = | height+centerAboveHorizontal | + |Yc3| | Zey | | r * cos (azimuth) | |Zc3| */ double[][] aT1 = { { radius * sAZ }, { (height + centerAboveHorizontal) }, { radius * cAZ } }; // {{subCam.radius*sAZ},{subCam.height},{subCam.radius*cAZ}}; Matrix T1 = new Matrix(aT1); /** 5) rotate around moving goniometer axis, by (-goniometerAxial) - same as around EY: Vgm1=R4*Vey | Xgm1 | | 1 0 0 | |Xey| | Ygm1 | = | 0 cos(goniometerAxial) sin(goniometerAxial) | * |Yey| | Zgm1 | | 0 -sin(goniometerAxial) cos(goniometerAxial) | |Zey| */ double[][] aR4_tripod = { { 1.0, 0.0, 0.0 }, { 0.0, cGA, sGA }, { 0.0, -sGA, cGA } }; /* 5) rotate around moving goniometer axis, by (-goniometerAxial) - same as around EY: Vgm1=R4*Vey | Xgm1 | | cos(goniometerAxial) 0 sin(goniometerAxial) | |Xey| | Ygm1 | = | 0 1 0 | * |Yey| | Zgm1 | |-sin(goniometerAxial) 0 cos(goniometerAxial) | |Zey| */ double[][] aR4_goniometer = { { cGA, 0.0, sGA }, { 0.0, 1.0, 0.0 }, { -sGA, 0.0, cGA } }; Matrix R4 = new Matrix(isTripod ? aR4_tripod : aR4_goniometer); /* 6) move to the goniometer horizontal axis:Vgm2=T2+Vgm1 | Xgm2 | | 0 | |Xgm1| | Ygm2 | = | 0 | + |Ygm1| | Zgm2 | | interAxisDistance | |Zgm1| */ double[][] aT2 = { { 0.0 }, { 0.0 }, { interAxisDistance } }; //eyesisCameraParameters.interAxisDistance Matrix T2 = new Matrix(aT2); /* 7) rotate around Zgm2 by -interAxisAngle, so Xgm3 axis is the same as horizontal goniometer axis: Vgm3=R5*Vgm2 | Xgm3 | | cos(interAxisAngle) sin(interAxisAngle) 0 | |Xgm2| | Ygm3 | = |-sin(interAxisAngle) cos(interAxisAngle) 0 | * |Ygm2| | Zgm3 | | 0 0 1 | |Zgm2| */ double[][] aR5 = { { cGIAA, sGIAA, 0.0 }, { -sGIAA, cGIAA, 0.0 }, { 0.0, 0.0, 1.0 } }; Matrix R5 = new Matrix(aR5); /** 8) rotate around goniometer horizontal axis (Xgm3) by -goniometerHorizontal: Vgm4=R6*Vgm3 | Xgm4 | | cos(goniometerHorizontal) 0 sin(goniometerHorizontal) | |Xgm3| | Ygm4 | = | 0 1 0 | * |Ygm3| | Zgm4 | | -sin(goniometerHorizontal) 0 cos(goniometerHorizontal) | |Zgm3| */ double[][] aR6_tripod = { { cGH, 0.0, sGH }, { 0.0, 1.0, 0.0 }, { -sGH, 0.0, cGH } }; /* 8) rotate around goniometer horizontal axis (Xgm3) by -goniometerHorizontal: Vgm4=R6*Vgm3 | Xgm4 | | 1 0 0 | |Xgm3| | Ygm4 | = | 0 cos(goniometerHorizontal) sin(goniometerHorizontal) | * |Ygm3| | Zgm4 | | 0 -sin(goniometerHorizontal) cos(goniometerHorizontal) | |Zgm3| */ double[][] aR6_goniometer = { { 1.0, 0.0, 0.0 }, { 0.0, cGH, sGH }, { 0.0, -sGH, cGH } }; Matrix R6 = new Matrix(isTripod ? aR6_tripod : aR6_goniometer); /* 9) Correct roll error of the goniometer horizontal axis - rotate by -horAxisErrPsi around Zgm4: Vgm5=R7*Vgm4 | Xgm5 | | cos(horAxisErrPsi) sin(horAxisErrPsi) 0 | |Xgm4| | Ygm5 | = |-sin(horAxisErrPsi) cos(horAxisErrPsi) 0 | * |Ygm4| | Zgm5 | | 0 0 1 | |Zgm4| */ double[][] aR7 = { { cHAEPS, sHAEPS, 0.0 }, { -sHAEPS, cHAEPS, 0.0 }, { 0.0, 0.0, 1.0 } }; Matrix R7 = new Matrix(aR7); /* 10) Correct azimuth error of the goniometer hoirizontal axis - rotate by -horAxisErrPhi around Ygm5: Vgm6=R8*Vgm5 | Xgm6 | | cos(horAxisErrPhi) 0 sin(horAxisErrPhi) | |Xgm5| | Ygm6 | = | 0 1 0 | * |Ygm5| | Zgm6 | |-sin(horAxisErrPhi) 0 cos(horAxisErrPhi) | |Zgm5| For Tripod - rotate around X-axis (like theta) | Xgm6 | | 1 0 0 | |Xgm5| | Ygm6 | = | 0 cos(horAxisErrPhi) sin(horAxisErrPhi) | * |Ygm5| | Zgm6 | | 0 -sin(horAxisErrPhi) cos(horAxisErrPhi) | |Zgm5| */ double[][] aR8_tripod = { { 1.0, 0.0, 0.0 }, { 0.0, cHAEPH, sHAEPH }, { 0.0, -sHAEPH, cHAEPH } }; double[][] aR8_goniometer = { { cHAEPH, 0.0, sHAEPH }, { 0.0, 1.0, 0.0 }, { -sHAEPH, 0.0, cHAEPH } }; Matrix R8 = new Matrix(isTripod ? aR8_tripod : aR8_goniometer); /* 11) translate to the target zero point: Vt= T3+Vgm6 | Xt | | GXYZ[0] | |Xgm6| | Yt | = |-GXYZ[1] | + |Ygm6| // Y - up positive | Zt | |-GXYZ[2] | |Zgm6| // Z - away positive */ // double [][] aT3={{parVect[12]},{-parVect[13]},{-parVect[14]}};//{{eyesisCameraParameters.GXYZ[0]},{eyesisCameraParameters.GXYZ[1]},{eyesisCameraParameters.GXYZ[2]}}; double[][] aT3 = { { GXYZ0 }, { -GXYZ1 }, { -GXYZ2 } };//{{eyesisCameraParameters.GXYZ[0]},{eyesisCameraParameters.GXYZ[1]},{eyesisCameraParameters.GXYZ[2]}}; // double [][] aT3={{parVect[12]},{ parVect[13]},{-parVect[14]}};//{{eyesisCameraParameters.GXYZ[0]},{eyesisCameraParameters.GXYZ[1]},{eyesisCameraParameters.GXYZ[2]}}; Matrix T3 = new Matrix(aT3); // MA=R8*R7*R6*R5*R4*R3*R2*R1; // MB=T3+(R8*R7*R6*R5*(T2+R4*T1)); - old // MB=T3+(R8*R7*R6*R5*(T2+R4*(T1+R3*R2*R1*T0))); Matrix MA = R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); // Matrix MB=T3.plus(R8.times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1))))))); Matrix MB = T3.plus(R8 .times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1.plus(R3.times(R2.times(R1.times(T0))))))))))); if (this.debugLevel > 2) { System.out.println("MA:"); MA.print(10, 5); System.out.println("MB:"); MB.print(10, 5); System.out.println("T3:"); T3.print(10, 5); // System.out.println("interParameterDerivatives[0]="+sprintfArray(interParameterDerivatives[0])); } double[] extrinsicParams = parametersFromMAMB(MA, MB); // all after 6 are 0; lensDistortionParameters.distance = extrinsicParams[2]; lensDistortionParameters.x0 = extrinsicParams[0]; lensDistortionParameters.y0 = extrinsicParams[1]; lensDistortionParameters.z0 = 0.0; // used here lensDistortionParameters.pitch = extrinsicParams[4]; lensDistortionParameters.yaw = extrinsicParams[3]; lensDistortionParameters.roll = extrinsicParams[5]; // lensDistortionParameters.focalLength=parVect[15]; //subCam.focalLength; // lensDistortionParameters.px0=parVect[16]; //subCam.px0; // lensDistortionParameters.py0=parVect[17]; //subCam.py0; // lensDistortionParameters.distortionA5=parVect[18]; //subCam.distortion5; // lensDistortionParameters.distortionA=parVect[19]; //subCam.distortionA; // lensDistortionParameters.distortionB=parVect[20]; //subCam.distortionB; // lensDistortionParameters.distortionC=parVect[21]; //subCam.distortionC; lensDistortionParameters.focalLength = parVect[17]; //subCam.focalLength; lensDistortionParameters.px0 = parVect[18]; //subCam.px0; lensDistortionParameters.py0 = parVect[19]; //subCam.py0; lensDistortionParameters.distortionA8 = parVect[20]; //subCam.distortion5; lensDistortionParameters.distortionA7 = parVect[21]; //subCam.distortion5; lensDistortionParameters.distortionA6 = parVect[22]; //subCam.distortion5; lensDistortionParameters.distortionA5 = parVect[23]; //subCam.distortion5; lensDistortionParameters.distortionA = parVect[24]; //subCam.distortionA; lensDistortionParameters.distortionB = parVect[25]; //subCam.distortionB; lensDistortionParameters.distortionC = parVect[26]; //subCam.distortionC; lensDistortionParameters.recalcCommons(); if (this.debugLevel > 2) { System.out.println("lensDistortionParameters.recalcCommons()"); System.out.println("lensDistortionParameters.distance=" + IJ.d2s(lensDistortionParameters.distance, 3)); System.out.println("lensDistortionParameters.x0=" + IJ.d2s(lensDistortionParameters.x0, 3)); System.out.println("lensDistortionParameters.y0=" + IJ.d2s(lensDistortionParameters.y0, 3)); System.out.println("lensDistortionParameters.z0=" + IJ.d2s(lensDistortionParameters.z0, 3)); System.out.println("lensDistortionParameters.pitch=" + IJ.d2s(lensDistortionParameters.pitch, 3)); System.out.println("lensDistortionParameters.yaw=" + IJ.d2s(lensDistortionParameters.yaw, 3)); System.out.println("lensDistortionParameters.roll=" + IJ.d2s(lensDistortionParameters.roll, 3)); System.out.println( "lensDistortionParameters.focalLength=" + IJ.d2s(lensDistortionParameters.focalLength, 3)); System.out.println("lensDistortionParameters.px0=" + IJ.d2s(lensDistortionParameters.px0, 3)); System.out.println("lensDistortionParameters.py0=" + IJ.d2s(lensDistortionParameters.py0, 3)); System.out.println( "lensDistortionParameters.distortionA8=" + IJ.d2s(lensDistortionParameters.distortionA8, 4)); System.out.println( "lensDistortionParameters.distortionA7=" + IJ.d2s(lensDistortionParameters.distortionA7, 4)); System.out.println( "lensDistortionParameters.distortionA6=" + IJ.d2s(lensDistortionParameters.distortionA6, 4)); System.out.println( "lensDistortionParameters.distortionA5=" + IJ.d2s(lensDistortionParameters.distortionA5, 4)); System.out.println( "lensDistortionParameters.distortionA=" + IJ.d2s(lensDistortionParameters.distortionA, 4)); System.out.println( "lensDistortionParameters.distortionB=" + IJ.d2s(lensDistortionParameters.distortionB, 4)); System.out.println( "lensDistortionParameters.distortionC=" + IJ.d2s(lensDistortionParameters.distortionC, 4)); } if (!calculateDerivatives) return; /* Calculate all derivativs as a matrix. * Input parameters (columns): 0 public double azimuth; // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 1 public double radius; // mm, distance from the rotation axis 2 public double height; // mm, up (was downwards?) - from the origin point 3 public double phi; // degrees, optical axis from azimuth/r vector, clockwise 4 public double theta; // degrees, optical axis from the eyesis horizon, positive - up 5 public double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 6 public double goniometerHorizontal; // goniometer rotation around "horizontal" axis (tilting from the target - positive) 7 public double goniometerAxial; // goniometer rotation around Eyesis axis (clockwise in plan - positive 8 public double interAxisDistance; // distance in mm between two goniometer axes 9 public double interAxisAngle; // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated 10 public double horAxisErrPhi; // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) 11 public double horAxisErrPsi; // angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) Two new parameters 12 public double entrancePupilForward; // common to all lenses - distance from the sensor to the lens entrance pupil 13 public double centerAboveHorizontal; // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to 14(12) x public double [] GXYZ=null; // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system 15(13) y 16(14) z 17(15) public double focalLength=4.5; 18(16) public double px0=1296.0; // center of the lens on the sensor, pixels 19(17) public double py0=968.0; // center of the lens on the sensor, pixels 20(18) public double distortionA8=0.0; // r^5 (normalized to focal length or to sensor half width?) 21(19) public double distortionA7=0.0; // r^5 (normalized to focal length or to sensor half width?) 22(20) public double distortionA6=0.0; // r^5 (normalized to focal length or to sensor half width?) 23(21) public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 24(22) public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) 25(23) public double distortionB=0.0; // r^3 26(24) public double distortionC=0.0; // r^2 * Output parameters (rows): 0 public double x0=0; // lens axis from pattern center, mm (to the right) 1 public double y0=0; // lens axis from pattern center, mm (down) 2 public double distance=2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm 3 public double yaw=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top 4 public double pitch=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up 5 public double roll=0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise 6 public double focalLength=4.5; 7 public double px0=1296.0; // center of the lens on the sensor, pixels 8 public double py0=968.0; // center of the lens on the sensor, pixels public double distortionRadius= 2.8512; // mm - half width of the sensor 9 public double distortionA8=0.0; // r^8 (normalized to focal length or to sensor half width?) 10 public double distortionA7=0.0; // r^7 (normalized to focal length or to sensor half width?) 11 public double distortionA6=0.0; // r^6 (normalized to focal length or to sensor half width?) 12 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 13 public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) 14 public double distortionB=0.0; // r^3 15 public double distortionC=0.0; // r^2 */ // interParameterDerivatives=new double[getNumInputs()][]; //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) //calculate dMA_azimuth //calculate dMB_azimuth /* // MA=R8*R7*R6*R5*R4*R3*R2*R1; // MB=T3+(R8*R7*R6*R5*(T2+R4*T1)); - old // MB=T3+(R8*R7*R6*R5*(T2+R4*(T1+R3*R2*R1*T0))); Matrix MA=R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix MB=T3.plus(R8.times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1))))))); old Matrix MB=T3.plus(R8.times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1.plus(R3.times(R2.times(R1.times(T0)))) ))))))); 3) rotate by -(azimuth+phi) around C2Y:Vc3= R3*Vc2 | Xc3 | | cos(azimuth+phi) 0 sin(azimuth+phi) | |Xc2| | Yc3 | = | 0 1 0 | * |Yc2| | Zc3 | | -sin(azimuth+phi) 0 cos(azimuth+phi) | |Zc2| double [][] aR3={{cAZP,0.0,sAZP},{0.0,1.0,0.0},{-sAZP,0.0,cAZP}}; Matrix R3=new Matrix(aR3); 4) Now axes are aligned, just translate to get to eyesis coordinates: Vey= T1+Vc3 | Xey | | r * sin (azimuth) | |Xc3| | Yey | = | height | + |Yc3| | Zey | | r * cos (azimuth) | |Zc3| double [][] aT1={{subCam.radius*sAZ},{subCam.height},{subCam.radius*cAZ}}; Matrix T1=new Matrix(aT1); Matrix // Make a function MA, MB - >parameters (column) - reuse it above and for each interParameterDerivatives row Matrix dMA_azimuth=R8*R7*R6*R5*R4*dR3_azimuth*R2*R1; Matrix dMB_azimuth=T3+(R8*R7*R6*R5*(R4*dT1_azimuth)); Use extrinsicParams=parametersFromMAMB(dMA_azimuth,dMB_azimuth); if (this.debugLevel>3) { System.out.println("calculateFxAndJacobian->calcPartialDerivatives("+IJ.d2s(targetXYZ[fullIndex][0],2)+","+ IJ.d2s(targetXYZ[fullIndex][1],2)+","+ IJ.d2s(targetXYZ[fullIndex][2],2)+" ("+calcJacobian+") -> "+ IJ.d2s(derivatives14[0][0],2)+"/"+IJ.d2s(derivatives14[0][1],2)); } Which parameters affect which matrices R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 || T0 | T1 | T2 | T3 | 0 public double azimuth; // | | + | | | | | || | + | | | 1 public double radius; // | | | | | | | || | + | | | 2 public double height; // | | | | | | | || | + | | | 3 public double phi; // | | + | | | | | || | | | | 4 public double theta; // | + | | | | | | || | | | | 5 public double psi; // + | | | | | | | || | | | | 6 public double goniometerHorizontal; // | | | | | + | | || | | | | 7 public double goniometerAxial; // | | | + | | | | || | | | | 8 public double interAxisDistance; // | | | | | | | || | | + | | 9 public double interAxisAngle; // | | | | + | | | || | | | | 10 public double horAxisErrPhi; // | | | | | | | + || | | | | 11 public double horAxisErrPsi; // | | | | | | + | || | | | | 12 public double entrancePupilForward // | | | | | | | || + | | | | 13 public double centerAboveHorizontal // | | | | | | | || | + | | | 14 x public double [] GXYZ=null; // | | | | | | | || | | | + | 15 y // | | | | | | | || | | | + | 16 z // | | | | | | | || | | | + | */ // Below can be optimized with common intermediate results if (this.debugLevel > 2) { for (int i = 0; i < parVect.length; i++) { System.out.println("calcInterParamers(): parVect[" + i + "]=" + parVect[i]); } } //0 public double azimuth; // azimuth of the lens entrance pupil center, degrees, clockwise looking from top if (mask[0]) { double[][] adR3_azimuth = { { -sAZP, 0.0, cAZP }, { 0.0, 0.0, 0.0 }, { -cAZP, 0.0, -sAZP } }; Matrix dR3_azimuth = new Matrix(adR3_azimuth); // double [][] adT1_azimuth={{radius*cAZ},{height},{-radius*sAZ}}; //{{subCam.radius*cAZ},{subCam.height},{-subCam.radius*sAZ}} double[][] adT1_azimuth = { { radius * cAZ }, { 0.0 }, { -radius * sAZ } }; //{{subCam.radius*cAZ},{subCam.height},{-subCam.radius*sAZ}} Matrix dT1_azimuth = new Matrix(adT1_azimuth); Matrix dMA_azimuth = R8.times(R7.times(R6.times(R5.times(R4.times(dR3_azimuth.times(R2.times(R1))))))); Matrix dMB0_azimuth = R8.times(R7.times(R6.times(R5.times(R4.times(dT1_azimuth))))); Matrix dMB_azimuth = dMB0_azimuth.plus(dMA_azimuth.times(T0)); // new term interParameterDerivatives[0] = d_parametersFromMAMB(dMA_azimuth, dMB_azimuth, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_azimuth:"); dMA_azimuth.print(10, 5); System.out.println("dMB_azimuth:"); dMB_azimuth.print(10, 5); System.out.println("interParameterDerivatives[0]=" + sprintfArray(interParameterDerivatives[0])); } } else interParameterDerivatives[0] = null; //1 public double radius; // mm, distance from the rotation axis if (mask[1]) { double[][] adT1_radius = { { sAZ }, { 0.0 }, { cAZ } }; //{{subCam.radius*sAZ},{0.0},{subCam.radius*cAZ}} Matrix dT1_radius = new Matrix(adT1_radius); Matrix dMA_radius = new Matrix(3, 3, 0.0); Matrix dMB_radius = R8.times(R7.times(R6.times(R5.times(R4.times(dT1_radius))))); interParameterDerivatives[1] = d_parametersFromMAMB(dMA_radius, dMB_radius, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_radius:"); dMA_radius.print(10, 5); System.out.println("dMB_radius:"); dMB_radius.print(10, 5); System.out.println("interParameterDerivatives[1]=" + sprintfArray(interParameterDerivatives[1])); } } else interParameterDerivatives[1] = null; //2 public double height; // mm, downwards - from the origin point if (mask[2]) { double[][] adT1_height = { { 0.0 }, { 1.0 }, { 0.0 } }; Matrix dT1_height = new Matrix(adT1_height); Matrix dMA_height = new Matrix(3, 3, 0.0); Matrix dMB_height = R8.times(R7.times(R6.times(R5.times(R4.times(dT1_height))))); interParameterDerivatives[2] = d_parametersFromMAMB(dMA_height, dMB_height, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_height:"); dMA_height.print(10, 5); System.out.println("dMB_height:"); dMB_height.print(10, 5); System.out.println("interParameterDerivatives[2]=" + sprintfArray(interParameterDerivatives[2])); } } else interParameterDerivatives[2] = null; //3 public double phi; // degrees, optical axis from azimuth/r vector, clockwise if (mask[3]) { double[][] adR3_phi = { { -sAZP, 0.0, cAZP }, { 0.0, 0.0, 0.0 }, { -cAZP, 0.0, -sAZP } }; // same as adR3_azimuth Matrix dR3_phi = new Matrix(adR3_phi); // same as dR3_azimuth Matrix dMA_phi = R8.times(R7.times(R6.times(R5.times(R4.times(dR3_phi.times(R2.times(R1))))))); //same as dMA_azimuth // Matrix dMB_phi=new Matrix(3,1,0.0); Matrix dMB_phi = dMA_phi.times(T0); // new term interParameterDerivatives[3] = d_parametersFromMAMB(dMA_phi, dMB_phi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_phi:"); dMA_phi.print(10, 5); System.out.println("dMB_phi:"); dMB_phi.print(10, 5); System.out.println("interParameterDerivatives[3]=" + sprintfArray(interParameterDerivatives[3])); } } else interParameterDerivatives[3] = null; //4 public double theta; // degrees, optical axis from the eyesis horizon, positive - up if (mask[4]) { double[][] adR2_theta = { { 0.0, 0.0, 0.0 }, { 0.0, -sTH, cTH }, { 0.0, -cTH, -sTH } }; Matrix dR2_theta = new Matrix(adR2_theta); Matrix dMA_theta = R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(dR2_theta.times(R1))))))); // Matrix dMB_theta=new Matrix(3,1,0.0); Matrix dMB_theta = dMA_theta.times(T0); // new term interParameterDerivatives[4] = d_parametersFromMAMB(dMA_theta, dMB_theta, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_theta:"); dMA_theta.print(10, 5); System.out.println("dMB_theta:"); dMB_theta.print(10, 5); System.out.println("interParameterDerivatives[4]=" + sprintfArray(interParameterDerivatives[4])); } } else interParameterDerivatives[4] = null; //5 public double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target if (mask[5]) { double[][] adR1_psi = { { -sPS, cPS, 0.0 }, { -cPS, -sPS, 0.0 }, { 0.0, 0.0, 0.0 } }; Matrix dR1_psi = new Matrix(adR1_psi); Matrix dMA_psi = R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(dR1_psi))))))); // Matrix dMB_psi=new Matrix(3,1,0.0); Matrix dMB_psi = dMA_psi.times(T0); // new term interParameterDerivatives[5] = d_parametersFromMAMB(dMA_psi, dMB_psi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { /* System.out.print("R1:"); R1.print(10, 5); System.out.print("dR1_psi:"); dR1_psi.print(10, 5); Matrix R82_psi=R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2)))))); System.out.print("R82_psi:"); R82_psi.print(10, 5); */ System.out.print("dMA_psi:"); dMA_psi.print(10, 5); System.out.print("dMB_psi:"); dMB_psi.print(10, 5); System.out.println("interParameterDerivatives[5]=" + sprintfArray(interParameterDerivatives[5])); } } else interParameterDerivatives[5] = null; //6 public double goniometerHorizontal; // goniometer rotation around "horizontal" axis (tilting from the target - positive) if (mask[6]) { /* define for isTripod */ double[][] adR6_goniometerHorizontal_tripod = { { -sGH, 0.0, cGH }, { 0.0, 0.0, 0.0 }, { -cGH, 0.0, -sGH } }; double[][] adR6_goniometerHorizontal_goniometer = { { 0.0, 0.0, 0.0 }, { 0.0, -sGH, cGH }, { 0.0, -cGH, -sGH } }; Matrix dR6_goniometerHorizontal = new Matrix( isTripod ? adR6_goniometerHorizontal_tripod : adR6_goniometerHorizontal_goniometer); Matrix dMA_goniometerHorizontal = R8 .times(R7.times(dR6_goniometerHorizontal.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_goniometerHorizontal = R8 .times(R7.times(dR6_goniometerHorizontal.times(R5.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[6] = d_parametersFromMAMB(dMA_goniometerHorizontal, dMB_goniometerHorizontal, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_goniometerHorizontal:"); dMA_goniometerHorizontal.print(10, 5); System.out.println("dMB_goniometerHorizontal:"); dMB_goniometerHorizontal.print(10, 5); System.out.println("interParameterDerivatives[6]=" + sprintfArray(interParameterDerivatives[6])); } } else interParameterDerivatives[6] = null; //7 public double goniometerAxial; // goniometer rotation around Eyesis axis (clockwise in plan - positive if (mask[7]) { // define for isTripod double[][] adR4_goniometerAxial_tripod = { { 0.0, 0.0, 0.0 }, { 0.0, -sGA, cGA }, { 0.0, -cGA, -sGA } }; double[][] adR4_goniometerAxial_goniometer = { { -sGA, 0.0, cGA }, { 0.0, 0.0, 0.0 }, { -cGA, 0.0, -sGA } }; Matrix dR4_goniometerAxial = new Matrix( isTripod ? adR4_goniometerAxial_tripod : adR4_goniometerAxial_goniometer); Matrix dMA_goniometerAxial = R8 .times(R7.times(R6.times(R5.times(dR4_goniometerAxial.times(R3.times(R2.times(R1))))))); Matrix dMB_goniometerAxial = R8.times(R7.times(R6.times(R5.times(dR4_goniometerAxial.times(T1))))); interParameterDerivatives[7] = d_parametersFromMAMB(dMA_goniometerAxial, dMB_goniometerAxial, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_goniometerAxial:"); dMA_goniometerAxial.print(10, 5); System.out.println("dMB_goniometerAxial:"); dMB_goniometerAxial.print(10, 5); System.out.println("interParameterDerivatives[7]=" + sprintfArray(interParameterDerivatives[7])); } } else interParameterDerivatives[7] = null; //8 public double interAxisDistance; // distance in mm between two goniometer axes if (mask[8]) { double[][] adT2_interAxisDistance = { { 0.0 }, { 0.0 }, { 1.0 } }; Matrix dT2_interAxisDistance = new Matrix(adT2_interAxisDistance); Matrix dMA_interAxisDistance = new Matrix(3, 3, 0.0); Matrix dMB_interAxisDistance = R8.times(R7.times(R6.times(R5.times(dT2_interAxisDistance)))); interParameterDerivatives[8] = d_parametersFromMAMB(dMA_interAxisDistance, dMB_interAxisDistance, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_interAxisDistance:"); dMA_interAxisDistance.print(10, 5); System.out.println("dMB_interAxisDistance:"); dMB_interAxisDistance.print(10, 5); System.out.println("interParameterDerivatives[8]=" + sprintfArray(interParameterDerivatives[8])); } } else interParameterDerivatives[8] = null; //9 public double interAxisAngle; // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated if (mask[9]) { double[][] adR5_interAxisAngle = { { -sGIAA, cGIAA, 0.0 }, { -cGIAA, -sGIAA, 0.0 }, { 0.0, 0.0, 0.0 } }; Matrix dR5_interAxisAngle = new Matrix(adR5_interAxisAngle); Matrix dMA_interAxisAngle = R8 .times(R7.times(R6.times(dR5_interAxisAngle.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_interAxisAngle = R8 .times(R7.times(R6.times(dR5_interAxisAngle.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[9] = d_parametersFromMAMB(dMA_interAxisAngle, dMB_interAxisAngle, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_interAxisAngle:"); dMA_interAxisAngle.print(10, 5); System.out.println("dMB_interAxisAngle:"); dMB_interAxisAngle.print(10, 5); System.out.println("interParameterDerivatives[9]=" + sprintfArray(interParameterDerivatives[9])); } } else interParameterDerivatives[9] = null; //10 public double horAxisErrPhi; // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) // change sHAEPH to rotate like theta /1 0 0 /0 c s/ 0 -s c/ (derivative -/0 0 0 /0 -s c/ 0 -c -s/ if (mask[10]) { double[][] adR8_horAxisErrPhi_tripod = { { 0.0, 0.0, 0.0 }, { 0.0, -sHAEPH, cHAEPH }, { 0.0, -cHAEPH, -sHAEPH } }; double[][] adR8_horAxisErrPhi_goniometer = { { -sHAEPH, 0.0, cHAEPH }, { 0.0, 0.0, 0.0 }, { -cHAEPH, 0.0, -sHAEPH } }; Matrix dR8_horAxisErrPhi = new Matrix( isTripod ? adR8_horAxisErrPhi_tripod : adR8_horAxisErrPhi_goniometer); Matrix dMA_horAxisErrPhi = dR8_horAxisErrPhi .times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_horAxisErrPhi = dR8_horAxisErrPhi.times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[10] = d_parametersFromMAMB(dMA_horAxisErrPhi, dMB_horAxisErrPhi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_horAxisErrPhi:"); dMA_horAxisErrPhi.print(10, 5); System.out.println("dMB_horAxisErrPhi:"); dMB_horAxisErrPhi.print(10, 5); System.out.println("interParameterDerivatives[10]=" + sprintfArray(interParameterDerivatives[10])); } } else interParameterDerivatives[10] = null; //11 public double horAxisErrPsi; // angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) if (mask[11]) { double[][] adR7_horAxisErrPsi = { { -sHAEPS, cHAEPS, 0.0 }, { -cHAEPS, -sHAEPS, 0.0 }, { 0.0, 0.0, 0.0 } }; Matrix dR7_horAxisErrPsi = new Matrix(adR7_horAxisErrPsi); Matrix dMA_horAxisErrPsi = R8 .times(dR7_horAxisErrPsi.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_horAxisErrPsi = R8.times(dR7_horAxisErrPsi.times(R6.times(R5.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[11] = d_parametersFromMAMB(dMA_horAxisErrPsi, dMB_horAxisErrPsi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_horAxisErrPsi:"); dMA_horAxisErrPsi.print(10, 5); System.out.println("dMB_horAxisErrPsi:"); dMB_horAxisErrPsi.print(10, 5); System.out.println("interParameterDerivatives[11]=" + sprintfArray(interParameterDerivatives[11])); } } else interParameterDerivatives[11] = null; // // R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 || T0 | T1 | T2 | T3 | //12 public double entrancePupilForward // | | | | | | | || + | | | | //13 public double centerAboveHorizontal // | | | | | | | || | + | | | if (mask[12]) { double[][] adT0_entrancePupilForward = { { 0.0 }, { 0.0 }, { 1.0 } }; Matrix dT0_entrancePupilForward = new Matrix(adT0_entrancePupilForward); Matrix dMA_entrancePupilForward = new Matrix(3, 3, 0.0); Matrix dMB_entrancePupilForward = MA.times(dT0_entrancePupilForward); interParameterDerivatives[12] = d_parametersFromMAMB(dMA_entrancePupilForward, dMB_entrancePupilForward, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_centerAboveHorizontal:"); dMA_entrancePupilForward.print(10, 5); System.out.println("dMB_centerAboveHorizontal:"); dMB_entrancePupilForward.print(10, 5); System.out.println("interParameterDerivatives[12]=" + sprintfArray(interParameterDerivatives[12])); } } else interParameterDerivatives[12] = null; if (mask[13]) { double[][] adT1_centerAboveHorizontal = { { 0.0 }, { 1.0 }, { 0.0 } }; Matrix dT1_centerAboveHorizontal = new Matrix(adT1_centerAboveHorizontal); Matrix dMA_centerAboveHorizontal = new Matrix(3, 3, 0.0); Matrix dMB_centerAboveHorizontal = R8 .times(R7.times(R6.times(R5.times(R4.times(dT1_centerAboveHorizontal))))); interParameterDerivatives[13] = d_parametersFromMAMB(dMA_centerAboveHorizontal, dMB_centerAboveHorizontal, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_centerAboveHorizontal:"); dMA_centerAboveHorizontal.print(10, 5); System.out.println("dMB_centerAboveHorizontal:"); dMB_centerAboveHorizontal.print(10, 5); System.out.println("interParameterDerivatives[13]=" + sprintfArray(interParameterDerivatives[13])); } } else interParameterDerivatives[13] = null; //14 x public double [] GXYZ=null; // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system if (mask[14]) { double[][] adT3_GXYZ0 = { { 1.0 }, { 0.0 }, { 0.0 } }; Matrix dMA_GXYZ0 = new Matrix(3, 3, 0.0); Matrix dMB_GXYZ0 = new Matrix(adT3_GXYZ0); interParameterDerivatives[14] = d_parametersFromMAMB(dMA_GXYZ0, dMB_GXYZ0, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_GXYZ0:"); dMA_GXYZ0.print(10, 5); System.out.println("dMA_GXYZ0:"); dMB_GXYZ0.print(10, 5); System.out.println("interParameterDerivatives[14]=" + sprintfArray(interParameterDerivatives[14])); } } else interParameterDerivatives[14] = null; //13 y if (mask[15]) { // double [][] adT3_GXYZ1={{0.0},{1.0},{0.0}}; double[][] adT3_GXYZ1 = { { 0.0 }, { -1.0 }, { 0.0 } }; // up - positive // double [][] adT3_GXYZ1={{0.0},{ 1.0},{0.0}}; // up - positive Matrix dMA_GXYZ1 = new Matrix(3, 3, 0.0); Matrix dMB_GXYZ1 = new Matrix(adT3_GXYZ1); interParameterDerivatives[15] = d_parametersFromMAMB(dMA_GXYZ1, dMB_GXYZ1, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_GXYZ1:"); dMA_GXYZ1.print(10, 5); System.out.println("dMB_GXYZ1:"); dMB_GXYZ1.print(10, 5); System.out.println("interParameterDerivatives[15]=" + sprintfArray(interParameterDerivatives[15])); } } else interParameterDerivatives[15] = null; //14 z if (mask[16]) { // double [][] adT3_GXYZ2={{0.0},{0.0},{1.0}}; double[][] adT3_GXYZ2 = { { 0.0 }, { 0.0 }, { -1.0 } }; // away - positive Matrix dMA_GXYZ2 = new Matrix(3, 3, 0.0); Matrix dMB_GXYZ2 = new Matrix(adT3_GXYZ2); interParameterDerivatives[16] = d_parametersFromMAMB(dMA_GXYZ2, dMB_GXYZ2, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_GXYZ2:"); dMA_GXYZ2.print(10, 5); System.out.println("dMB_GXYZ2:"); dMB_GXYZ2.print(10, 5); System.out.println("interParameterDerivatives[16]=" + sprintfArray(interParameterDerivatives[16])); } } else interParameterDerivatives[16] = null; // now fill the rest, unchanged parameters /* int numInputs=22; //was 21 parameters in subcamera+... int numOutputs=13; //was 12 parameters in a single camera */ //17 (15) public double focalLength=4.5; //18 (16) public double px0=1296.0; // center of the lens on the sensor, pixels //19 (17) public double py0=968.0; // center of the lens on the sensor, pixels //20 (18) public double distortionA8=0.0; // r^8 (normalized to focal length or to sensor half width?) //21 (19) public double distortionA7=0.0; // r^7 (normalized to focal length or to sensor half width?) //22 (20) public double distortionA6=0.0; // r^6 (normalized to focal length or to sensor half width?) //23 (21) public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) //24 (22) public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) //25 (23) public double distortionB=0.0; // r^3 //26 (24) public double distortionC=0.0; // r^2 // for (int inpPar=15; inpPar<getNumInputs(); inpPar++){ for (int inpPar = 17; inpPar < getNumInputs(); inpPar++) { // OK with A8 if (mask[inpPar]) { interParameterDerivatives[inpPar] = new double[getNumOutputs()]; for (int outPar = 0; outPar < getNumOutputs(); outPar++) { interParameterDerivatives[inpPar][outPar] = ((getNumOutputs() - outPar) == (getNumInputs() - inpPar)) ? 1.0 : 0.0; if (this.debugLevel > 2) { System.out.println("interParameterDerivatives[" + inpPar + "][" + outPar + "]=" + interParameterDerivatives[inpPar][outPar]); } } } else interParameterDerivatives[inpPar] = null; } } public String sprintfArray(double[] arr) { String result = ""; for (int i = 0; i < arr.length; i++) result += ((i > 0) ? ", " : "") + arr[i]; return "[" + result + "]"; } public double[] parametersFromMAMB(Matrix MA, Matrix MB) { double[] result = new double[getNumOutputs()]; // only first 6 are used for (int i = 6; i < getNumOutputs(); i++) result[i] = 0.0; /* 0 public double x0=0; // lens axis from pattern center, mm (to the right) 1 public double y0=0; // lens axis from pattern center, mm (down) 2 public double distance=2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm 3 public double yaw=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top 4 public double pitch=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up 5 public double roll=0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise Vt=MB+MA*Vc calculate X0,Y0 (Z0==0), dist, phi, theta, psi from MB and MA | MA00 MA01 MA02 | | MA10 MA11 MA12 | | MA20 MA21 MA22 | =================== | MB0 | | MB1 | | MB2 | Take point Pc on a sub-camera axis and on the target Zt=0 plane: [0,0,dist] MA*Pc+MB= dist*[MA02,MA12,MA22]+MB =[Tx0,Ty0,0] MA02*dist+MB0=Tx0 MA12*dist+MB1=Ty0 MA22*dist+MB2=0 dist=-MB2/MA22; Tx0=MA02*(-MB2/MA22)+MB0 Ty0=MA12*(-MB2/MA22)+MB1 Tx0=MB0-MB2*MA02/MA22 Ty0=MB1-MB2*MA12/MA22 */ result[2] = -MB.get(2, 0) / MA.get(2, 2); // distance result[0] = MB.get(0, 0) - MB.get(2, 0) * MA.get(0, 2) / MA.get(2, 2); // x0 result[1] = MB.get(1, 0) - MB.get(2, 0) * MA.get(1, 2) / MA.get(2, 2); // y0 /* // now find phi, theta, psi MA - rotation from camera to target, transp(MA) - rotation from target to camera - same as rot(phi, theta,psi) MT=transp(MA), */ /* MA[1,2]= sin(theta) MA[0,2]= cos(theta)*sin(phi) MA[2,2]= cos(theta)*cos(phi) MA[1,0]=-cos(theta)*sin(psi) MA[1,1]= cos(theta)*cos(psi) theta=arcsin(MA[1,2]) phi= atan2(MA[0,2],MA[2,2]) psi= -atan2(MA[1,0],MA[1,1]) */ result[4] = 180.0 / Math.PI * Math.asin(MA.get(1, 2)); //pitch result[3] = 180.0 / Math.PI * Math.atan2(MA.get(0, 2), MA.get(2, 2));//yaw result[5] = -180.0 / Math.PI * Math.atan2(MA.get(1, 0), MA.get(1, 1));//roll return result; } /** * * @param d_MA differential of the rotational matrix MA by some parameter * @param d_MB differential of the translational matrix MB by some parameter * @param MA - rotation matrix * @param MB - translation matrix * @param isAngle - when true, the partial derivative is for angles, d_MA, d_MB should be divided by 180/pi * @return differentials of the {x0,y0,dist,phi,theta,psi} by that parameter */ public double[] d_parametersFromMAMB(Matrix d_MA, Matrix d_MB, Matrix MA, Matrix MB, boolean isAngle) { double[] result = new double[getNumOutputs()]; // only first 6 are used for (int i = 6; i < getNumOutputs(); i++) result[i] = 0.0; /* 0 public double x0=0; // lens axis from pattern center, mm (to the right) 1 public double y0=0; // lens axis from pattern center, mm (down) 2 public double distance=2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm 3 public double yaw=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top 4 public double pitch=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up 5 public double roll=0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise Vt=MB+MA*Vc calculate X0,Y0 (Z0==0), dist, phi, theta, psi from MB and MA | MA00 MA01 MA02 | | MA10 MA11 MA12 | | MA20 MA21 MA22 | =================== | MB0 | | MB1 | | MB2 | Take point Pc on a sub-camera axis and on the target Zt=0 plane: [0,0,dist] MA*Pc+MB= dist*[MA02,MA12,MA22]+MB =[Tx0,Ty0,0] MA02*dist+MB0=Tx0 MA12*dist+MB1=Ty0 MA22*dist+MB2=0 dist=-MB2/MA22; Tx0=MB0-MB2*MA02/MA22 Ty0=MB1-MB2*MA12/MA22 */ // result[2]=-MB.get(2,0)/MA.get(2,2); // distance /* d_dist=-(d_MB2/MA22 - d_MA22*MB2/(MA22^2) ; */ double K = isAngle ? (Math.PI / 180) : 1.0; result[2] = K * (-d_MB.get(2, 0) / MA.get(2, 2) + d_MA.get(2, 2) * MB.get(2, 0) / (MA.get(2, 2) * MA.get(2, 2))); // d_distance /* Tx0=MB0-MB2*MA02/MA22 d_Tx0=d_MB0 - d_MB2*MA02/MA22 -d_MA02*MB2/MA22 +d_MA22*MB2*MA02/(MA22^2) */ result[0] = K * (d_MB.get(0, 0) - d_MB.get(2, 0) * MA.get(0, 2) / MA.get(2, 2) - d_MA.get(0, 2) * MB.get(2, 0) / MA.get(2, 2) + d_MA.get(2, 2) * MB.get(2, 0) * MA.get(0, 2) / (MA.get(2, 2) * MA.get(2, 2))); // x0 /* Ty0=MB0-MB2*MA02/MA22 d_Ty0=d_MB1 - d_MB2*MA12/MA22 -d_MA12*MB2/MA22 +d_MA22*MB2*MA12/(MA22^2) */ result[1] = K * (d_MB.get(1, 0) - d_MB.get(2, 0) * MA.get(1, 2) / MA.get(2, 2) - d_MA.get(1, 2) * MB.get(2, 0) / MA.get(2, 2) + d_MA.get(2, 2) * MB.get(2, 0) * MA.get(1, 2) / (MA.get(2, 2) * MA.get(2, 2))); // y0 /* // now find phi, theta, psi MA - rotation from camera to target, transp(MA) - rotation from target to camera - same as rot(phi, theta,psi) MT=transp(MA), */ /* MA[1,2]= sin(theta) MA[0,2]= cos(theta)*sin(phi) MA[2,2]= cos(theta)*cos(phi) MA[1,0]=-cos(theta)*sin(psi) MA[1,1]= cos(theta)*cos(psi) theta=arcsin(MA[1,2]) phi= atan2(MA[0,2],MA[2,2]) psi= -atan2(MA[1,0],MA[1,1]) arcsin(x)'= 1/sqrt(1-x^2) arccos(x)'=-1/sqrt(1-x^2) arctan(x)'= 1/sqrt(1+x^2) */ /* theta=arcsin(MA12) d_theta=d_MA12/sqrt(1-MA12^2) */ result[4] = K * d_MA.get(1, 2) * 180.0 / Math.PI / Math.sqrt(1.0 - MA.get(1, 2) * MA.get(1, 2)); //pitch /* phi= atan2(MA02,MA22) d_phi=(d_MA02*MA22-d_MA22*MA02) / (MA22^2+MA02^2) */ result[3] = K * 180.0 / Math.PI * (d_MA.get(0, 2) * MA.get(2, 2) - d_MA.get(2, 2) * MA.get(0, 2)) / ((MA.get(2, 2) * MA.get(2, 2)) + (MA.get(0, 2) * MA.get(0, 2)));//yaw /* psi= -atan2(MA10,MA11) d_psi=-(d_MA10*MA11-d_MA11*MA10) / (MA10^2+MA11^2) */ result[5] = -K * 180.0 / Math.PI * (d_MA.get(1, 0) * MA.get(1, 1) - d_MA.get(1, 1) * MA.get(1, 0)) / ((MA.get(1, 1) * MA.get(1, 1)) + (MA.get(1, 0) * MA.get(1, 0)));//roll return result; } 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(Distortions.DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters) { // Distortions.DistortionCalibrationData distortionCalibrationData= new Distortions.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(Distortions.DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters) { 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.addMessage("Select which individual image parameters to be updated from the camera parameters"); 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.addCheckbox("Verticaly 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; 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; } // boolean updateDisabled= gd.getNextBoolean(); updateImageSetFromCamera(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, Distortions.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 if ((ta == null) || Double.isNaN(ta[0]) || Double.isNaN(ta[1])) return false; return true; } public boolean interactiveUpdateImageSetOld(Distortions.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(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(Distortions.DistortionCalibrationData distortionCalibrationData, EyesisCameraParameters eyesisCameraParameters, boolean[] parameterMask, boolean[] channelMask, boolean[] stationMask) { // Distortions.DistortionCalibrationData distortionCalibrationData= new Distortions.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]) oldVector[j] = newVector[j]; 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(Distortions.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(Distortions.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, Distortions.DistortionCalibrationData distortionCalibrationData) { int numEnabledSet = distortionCalibrationData.gIP[enabledImage].setNumber; 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 > 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; } 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; } } public static class EyesisCameraParameters { public double[] goniometerHorizontal; // goniometer rotation around "horizontal" axis (tilting from the target - positive) public double[] goniometerAxial; // goniometer rotation around Eyesis axis (clockwise in plan - positive public EyesisSubCameraParameters[][] eyesisSubCameras = null; public double[] interAxisDistance; // distance in mm between two goniometer axes public double[] interAxisAngle; // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated // clockwise when eyesis is in 'normal' position, looking to the target public double[] horAxisErrPhi; // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) public double[] horAxisErrPsi; // angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) public double[] entrancePupilForward; // common to all lenses - distance from the sensor to the lens entrance pupil public double[] centerAboveHorizontal; // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each SFE) public double[][] GXYZ = null; // [numStations]{x,y,z} coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system // non-adjustable parameters, not parts of vector public int numStations; public double[] stationWeight; // reprojection error weights (close station - relax errors) public boolean isTripod = false; // when true - make goniometerHorizontal rotation around "vertical" axis and "goniometerAxial" - around // rotated horizontal. public int sensorWidth = 2592; public int sensorHeight = 1936; public int shrinkGridForMask = 4; //2; //shrink detected grids by one point for/vert this number of times before calculating masks public double maskBlurSigma = -3; //2.0; // blur sensor masks (>0 - pixels, <0 - in grid units) public int decimateMasks = 1; public double badNodeThreshold = 0.1; // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels public int maxBadNeighb = 1; // maximal number of bad nodes around the corrected one to fix public int minimalValidNodes = 50; // do not use images with less than this number of non-zero nodes (after all applicable weight masks) public int weightMultiImageMode = 1; // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set to weightMultiExponent power) public double weightMultiExponent = 1.0; public double weightDiameterExponent = 1.0; // if( >0) use grid diameter to scale weights of this image public double weightYtoX = 1.0; // relative Y-to-X errors weight (to somewhat compensate for rectabular shape of the sensor) public double minimalGridContrast = 0.4; // (normally max ~0.8) public double shrinkBlurSigma = 4.0; public double shrinkBlurLevel = 0.5; public double balanceChannelWeightsMode = -1.0; // <0 - use defaults, 0 - keep, >0 balance by number of points to this power public double removeOverRMS = 2.0; // error is multiplied by weight function before comparison (more permissive on the borders public double removeOverRMSNonweighted = 4.0; // error is not multiplied (no more permissions on tyhe borders public int[] extrinsicIndices = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; //support variations public double[][] variationsDefaults = { null, // 0 null, // 1 null, // 2 null, // 3 null, // 4 null, // 5 { 10.0, 0.1, 0.0, 1.0 }, // 6 goniometerHorizontal { 10.0, 0.1, 0.0, 1.0 }, // 7 goniometerAxial { 10.0, 2.0, 0.0, 1.0 }, // 8 interAxisDistance { 10.0, 0.2, 0.0, 1.0 }, // 9 interAxisAngle { 10.0, 0.2, 0.0, 1.0 }, // 10 horAxisErrPhi { 10.0, 0.2, 0.0, 1.0 }, // 11 horAxisErrPsi { 10.0, 2.0, 0.0, 1.0 }, // 12 entrancePupilForward { 10.0, 2.0, 0.0, 1.0 }, // 13 centerAboveHorizontal { 10.0, 2.0, 0.0, 1.0 }, // 14 GXYZ0 { 10.0, 2.0, 0.0, 1.0 }, // 15 GXYZ1 { 10.0, 2.0, 0.0, 1.0 } // 16 GXYZ2 }; public int tiltIndex = 6; private ParameterVariationCosts[] parameterVariationCosts = null; public boolean isExtrinsic(int index) { for (int i = 0; i < this.extrinsicIndices.length; i++) if (this.extrinsicIndices[i] == index) return true; return false; } public boolean isTilt(int index) { return (index == this.tiltIndex); } public boolean isVarianceCostSet(int index) { return (parameterVariationCosts != null) && (index < parameterVariationCosts.length) && (parameterVariationCosts[index] != null); } public double varianceCostScale(int index) { if (!isVarianceCostSet(index)) return -1.0; return parameterVariationCosts[index].scale; } public double varianceCostVariationAbs(int index) { if (!isVarianceCostSet(index)) return -1.0; return parameterVariationCosts[index].variationAbs; } public double varianceCostVariationDiff(int index) { if (!isVarianceCostSet(index)) return -1.0; return parameterVariationCosts[index].variationDiff; } public double varianceCostVariationExponent(int index) { if (!isVarianceCostSet(index)) return -1.0; return parameterVariationCosts[index].exponent; } public String varianceCostVariationName(int index) { if (!isVarianceCostSet(index)) return null; return parameterVariationCosts[index].parName; } public int getVarParsLength() { return parameterVariationCosts.length; } public boolean editCostProperties(int index, String parameterName, String parameterDescription, String parameterUnits) { if (!isExtrinsic(index)) return false; // does not have varaince parameters getVariationCosts(); if (parameterVariationCosts[index] == null) parameterVariationCosts[index] = new ParameterVariationCosts(this.variationsDefaults[index][0], this.variationsDefaults[index][1], this.variationsDefaults[index][2], this.variationsDefaults[index][3]); return parameterVariationCosts[index].showVarianceDialog(parameterName, parameterDescription, parameterUnits); } private ParameterVariationCosts[] getVariationCosts() { if (this.parameterVariationCosts == null) { int max = 0; for (int i = 0; i < this.extrinsicIndices.length; i++) if (this.extrinsicIndices[i] > max) max = this.extrinsicIndices[i]; this.parameterVariationCosts = new ParameterVariationCosts[max + 1]; for (int i = 0; i < this.parameterVariationCosts.length; i++) this.parameterVariationCosts[i] = null; } return this.parameterVariationCosts; } public void getCostsPropertiesXML(String prefix, XMLConfiguration hConfig) { getVariationCosts(); for (int i = 0; i < this.extrinsicIndices.length; i++) { int index = this.extrinsicIndices[i]; // System.out.println("getCostsPropertiesXML("+prefix+",hconfig) index="+index); if (hConfig.configurationsAt(prefix + "varianceCosts_" + index).size() != 0) { // System.out.println("hConfig.configurationAt(prefix+\"varianceCosts_\"+index).isEmpty()=false"); this.parameterVariationCosts[index] = new ParameterVariationCosts( this.variationsDefaults[index][0], this.variationsDefaults[index][1], this.variationsDefaults[index][2], this.variationsDefaults[index][3]); boolean isSet = this.parameterVariationCosts[index] .getPropertiesXML(prefix + "varianceCosts_" + index + ".", hConfig); if (!isSet) this.parameterVariationCosts[index] = null; } } } public void setCostsPropertiesXML(String prefix, XMLConfiguration hConfig) { if (this.parameterVariationCosts == null) return; for (int i = 0; i < this.extrinsicIndices.length; i++) { int index = this.extrinsicIndices[i]; if (this.parameterVariationCosts[index] != null) { hConfig.addProperty(prefix + "varianceCosts_" + index, ""); this.parameterVariationCosts[index].setPropertiesXML(prefix + "varianceCosts_" + index + ".", hConfig); } } } private class ParameterVariationCosts { public double scale = 1.0; // 1 pixel public double variationAbs = 0.0; // variation of the parameter to cost 1 pixel public double variationDiff = 0.0; // variation of the parameter to cost 1 pixel public double exponent = 1.0; // 1.0 - square diff public String parName = null; // public ParameterVariationCosts(){} public ParameterVariationCosts(double scale, double variationAbs, double variationDiff, double exponent, String parName) { this.scale = scale; this.variationAbs = variationAbs; this.variationDiff = variationDiff; this.exponent = exponent; this.parName = parName; } public ParameterVariationCosts(double scale, double variationAbs, double variationDiff, double exponent) { this.scale = scale; this.variationAbs = variationAbs; this.variationDiff = variationDiff; this.exponent = exponent; } public ParameterVariationCosts clone() { return new ParameterVariationCosts(this.scale, this.variationAbs, this.variationDiff, this.exponent, this.parName); } public void setPropertiesXML(String prefix, XMLConfiguration hConfig) { hConfig.addProperty(prefix + "scale", this.scale + ""); hConfig.addProperty(prefix + "variationAbs", this.variationAbs + ""); hConfig.addProperty(prefix + "variationDiff", this.variationDiff + ""); hConfig.addProperty(prefix + "exponent", this.exponent + ""); if (this.parName != null) hConfig.addProperty(prefix + "parName", this.parName + ""); } public boolean getPropertiesXML(String prefix, XMLConfiguration hConfig) { // System.out.println("getPropertiesXML("+prefix+",hconfig)"); boolean isSet = false; if (hConfig.getString(prefix + "scale") != null) { // System.out.println("getPropertiesXML("+prefix+",hconfig), hConfig.getString(prefix+\"scale\")!=null"); this.scale = Double.parseDouble(hConfig.getString(prefix + "scale")); isSet = true; } if (hConfig.getString(prefix + "variationAbs") != null) { // System.out.println("getPropertiesXML("+prefix+",hconfig), hConfig.getString(prefix+\"variationAbs\")!=null"); this.variationAbs = Double.parseDouble(hConfig.getString(prefix + "variationAbs")); isSet = true; } if (hConfig.getString(prefix + "variationDiff") != null) { // System.out.println("getPropertiesXML("+prefix+",hconfig), hConfig.getString(prefix+\"variationDiff\")!=null"); this.variationDiff = Double.parseDouble(hConfig.getString(prefix + "variationDiff")); isSet = true; } if (hConfig.getString(prefix + "exponent") != null) { // System.out.println("getPropertiesXML("+prefix+",hconfig), hConfig.getString(prefix+\"exponent\")!=null"); this.exponent = Double.parseDouble(hConfig.getString(prefix + "exponent")); isSet = true; } if (hConfig.getString(prefix + "parName") != null) { // System.out.println("getPropertiesXML("+prefix+",hconfig), hConfig.getString(prefix+\"parName\")!=null"); this.parName = hConfig.getString(prefix + "parName"); isSet = true; } return isSet; } public boolean showVarianceDialog(String parameterName, String parameterDescription, String parameterUnits) { String title = "Setup costs for image set variance of " + parameterName; GenericDialog gd = new GenericDialog(title); gd.addMessage("Parameter: " + parameterName + " - " + parameterDescription); gd.addNumericField("Effective cost of the image set when the " + parameterName + " variance reaches value below", this.scale, 2, 4, "pix"); gd.addNumericField( "Variance amount from the average of " + parameterName + " to result in the specified cost", this.variationAbs, 3, 8, parameterUnits); gd.addNumericField("Variance amount from the p to result in the specified cost", this.variationDiff, 3, 8, parameterUnits); gd.addNumericField("Exponent of the cost vs. variance (1.0 - squared err " + parameterName + " to result in the specified cost", this.exponent, 3, 8, ""); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return false; this.scale = gd.getNextNumber(); this.variationAbs = gd.getNextNumber(); this.variationDiff = gd.getNextNumber(); this.exponent = gd.getNextNumber(); this.parName = parameterName; return true; } } public int getNumStations() { return this.numStations; } public EyesisCameraParameters() { } // just create new instance, all parameters data will be provided additionally public EyesisCameraParameters(int numStations, boolean isTripod, double goniometerHorizontal, // goniometer rotation around "horizontal" axis (tilting from the target - positive) double goniometerAxial, // goniometer rotation around Eyesis axis (clockwise in plan - positive int numSubCameras, double interAxisDistance, // distance in mm between two goniometer axes, positive if the vertical axis (when Eyesis is head up) is closer to the target double interAxisAngle, // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated // clockwise when eyesis is in 'normal' position, looking to the target double horAxisErrPhi, // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) double horAxisErrPsi, // angle in degrees "horizontal" goniometer axis is rotated around moving Z axis (CW looking at target) double entrancePupilForward, // common to all lenses - distance from the sensor to the lens entrance pupil double centerAboveHorizontal, // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each double GXYZ_0, // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system double GXYZ_1, // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system double GXYZ_2, // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system int sensorWidth, int sensorHeight, int shrinkGridForMask, //shrink detected grids by one point for/vert this number of times before calculating masks double maskBlurSigma, // blur sensor masks (in grid units) int decimateMasks, // reduce masks resolution double badNodeThreshold, // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels int maxBadNeighb, // maximal number of bad nodes around the corrected one to fix int minimalValidNodes, int weightMultiImageMode, // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set) double weightMultiExponent, double weightDiameterExponent, // if( >0) use grid diameter to scale weights of this image double weightYtoX, double minimalGridContrast, double shrinkBlurSigma, double shrinkBlurLevel, double balanceChannelWeightsMode, double removeOverRMS, double removeOverRMSNonweighted) { double[] GXYZ = { GXYZ_0, GXYZ_1, GXYZ_2 }; setSameEyesisCameraParameters(numStations, isTripod, goniometerHorizontal, // goniometer rotation around "horizontal" axis (tilting from the target - positive) goniometerAxial, // goniometer rotation around Eyesis axis (clockwise in plan - positive numSubCameras, interAxisDistance, // distance in mm between two goniometer axes interAxisAngle, // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated // clockwise when eyesis is in 'normal' position, looking to the target horAxisErrPhi, // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) horAxisErrPsi, // angle in degrees "horizontal" goniometer axis is rotated moving Z axis (CW looking at target) entrancePupilForward, // common to all lenses - distance from the sensor to the lens entrance pupil centerAboveHorizontal, // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each GXYZ, // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system sensorWidth, sensorHeight, shrinkGridForMask, //shrink detected grids by one point for/vert this number of times before calculating masks maskBlurSigma, // blur sensor masks (in grid units) decimateMasks, // reduce masks resolution badNodeThreshold, // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels maxBadNeighb, // maximal number of bad nodes around the corrected one to fix minimalValidNodes, weightMultiImageMode, // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set) weightMultiExponent, weightDiameterExponent, // if( >0) use grid diameter to scale weights of this image weightYtoX, minimalGridContrast, shrinkBlurSigma, shrinkBlurLevel, balanceChannelWeightsMode, removeOverRMS, removeOverRMSNonweighted); } public EyesisCameraParameters(int numStations, boolean isTripod, double goniometerHorizontal, // goniometer rotation around "horizontal" axis (tilting from the target - positive) double goniometerAxial, // goniometer rotation around Eyesis axis (clockwise in plan - positive int numSubCameras, double interAxisDistance, // distance in mm between two goniometer axes double interAxisAngle, // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated // clockwise when eyesis is in 'normal' position, looking to the target double horAxisErrPhi, // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) double horAxisErrPsi, // angle in degrees "horizontal" goniometer axis is rotated moving Z axis (CW looking at target) double entrancePupilForward, // common to all lenses - distance from the sensor to the lens entrance pupil double centerAboveHorizontal, // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each double[] GXYZ, // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system int sensorWidth, int sensorHeight, int shrinkGridForMask, //shrink detected grids by one point for/vert this number of times before calculating masks double maskBlurSigma, // blur sensor masks (in grid units) int decimateMasks, // reduce masks resolution double badNodeThreshold, // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels int maxBadNeighb, // maximal number of bad nodes around the corrected one to fix int minimalValidNodes, int weightMultiImageMode, // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set) double weightMultiExponent, double weightDiameterExponent, // if( >0) use grid diameter to scale weights of this image double weightYtoX, double minimalGridContrast, double shrinkBlurSigma, double shrinkBlurLevel, double balanceChannelWeightsMode, double removeOverRMS, double removeOverRMSNonweighted) { setSameEyesisCameraParameters(numStations, isTripod, goniometerHorizontal, // goniometer rotation around "horizontal" axis (tilting from the target - positive) goniometerAxial, // goniometer rotation around Eyesis axis (clockwise in plan - positive numSubCameras, interAxisDistance, // distance in mm between two goniometer axes interAxisAngle, // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated // clockwise when eyesis is in 'normal' position, looking to the target horAxisErrPhi, // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) horAxisErrPsi, // angle in degrees "horizontal" goniometer axis is rotated moving Z axis (CW looking at target) entrancePupilForward, // common to all lenses - distance from the sensor to the lens entrance pupil centerAboveHorizontal, // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each GXYZ, // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system sensorWidth, sensorHeight, shrinkGridForMask, //shrink detected grids by one point for/vert this number of times before calculating masks maskBlurSigma, // blur sensor masks (in grid units) decimateMasks, // reduce masks resolution badNodeThreshold, // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels maxBadNeighb, // maximal number of bad nodes around the corrected one to fix minimalValidNodes, weightMultiImageMode, // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set) weightMultiExponent, weightDiameterExponent, // if( >0) use grid diameter to scale weights of this image weightYtoX, minimalGridContrast, shrinkBlurSigma, shrinkBlurLevel, balanceChannelWeightsMode, removeOverRMS, removeOverRMSNonweighted); } void setSameEyesisCameraParameters(int numStations, boolean isTripod, double goniometerHorizontal, // goniometer rotation around "horizontal" axis (tilting from the target - positive) double goniometerAxial, // goniometer rotation around Eyesis axis (clockwise in plan - positive int numSubCameras, double interAxisDistance, // distance in mm between two goniometer axes double interAxisAngle, // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated // clockwise when eyesis is in 'normal' position, looking to the target double horAxisErrPhi, // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) double horAxisErrPsi, // angle in degrees "horizontal" goniometer axis is rotated moving Z axis (CW looking at target) double entrancePupilForward, // common to all lenses - distance from the sensor to the lens entrance pupil double centerAboveHorizontal, // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each double[] GXYZ, // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system int sensorWidth, int sensorHeight, int shrinkGridForMask, //shrink detected grids by one point for/vert this number of times before calculating masks double maskBlurSigma, // blur sensor masks (in grid units) int decimateMasks, // reduce masks resolution double badNodeThreshold, // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels int maxBadNeighb, // maximal number of bad nodes around the corrected one to fix int minimalValidNodes, int weightMultiImageMode, // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set) double weightMultiExponent, double weightDiameterExponent, // if( >0) use grid diameter to scale weights of this image double weightYtoX, double minimalGridContrast, double shrinkBlurSigma, double shrinkBlurLevel, double balanceChannelWeightsMode, double removeOverRMS, double removeOverRMSNonweighted) { this.numStations = numStations; this.isTripod = isTripod; this.sensorWidth = sensorWidth; this.sensorHeight = sensorHeight; this.shrinkGridForMask = shrinkGridForMask; //shrink detected grids by one point for/vert this number of times before calculating masks this.maskBlurSigma = maskBlurSigma; // blur sensor masks (in grid units) this.decimateMasks = decimateMasks; this.badNodeThreshold = badNodeThreshold; // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels this.maxBadNeighb = maxBadNeighb; // maximal number of bad nodes around the corrected one to fix this.minimalValidNodes = minimalValidNodes; this.weightMultiImageMode = weightMultiImageMode; // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set) this.weightMultiExponent = weightMultiExponent; this.weightDiameterExponent = weightDiameterExponent; // if( >0) use grid diameter to scale weights of this image this.weightYtoX = weightYtoX; this.minimalGridContrast = minimalGridContrast; this.goniometerHorizontal = new double[numStations]; this.goniometerAxial = new double[numStations]; this.interAxisDistance = new double[numStations]; this.interAxisAngle = new double[numStations]; this.horAxisErrPhi = new double[numStations]; this.horAxisErrPsi = new double[numStations]; this.entrancePupilForward = new double[numStations]; // common to all lenses - distance from the sensor to the lens entrance pupil this.centerAboveHorizontal = new double[numStations]; // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each this.GXYZ = new double[numStations][]; if (numSubCameras > 0) this.eyesisSubCameras = new EyesisSubCameraParameters[numStations][]; this.stationWeight = new double[numStations]; for (int numStation = 0; numStation < numStations; numStation++) { this.stationWeight[numStation] = 1.0; this.goniometerHorizontal[numStation] = goniometerHorizontal; this.goniometerAxial[numStation] = goniometerAxial; this.interAxisDistance[numStation] = interAxisDistance; this.interAxisAngle[numStation] = interAxisAngle; this.horAxisErrPhi[numStation] = horAxisErrPhi; this.horAxisErrPsi[numStation] = horAxisErrPsi; this.entrancePupilForward[numStation] = entrancePupilForward; // common to all lenses - distance from the sensor to the lens entrance pupil this.centerAboveHorizontal[numStation] = centerAboveHorizontal; // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each this.GXYZ[numStation] = new double[3]; for (int i = 0; i < 3; i++) this.GXYZ[numStation][i] = GXYZ[i]; if (numSubCameras > 0) initSubCameras(numStation, numSubCameras); } } public EyesisCameraParameters clone() { EyesisCameraParameters result = new EyesisCameraParameters(); copyData(this.numStations, this, result); return result; } /** * Capoy parameters from source EysesisCamerParameters to destination, trimming/expanding nu,ber of stations * @param newNumStations new number of stations * @param source source EysesisCamerParameters * @param destination destination EysesisCamerParameters */ public void copyData(int newNumStations, EyesisCameraParameters source, EyesisCameraParameters destination) { destination.numStations = newNumStations; destination.isTripod = source.isTripod; destination.sensorWidth = source.sensorWidth; destination.sensorHeight = source.sensorHeight; destination.shrinkGridForMask = source.shrinkGridForMask; //shrink detected grids by one point for/vert this number of times before calculating masks destination.maskBlurSigma = source.maskBlurSigma; // blur sensor masks (in grid units) destination.decimateMasks = source.decimateMasks; destination.badNodeThreshold = source.badNodeThreshold; // filter out grid nodes with difference from quadratically predicted from 8 neighbors in pixels destination.maxBadNeighb = source.maxBadNeighb; // maximal number of bad nodes around the corrected one to fix destination.minimalValidNodes = source.minimalValidNodes; destination.weightMultiImageMode = source.weightMultiImageMode; // increase weight for multi-image sets (0 - do not increase, 1 - multiply by number of images in a set) destination.weightMultiExponent = source.weightMultiExponent; // if( >0) use grid diameter to scale weights of this image destination.weightDiameterExponent = source.weightDiameterExponent; destination.weightYtoX = source.weightYtoX; destination.minimalGridContrast = source.minimalGridContrast; destination.shrinkBlurSigma = source.shrinkBlurSigma; destination.shrinkBlurLevel = source.shrinkBlurLevel; destination.balanceChannelWeightsMode = source.balanceChannelWeightsMode; destination.removeOverRMSNonweighted = source.removeOverRMSNonweighted; destination.goniometerHorizontal = new double[destination.numStations]; destination.goniometerAxial = new double[destination.numStations]; destination.interAxisDistance = new double[destination.numStations]; destination.interAxisAngle = new double[destination.numStations]; destination.horAxisErrPhi = new double[destination.numStations]; destination.horAxisErrPsi = new double[destination.numStations]; destination.entrancePupilForward = new double[destination.numStations]; // common to all lenses - distance from the sensor to the lens entrance pupil destination.centerAboveHorizontal = new double[destination.numStations]; // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each destination.GXYZ = new double[destination.numStations][]; if (source.eyesisSubCameras != null) destination.eyesisSubCameras = new EyesisSubCameraParameters[destination.numStations][]; else destination.eyesisSubCameras = null; destination.stationWeight = new double[destination.numStations]; for (int numStation = 0; numStation < destination.numStations; numStation++) { int srcNumStation = (numStation < source.numStations) ? numStation : (source.numStations - 1); destination.stationWeight[numStation] = source.stationWeight[srcNumStation]; destination.goniometerHorizontal[numStation] = source.goniometerHorizontal[srcNumStation]; destination.goniometerAxial[numStation] = source.goniometerAxial[srcNumStation]; destination.interAxisDistance[numStation] = source.interAxisDistance[srcNumStation]; destination.interAxisAngle[numStation] = source.interAxisAngle[srcNumStation]; destination.horAxisErrPhi[numStation] = source.horAxisErrPhi[srcNumStation]; destination.horAxisErrPsi[numStation] = source.horAxisErrPsi[srcNumStation]; destination.entrancePupilForward[numStation] = source.entrancePupilForward[srcNumStation]; // common to all lenses - distance from the sensor to the lens entrance pupil destination.centerAboveHorizontal[numStation] = source.centerAboveHorizontal[srcNumStation]; // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each destination.GXYZ[numStation] = new double[3]; for (int i = 0; i < 3; i++) destination.GXYZ[numStation][i] = source.GXYZ[srcNumStation][i]; if (destination.eyesisSubCameras != null) { destination.eyesisSubCameras[numStation] = new EyesisSubCameraParameters[source.eyesisSubCameras[srcNumStation].length]; for (int i = 0; i < destination.eyesisSubCameras[numStation].length; i++) if (source.eyesisSubCameras[srcNumStation][i] != null) { destination.eyesisSubCameras[numStation][i] = source.eyesisSubCameras[srcNumStation][i] .clone(); } } } } public void setProperties(String prefix, Properties properties) { properties.setProperty(prefix + "isTripod", this.isTripod + ""); properties.setProperty(prefix + "sensorWidth", this.sensorWidth + ""); properties.setProperty(prefix + "sensorHeight", this.sensorHeight + ""); properties.setProperty(prefix + "shrinkGridForMask", this.shrinkGridForMask + ""); properties.setProperty(prefix + "maskBlurSigma", this.maskBlurSigma + ""); properties.setProperty(prefix + "decimateMasks", this.decimateMasks + ""); properties.setProperty(prefix + "badNodeThreshold", this.badNodeThreshold + ""); properties.setProperty(prefix + "maxBadNeighb", this.maxBadNeighb + ""); properties.setProperty(prefix + "minimalValidNodes", this.minimalValidNodes + ""); properties.setProperty(prefix + "weightMultiImageMode", this.weightMultiImageMode + ""); properties.setProperty(prefix + "weightMultiExponent", this.weightMultiExponent + ""); properties.setProperty(prefix + "weightDiameterExponent", this.weightDiameterExponent + ""); properties.setProperty(prefix + "weightYtoX", this.weightYtoX + ""); properties.setProperty(prefix + "minimalGridContrast", this.minimalGridContrast + ""); properties.setProperty(prefix + "shrinkBlurSigma", this.shrinkBlurSigma + ""); properties.setProperty(prefix + "shrinkBlurLevel", this.shrinkBlurLevel + ""); properties.setProperty(prefix + "balanceChannelWeightsMode", this.balanceChannelWeightsMode + ""); properties.setProperty(prefix + "removeOverRMS", this.removeOverRMS + ""); properties.setProperty(prefix + "removeOverRMSNonweighted", this.removeOverRMSNonweighted + ""); properties.setProperty(prefix + "numSubCameras", this.eyesisSubCameras[0].length + ""); properties.setProperty(prefix + "numStations", this.numStations + ""); for (int numStation = 0; numStation < this.numStations; numStation++) { properties.setProperty(prefix + "stationWeight_" + numStation, this.stationWeight[numStation] + ""); properties.setProperty(prefix + "goniometerHorizontal_" + numStation, this.goniometerHorizontal[numStation] + ""); properties.setProperty(prefix + "goniometerAxial_" + numStation, this.goniometerAxial[numStation] + ""); properties.setProperty(prefix + "interAxisDistance_" + numStation, this.interAxisDistance[numStation] + ""); properties.setProperty(prefix + "interAxisAngle_" + numStation, this.interAxisAngle[numStation] + ""); properties.setProperty(prefix + "horAxisErrPhi_" + numStation, this.horAxisErrPhi[numStation] + ""); properties.setProperty(prefix + "horAxisErrPsi_" + numStation, this.horAxisErrPsi[numStation] + ""); properties.setProperty(prefix + "entrancePupilForward_" + numStation, this.entrancePupilForward[numStation] + ""); properties.setProperty(prefix + "centerAboveHorizontal_" + numStation, this.centerAboveHorizontal[numStation] + ""); properties.setProperty(prefix + "GXYZ_0_" + numStation, this.GXYZ[numStation][0] + ""); properties.setProperty(prefix + "GXYZ_1_" + numStation, this.GXYZ[numStation][1] + ""); properties.setProperty(prefix + "GXYZ_2_" + numStation, this.GXYZ[numStation][2] + ""); for (int i = 0; i < this.eyesisSubCameras[numStation].length; i++) { this.eyesisSubCameras[numStation][i] .setProperties(prefix + numStation + "_subCamera_" + i + '.', properties); } } // setCostsProperties(prefix,properties); } public void getProperties(String prefix, Properties properties) { if (properties.getProperty(prefix + "isTripod") != null) this.isTripod = Boolean.parseBoolean(properties.getProperty(prefix + "isTripod")); if (properties.getProperty(prefix + "sensorWidth") != null) this.sensorWidth = Integer.parseInt(properties.getProperty(prefix + "sensorWidth")); if (properties.getProperty(prefix + "sensorHeight") != null) this.sensorHeight = Integer.parseInt(properties.getProperty(prefix + "sensorHeight")); if (properties.getProperty(prefix + "shrinkGridForMask") != null) this.shrinkGridForMask = Integer.parseInt(properties.getProperty(prefix + "shrinkGridForMask")); if (properties.getProperty(prefix + "maskBlurSigma") != null) this.maskBlurSigma = Double.parseDouble(properties.getProperty(prefix + "maskBlurSigma")); if (properties.getProperty(prefix + "decimateMasks") != null) this.decimateMasks = Integer.parseInt(properties.getProperty(prefix + "decimateMasks")); if (properties.getProperty(prefix + "badNodeThreshold") != null) this.badNodeThreshold = Double.parseDouble(properties.getProperty(prefix + "badNodeThreshold")); if (properties.getProperty(prefix + "maxBadNeighb") != null) this.maxBadNeighb = Integer.parseInt(properties.getProperty(prefix + "maxBadNeighb")); if (properties.getProperty(prefix + "minimalValidNodes") != null) this.minimalValidNodes = Integer.parseInt(properties.getProperty(prefix + "minimalValidNodes")); if (properties.getProperty(prefix + "weightMultiImageMode") != null) this.weightMultiImageMode = Integer .parseInt(properties.getProperty(prefix + "weightMultiImageMode")); if (properties.getProperty(prefix + "weightMultiExponent") != null) this.weightMultiExponent = Double .parseDouble(properties.getProperty(prefix + "weightMultiExponent")); if (properties.getProperty(prefix + "weightDiameterExponent") != null) this.weightDiameterExponent = Double .parseDouble(properties.getProperty(prefix + "weightDiameterExponent")); if (properties.getProperty(prefix + "weightYtoX") != null) this.weightYtoX = Double.parseDouble(properties.getProperty(prefix + "weightYtoX")); if (properties.getProperty(prefix + "minimalGridContrast") != null) this.minimalGridContrast = Double .parseDouble(properties.getProperty(prefix + "minimalGridContrast")); if (properties.getProperty(prefix + "shrinkBlurSigma") != null) this.shrinkBlurSigma = Double.parseDouble(properties.getProperty(prefix + "shrinkBlurSigma")); if (properties.getProperty(prefix + "shrinkBlurLevel") != null) this.shrinkBlurLevel = Double.parseDouble(properties.getProperty(prefix + "shrinkBlurLevel")); if (properties.getProperty(prefix + "balanceChannelWeightsMode") != null) this.balanceChannelWeightsMode = Double .parseDouble(properties.getProperty(prefix + "balanceChannelWeightsMode")); if (properties.getProperty(prefix + "removeOverRMS") != null) this.removeOverRMS = Double.parseDouble(properties.getProperty(prefix + "removeOverRMS")); if (properties.getProperty(prefix + "removeOverRMSNonweighted") != null) this.removeOverRMSNonweighted = Double .parseDouble(properties.getProperty(prefix + "removeOverRMSNonweighted")); boolean multiStation = true; // new default int newNumStations = 1; int numSubCameras = 0; if (properties.getProperty(prefix + "numSubCameras") != null) numSubCameras = Integer.parseInt(properties.getProperty(prefix + "numSubCameras")); if (properties.getProperty(prefix + "numStations") != null) { newNumStations = Integer.parseInt(properties.getProperty(prefix + "numStations")); } else { multiStation = false; // old config format } // TODO: trim/expand stations updateNumstations(newNumStations); // this.numStations // read old/new format data // this.numStations=newNumStations; if (multiStation) { for (int numStation = 0; numStation < this.numStations; numStation++) { if (properties.getProperty(prefix + "stationWeight_" + numStation) != null) this.stationWeight[numStation] = Double .parseDouble(properties.getProperty(prefix + "stationWeight_" + numStation)); if (properties.getProperty(prefix + "goniometerHorizontal_" + numStation) != null) this.goniometerHorizontal[numStation] = Double .parseDouble(properties.getProperty(prefix + "goniometerHorizontal_" + numStation)); if (properties.getProperty(prefix + "goniometerAxial_" + numStation) != null) this.goniometerAxial[numStation] = Double .parseDouble(properties.getProperty(prefix + "goniometerAxial_" + numStation)); if (properties.getProperty(prefix + "interAxisDistance_" + numStation) != null) this.interAxisDistance[numStation] = Double .parseDouble(properties.getProperty(prefix + "interAxisDistance_" + numStation)); if (properties.getProperty(prefix + "interAxisAngle_" + numStation) != null) this.interAxisAngle[numStation] = Double .parseDouble(properties.getProperty(prefix + "interAxisAngle_" + numStation)); if (properties.getProperty(prefix + "horAxisErrPhi_" + numStation) != null) this.horAxisErrPhi[numStation] = Double .parseDouble(properties.getProperty(prefix + "horAxisErrPhi_" + numStation)); if (properties.getProperty(prefix + "horAxisErrPsi_" + numStation) != null) this.horAxisErrPsi[numStation] = Double .parseDouble(properties.getProperty(prefix + "horAxisErrPsi_" + numStation)); if (properties.getProperty(prefix + "entrancePupilForward_" + numStation) != null) this.entrancePupilForward[numStation] = Double .parseDouble(properties.getProperty(prefix + "entrancePupilForward_" + numStation)); if (properties.getProperty(prefix + "centerAboveHorizontal_" + numStation) != null) this.centerAboveHorizontal[numStation] = Double.parseDouble( properties.getProperty(prefix + "centerAboveHorizontal_" + numStation)); if (properties.getProperty(prefix + "GXYZ_0_" + numStation) != null) this.GXYZ[numStation][0] = Double .parseDouble(properties.getProperty(prefix + "GXYZ_0_" + numStation)); if (properties.getProperty(prefix + "GXYZ_1_" + numStation) != null) this.GXYZ[numStation][1] = Double .parseDouble(properties.getProperty(prefix + "GXYZ_1_" + numStation)); if (properties.getProperty(prefix + "GXYZ_2_" + numStation) != null) this.GXYZ[numStation][2] = Double .parseDouble(properties.getProperty(prefix + "GXYZ_2_" + numStation)); if (numSubCameras > 0) { initSubCameras(numStation, numSubCameras); // set array with default parameters for (int i = 0; i < numSubCameras; i++) { this.eyesisSubCameras[numStation][i] .getProperties(prefix + numStation + "_subCamera_" + i + '.', properties); } } } } else { // read old format if (properties.getProperty(prefix + "goniometerHorizontal") != null) this.goniometerHorizontal[0] = Double .parseDouble(properties.getProperty(prefix + "goniometerHorizontal")); if (properties.getProperty(prefix + "goniometerAxial") != null) this.goniometerAxial[0] = Double .parseDouble(properties.getProperty(prefix + "goniometerAxial")); if (properties.getProperty(prefix + "interAxisDistance") != null) this.interAxisDistance[0] = Double .parseDouble(properties.getProperty(prefix + "interAxisDistance")); if (properties.getProperty(prefix + "interAxisAngle") != null) this.interAxisAngle[0] = Double.parseDouble(properties.getProperty(prefix + "interAxisAngle")); if (properties.getProperty(prefix + "horAxisErrPhi") != null) this.horAxisErrPhi[0] = Double.parseDouble(properties.getProperty(prefix + "horAxisErrPhi")); if (properties.getProperty(prefix + "horAxisErrPsi") != null) this.horAxisErrPsi[0] = Double.parseDouble(properties.getProperty(prefix + "horAxisErrPsi")); if (properties.getProperty(prefix + "entrancePupilForward") != null) this.entrancePupilForward[0] = Double .parseDouble(properties.getProperty(prefix + "entrancePupilForward")); if (properties.getProperty(prefix + "centerAboveHorizontal") != null) this.centerAboveHorizontal[0] = Double .parseDouble(properties.getProperty(prefix + "centerAboveHorizontal")); if (properties.getProperty(prefix + "GXYZ_0") != null) this.GXYZ[0][0] = Double.parseDouble(properties.getProperty(prefix + "GXYZ_0")); if (properties.getProperty(prefix + "GXYZ_1") != null) this.GXYZ[0][1] = Double.parseDouble(properties.getProperty(prefix + "GXYZ_1")); if (properties.getProperty(prefix + "GXYZ_2") != null) this.GXYZ[0][2] = Double.parseDouble(properties.getProperty(prefix + "GXYZ_2")); System.out.println(" === numSubCameras=" + numSubCameras); if (numSubCameras > 0) { initSubCameras(0, numSubCameras); // set array with default parameters for (int i = 0; i < numSubCameras; i++) { System.out.println("this.eyesisSubCameras[0][" + i + "].getProperties(" + prefix + "subCamera_" + i + ".,properties);"); this.eyesisSubCameras[0][i].getProperties(prefix + "subCamera_" + i + '.', properties); } } } // getCostsProperties(prefix,properties); } void updateNumstations(int newNumStations) { // System.out.println("updateNumstations("+newNumStations+"), was "+this.numStations); if (newNumStations == this.numStations) return; System.out.println("updateNumstations(" + newNumStations + "), was " + this.numStations); EyesisCameraParameters newData = this.clone(); copyData(newNumStations, newData, this); System.out.println("updateNumstations() after copyData() this.numStations=" + this.numStations); } // returns -1 - canceled, 0 - done, >0 - number of sub-camera to edit private int subShowDialog(String title, int nextSubCamera) { GenericDialog gd = new GenericDialog(title); // public double goniometerHorizontal; // goniometer rotation around "horizontal" axis (tilting from the target - positive) // public double goniometerAxial; // goniometer rotation around Eyesis axis (clockwise in plan - positive //this.isTripod gd.addCheckbox( "Tripod mode (first vertical axis, then horizontal), changes meaning of the next 2 fields", this.isTripod); gd.addMessage("=== Camera parameters to be fitted ==="); for (int numStation = 0; numStation < this.numStations; numStation++) { if (this.numStations > 1) { gd.addMessage("--- Station number " + numStation + " ---"); gd.addNumericField("Station reprojection errors weight", 100 * this.stationWeight[numStation], 1, 5, "%"); } if (this.isTripod) { gd.addNumericField("Tripod rotation around vertical axis (clockwise from top - positive)", this.goniometerHorizontal[numStation], 3, 7, "degrees"); gd.addNumericField("Tripod rotation around horizontal axis (camera up - positive)", this.goniometerAxial[numStation], 3, 7, "degrees"); } else { gd.addNumericField( "Goniometer rotation around 'horizontal' axis (tilting from the target - positive)", this.goniometerHorizontal[numStation], 3, 7, "degrees"); gd.addNumericField("Rotation around Eyesis main axis (clockwise in plan - positive)", this.goniometerAxial[numStation], 3, 7, "degrees"); } gd.addNumericField("Distance between goniometer axes", this.interAxisDistance[numStation], 3, 7, "mm"); gd.addNumericField("Angle error between goniometer axes (<0 if vertical axis rotated CW )", this.interAxisAngle[numStation], 3, 7, "degrees"); if (this.isTripod) { gd.addNumericField("Vertical tripod axis tilt from true vertical", this.horAxisErrPhi[numStation], 3, 7, "degrees"); gd.addNumericField("Vertical tripod axis roll error from true vertical", this.horAxisErrPsi[numStation], 3, 7, "degrees"); } else { gd.addNumericField("Horizontal axis azimuth error (CW in plan)", this.horAxisErrPhi[numStation], 3, 7, "degrees"); gd.addNumericField("Horizontal axis roll error (CW looking to target)", this.horAxisErrPsi[numStation], 3, 7, "degrees"); } gd.addNumericField("Distance between the lens entrace pupil and the sensor", this.entrancePupilForward[numStation], 3, 7, "mm"); gd.addNumericField("Camera center above goniometer horizontal axis", this.centerAboveHorizontal[numStation], 3, 7, "mm"); gd.addNumericField("Goniometer reference point position X (target coordinates, left)", this.GXYZ[numStation][0], 3, 7, "mm"); gd.addNumericField("Goniometer reference point position Y (target coordinates, up)", this.GXYZ[numStation][1], 3, 7, "mm"); gd.addNumericField("Goniometer reference point position Z (target coordinates, away)", this.GXYZ[numStation][2], 3, 7, "mm"); } gd.addMessage("=== Other parameters ==="); gd.addNumericField("Image sensor width (maximal if different)", this.sensorWidth, 0, 4, "pix"); gd.addNumericField("Image sensor height (maximal if different)", this.sensorHeight, 0, 4, "pix"); gd.addNumericField("Shrink detected grid by this number of nodes (half/periods) for masks", this.shrinkGridForMask, 0, 4, "grid nodes"); gd.addNumericField( "Gaussian blur masks for the sensors (positive - pixels, negative - grid half-periods)", this.maskBlurSigma, 2, 6, "pix"); gd.addNumericField("Reduce sensor resolution when calculating masks", this.decimateMasks, 0); gd.addNumericField( "Filter out grid nodes with difference from quadratically predicted from 8 neighbors", this.badNodeThreshold, 2, 6, "pix"); gd.addNumericField("Maximal number of bad nodes around the corrected one to fix", this.maxBadNeighb, 0); gd.addNumericField("Minimal number of valid (with all filters applied) nodes in each image", this.minimalValidNodes, 0); gd.addNumericField( "Increase weight of the multi-image sets (0 - do not increase, 1 - multiply by number of images in a set (to power ), 2 - same but remove single-image ", this.weightMultiImageMode, 0); gd.addNumericField("Increase weight of the multi-image sets power (used with mode above)", this.weightMultiExponent, 2, 6, ""); gd.addNumericField("Increase weight of the images by the power of their diameters", this.weightDiameterExponent, 2, 6, ""); gd.addNumericField("Increase weight Y-error with respect to X-error", 100.0 * this.weightYtoX, 2, 6, "%"); gd.addNumericField("Filter out grid nodes with the contrast less than this value (maximal is ~0.8) ", this.minimalGridContrast, 2, 6, ""); gd.addNumericField("Shrink-blur detected grids alpha", this.shrinkBlurSigma, 2, 6, "grid nodes"); gd.addNumericField("Shrink-blur detected grids level (-1..+1)", this.shrinkBlurLevel, 2, 6, ""); gd.addNumericField( "Channel balace mode: <0 - use specified defaults, 0 - keep curent, >0 - exponent for correction (1.0 - precise equalization)", this.balanceChannelWeightsMode, 3, 6, ""); gd.addNumericField("Remove nodes with error greater than scaled RMS in that image, weighted", this.removeOverRMS, 2, 6, "xRMS"); gd.addNumericField("Same, not weghted (not more permissive near the borders with low weight)", this.removeOverRMSNonweighted, 2, 6, "xRMS"); gd.addNumericField("Number of sub-camera modules", this.eyesisSubCameras[0].length, 0, 2, ""); gd.addNumericField("Number of sub-camera module to edit (<=0 - none)", nextSubCamera, 0, 2, ""); gd.enableYesNoCancel("OK", "Done"); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return -1; this.isTripod = gd.getNextBoolean(); for (int numStation = 0; numStation < this.numStations; numStation++) { if (this.numStations > 1) { this.stationWeight[numStation] = 0.01 * gd.getNextNumber(); } this.goniometerHorizontal[numStation] = gd.getNextNumber(); this.goniometerAxial[numStation] = gd.getNextNumber(); this.interAxisDistance[numStation] = gd.getNextNumber(); this.interAxisAngle[numStation] = gd.getNextNumber(); this.horAxisErrPhi[numStation] = gd.getNextNumber(); this.horAxisErrPsi[numStation] = gd.getNextNumber(); this.entrancePupilForward[numStation] = gd.getNextNumber(); this.centerAboveHorizontal[numStation] = gd.getNextNumber(); this.GXYZ[numStation][0] = gd.getNextNumber(); this.GXYZ[numStation][1] = gd.getNextNumber(); this.GXYZ[numStation][2] = gd.getNextNumber(); } this.sensorWidth = (int) gd.getNextNumber(); this.sensorHeight = (int) gd.getNextNumber(); this.shrinkGridForMask = (int) gd.getNextNumber(); this.maskBlurSigma = gd.getNextNumber(); this.decimateMasks = (int) gd.getNextNumber(); this.badNodeThreshold = gd.getNextNumber(); this.maxBadNeighb = (int) gd.getNextNumber(); this.minimalValidNodes = (int) gd.getNextNumber(); this.weightMultiImageMode = (int) gd.getNextNumber(); this.weightMultiExponent = gd.getNextNumber(); this.weightDiameterExponent = gd.getNextNumber(); this.weightYtoX = 0.01 * gd.getNextNumber(); this.minimalGridContrast = gd.getNextNumber(); this.shrinkBlurSigma = gd.getNextNumber(); this.shrinkBlurLevel = gd.getNextNumber(); this.balanceChannelWeightsMode = gd.getNextNumber(); this.removeOverRMS = gd.getNextNumber(); this.removeOverRMSNonweighted = gd.getNextNumber(); int numSubCams = (int) gd.getNextNumber(); int numSubCam = (int) gd.getNextNumber(); if (numSubCams != this.eyesisSubCameras[0].length) { this.eyesisSubCameras = new EyesisSubCameraParameters[this.numStations][]; for (int numStation = 0; numStation < numStations; numStation++) { initSubCameras(numStation, numSubCams); // re-initialize from defaults, discard current! } } if (numSubCam < 0) numSubCam = 0; return gd.wasOKed() ? numSubCam : -1; } public boolean showSubcameraDialog(int numSubCam, String title) { GenericDialog gd = new GenericDialog(title); for (int numStation = 0; numStation < this.numStations; numStation++) { EyesisSubCameraParameters subCam = this.eyesisSubCameras[numStation][numSubCam]; if (subCam != null) { gd.addMessage("Channel weight " + subCam.channelWeightCurrent); if (numStation == 0) { gd.addNumericField("Channel " + numSubCam + " default weight", subCam.channelWeightDefault, 3, 6, ""); } if (this.numStations > 1) gd.addMessage("--- Station number " + numStation + " ---"); gd.addNumericField("Subcamera azimuth", subCam.azimuth, 3, 7, "degrees"); gd.addNumericField("Subcamera distance from the axis", subCam.radius, 3, 7, "mm"); gd.addNumericField("Subcamera height from the 'equator'", subCam.height, 3, 7, "mm"); gd.addNumericField("Optical axis heading (relative to azimuth)", subCam.phi, 3, 7, "degrees"); gd.addNumericField("Optical axis elevation (up from equator)", subCam.theta, 3, 7, "degrees"); gd.addNumericField("Camera roll, positive CW looking to the target", subCam.psi, 3, 7, "degrees"); gd.addNumericField("Lens focal length", subCam.focalLength, 3, 6, "mm"); gd.addNumericField("Sensor pixel period", subCam.pixelSize, 3, 6, "um"); gd.addNumericField("Distortion radius (half width)", subCam.distortionRadius, 5, 8, "mm"); gd.addNumericField("Distortion A8 (r^8)", subCam.distortionA8, 6, 8, ""); gd.addNumericField("Distortion A7 (r^7)", subCam.distortionA7, 6, 8, ""); gd.addNumericField("Distortion A6 (r^6)", subCam.distortionA6, 6, 8, ""); gd.addNumericField("Distortion A5 (r^5)", subCam.distortionA5, 6, 8, ""); gd.addNumericField("Distortion A (r^4)", subCam.distortionA, 6, 8, ""); gd.addNumericField("Distortion B (r^3)", subCam.distortionB, 6, 8, ""); gd.addNumericField("Distortion C (r^2)", subCam.distortionC, 6, 8, ""); gd.addNumericField("Lens axis on the sensor (horizontal, from left edge)", subCam.px0, 2, 7, "pixels"); gd.addNumericField("Lens axis on the sensor (vertical, from top edge)", subCam.py0, 2, 7, "pixels"); } } gd.enableYesNoCancel("OK", "Done"); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return false; double channelWeightDefault = 1.0; for (int numStation = 0; numStation < this.numStations; numStation++) { EyesisSubCameraParameters subCam = this.eyesisSubCameras[numStation][numSubCam]; if (subCam != null) { if (numStation == 0) { gd.addMessage("Channel weight " + subCam.channelWeightCurrent); channelWeightDefault = gd.getNextNumber(); } subCam.channelWeightDefault = channelWeightDefault; // assign to all stations subCam.azimuth = gd.getNextNumber(); subCam.radius = gd.getNextNumber(); subCam.height = gd.getNextNumber(); subCam.phi = gd.getNextNumber(); subCam.theta = gd.getNextNumber(); subCam.psi = gd.getNextNumber(); subCam.focalLength = gd.getNextNumber(); subCam.pixelSize = gd.getNextNumber(); subCam.distortionRadius = gd.getNextNumber(); subCam.distortionA8 = gd.getNextNumber(); subCam.distortionA7 = gd.getNextNumber(); subCam.distortionA6 = gd.getNextNumber(); subCam.distortionA5 = gd.getNextNumber(); subCam.distortionA = gd.getNextNumber(); subCam.distortionB = gd.getNextNumber(); subCam.distortionC = gd.getNextNumber(); subCam.px0 = gd.getNextNumber(); subCam.py0 = gd.getNextNumber(); } } return gd.wasOKed(); } // TODO: make subcameras show numbers from 0 public boolean showDialog(String title) { System.out.println("this.eyesisSubCameras.length=" + this.eyesisSubCameras.length); System.out.println("this.eyesisSubCameras[0].length=" + this.eyesisSubCameras[0].length); int subCam = 1; while (subCam <= this.eyesisSubCameras[0].length) { System.out.println("subCam=" + subCam); subCam = subShowDialog(title, subCam); if (subCam < 0) return false; if (subCam == 0) return true; while (subCam <= this.eyesisSubCameras[0].length) { // if (!this.eyesisSubCameras[subCam-1].showDialog(title+": subcamera "+subCam)) break; if (!showSubcameraDialog(subCam - 1, title + ": subcamera " + subCam + " (" + (subCam - 1) + ")")) break; subCam++; } } return true; } /** * * @param eyesisCameraParameters current parameters of the Eyesis camera, subcameras and goniometer * @param subCamNumber number of sub-camera (from 0) * @return array of the parameters (both individual sub-camera and common to all sub-cameras) * */ public double[] getParametersVector(int stationNumber, int subCamNumber // ) { if ((this.eyesisSubCameras == null) || (this.numStations <= stationNumber) || (this.eyesisSubCameras.length <= stationNumber) || (this.eyesisSubCameras[stationNumber].length <= subCamNumber)) throw new IllegalArgumentException("Nonexistent subcamera " + subCamNumber + " and/or station number=" + stationNumber + " this.numStations=" + this.numStations + " this.eyesisSubCameras.length=" + this.eyesisSubCameras.length); EyesisSubCameraParameters subCam = this.eyesisSubCameras[stationNumber][subCamNumber]; // System.out.println("getParametersVector("+stationNumber+","+subCamNumber+"), subCam is "+((subCam==null)?"null":"NOT null")); double[] parVect = { subCam.azimuth, // 0 azimuth of the lens entrance pupil center, degrees, clockwise looking from top subCam.radius, // 1 mm, distance from the rotation axis subCam.height, // 2 mm, up (was downwards?) - from the origin point subCam.phi, // 3 degrees, optical axis from azimuth/r vector, clockwise subCam.theta, // 4 degrees, optical axis from the eyesis horizon, positive - up subCam.psi, // 5 degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target this.goniometerHorizontal[stationNumber], // 6 goniometer rotation around "horizontal" axis (tilting from the target - positive) this.goniometerAxial[stationNumber], // 7 goniometer rotation around Eyesis axis (clockwise in plan - positive this.interAxisDistance[stationNumber], // 8 distance in mm between two goniometer axes this.interAxisAngle[stationNumber], // 9 angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated this.horAxisErrPhi[stationNumber], //10 angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) this.horAxisErrPsi[stationNumber], //11 angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) this.entrancePupilForward[stationNumber], //12 common to all lenses - distance from the sensor to the lens entrance pupil this.centerAboveHorizontal[stationNumber], //13 camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each this.GXYZ[stationNumber][0], //14 (12) coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system this.GXYZ[stationNumber][1], //15 (13) y this.GXYZ[stationNumber][2], //16 (14) z subCam.focalLength, //17 (15) lens focal length subCam.px0, //18 (16) center of the lens on the sensor, pixels subCam.py0, //19 (17) center of the lens on the sensor, pixels subCam.distortionA8, //20 (18) r^8 (normalized to focal length or to sensor half width?) subCam.distortionA7, //21 (19) r^7 (normalized to focal length or to sensor half width?) subCam.distortionA6, //22 (20) r^6 (normalized to focal length or to sensor half width?) subCam.distortionA5, //23 (21) r^5 (normalized to focal length or to sensor half width?) subCam.distortionA, //24 (22) r^4 (normalized to focal length or to sensor half width?) subCam.distortionB, //25 (23) r^3 subCam.distortionC, //26 (24) r^2 }; // Global parameters, not adjusted - just copied once when camera is selected // or should they stay fixed and not copied at all? // this.lensDistortionParameters.pixelSize=subCam.pixelSize; // has to be set separately // this.lensDistortionParameters.distortionRadius=subCam.distortionRadius; return parVect; } // public int getNumSubCameras (){return (this.eyesisSubCameras==null)?0:this.eyesisSubCameras.length;} public int getGoniometerHorizontalIndex() { return 6; } public int getGoniometerAxialIndex() { return 7; } public int getSensorWidth() { return this.sensorWidth; } public int getSensorHeight() { return this.sensorHeight; } public int getSensorWidth(int subCam) { return this.sensorWidth; } // for the future? different sensors public int getSensorHeight(int subCam) { return this.sensorHeight; }// for the future? different sensors public double getPixelSize(int subCamNumber) { return this.eyesisSubCameras[0][subCamNumber].pixelSize; } // use station 0's pixel size public double getDistortionRadius(int subCamNumber) { return this.eyesisSubCameras[0][subCamNumber].distortionRadius; } public void setParametersVectorAllStations(double[] parVect, boolean[] update, int subCamNumber // ) { for (int stationNumber = 0; stationNumber < this.numStations; stationNumber++) { setParametersVector(parVect, update, stationNumber, subCamNumber); } } /** * Set camera/subcamera parameters from the parameters vector * @param parVect array of the parameters (both individual sub-camera and common to all sub-cameras) * @param update - which parameter of the vector to update * @param eyesisCameraParameters current parameters of the Eyesis camera, subcameras and goniometer * @param subCamNumber number of sub-camera (from 0) */ public void setParametersVector(double[] parVect, boolean[] update, int stationNumber, int subCamNumber // ) { if (parVect.length != 27) throw new IllegalArgumentException( "Wrong length of the parameters vector: " + parVect.length + "(should be 27)"); if ((this.eyesisSubCameras == null) || (this.numStations <= stationNumber) || (this.eyesisSubCameras.length <= stationNumber) || (this.eyesisSubCameras[stationNumber].length <= subCamNumber)) throw new IllegalArgumentException( "Nonexistent subcamera " + subCamNumber + " and/or station number=" + stationNumber); EyesisSubCameraParameters subCam = this.eyesisSubCameras[stationNumber][subCamNumber]; if (update[0]) subCam.azimuth = parVect[0]; // 0 azimuth of the lens entrance pupil center, degrees, clockwise looking from top if (update[1]) subCam.radius = parVect[1]; // 1 mm, distance from the rotation axis if (update[2]) subCam.height = parVect[2]; // 2 mm, up (was downwards?) - from the origin point if (update[2]) subCam.phi = parVect[3]; // 3 degrees, optical axis from azimuth/r vector, clockwise if (update[4]) subCam.theta = parVect[4]; // 4 degrees, optical axis from the eyesis horizon, positive - up if (update[5]) subCam.psi = parVect[5]; // 5 degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target if (update[6]) this.goniometerHorizontal[stationNumber] = parVect[6]; // 6 goniometer rotation around "horizontal" axis (tilting from the target - positive) if (update[7]) this.goniometerAxial[stationNumber] = parVect[7]; // 7 goniometer rotation around Eyesis axis (clockwise in plan - positive if (update[8]) this.interAxisDistance[stationNumber] = parVect[8]; // 8 distance in mm between two goniometer axes if (update[9]) this.interAxisAngle[stationNumber] = parVect[9]; // 9 angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated if (update[10]) this.horAxisErrPhi[stationNumber] = parVect[10]; //10 angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) if (update[11]) this.horAxisErrPsi[stationNumber] = parVect[11]; //11 angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) if (update[12]) this.entrancePupilForward[stationNumber] = parVect[12]; //12 coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system if (update[13]) this.centerAboveHorizontal[stationNumber] = parVect[13]; //13 y if (update[14]) this.GXYZ[stationNumber][0] = parVect[14]; //14 coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system if (update[15]) this.GXYZ[stationNumber][1] = parVect[15]; //15 y if (update[16]) this.GXYZ[stationNumber][2] = parVect[16]; //16 z if (update[17]) subCam.focalLength = parVect[17]; //17 lens focal length if (update[18]) subCam.px0 = parVect[18]; //18 center of the lens on the sensor, pixels if (update[19]) subCam.py0 = parVect[19]; //19 center of the lens on the sensor, pixels if (update[20]) subCam.distortionA8 = parVect[20]; //20 r^8 (normalized to focal length or to sensor half width?) if (update[21]) subCam.distortionA7 = parVect[21]; //21 r^7 (normalized to focal length or to sensor half width?) if (update[22]) subCam.distortionA6 = parVect[22]; //22 r^6 (normalized to focal length or to sensor half width?) if (update[23]) subCam.distortionA5 = parVect[23]; //23 r^5 (normalized to focal length or to sensor half width?) if (update[24]) subCam.distortionA = parVect[24]; //24 r^4 (normalized to focal length or to sensor half width?) if (update[25]) subCam.distortionB = parVect[25]; //25 r^3 if (update[26]) subCam.distortionC = parVect[26]; //26 r^2 } public void initSubCameras(int numStation, int numSubCameras) { System.out.println("initSubCameras(" + numStation + "," + numSubCameras + ")"); this.eyesisSubCameras[numStation] = new EyesisSubCameraParameters[numSubCameras]; for (int i = 0; i < numSubCameras; i++) this.eyesisSubCameras[numStation][i] = null; if (numSubCameras == 3) { this.eyesisSubCameras[numStation][0] = new EyesisSubCameraParameters( //TODO: modify for lens adjustment defaults? 0.0, // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 52.53, // double radius, // mm, distance from the rotation axis 34.64, // double height, // mm, up (was downwards) - from the origin point 0.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 0.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up 180.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 1.0); //channelWeightDefault this.eyesisSubCameras[numStation][1] = new EyesisSubCameraParameters( //TODO: modify for lens adjustment defaults? 30.0, // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 60.0, // double radius, // mm, distance from the rotation axis -17.32, // double height, // mm, up (was downwards) - from the origin point -30.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 0.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up 180.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 1.0); //channelWeightDefault this.eyesisSubCameras[numStation][2] = new EyesisSubCameraParameters( //TODO: modify for lens adjustment defaults? -30.0, // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 60.0, // double radius, // mm, distance from the rotation axis -17.32, // double height, // mm, up (was downwards) - from the origin point 30.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 0.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up 180.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 1.0); //channelWeightDefault } else if (numSubCameras == 1) { this.eyesisSubCameras[numStation][0] = new EyesisSubCameraParameters( //TODO: modify for lens adjustment defaults? 0.0, // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 0.0, // double radius, // mm, distance from the rotation axis 0.0, // double height, // mm, up (was downwards) - from the origin point 0.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 0.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up 0.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 1.0); //channelWeightDefault } else { // default setup for the 26 sub-cameras for (int i = 0; i < 8; i++) if (i < numSubCameras) this.eyesisSubCameras[numStation][i] = new EyesisSubCameraParameters( // top 8 cameras 45.0 * i, // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 41.540, // double radius, // mm, distance from the rotation axis 42.883, // double height, // mm, up (was downwards?) - from the origin point 0.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 60.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up 90.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 1.0); //channelWeightDefault for (int i = 8; i < 16; i++) if (i < numSubCameras) this.eyesisSubCameras[numStation][i] = new EyesisSubCameraParameters( // middle 8 cameras 45.0 * (i - 8), // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 54.525, // double radius, // mm, distance from the rotation axis 0.0, // double height, // mm, up (was downwards) - from the origin point 0.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 0.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up 90.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 1.0); //channelWeightDefault for (int i = 16; i < 24; i++) if (i < numSubCameras) this.eyesisSubCameras[numStation][i] = new EyesisSubCameraParameters( // bottom eight cameras 45.0 * (i - 16), // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 41.540, // double radius, // mm, distance from the rotation axis -42.883, // double height, // mm, up (was downwards?) - from the origin point 0.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise -60.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up -90.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 1.0); //channelWeightDefault if (24 < numSubCameras) this.eyesisSubCameras[numStation][24] = new EyesisSubCameraParameters(90, // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 12.025, // double radius, // mm, distance from the rotation axis -807.0, // double height, // mm, up - from the origin point 0.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 0.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up 90.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 4.0); //channelWeightDefault if (25 < numSubCameras) this.eyesisSubCameras[numStation][25] = new EyesisSubCameraParameters(270, // double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 12.025, // double radius, // mm, distance from the rotation axis -841.0, // double height, // mm, up - from the origin point 0.0, // double phi, // degrees, optical axis from azimuth/r vector, clockwise 0.0, //double theta, // degrees, optical axis from the eyesis horizon, positive - up -90.0, //double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 4.5, // double focalLength 2.2, // double pixelSize (um) 2.8512, //double distortionRadius mm - half width of the sensor 0.0, // double distortionA8 // r^8 (normalized to focal length or to sensor half width?) 0.0, // double distortionA7 // r^7 (normalized to focal length or to sensor half width?) 0.0, // double distortionA6 // r^6 (normalized to focal length or to sensor half width?) 0.0, // double distortionA5 // r^5 (normalized to focal length or to sensor half width?) 0.0, // double distortionA // r^4 (normalized to focal length or to sensor half width?) 0.0, // double distortionB // r^3 0.0, // double distortionC // r^2 1296.0, // double px0=1296.0; // center of the lens on the sensor, pixels 968.0, // double py0=968.0; // center of the lens on the sensor, pixels 4.0); //channelWeightDefault } } public void recenterVertically(boolean[] subcams, boolean[] stations) { boolean sameCenterAboveHorizontal = true; double cah = Double.NaN; for (int i = 0; i < stations.length; i++) { if (stations[i]) { if (Double.isNaN(cah)) cah = this.centerAboveHorizontal[i]; else if (cah != this.centerAboveHorizontal[i]) sameCenterAboveHorizontal = false; } } if (Double.isNaN(cah)) { System.out.println("No stations enabled, nothing to do for vertical centering"); return; } System.out.println("centerAboveHorizontal " + (sameCenterAboveHorizontal ? "is common for all stations" : "differs between stations")); if (sameCenterAboveHorizontal) { System.out.println("Re-centering vertically for centerAboveHorizontal common for all stations "); double sumWeightedHeights = 0.0; double sumWeights = 0.0; for (int i = 0; i < stations.length; i++) if (stations[i]) { for (int subIndex = 0; subIndex < subcams.length; subIndex++) if (subcams[subIndex]) { System.out.println("Averaging station " + i + ", subcamera " + subIndex); sumWeights += stationWeight[i]; sumWeightedHeights += stationWeight[i] * eyesisSubCameras[i][subIndex].height; } } if (sumWeights == 0.0) { System.out.println("No subcams are enabled, nothing to do for vertical centering"); return; } sumWeightedHeights /= sumWeights; for (int i = 0; i < stations.length; i++) if (stations[i]) { for (int subIndex = 0; subIndex < subcams.length; subIndex++) { // need to update all channels, not only averaged eyesisSubCameras[i][subIndex].height -= sumWeightedHeights; } this.centerAboveHorizontal[i] += sumWeightedHeights; } } else { System.out.println("Re-centering vertically for centerAboveHorizontal individual for each station"); for (int i = 0; i < stations.length; i++) if (stations[i]) { double sumWeightedHeights = 0.0; double sumWeights = 0.0; for (int subIndex = 0; subIndex < subcams.length; subIndex++) if (subcams[subIndex]) { System.out.println("Averaging station " + i + ", subcamera " + subIndex); sumWeights += stationWeight[i]; sumWeightedHeights += stationWeight[i] * eyesisSubCameras[i][subIndex].height; } if (sumWeights == 0.0) { System.out.println("No subcams are enabled, nothing to do for vertical centering"); return; } sumWeightedHeights /= sumWeights; for (int subIndex = 0; subIndex < subcams.length; subIndex++) { // need to update all channels, not only averaged eyesisSubCameras[i][subIndex].height -= sumWeightedHeights; } this.centerAboveHorizontal[i] += sumWeightedHeights; } } } } public static class EyesisSubCameraParameters { // origin is on the rotation axis of the tube body closest to the goniometer horizontal axis public double azimuth; // azimuth of the lens entrance pupil center, degrees, clockwise looking from top public double radius; // mm, distance from the rotation axis public double height; // mm, up - from the origin point public double phi; // degrees, optical axis from azimuth/r vector, clockwise public double theta; // degrees, optical axis from the eyesis horizon, positive - up public double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target public double focalLength = 4.5; public double pixelSize = 2.2; //um public double distortionRadius = 2.8512; // mm - half width of the sensor public double distortionA8 = 0.0; //r^8 (normalized to focal length or to sensor half width?) public double distortionA7 = 0.0; //r^7 (normalized to focal length or to sensor half width?) public double distortionA6 = 0.0; //r^6 (normalized to focal length or to sensor half width?) public double distortionA5 = 0.0; //r^5 (normalized to focal length or to sensor half width?) public double distortionA = 0.0; // r^4 (normalized to focal length or to sensor half width?) public double distortionB = 0.0; // r^3 public double distortionC = 0.0; // r^2 public double px0 = 1296.0; // center of the lens on the sensor, pixels public double py0 = 968.0; // center of the lens on the sensor, pixels public double channelWeightDefault = 1.0; public double channelWeightCurrent = 1.0; public int[][] defectsXY = null; // pixel defects coordinates list (starting with worst) public double[] defectsDiff = null; // pixel defects value (diff from average of neighbors), matching defectsXY public EyesisSubCameraParameters(double azimuth, // azimuth of the lens entrance pupil center, degrees, clockwise looking from top double radius, // mm, distance from the rotation axis double height, // mm, up from the origin point double phi, // degrees, optical axis from azimuth/r vector, clockwise double theta, // degrees, optical axis from the eyesis horizon, positive - up double psi, // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target double focalLength, double pixelSize, //um double distortionRadius, //mm - half width of the sensor double distortionA8, // r^8 double distortionA7, // r^7 double distortionA6, // r^5 double distortionA5, // r^5 double distortionA, // r^4 (normalized to focal length or to sensor half width?) double distortionB, // r^3 double distortionC, // r^2 double px0, // center of the lens on the sensor, pixels double py0, // center of the lens on the sensor, pixels double channelWeightDefault) { this.azimuth = azimuth; this.radius = radius; this.height = height; this.phi = phi; this.theta = theta; this.psi = psi; this.focalLength = focalLength; this.pixelSize = pixelSize; //um this.distortionRadius = distortionRadius; // mm - half width of the sensor this.distortionA8 = distortionA8; this.distortionA7 = distortionA7; this.distortionA6 = distortionA6; this.distortionA5 = distortionA5; this.distortionA = distortionA; // r^4 (normalized to focal length or to sensor half width?) this.distortionB = distortionB; // r^3 this.distortionC = distortionC; // r^2 this.px0 = px0; this.py0 = py0; this.channelWeightDefault = channelWeightDefault; this.channelWeightCurrent = this.channelWeightDefault; this.defectsXY = null; // pixel defects coordinates list (starting with worst) this.defectsDiff = null; // pixel defects value (diff from average of neighbors), matching defectsXY } // defects are not cloned! public EyesisSubCameraParameters clone() { return new EyesisSubCameraParameters(this.azimuth, this.radius, this.height, this.phi, this.theta, this.psi, this.focalLength, this.pixelSize, this.distortionRadius, this.distortionA8, this.distortionA7, this.distortionA6, this.distortionA5, this.distortionA, this.distortionB, this.distortionC, this.px0, this.py0, this.channelWeightDefault); } public void setProperties(String prefix, Properties properties) { properties.setProperty(prefix + "azimuth", this.azimuth + ""); properties.setProperty(prefix + "radius", this.radius + ""); properties.setProperty(prefix + "height", this.height + ""); properties.setProperty(prefix + "phi", this.phi + ""); properties.setProperty(prefix + "theta", this.theta + ""); properties.setProperty(prefix + "psi", this.psi + ""); properties.setProperty(prefix + "focalLength", this.focalLength + ""); properties.setProperty(prefix + "pixelSize", this.pixelSize + ""); properties.setProperty(prefix + "distortionRadius", this.distortionRadius + ""); properties.setProperty(prefix + "distortionA8", this.distortionA8 + ""); properties.setProperty(prefix + "distortionA7", this.distortionA7 + ""); properties.setProperty(prefix + "distortionA6", this.distortionA6 + ""); properties.setProperty(prefix + "distortionA5", this.distortionA5 + ""); properties.setProperty(prefix + "distortionA", this.distortionA + ""); properties.setProperty(prefix + "distortionB", this.distortionB + ""); properties.setProperty(prefix + "distortionC", this.distortionC + ""); properties.setProperty(prefix + "px0", this.px0 + ""); properties.setProperty(prefix + "py0", this.py0 + ""); properties.setProperty(prefix + "channelWeightDefault", this.channelWeightDefault + ""); } public void getProperties(String prefix, Properties properties) { if (properties.getProperty(prefix + "azimuth") != null) this.azimuth = Double.parseDouble(properties.getProperty(prefix + "azimuth")); if (properties.getProperty(prefix + "radius") != null) this.radius = Double.parseDouble(properties.getProperty(prefix + "radius")); if (properties.getProperty(prefix + "height") != null) this.height = Double.parseDouble(properties.getProperty(prefix + "height")); if (properties.getProperty(prefix + "phi") != null) this.phi = Double.parseDouble(properties.getProperty(prefix + "phi")); if (properties.getProperty(prefix + "theta") != null) this.theta = Double.parseDouble(properties.getProperty(prefix + "theta")); if (properties.getProperty(prefix + "psi") != null) this.psi = Double.parseDouble(properties.getProperty(prefix + "psi")); if (properties.getProperty(prefix + "focalLength") != null) this.focalLength = Double.parseDouble(properties.getProperty(prefix + "focalLength")); if (properties.getProperty(prefix + "pixelSize") != null) this.pixelSize = Double.parseDouble(properties.getProperty(prefix + "pixelSize")); if (properties.getProperty(prefix + "distortionRadius") != null) this.distortionRadius = Double.parseDouble(properties.getProperty(prefix + "distortionRadius")); if (properties.getProperty(prefix + "distortionA8") != null) this.distortionA8 = Double.parseDouble(properties.getProperty(prefix + "distortionA8")); if (properties.getProperty(prefix + "distortionA7") != null) this.distortionA7 = Double.parseDouble(properties.getProperty(prefix + "distortionA7")); if (properties.getProperty(prefix + "distortionA6") != null) this.distortionA6 = Double.parseDouble(properties.getProperty(prefix + "distortionA6")); if (properties.getProperty(prefix + "distortionA5") != null) this.distortionA5 = Double.parseDouble(properties.getProperty(prefix + "distortionA5")); if (properties.getProperty(prefix + "distortionA") != null) this.distortionA = Double.parseDouble(properties.getProperty(prefix + "distortionA")); if (properties.getProperty(prefix + "distortionB") != null) this.distortionB = Double.parseDouble(properties.getProperty(prefix + "distortionB")); if (properties.getProperty(prefix + "distortionC") != null) this.distortionC = Double.parseDouble(properties.getProperty(prefix + "distortionC")); if (properties.getProperty(prefix + "px0") != null) this.px0 = Double.parseDouble(properties.getProperty(prefix + "px0")); if (properties.getProperty(prefix + "py0") != null) this.py0 = Double.parseDouble(properties.getProperty(prefix + "py0")); if (properties.getProperty(prefix + "channelWeightDefault") != null) { this.channelWeightDefault = Double .parseDouble(properties.getProperty(prefix + "channelWeightDefault")); this.channelWeightCurrent = this.channelWeightDefault; } } public void setChannelWeightCurrent(double weight) { this.channelWeightCurrent = weight; } public double getChannelWeightCurrent() { return this.channelWeightCurrent; } public double getChannelWeightDefault() { return this.channelWeightDefault; } } /* gridGeometry: * [v][u][0] - x(mm) of the node(u,v), right (looking to the wall) - positive * [v][u][1] - y(mm) of the node(u,v), down - positive * [v][u][2] - z(mm) of the node(u,v), away (into the wall) - positive * [v][u][3] - mask 0.0 - outside of the pattern rectangle, 1.0 - on the pattern * RGB moved to Photometric * [v][u][4] - R-intensity (normalized to 1.0 full scale) * [v][u][5] - G-intensity * [v][u][6] - B-intensity * ... repeat 3,4,5,6 for different views of the pattern (from different points) */ public static class PatternParameters { public double patternWidth; // pattern full width in mm public double patternHeight; // pattern full height in mm public double patternHalfPeriod; // distance between opposite sign nodes public double patternTilt; // pattern tilt (degrees) - U clockwise from X-right (V clockwise from Y-down) public int[] viewMap = null; // should have public int numStations = 1; public int numberOfViews = 1; public double[][][] gridGeometry = null; // [v][u]{x,y,z,{"alpha", R-intensity, G--intensity, B--intensity} alpha=0 - no ghrid, 1 - grid} public double[][][] stationZCorr = null; // [v][u]{station0... stationN} - per-station addition to the gridGeometry[][][2] public double[] averageRGB = { 1.0, 1.0, 1.0 }; public int numGeometricChannels = 4; // x,y,z,mask public int numPhotometricChannels = 4; // r,g,b,a public int defaultNumberOfChannels = 26; // public int numLayers=7; // number of layers in gridGeometry public int U0 = 0; //39 public int V0 = 0; //36 public int debugLevel = 2; public boolean updateStatus = true; public String pathName = null; // path from which the grid was read/ where it was last saved to public double[][][][] photometricByView = null; // [numStation][numView]{a,r,g,b}[pixel] // public double [][][] photometricBySensor=null; public double[] patternErrors = null; // weighted RMS error for each pattern node, calculated public double[] patternErrorMask = null; // weighted RMS error for each pattern node, calculated public double[] patternErrorMaskSaved = null; // weighted RMS error for each pattern node, calculated public void initStationZCorr() { this.stationZCorr = new double[this.gridGeometry.length][this.gridGeometry[0].length][this.numStations]; for (int v = 0; v < this.stationZCorr.length; v++) for (int u = 0; u < this.stationZCorr[0].length; u++) for (int s = 0; s < this.stationZCorr[0][0].length; s++) { this.stationZCorr[v][u][s] = 0.0; } } public void resetStationZCorr() { this.stationZCorr = null; } public Rectangle getUVDimensions() { return new Rectangle(U0, V0, gridGeometry[0].length, gridGeometry.length); } public void setPatternErrors(double[] patternErrors) { this.patternErrors = patternErrors; } public double[] getPatternErrors() { return this.patternErrors; } public double[] getPatternErrorMask() { return this.patternErrorMask; } public double[] calculatePatternErrorMask(double maxRMS, double minRMS) { double[] mask = new double[this.patternErrors.length]; // null pointer double a = 1.0 / (maxRMS - minRMS); boolean binary = maxRMS == minRMS; for (int i = 0; i < this.patternErrors.length; i++) { if (Double.isNaN(this.patternErrors[i])) mask[i] = 0.0; else { if (binary) mask[i] = (this.patternErrors[i] >= maxRMS) ? 0.0 : 1.0; else { double x = a * (this.patternErrors[i] - minRMS); if (x <= 0) mask[i] = 1.0; else if (x >= 1.0) mask[i] = 0.0; else mask[i] = 1.0 + 2 * x * x * x - 3 * x * x; } } } this.patternErrorMask = mask; return mask; } public double[] expandPatternErrorMask() { Rectangle dimensions = getUVDimensions(); int[] dirs = { 1, dimensions.width + 1, dimensions.width, dimensions.width - 1, -1, -dimensions.width - 1, -dimensions.width, dimensions.width + 1 }; double[] mask = this.patternErrorMask.clone(); for (int v = 1; v < (dimensions.height - 1); v++) for (int u = 1; u < (dimensions.width - 1); u++) { int index = v * dimensions.width + u; double min = this.patternErrorMask[index]; for (int iDir = 0; iDir < dirs.length; iDir++) { if (this.patternErrorMask[index + dirs[iDir]] < min) min = this.patternErrorMask[index + dirs[iDir]]; } mask[index] = min; } this.patternErrorMask = mask; return this.patternErrorMask; } //patternErrorMaskSaved public void resetPatternErrorMask() { this.patternErrorMask = null; } public void savePatternErrorMask() { if (this.patternErrorMask != null) this.patternErrorMaskSaved = this.patternErrorMask.clone(); else this.patternErrorMaskSaved = null; } public void restorePatternErrorMask() { if (this.patternErrorMaskSaved != null) this.patternErrorMask = this.patternErrorMaskSaved.clone(); else this.patternErrorMask = null; } public double[] getSavedPatternErrorMask() { return this.patternErrorMaskSaved; } public PatternParameters(int[] viewMap, int numStations, double patternWidth, // pattern full width in mm double patternHeight, // pattern full height in mm double patternHalfPeriod, // distance between opposite sign nodes double patternTilt // pattern tilt (degrees) - U clockwise from X-right (V clockwise from Y-down) ) { this.numStations = numStations; this.patternWidth = patternWidth; this.patternHeight = patternHeight; this.patternHalfPeriod = patternHalfPeriod; this.patternTilt = patternTilt; setPhotometric(viewMap); calculateGridGeometryAndPhotometric(true); } public void updateNumStations(int numStations) { this.numStations = numStations; if (numStations == this.photometricByView.length) { return; } System.out.println("Updating pattern number of stations from " + this.photometricByView.length + " to " + numStations); double[][][][] photometricByViewCopy = this.photometricByView; this.photometricByView = new double[this.numStations][][][]; for (int i = 0; i < this.photometricByView.length; i++) { if (i < photometricByViewCopy.length) { this.photometricByView[i] = photometricByViewCopy[i]; } else { // deep clone last station int iSource = photometricByViewCopy.length - 1; this.photometricByView[i] = new double[photometricByViewCopy[iSource].length][][]; for (int j = 0; j < photometricByViewCopy[iSource].length; j++) { if (photometricByViewCopy[iSource][j] == null) { this.photometricByView[i][j] = null; } else { this.photometricByView[i][j] = new double[photometricByViewCopy[iSource][j].length][]; for (int k = 0; k < photometricByViewCopy[iSource][j].length; k++) { if (photometricByViewCopy[iSource][j][k] == null) { this.photometricByView[i][j][k] = null; } else { this.photometricByView[i][j][k] = photometricByViewCopy[iSource][j][k].clone(); } } } } } } } public void setPhotometric(int[] viewMap) { if (viewMap == null) { this.viewMap = null; } else { this.viewMap = viewMap.clone(); } setPhotometric(); } public void setPhotometric() { if (this.viewMap == null) { this.viewMap = new int[1]; this.viewMap[0] = 0; } int maxView = 0; for (int i = 0; i < this.viewMap.length; i++) if (this.viewMap[i] > maxView) maxView = this.viewMap[i]; this.numberOfViews = maxView + 1; this.photometricByView = new double[this.numStations][this.numberOfViews][4][]; } public void initDefaultChannels(int num) { this.viewMap = new int[num]; for (int i = 0; i < num; i++) this.viewMap[i] = (i < 24) ? 0 : 1; this.numberOfViews = 2; } public double[][] getPhotometricByView(int stationNumber, int nView) { if (stationNumber >= this.photometricByView.length) stationNumber = this.photometricByView.length - 1; if (nView >= this.photometricByView[stationNumber].length) nView = this.photometricByView[stationNumber].length - 1; return this.photometricByView[stationNumber][nView]; } public double[][] getPhotometricBySensor(int stationNumber, int nSensor) { if (getNumStations() <= stationNumber) updateNumStations(stationNumber + 1); // if (stationNumber>=this.photometricByView.length) stationNumber=this.photometricByView.length-1; int isens = nSensor; if (nSensor >= this.viewMap.length) { System.out.println("nSensor=" + nSensor + " this.viewMap.length=" + this.viewMap.length + " this.photometricByView.length=" + this.photometricByView.length); nSensor = this.viewMap.length - 1; } int nView = this.viewMap[nSensor]; if (nView >= this.photometricByView[stationNumber].length) { System.out.println("nSensor was " + isens + ", nView=" + nView + " this.photometricByView[" + stationNumber + "].length=" + this.photometricByView[stationNumber].length); nView = this.photometricByView.length - 1; } return this.photometricByView[stationNumber][nView]; } // public int getNumViews(){return this.photometricByView[0].length;} public int getNumViews() { return this.numberOfViews; } public int getNumStations() { return this.numStations; } public void setNumStations(int numStations) { this.numStations = numStations; } public int[] getViewMap() { return this.viewMap; } public int getNumGeometricChannels() { return this.numGeometricChannels; } public int getNumPhotometricChannels() { return this.numPhotometricChannels; } public PatternParameters(int[] viewMap, double patternWidth, // pattern full width in mm double patternHeight, // pattern full height in mm double patternHalfPeriod, // distance between opposite sign nodes double patternTilt, // pattern tilt (degrees) - U clockwise from X-right (V clockwise from Y-down) double[] averageRGB) { this.patternWidth = patternWidth; this.patternHeight = patternHeight; this.patternHalfPeriod = patternHalfPeriod; this.patternTilt = patternTilt; this.averageRGB = averageRGB.clone(); setPhotometric(viewMap); calculateGridGeometryAndPhotometric(true); } public PatternParameters clone() { PatternParameters patternParameters = new PatternParameters(this.viewMap, this.patternWidth, // pattern full width in mm this.patternHeight, // pattern full height in mm this.patternHalfPeriod, // distance between opposite sign nodes this.patternTilt, // pattern tilt (degrees) - U clockwise from X-right (V clockwise from Y-down) this.averageRGB); patternParameters.debugLevel = this.debugLevel; patternParameters.updateStatus = this.updateStatus; patternParameters.pathName = this.pathName; return patternParameters; } public ImagePlus saveGridAsImageStack(String title, String path) { ImagePlus imp = getGridAsImageStack(title); if (imp == null) return null; FileSaver fs = new FileSaver(imp); if (updateStatus) IJ.showStatus("Saving grid " + path); if (imp.getStackSize() > 1) fs.saveAsTiffStack(path); else fs.saveAsTiff(path); this.pathName = path; if (this.debugLevel > 0) System.out.println("Pattern saved as " + this.pathName); return imp; } public String selectAndSave(boolean smart, String defaultPath) { String[] extensions = { ".grid-tiff", "-grid.tiff" }; CalibrationFileManagement.MultipleExtensionsFileFilter parFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter( "", extensions, "Pattern grid *.grid-tiff files"); String pathname = CalibrationFileManagement.selectFile(smart, true, "Save Pattern Grid Geometry", "Save", parFilter, (defaultPath == null) ? this.pathName : defaultPath); //String defaultPath if ((pathname != null) && (pathname.length() != 0)) saveGridAsImageStack("Pattern Grid", pathname); return pathname; } public String selectAndRestore(boolean smart, String defaultPath, int numStations) { String[] extensions = { ".grid-tiff", "-grid.tiff" }; CalibrationFileManagement.MultipleExtensionsFileFilter parFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter( "", extensions, "Pattern grid *.grid-tiff files"); String pathname = CalibrationFileManagement.selectFile(smart, false, "Restore Pattern Grid Geometry", "Restore", parFilter, (defaultPath == null) ? this.pathName : defaultPath); //String defaultPath if ((pathname == null) || (pathname == "")) return null; setNumStations(numStations); setGridFromImageStack(pathname); return pathname; } public void setGridFromImageStack(String path) { Opener opener = new Opener(); ImagePlus imp = opener.openImage("", path); if (imp == null) { String msg = "Failed to read grid geometry file " + path; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } (new JP46_Reader_camera(false)).decodeProperiesFromInfo(imp); setGridFromImageStack(imp); this.pathName = path; if (this.debugLevel > 0) System.out.println("Opened " + path + " as a stack of the pattern grid geometry"); } /** * Loads grid geomnetry (data in mm, X - left, Y - down, Z - into the wall) frome mult-slice * ImagePlus. Properties should be set or decoded from the info in the tiff file * @param imp - ImagePlus stack, containg x,y,z,alpha slices */ public void setGridFromImageStack(ImagePlus imp) { int indexAlpha = 3; if (imp == null) { String msg = "Grid image is null"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (imp.getStackSize() < 4) { String msg = "Expected >=4 slice image with grid geometry"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if ((imp.getProperty("U0") == null) || (imp.getProperty("V0") == null)) { String msg = "Properties \"U0\" and/or \"V0\" do not exist"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.U0 = Integer.parseInt((String) imp.getProperty("U0")); this.V0 = Integer.parseInt((String) imp.getProperty("V0")); // Other properties that are needed only during pattern generation: if (imp.getProperty("patternWidth") != null) this.patternWidth = Double.parseDouble((String) imp.getProperty("patternWidth")); if (imp.getProperty("patternHeight") != null) this.patternHeight = Double.parseDouble((String) imp.getProperty("patternHeight")); if (imp.getProperty("patternHalfPeriod") != null) this.patternHalfPeriod = Double.parseDouble((String) imp.getProperty("patternHalfPeriod")); if (imp.getProperty("patternTilt") != null) this.patternTilt = Double.parseDouble((String) imp.getProperty("patternTilt")); if (imp.getProperty("AverageRed") != null) this.averageRGB[0] = Double.parseDouble((String) imp.getProperty("AverageRed")); if (imp.getProperty("AverageGreen") != null) this.averageRGB[1] = Double.parseDouble((String) imp.getProperty("AverageGreen")); if (imp.getProperty("AverageBlue") != null) this.averageRGB[2] = Double.parseDouble((String) imp.getProperty("AverageBlue")); int numZCorr = 0; if (imp.getProperty("numZCorr") != null) numZCorr = Integer.parseInt((String) imp.getProperty("numZCorr")); // if (imp.getProperty("numStations")!=null) this.numStations=Integer.parseInt((String) imp.getProperty("numStations")); int width = imp.getWidth(); int height = imp.getHeight(); ImageStack stack = imp.getStack(); if (stack == null) { String msg = "Expected a image stack with grid geometry"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } float[][] pixels = new float[stack.getSize()][]; for (int i = 0; i < pixels.length; i++) pixels[i] = (float[]) stack.getPixels(i + 1); if (this.debugLevel > 3) { System.out.println("setGridFromImageStack() width=" + width + ", height=" + height + ", pixels[0].length=" + pixels[0].length); } this.gridGeometry = new double[height][width][getNumGeometricChannels()]; // x,y,z, alpha boolean geometryMaskOnly = (pixels.length == getNumGeometricChannels()); boolean singleNoAlpha = (pixels.length == 7); int totalNumViews = (geometryMaskOnly || singleNoAlpha) ? 1 : ((pixels.length - getNumGeometricChannels() - numZCorr) / getNumPhotometricChannels()); int fileNumStations = totalNumViews / getNumViews(); // keep current number of stations int length = height * width; for (int v = 0; v < height; v++) for (int u = 0; u < width; u++) for (int n = 0; n < getNumGeometricChannels(); n++) { // x,y,z, alpha this.gridGeometry[v][u][n] = pixels[n][v * width + u]; } if (fileNumStations != getNumStations()) { if (this.debugLevel > 0) { System.out.println("File has " + totalNumViews + " photometric slices, expected " + (getNumStations() * getNumViews()) + " (" + getNumStations() + " stations, " + getNumViews() + " views ), skipping loading photometric data"); } /// TODO: Fix me! return; // } if (numZCorr > 0) { if (numZCorr == getNumStations()) { if (this.debugLevel > 0) { System.out.println("Loading zCorr data: " + getNumStations() + " slices"); } this.stationZCorr = new double[height][width][numZCorr]; for (int v = 0; v < height; v++) for (int u = 0; u < width; u++) for (int n = 0; n < numZCorr; n++) { this.stationZCorr[v][u][n] = pixels[n + getNumGeometricChannels()][v * width + u]; } } else { System.out.println("File has " + numZCorr + " ZCorr slices, current number of stations is " + getNumStations() + ", skipping loading zCorr data (per-station pattern Z-correction from the average Z)"); } } if (this.debugLevel > 0) { System.out.println("Loading photometric data: " + (getNumStations() * getNumViews()) + " slices " + " (" + getNumStations() + " stations, " + getNumViews() + " vies )"); } this.photometricByView = new double[this.numStations][this.numberOfViews][getNumPhotometricChannels()][length]; // r,g,b,a for (int numStation = 0; numStation < this.numStations; numStation++) { int useNumStation = numStation; if ((useNumStation > 0) && (useNumStation >= fileNumStations)) useNumStation = fileNumStations - 1; for (int nView = 0; nView < getNumViews(); nView++) { for (int chn = 0; chn < getNumPhotometricChannels(); chn++) { int pixIndex = numGeometricChannels + numZCorr + (nView + getNumViews() * useNumStation) * getNumPhotometricChannels() + chn; if ((pixIndex >= pixels.length) && (chn > 2)) pixIndex = indexAlpha; // use mask for non-existent alpha if (pixIndex >= pixels.length) { for (int i = 0; i < length; i++) { this.photometricByView[useNumStation][nView][chn][i] = (pixels[indexAlpha][i] > 0.5) ? this.averageRGB[chn] : 0.0; // 0<=chn<=2 here OOB =3 } } else { for (int i = 0; i < length; i++) { this.photometricByView[useNumStation][nView][chn][i] = pixels[pixIndex][i]; //OOB12 } } } } } } //getNumPhotometricChannels() public ImagePlus getGridAsImageStack(String title) { if (this.gridGeometry == null) { String msg = "Grid geometry does not exist, nothing to convert"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } int width = this.gridGeometry[0].length; int height = this.gridGeometry.length; int length = height * width; int numZCorr = 0; if (this.stationZCorr != null) { for (int v = 0; (v < height) && (numZCorr == 0); v++) for (int u = 0; u < width; u++) if (this.stationZCorr[v][u] != null) { numZCorr = this.stationZCorr[v][u].length; break; } } // float [][]pixels=new float [this.gridGeometry[0][0].length][width*height]; float[][] pixels = new float[getNumGeometricChannels() + numZCorr + getNumViews() * getNumStations() * getNumPhotometricChannels()][length]; String[] titles = new String[pixels.length]; String[] geometricTitles = { "X", "Y", "Z", "Mask" }; String[] zCorrTitles = new String[numZCorr]; for (int i = 0; i < numZCorr; i++) zCorrTitles[i] = "dZ" + i; String[] photometricTitles = { "red", "green", "blue", "alpha" }; int index = 0; for (int v = 0; v < height; v++) for (int u = 0; u < width; u++) { for (int n = 0; n < getNumGeometricChannels(); n++) { // should be 4==numGeometricChannels pixels[n][index] = (float) this.gridGeometry[v][u][n]; } for (int n = 0; n < numZCorr; n++) { // should be 4==numGeometricChannels pixels[n + getNumGeometricChannels()][index] = (this.stationZCorr[v][u] != null) ? ((float) this.stationZCorr[v][u][n]) : 0.0f; } index++; } for (int n = 0; n < getNumGeometricChannels(); n++) titles[n] = geometricTitles[n]; for (int numStation = 0; numStation < this.numStations; numStation++) { for (int nView = 0; nView < getNumViews(); nView++) { for (int chn = 0; chn < getNumPhotometricChannels(); chn++) { // int pixIndex=getNumGeometricChannels()+chn*this.getNumPhotometricChannels()(); int pixIndex = getNumGeometricChannels() + numZCorr + (nView + numStation * getNumViews()) * getNumPhotometricChannels() + chn; for (int i = 0; i < length; i++) pixels[pixIndex][i] = (float) this.photometricByView[numStation][nView][chn][i]; //OOB 8 //oob 12 titles[pixIndex] = photometricTitles[chn] + nView; } } } for (int i = 0; i < numZCorr; i++) { titles[getNumGeometricChannels() + i] = zCorrTitles[i]; } 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); imp.setProperty("patternWidth", "" + this.patternWidth); imp.setProperty("patternHeight", "" + this.patternHeight); imp.setProperty("patternHalfPeriod", "" + this.patternHalfPeriod); imp.setProperty("patternTilt", "" + this.patternTilt); imp.setProperty("U0", "" + this.U0); imp.setProperty("V0", "" + this.V0); imp.setProperty("AverageRed", "" + this.averageRGB[0]); imp.setProperty("AverageGreen", "" + this.averageRGB[1]); imp.setProperty("AverageBlue", "" + this.averageRGB[2]); if (numZCorr > 0) imp.setProperty("numZCorr", "" + numZCorr); (new JP46_Reader_camera(false)).encodeProperiesToInfo(imp); imp.getProcessor().resetMinAndMax(); return imp; } public void applyGridCorrection(double[][] gridCorr) { applyGridCorrection(gridCorr, 1.0); } /** * Apply X,Y,Z correction to the current grid geometry * @param gridCorr array [4][width*height] of the corrections to grid geometry (x,y,z,weight). * Weight is not used, just for information (1.0 - one image file used with full weight) * @param scale scale correction */ public void applyGridCorrection(double[][] gridCorr, double scale) { if (this.gridGeometry == null) { String msg = "Grid geometry does not exist, nothing to apply correction to"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } int height = this.gridGeometry.length; int width = this.gridGeometry[0].length; if ((gridCorr == null) || (gridCorr.length != 4) || (gridCorr[0].length != (width * height))) { String msg = "Correction is null or does not match pattern grid geometry"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } for (int v = 0; v < height; v++) for (int u = 0; u < width; u++) if (this.gridGeometry[v][u][3] > 0.0) { // only apply to defined grid int vu = v * width + u; for (int n = 0; n < 3; n++) this.gridGeometry[v][u][n] += scale * gridCorr[n][vu]; } } public void applyZGridCorrection(double[][] gridZCorr3d, double scale) { int height = this.gridGeometry.length; int width = this.gridGeometry[0].length; if ((gridZCorr3d == null) || (gridZCorr3d.length != this.numStations) || (gridZCorr3d[0].length != (width * height))) { String msg = "Correction is null or does not match pattern grid geometry"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.stationZCorr = new double[height][width][this.numStations]; for (int v = 0; v < height; v++) for (int u = 0; u < width; u++) { int vu = v * width + u; for (int s = 0; s < this.numStations; s++) { this.stationZCorr[v][u][s] = scale * gridZCorr3d[s][vu]; } } } public void setProperties(String prefix, Properties properties) { properties.setProperty(prefix + "patternWidth", this.patternWidth + ""); properties.setProperty(prefix + "patternHeight", this.patternHeight + ""); properties.setProperty(prefix + "patternHalfPeriod", this.patternHalfPeriod + ""); properties.setProperty(prefix + "patternTilt", this.patternTilt + ""); properties.setProperty(prefix + "averageRGB_0", this.averageRGB[0] + ""); properties.setProperty(prefix + "averageRGB_1", this.averageRGB[1] + ""); properties.setProperty(prefix + "averageRGB_2", this.averageRGB[2] + ""); if (this.viewMap != null) { properties.setProperty(prefix + "viewMap_length", this.viewMap.length + ""); for (int i = 0; i < this.viewMap.length; i++) { properties.setProperty(prefix + "viewMap_" + i, this.viewMap[i] + ""); } } // this.viewMap; } public void getProperties(String prefix, Properties properties) { if (properties.getProperty(prefix + "patternWidth") != null) this.patternWidth = Double.parseDouble(properties.getProperty(prefix + "patternWidth")); if (properties.getProperty(prefix + "patternHeight") != null) this.patternHeight = Double.parseDouble(properties.getProperty(prefix + "patternHeight")); if (properties.getProperty(prefix + "patternHalfPeriod") != null) this.patternHalfPeriod = Double.parseDouble(properties.getProperty(prefix + "patternHalfPeriod")); if (properties.getProperty(prefix + "patternTilt") != null) this.patternTilt = Double.parseDouble(properties.getProperty(prefix + "patternTilt")); if (properties.getProperty(prefix + "averageRGB_0") != null) this.averageRGB[0] = Double.parseDouble(properties.getProperty(prefix + "averageRGB_0")); if (properties.getProperty(prefix + "averageRGB_1") != null) this.averageRGB[1] = Double.parseDouble(properties.getProperty(prefix + "averageRGB_1")); if (properties.getProperty(prefix + "averageRGB_2") != null) this.averageRGB[2] = Double.parseDouble(properties.getProperty(prefix + "averageRGB_2")); if (properties.getProperty(prefix + "viewMap_length") != null) { this.viewMap = new int[Integer.parseInt(properties.getProperty(prefix + "viewMap_length"))]; for (int i = 0; i < this.viewMap.length; i++) { this.viewMap[i] = 0; if (properties.getProperty(prefix + "viewMap_" + i) != null) this.viewMap[i] = Integer.parseInt(properties.getProperty(prefix + "viewMap_" + i)); } } setPhotometric(); calculateGridGeometryAndPhotometric(true); // may need to setup this.photometricBySensor } public boolean showDialog() { GenericDialog gd = new GenericDialog("Initial Wall pattern parameters"); gd.addNumericField("Pattern full width", this.patternWidth, 1, 6, "mm"); // pattern full width in mm gd.addNumericField("Pattern full height", this.patternHeight, 1, 6, "mm"); // pattern full width in mm gd.addNumericField("Distance between opposite sign nodes", this.patternHalfPeriod, 4, 8, "mm"); // istance between opposite sign nodes in mm gd.addNumericField("Pattern tilt (clockwise)", this.patternTilt, 1, 5, "degrees"); // pattern tilt (degrees) - U clockwise from X-right (V clockwise from Y-down) gd.addNumericField("Average grid RED (1.0 for white)", this.averageRGB[0], 3, 5, "x"); // gd.addNumericField("Average grid GREEN (1.0 for white)", this.averageRGB[1], 3, 5, "x"); // gd.addNumericField("Average grid BLUE (1.0 for white)", this.averageRGB[2], 3, 5, "x"); // gd.addNumericField("Number of sensors (>24 - two groups, 0 - do not change)", this.defaultNumberOfChannels, 0); // gd.addMessage("Pressing OK will recalculate grid and clear current grid calibration"); gd.showDialog(); if (gd.wasCanceled()) return false; this.patternWidth = gd.getNextNumber(); this.patternHeight = gd.getNextNumber(); this.patternHalfPeriod = gd.getNextNumber(); this.patternTilt = gd.getNextNumber(); this.averageRGB[0] = gd.getNextNumber(); this.averageRGB[1] = gd.getNextNumber(); this.averageRGB[2] = gd.getNextNumber(); int numberOfChannels = (int) gd.getNextNumber(); if (numberOfChannels > 0) { initDefaultChannels(numberOfChannels); // setPhotometric(); } setPhotometric(); calculateGridGeometryAndPhotometric(true); return true; } /** * Calculate pattern x,y,z==0 and alpha (1.0 - inside, 0.0 - outside) for the grid * @param resetAll - if true - reset all grid info, if false - only recalculate mask and reset flat field info, keep distortions */ public void calculateGridGeometryAndPhotometric(boolean resetAll) { // this.photometricByView should be initialized int indexAlpha = 3; int indexMask = 3; double cosA = Math.cos(this.patternTilt / 180 * Math.PI); double sinA = Math.sin(this.patternTilt / 180 * Math.PI); double halfWidth = 0.5 * this.patternWidth; double halfHeight = 0.5 * this.patternHeight; double[][] uv = { { (halfWidth * cosA + halfHeight * sinA) / this.patternHalfPeriod, (-halfWidth * sinA + halfHeight * cosA) / this.patternHalfPeriod }, { (halfWidth * cosA - halfHeight * sinA) / this.patternHalfPeriod, (-halfWidth * sinA - halfHeight * cosA) / this.patternHalfPeriod } }; double[] maxUV = { Math.max(Math.abs(uv[0][0]), Math.abs(uv[1][0])), Math.max(Math.abs(uv[0][1]), Math.abs(uv[1][1])) }; this.U0 = (int) Math.ceil(maxUV[0]); this.V0 = (int) Math.ceil(maxUV[1]); resetAll |= (this.gridGeometry == null); double x, y; // in any case int len = (2 * this.U0 + 1) * (2 * this.V0 + 1); for (int station = 0; station < getNumStations(); station++) { for (int i = 0; i < getNumViews(); i++) { for (int chn = 0; chn < this.getNumPhotometricChannels(); chn++) { this.photometricByView[station][i][chn] = new double[len]; // r,g,b,alpha } } } if (resetAll) { for (int station = 0; station < getNumStations(); station++) { for (int i = 0; i < getNumViews(); i++) { for (int chn = 0; chn < this.getNumPhotometricChannels(); chn++) { this.photometricByView[station][i][chn] = new double[len]; // r,g,b,alpha for (int j = 0; j < len; j++) { this.photometricByView[station][i][chn][j] = 0.0; } } } } this.gridGeometry = new double[2 * this.V0 + 1][2 * this.U0 + 1][getNumGeometricChannels()]; // without resetAll all properties should be the same for (int v = -this.V0; v <= this.V0; v++) for (int u = -this.U0; u <= this.U0; u++) { x = (u * cosA - v * sinA) * this.patternHalfPeriod; y = (u * sinA + v * cosA) * this.patternHalfPeriod; boolean inGrid = ((x >= -halfWidth) && (x <= halfWidth) && (y >= -halfHeight) && (y <= halfHeight)); this.gridGeometry[v + this.V0][u + this.U0][0] = x; this.gridGeometry[v + this.V0][u + this.U0][1] = y; this.gridGeometry[v + this.V0][u + this.U0][2] = 0.0; this.gridGeometry[v + this.V0][u + this.U0][3] = inGrid ? 1.0 : 0.0; // common for all views - to be combined with per-view? } } // always - copy mask to alpha for each view int index = 0; for (int v = -this.V0; v <= this.V0; v++) for (int u = -this.U0; u <= this.U0; u++) { for (int station = 0; station < getNumStations(); station++) { for (int nView = 0; nView < getNumViews(); nView++) { this.photometricByView[station][nView][indexAlpha][index] = this.gridGeometry[v + this.V0][u + this.U0][indexMask]; for (int c = 0; c < 3; c++) { this.photometricByView[station][nView][c][index] = averageRGB[c]; } } } index++; } } public int[] uvIndicesToUV(int u1, int v1) { if ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length) || (this.gridGeometry[v1][u1][3] == 0)) return null; int[] iUV = { u1 - this.U0, v1 - this.V0 }; return iUV; } public double[] getXYZM(int u, int v, boolean verbose, int station) { // u=0,v=0 - center! int u1 = u + this.U0; int v1 = v + this.V0; if ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length) || (this.gridGeometry[v1][u1][3] == 0)) { if ((this.debugLevel > 1) && verbose && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point u=" + u + ",v=" + v + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; //IJ.showMessage("Error",msg); System.out.println(msg); } return null; } if (this.stationZCorr == null) return this.gridGeometry[v1][u1]; double[] result = this.gridGeometry[v1][u1].clone(); // use lower station if grid file does not have current int useStation = (this.stationZCorr[v1][u1].length > station) ? station : (this.stationZCorr[v1][u1].length - 1); result[2] += this.stationZCorr[v1][u1][useStation]; return result; // return this.gridGeometry[v1][u1]; } public int getGridIndex(int u, int v) { // u=0,v=0 - center! int u1 = u + this.U0; int v1 = v + this.V0; if ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length) || (this.gridGeometry[v1][u1][3] == 0)) { return -1; } return u1 + this.gridGeometry[0].length * v1; } public double[] getXYZM(int u, int v, int station) { // u=0,v=0 - center! int u1 = u + this.U0; int v1 = v + this.V0; if ((this.debugLevel > 1) && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point u=" + u + ",v=" + v + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (this.stationZCorr == null) return this.gridGeometry[v1][u1]; double[] result = this.gridGeometry[v1][u1].clone(); result[2] += this.stationZCorr[v1][u1][station]; return result; } public double[] getXYZMAverage(int u, int v) { // u=0,v=0 - center! int u1 = u + this.U0; int v1 = v + this.V0; if ((this.debugLevel > 1) && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point u=" + u + ",v=" + v + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.gridGeometry[v1][u1]; } public double getZCorr(int u, int v, int station) { // u=0,v=0 - center! if (this.stationZCorr == null) return 0.0; int u1 = u + this.U0; int v1 = v + this.V0; if ((this.debugLevel > 1) && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point u=" + u + ",v=" + v + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.stationZCorr[v1][u1][station]; } public double[] getXYZMAverage(int vu) { // u=0,v=0 - center! int width = this.gridGeometry[0].length; int u1 = vu % width; int v1 = vu / width; if ((this.debugLevel > 1) && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point vu=" + vu + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.gridGeometry[v1][u1]; } public double getZCorr(int vu, int station) { // u=0,v=0 - center! if (this.stationZCorr == null) return 0.0; int width = this.gridGeometry[0].length; int u1 = vu % width; int v1 = vu / width; if ((this.debugLevel > 1) && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point vu=" + vu + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.stationZCorr[v1][u1][station]; } /** * Return grid geometry and photometics: X,Y,Z,mask,R,G,B,Alpha * @param u signed grid U (0 in the center) * @param v signed grid V (0 in the center) * @param station station number * @param channel channel (sensor) number * @param verbose report out of grid * @return null if out of grid, otherwise X,Y,Z,mask (binary),R (~0.5..1.2),G,B,alpha (0.0..1.0) */ public double[] getXYZMP(int u, int v, int station, int channel, boolean verbose) { // u=0,v=0 - center! int u1 = u + this.U0; int v1 = v + this.V0; if ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length) || (this.gridGeometry[v1][u1][3] == 0)) { if ((this.debugLevel > 1) && verbose && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point u=" + u + ",v=" + v + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; //IJ.showMessage("Error",msg); System.out.println(msg); } return null; } int index = u1 + v1 * this.gridGeometry[0].length; if (getNumStations() <= station) updateNumStations(station + 1); int nView = this.viewMap[channel]; if (nView >= this.photometricByView[station].length) { // OOB 1// NUll pointer - need to run F-field first? nView = this.photometricByView.length - 1; } double[] result = { // null this.gridGeometry[v1][u1][0], this.gridGeometry[v1][u1][1], this.gridGeometry[v1][u1][2] + ((this.stationZCorr != null) ? this.stationZCorr[v1][u1][station] : 0.0), // per-station correction this.gridGeometry[v1][u1][3], this.photometricByView[station][nView][0][index], this.photometricByView[station][nView][1][index], this.photometricByView[station][nView][2][index], this.photometricByView[station][nView][3][index] }; return result; } public double[] getXYZMPE(int u, int v, int station, int channel, boolean verbose) { // u=0,v=0 - center! int u1 = u + this.U0; int v1 = v + this.V0; if ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length) || (this.gridGeometry[v1][u1][3] == 0)) { if ((this.debugLevel > 1) && verbose && ((v1 < 0) || (u1 < 0) || (v1 >= this.gridGeometry.length) || (u1 >= this.gridGeometry[0].length))) { String msg = "Requested (acquired) grid (point u=" + u + ",v=" + v + ") is outside of the physical grid [" + (-this.U0) + "..." + (this.gridGeometry[0].length - this.U0 - 1) + "]x[" + (-this.V0) + "..." + (this.gridGeometry.length - this.V0 - 1) + "]"; //IJ.showMessage("Error",msg); System.out.println(msg); } return null; } int index = u1 + v1 * this.gridGeometry[0].length; if (getNumStations() <= station) updateNumStations(station + 1); int nView = this.viewMap[channel]; int useStation = (this.stationZCorr != null) ? ((this.stationZCorr[v1][u1].length > station) ? station : (this.stationZCorr[v1][u1].length - 1)) : 0; if (nView >= this.photometricByView[station].length) { // OOB 1// NUll pointer - need to run F-field first? (oob1 when grid had less than now nView = this.photometricByView.length - 1; } double[] result = { // null this.gridGeometry[v1][u1][0], this.gridGeometry[v1][u1][1], // this.gridGeometry[v1][u1][2]+((this.stationZCorr!=null)?this.stationZCorr[v1][u1][station]:0.0), // per-station correction this.gridGeometry[v1][u1][2] + ((this.stationZCorr != null) ? this.stationZCorr[v1][u1][useStation] : 0.0), // per-station correction this.gridGeometry[v1][u1][3], this.photometricByView[station][nView][0][index], this.photometricByView[station][nView][1][index], this.photometricByView[station][nView][2][index], this.photometricByView[station][nView][3][index], (this.patternErrorMask == null) ? 1.0 : this.patternErrorMask[index] }; return result; } public double[][][] getGeometry() { return this.gridGeometry; } } public class LensDistortionParameters { /* * Hugin Rsrc=a*Rdest^4+b*Rdest^3+c*Rdest^2+d*Rdest; d=1-(a+b+c) */ // lens parameters (add more later) public double focalLength = 4.5; public double pixelSize = 2.2; //um public double distortionRadius = 2.8512; // mm - half width of the sensor public double distortionA8 = 0.0; // r^8 (normalized to focal length or to sensor half width?) public double distortionA7 = 0.0; // r^7 (normalized to focal length or to sensor half width?) public double distortionA6 = 0.0; // r^6 (normalized to focal length or to sensor half width?) public double distortionA5 = 0.0; // r^5 (normalized to focal length or to sensor half width?) public double distortionA = 0.0; // r^4 (normalized to focal length or to sensor half width?) public double distortionB = 0.0; // r^3 public double distortionC = 0.0; // r^2 // orientation/position parameters public double yaw = 0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top public double pitch = 0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up public double roll = 0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise public double x0 = 0; // lens axis from pattern center, mm (to the right) public double y0 = 0; // lens axis from pattern center, mm (down) public double z0 = 0; // lens axis from pattern center, mm (away from the camera) public double distance = 2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm public double px0 = 1296.0; // center of the lens on the sensor, pixels public double py0 = 968.0; // center of the lens on the sensor, pixels public boolean flipVertical; // acquired image is mirrored vertically (mirror used) public int debugLevel = 1; // was 2 // intermediate values public double phi, theta, psi, cPH, sPH, cTH, sTH, cPS, sPS; public double[][] rotMatrix = new double[3][3]; // includes mirroring for Y (target coordinates y- down, camera - y up) public LensDistortionParameters( // LensDistortionParameters lensDistortionParameters, boolean isTripod, double[][] interParameterDerivatives, //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) if null - just values, no derivatives double[] parVect, boolean[] mask, // calculate only selected derivatives (all parVect values are still int debugLevel // boolean calculateDerivatives // calculate this.interParameterDerivatives -derivatives array (false - just this.values) ) { this.debugLevel = debugLevel; lensCalcInterParamers( // changed name to move calcInterParamers method from enclosing class isTripod, interParameterDerivatives, //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) if null - just values, no derivatives parVect, mask // calculate only selected derivatives (all parVect values are still // boolean calculateDerivatives // calculate this.interParameterDerivatives -derivatives array (false - just this.values) ); } public LensDistortionParameters(double focalLength, double pixelSize, //um double distortionRadius, // mm double distortionA8, // r^8 double distortionA7, // r^7 double distortionA6, // r^6 double distortionA5, // r^5 double distortionA, // r^4 double distortionB, // r^3 double distortionC, // r^2 // orientation/position parameters double yaw, // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top double pitch, // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up double roll, // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise double x0, // lens axis from pattern center, mm (to the right) double y0, // lens axis from pattern center, mm (down) double z0, // lens axis from pattern center, mm (away from the camera) double distance, // distance from the lens input pupil to the pattern plane along the camera axis, mm double px0, // center of the lens on the senosr, pixels double py0, // center of the lens on the senosr, pixels boolean flipVertical // acquired image is mirrored vertically (mirror used) ) { setLensDistortionParameters(focalLength, pixelSize, distortionRadius, distortionA8, distortionA7, distortionA6, distortionA5, distortionA, distortionB, distortionC, yaw, pitch, roll, x0, y0, z0, distance, px0, py0, flipVertical); } public LensDistortionParameters() { this.focalLength = 4.5; this.pixelSize = 2.2; this.distortionRadius = 2.8512; this.distortionA8 = 0.0; this.distortionA7 = 0.0; this.distortionA6 = 0.0; this.distortionA5 = 0.0; this.distortionA = 0.0; this.distortionB = 0.0; this.distortionC = 0.0; this.yaw = 0.0; this.pitch = 0.0; this.roll = 0.0; this.x0 = 0.0; this.y0 = 0.0; this.z0 = 0.0; this.distance = 2360.0; this.px0 = 1296.0; this.py0 = 968.0; this.flipVertical = true; } public LensDistortionParameters clone() { return new LensDistortionParameters(this.focalLength, this.pixelSize, this.distortionRadius, this.distortionA8, this.distortionA7, this.distortionA6, this.distortionA5, this.distortionA, this.distortionB, this.distortionC, this.yaw, this.pitch, this.roll, this.x0, this.y0, this.z0, this.distance, this.px0, this.py0, this.flipVertical); } public void setLensDistortionParameters(double focalLength, double pixelSize, //um double distortionRadius, // mm double distortionA8, // r^7 double distortionA7, // r^6 double distortionA6, // r^5 double distortionA5, // r^4 double distortionA, // r^4 double distortionB, // r^3 double distortionC, // r^2 // orientation/position parameters double yaw, // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top double pitch, // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up double roll, // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise double x0, // lens axis from pattern center, mm (to the right) double y0, // lens axis from pattern center, mm (down) double z0, // lens axis from pattern center, mm (away) double distance, // distance from the lens input pupil to the pattern plane along the camera axis, mm double px0, // center of the lens on the sensor, pixels double py0, // center of the lens on the sensor, pixels boolean flipVertical // acquired image is mirrored vertically (mirror used) ) { this.focalLength = focalLength; this.pixelSize = pixelSize; this.distortionRadius = distortionRadius; this.distortionA8 = distortionA8; this.distortionA7 = distortionA7; this.distortionA6 = distortionA6; this.distortionA5 = distortionA5; this.distortionA = distortionA; this.distortionB = distortionB; this.distortionC = distortionC; this.yaw = yaw; this.pitch = pitch; this.roll = roll; this.x0 = x0; this.y0 = y0; this.z0 = z0; this.distance = distance; this.px0 = px0; this.py0 = py0; this.flipVertical = flipVertical; recalcCommons(); } public void setIntrincicFromSubcamera(EyesisSubCameraParameters pars) { setLensDistortionParameters(pars.focalLength, pars.pixelSize, //um pars.distortionRadius, // mm pars.distortionA8, // r^7 pars.distortionA7, // r^6 pars.distortionA6, // r^5 pars.distortionA5, // r^4 pars.distortionA, // r^4 pars.distortionB, // r^3 pars.distortionC, // r^2 // orientation/position parameters this.yaw, // (keep) angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top this.pitch, // (keep) angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up this.roll, // (keep) angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise this.x0, // (keep) lens axis from pattern center, mm (to the right) this.y0, // (keep) lens axis from pattern center, mm (down) this.z0, // (keep) lens axis from pattern center, mm (away) this.distance, // (keep) distance from the lens input pupil to the pattern plane along the camera axis, mm pars.px0, // center of the lens on the sensor, pixels pars.py0, // center of the lens on the sensor, pixels this.flipVertical // (keep) acquired image is mirrored vertically (mirror used) ); } public void setLensDistortionParameters(LensDistortionParameters ldp) { this.focalLength = ldp.focalLength; this.pixelSize = ldp.pixelSize; this.distortionRadius = ldp.distortionRadius; this.distortionA8 = ldp.distortionA8; this.distortionA7 = ldp.distortionA7; this.distortionA6 = ldp.distortionA6; this.distortionA5 = ldp.distortionA5; this.distortionA = ldp.distortionA; this.distortionB = ldp.distortionB; this.distortionC = ldp.distortionC; this.yaw = ldp.yaw; this.pitch = ldp.pitch; this.roll = ldp.roll; this.x0 = ldp.x0; this.y0 = ldp.y0; this.z0 = ldp.z0; this.distance = ldp.distance; this.px0 = ldp.px0; this.py0 = ldp.py0; this.flipVertical = ldp.flipVertical; recalcCommons(); } // just for debugging public void setLensDistortionParameters(LensDistortionParameters ldp, int index, // parameter to add delta, 1..13->14->17 double delta) { this.focalLength = ldp.focalLength + ((index == 7) ? delta : 0); this.pixelSize = ldp.pixelSize; this.distortionRadius = ldp.distortionRadius; this.distortionA8 = ldp.distortionA8 + ((index == 9) ? delta : 0); this.distortionA7 = ldp.distortionA7 + ((index == 10) ? delta : 0); this.distortionA6 = ldp.distortionA6 + ((index == 11) ? delta : 0); this.distortionA5 = ldp.distortionA5 + ((index == 12) ? delta : 0); this.distortionA = ldp.distortionA + ((index == 13) ? delta : 0); this.distortionB = ldp.distortionB + ((index == 14) ? delta : 0); this.distortionC = ldp.distortionC + ((index == 15) ? delta : 0); this.yaw = ldp.yaw + ((index == 1) ? delta : 0); this.pitch = ldp.pitch + ((index == 2) ? delta : 0); this.roll = ldp.roll + ((index == 3) ? delta : 0); this.x0 = ldp.x0 + ((index == 4) ? delta : 0); this.y0 = ldp.y0 + ((index == 5) ? delta : 0); this.z0 = ldp.z0 + ((index == 6) ? delta : 0); this.distance = ldp.distance + ((index == 8) ? delta : 0); this.px0 = ldp.px0 + ((index == 16) ? delta : 0); this.py0 = ldp.py0 + ((index == 17) ? delta : 0); this.flipVertical = ldp.flipVertical; recalcCommons(); } // recalculate common (point-invariant) intermediate values (cos, sin, rotation matrix) public void recalcCommons() { // public double phi, theta,psi,cPH,sPH,cTH,sTH,cPS,sPS; // public double [][] rotMatrix=new double[3][3]; this.phi = this.yaw * Math.PI / 180; this.theta = this.pitch * Math.PI / 180; this.psi = this.roll * Math.PI / 180; this.sPH = Math.sin(this.phi); this.cPH = Math.cos(this.phi); this.sTH = Math.sin(this.theta); this.cTH = Math.cos(this.theta); this.sPS = Math.sin(this.psi); this.cPS = Math.cos(this.psi); /* | Xe | | 0 | | cPS*cPH+sPS*sTH*sPH -sPS*cTH -cPS*sPH+sPS*sTH*cPH | | Xp | | Ye | = | 0 | + | sPS*cPH-cPS*sTH*sPH cPS*cTH -sPS*sPH-cPS*sTH*cPH | * |-Yp | | Ze | | dist | | cTH*sPH sTH cTH*cPH | | Zp | | PX | =(1000*f)/(Ze*Psz) * | Xe | + | PX0 | | PY | = | -Ye | | PY0 | Xe = (cPS*cPH+sPS*sTH*sPH)*Xp +sPS*cTH*Yp +(-cPS*sPH+sPS*sTH*cPH)*Zp Ye = (sPS*cPH-cPS*sTH*sPH)*Xp -cPS*cTH*Yp +(-sPS*sPH-cPS*sTH*cPH)*Zp Ze = (cTH*sPH)*Xp -sTH*Yp +( cTH*cPH)* Zp + dist theta==0, psi==0: Xe = (cPH)*Xp Ye = Yp Ze = cPH* Zp + dist (4) PXmmc =f/(cPH* Zp + dist)* (cPH)*Xp // mm, left from the lens axis intersection with the sensor dPXmmc/dphi= */ this.rotMatrix[0][0] = cPS * cPH + sPS * sTH * sPH; this.rotMatrix[0][1] = sPS * cTH; this.rotMatrix[0][2] = -cPS * sPH + sPS * sTH * cPH; this.rotMatrix[1][0] = sPS * cPH - cPS * sTH * sPH; this.rotMatrix[1][1] = -cPS * cTH; this.rotMatrix[1][2] = -sPS * sPH - cPS * sTH * cPH; this.rotMatrix[2][0] = cTH * sPH; this.rotMatrix[2][1] = -sTH; this.rotMatrix[2][2] = cTH * cPH; if (this.debugLevel > 2) { System.out.println("recalcCommons():this.rotMatrix:"); (new Matrix(this.rotMatrix)).print(10, 5); } } private String[][] descriptions = { { "distance", "Distance from the intersection of the lens axis with z=0 target plane to the camera lens entrance pupil", "mm", "e" }, { "x0", "Lens axis from pattern center, (to the right)", "mm", "e" }, { "y0", "Lens axis from pattern center, (down)", "mm", "e" }, { "yaw", "Angle from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top", "degrees", "e" }, { "pitch", "Angle from perpendicular to the pattern, 0 - towards wall, positive - up", "degrees", "e" }, { "roll", "Angle around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise", "degrees", "e" }, { "focalLength", "Lens focal length", "mm", "i" }, { "px0", "horizontal (left to right) pixel number of the lens axis intersection with the sensor", "pix", "i" }, { "py0", "vertical (up to down) pixel number of the lens axis intersection with the sensor", "pix", "i" }, { "distortionA8", "Lens distortion coefficient for r^8 (r0=half sensor width)", "r0", "i" }, { "distortionA7", "Lens distortion coefficient for r^7 (r0=half sensor width)", "r0", "i" }, { "distortionA6", "Lens distortion coefficient for r^6 (r0=half sensor width)", "r0", "i" }, { "distortionA5", "Lens distortion coefficient for r^5 (r0=half sensor width)", "r0", "i" }, { "distortionA", "Lens distortion coefficient for r^4 (r0=half sensor width)", "r0", "i" }, { "distortionB", "Lens distortion coefficient for r^3 (r0=half sensor width)", "r0", "i" }, { "distortionC", "Lens distortion coefficient for r^2 (r0=half sensor width)", "r0", "i" } }; private int numberIntExtrisic(String type) { int num = 0; for (int i = 0; i < this.descriptions.length; i++) if (type.indexOf(descriptions[i][3].charAt(0)) >= 0) num++; return num; } /** * Verifies that the camera is looking towards the target * @return true if looking tio the target, false - if away */ public boolean isTargetVisible(boolean verbose) { if (verbose) System.out.println("isTargetVisible(): this.distance=" + this.distance + ", this.yaw=" + this.yaw + ", this.pitch=" + this.pitch); if (this.distance <= 0.0) return false; if (Math.cos(this.yaw * Math.PI / 180) < 0.0) return false; if (Math.cos(this.pitch * Math.PI / 180) < 0.0) return false; return true; } public double[] getExtrinsicVector() { double[] extVector = { this.distance, this.x0, this.y0, this.yaw, this.pitch, this.roll }; return extVector; } public double[] getIntrinsicVector() { double[] extVector = { this.focalLength, this.px0, this.py0, this.distortionA8, this.distortionA7, this.distortionA6, this.distortionA5, this.distortionA, this.distortionB, this.distortionC }; return extVector; } public double[] getAllVector() { double[] allVector = new double[getExtrinsicVector().length + getIntrinsicVector().length]; int index = 0; double[] extVector = getExtrinsicVector(); double[] intVector = getIntrinsicVector(); for (int i = 0; i < extVector.length; i++) allVector[index++] = extVector[i]; for (int i = 0; i < intVector.length; i++) allVector[index++] = intVector[i]; return allVector; } public void setAllVector(double[] vector) { if (vector.length != (getExtrinsicVector().length + getIntrinsicVector().length)) { String msg = "Parameter vector should have exactly" + (getExtrinsicVector().length + getIntrinsicVector().length) + " elements"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.distance = vector[0]; this.x0 = vector[1]; this.y0 = vector[2]; this.yaw = vector[3]; this.pitch = vector[4]; this.roll = vector[5]; this.focalLength = vector[6]; this.px0 = vector[7]; this.py0 = vector[8]; this.distortionA8 = vector[9]; this.distortionA7 = vector[10]; this.distortionA6 = vector[11]; this.distortionA5 = vector[12]; this.distortionA = vector[13]; this.distortionB = vector[14]; this.distortionC = vector[15]; recalcCommons(); } public String[] getExtrinsicNames() { return getDescriptionStrings("e", 0); } public String[] getExtrinsicDescriptions() { return getDescriptionStrings("e", 1); } public String[] getExtrinsicUnits() { return getDescriptionStrings("e", 2); } public String[] getIntrinsicNames() { return getDescriptionStrings("i", 0); } public String[] getIntrinsicDescriptions() { return getDescriptionStrings("i", 1); } public String[] getIntrinsicUnits() { return getDescriptionStrings("i", 2); } public String[] getAllNames() { return getDescriptionStrings("ei", 0); } public String[] getAllDescriptions() { return getDescriptionStrings("ei", 1); } public String[] getAllUnits() { return getDescriptionStrings("ei", 2); } public String[] getAllFlags() { return getDescriptionStrings("ei", 3); } private String[] getDescriptionStrings(String type, int var) { String[] s = new String[numberIntExtrisic(type)]; int num = 0; for (int i = 0; i < this.descriptions.length; i++) if (type.indexOf(descriptions[i][3].charAt(0)) >= 0) s[num++] = this.descriptions[i][var]; return s; } /* * Calculate pixel value of projection from the pattern point [xp,yp,zp] using current distortion/position parameters (1) Xe = (cPS*cPH+sPS*sTH*sPH)*(Xp-X0) +sPS*cTH*(Yp-Y0) +(-cPS*sPH+sPS*sTH*cPH)*(Zp-Z0) (2) Ye = (sPS*cPH-cPS*sTH*sPH)*(Xp-X0) -cPS*cTH*(Yp-Y0) +(-sPS*sPH-cPS*sTH*cPH)*(Zp-Z0) (3) Ze = (cTH*sPH)*(Xp-X0) -sTH*(Yp-Y0) +( cTH*cPH)* (Zp-Z0) + dist (4) PXmmc =f/Ze* Xe // mm, left from the lens axis intersection with the sensor (5) PYmmc =f/Ze* Ye // mm, up from the lens axis intersection with the sensor (6) r=sqrt(PXmmc^2+PYmmc^2) // distance from the image point to the lens axis intersection with the sensor (pinhole model) (7) kD=(Da*(r/r0)^3+Db*(r/r0)^2+Dc*(r/r0)^1+(1-Da-Db-Dc)) correction to the actual distance from the image point to the lens axis due to distortion (8) xDist = kD * PXmmc // horisontal distance (mm) from the lens axis on the sensor to the image point, mm (positive - right) (9) yDist = kD * PYmmc // vertical distance (mm) from the lens axis on the sensor to the image point, mm (positive - up) (10) PX = 1000/Psz*( xDist) + PX0 // horizontal pixel of the image (positive - right) (11) PY = 1000/Psz*(-yDist) + PY0 // vertical pixel of the image (positive - down) */ public double[] patternToPixels(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 ) { return calcPartialDerivatives(xp, yp, zp, false)[0]; } /* 0 public double x0=0; // lens axis from pattern center, mm (to the right) 1 public double y0=0; // lens axis from pattern center, mm (down) 2 public double distance=2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm 3 public double yaw=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top 4 public double pitch=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up 5 public double roll=0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise 6 public double focalLength=4.5; 7 public double px0=1296.0; // center of the lens on the sensor, pixels 8 public double py0=968.0; // center of the lens on the sensor, pixels public double distortionRadius= 2.8512; // mm - half width of the sensor 9 public double distortionA8=0.0; // r^8 (normalized to focal length or to sensor half width?) 10 public double distortionA7=0.0; // r^7 (normalized to focal length or to sensor half width?) 11 public double distortionA6=0.0; // r^6 (normalized to focal length or to sensor half width?) 12 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 13 public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) 14 public double distortionB=0.0; // r^3 15 public double distortionC=0.0; // r^2 */ /** * extract needed ones, and reorder partial derivatives to match calcInterParameters * @param srcDerivatives - values and 15 derivatives for px, py */ public double[][] reorderPartialDerivatives(double[][] srcDerivatives) { int[] order = { 4, // 0 public double x0=0; // lens axis from pattern center, mm (to the right) 5, // 1 public double y0=0; // lens axis from pattern center, mm (down) 8, // 2 public double distance=2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm 1, // 3 public double yaw=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top 2, // 4 public double pitch=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up 3, // 5 public double roll=0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise 7, // 6 public double focalLength=4.5; 16, // 7 public double px0=1296.0; // center of the lens on the sensor, pixels 17, // 8 public double py0=968.0; // center of the lens on the sensor, pixels // public double distortionRadius= 2.8512; // mm - half width of the sensor 9, // 9 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 10, // 10 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 11, // 11 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 12, // 12 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 13, // 13 public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) 14, // 14 public double distortionB=0.0; // r^3 15 // 15 public double distortionC=0.0; // r^2 }; double[][] result = new double[order.length][2]; for (int i = 0; i < order.length; i++) { result[i][0] = srcDerivatives[order[i]][0]; result[i][1] = srcDerivatives[order[i]][1]; } return result; } /** * Reorder to match the sequence of names - seems to be different :-( * @param srcDerivatives values and 15 derivatives for px, py * @return */ public double[][] reorderPartialDerivativesAsNames(double[][] srcDerivatives) { int[] order = { 8, // 2 public double distance=2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm 4, // 0 public double x0=0; // lens axis from pattern center, mm (to the right) 5, // 1 public double y0=0; // lens axis from pattern center, mm (down) 1, // 3 public double yaw=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top 2, // 4 public double pitch=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up 3, // 5 public double roll=0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise 7, // 6 public double focalLength=4.5; 16, // 7 public double px0=1296.0; // center of the lens on the sensor, pixels 17, // 8 public double py0=968.0; // center of the lens on the sensor, pixels 9, // 9 public double distortionA8=0.0; // r^5 (normalized to focal length or to sensor half width?) 10, // 10 public double distortionA7=0.0; // r^5 (normalized to focal length or to sensor half width?) 11, // 11 public double distortionA6=0.0; // r^5 (normalized to focal length or to sensor half width?) 12, // 12 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 13, // 13 public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) 14, // 14 public double distortionB=0.0; // r^3 15 // 15 public double distortionC=0.0; // r^2 }; double[][] result = new double[order.length][2]; for (int i = 0; i < order.length; i++) { result[i][0] = srcDerivatives[order[i]][0]; result[i][1] = srcDerivatives[order[i]][1]; } return result; } /** * Calculate lens center location in target coordinates * @return lens center coordinates */ public double[] getLensCenterCoordinates() { double[] p = { this.x0 - this.distance * this.rotMatrix[2][0], -this.y0 - this.distance * this.rotMatrix[2][1], // this.y0 - up? this.z0 - this.distance * this.rotMatrix[2][2] }; /* Matrix MR=new Matrix(this.rotMatrix); Matrix MRT=MR.transpose(); Matrix E=MR.times(MRT); System.out.println("===MR=="); MR.print(8, 5);System.out.println(""); System.out.println("===MRT=="); MRT.print(8, 5);System.out.println(""); System.out.println("===E==="); E.print(8, 5);System.out.println(""); System.out.println("x0="+IJ.d2s(this.x0,1)+" y0="+IJ.d2s(this.y0,1)+" z0="+IJ.d2s(this.z0,1)+" this.distance="+IJ.d2s(this.distance,1)); System.out.println("phi="+IJ.d2s(this.phi,4)+" theta="+IJ.d2s(this.theta,4)+" psi="+IJ.d2s(this.psi,4)); System.out.println("|"+IJ.d2s(this.rotMatrix[0][0],4)+" "+IJ.d2s(this.rotMatrix[0][1],4)+" "+IJ.d2s(this.rotMatrix[0][2],4)+"|"); System.out.println("|"+IJ.d2s(this.rotMatrix[1][0],4)+" "+IJ.d2s(this.rotMatrix[1][1],4)+" "+IJ.d2s(this.rotMatrix[1][2],4)+"|"); System.out.println("|"+IJ.d2s(this.rotMatrix[2][0],4)+" "+IJ.d2s(this.rotMatrix[2][1],4)+" "+IJ.d2s(this.rotMatrix[2][2],4)+"|"); */ return p; } /* * Calculate pixel value and partial derivatives for different parameters * of projection from the pattern point [xp,yp,zp] using current distortion/position parameters * [0][0] - pixel x - NaN if looking away * [1][ 0] - pixel y- NaN if looking away * [*][ 1] - pixel x[or y] partial derivative for phi (yaw) * [*][ 2] - pixel x[or y] partial derivative for theta (pitch) * [*][ 3] - pixel x[or y] partial derivative for psi (roll) * [*][ 4] - pixel x[or y] partial derivative for X0 (intersection of the lens axis with zp==Z0 plane of the target) * [*][ 5] - pixel x[or y] partial derivative for Y0 (intersection of the lens axis with zp==Z0 plane of the target) * [*][ 6] - pixel x[or y] partial derivative for Z0 (intersection of the lens axis with zp==Z0 plane of the target) - not used * [*][ 7] - pixel x[or y] partial derivative for f (focal length) * [*][ 8] - pixel x[or y] partial derivative for dist (distance from [X0,Y0,Z0] to the lens entrance pupil * [*][ 9] - pixel x[or y] partial derivative for Da8 (distortion coefficient for r^8) * [*][10] - pixel x[or y] partial derivative for Da7 (distortion coefficient for r^7) * [*][11] - pixel x[or y] partial derivative for Da6 (distortion coefficient for r^6) * [*][12] - pixel x[or y] partial derivative for Da5 (distortion coefficient for r^5) * [*][13] - pixel x[or y] partial derivative for Da (distortion coefficient for r^4) * [*][14] - pixel x[or y] partial derivative for Db (distortion coefficient for r^3) * [*][15] - pixel x[or y] partial derivative for Dc (distortion coefficient for r^2) * [*][16] - pixel x[or y] partial derivative for PX0 (lens axis on the sensor, horizontal, right,in pixels) * [*][17] - pixel x[or y] partial derivative for PY0 (lens axis on the sensor, vertical, down, in pixels) */ /* * TODO: minimaize calculations for individual {xp,yp,zp} */ public double[][] 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 double partDeriv[][] = new double[calculateAll ? 18 : 1][2]; // double [] XYZ= {xp-this.x0, yp-this.y0, zp-this.z0}; double[] XYZ = { xp - this.x0, yp + this.y0, zp - this.z0 }; double[] XeYeZe = { this.rotMatrix[0][0] * XYZ[0] + this.rotMatrix[0][1] * XYZ[1] + this.rotMatrix[0][2] * XYZ[2], this.rotMatrix[1][0] * XYZ[0] + this.rotMatrix[1][1] * XYZ[1] + this.rotMatrix[1][2] * XYZ[2], this.rotMatrix[2][0] * XYZ[0] + this.rotMatrix[2][1] * XYZ[1] + this.rotMatrix[2][2] * XYZ[2] + this.distance }; double[] PXYmmc = { this.focalLength / XeYeZe[2] * XeYeZe[0], this.focalLength / XeYeZe[2] * XeYeZe[1] }; double r = Math.sqrt(PXYmmc[0] * PXYmmc[0] + PXYmmc[1] * PXYmmc[1]); double rr = r / this.distortionRadius; // double kD=((this.distortionA*rr+this.distortionB)*rr+this.distortionC)*rr + 1.0-this.distortionA-this.distortionB-this.distortionC; double kD = ((((((this.distortionA8 * rr + this.distortionA7) * rr + this.distortionA6) * rr + this.distortionA5) * rr + this.distortionA) * rr + this.distortionB) * rr + this.distortionC) * rr + 1.0 - this.distortionA8 - this.distortionA7 - this.distortionA6 - this.distortionA5 - this.distortionA - this.distortionB - this.distortionC; double[] xyDist = { kD * PXYmmc[0], kD * PXYmmc[1] }; partDeriv[0][0] = 1000.0 / this.pixelSize * xyDist[0] + this.px0; partDeriv[0][1] = -1000.0 / this.pixelSize * xyDist[1] + this.py0; if (!calculateAll) { // TODO: Looking away from the target, trying only with no dervatives. Do the same for derivatives too? if (XeYeZe[2] < 0.0) { partDeriv[0][0] = Double.NaN; partDeriv[0][1] = Double.NaN; } return partDeriv; } double[][] dXeYeZe = new double[9][3]; //[14]; dXeYeZe[0][0] = XeYeZe[0]; dXeYeZe[0][1] = XeYeZe[1]; dXeYeZe[0][2] = XeYeZe[2]; // /dphi dXeYeZe[1][0] = (-cPS * sPH + sPS * sTH * cPH) * XYZ[0] + (-cPS * cPH - sPS * sTH * sPH) * XYZ[2]; dXeYeZe[1][1] = (-sPS * sPH - cPS * sTH * cPH) * XYZ[0] + (-sPS * cPH + cPS * sTH * sPH) * XYZ[2]; dXeYeZe[1][2] = (cTH * cPH) * XYZ[0] - cTH * sPH * XYZ[2]; // /dtheta dXeYeZe[2][0] = (sPS * cTH * sPH) * XYZ[0] - sPS * sTH * XYZ[1] + (sPS * cTH * cPH) * XYZ[2]; dXeYeZe[2][1] = (-cPS * cTH * sPH) * XYZ[0] + cPS * sTH * XYZ[1] + (-cPS * cTH * cPH) * XYZ[2]; dXeYeZe[2][2] = (-sTH * sPH) * XYZ[0] - cTH * XYZ[1] - sTH * cPH * XYZ[2]; // /dpsi dXeYeZe[3][0] = (-sPS * cPH + cPS * sTH * sPH) * XYZ[0] + cPS * cTH * XYZ[1] + (sPS * sPH + cPS * sTH * cPH) * XYZ[2]; dXeYeZe[3][1] = (cPS * cPH + sPS * sTH * sPH) * XYZ[0] + sPS * cTH * XYZ[1] + (-cPS * sPH + sPS * sTH * cPH) * XYZ[2]; dXeYeZe[3][2] = 0.0; // /dX0 // dXeYeZe[4][0]=-cPS*cPH+sPS*sTH*sPH; // bad? dXeYeZe[4][0] = -cPS * cPH - sPS * sTH * sPH; // bad? dXeYeZe[4][1] = -sPS * cPH + cPS * sTH * sPH; dXeYeZe[4][2] = -cTH * sPH; // /dY0 // dXeYeZe[5][0]=-sPS*cTH; // dXeYeZe[5][1]= cPS*cTH; // dXeYeZe[5][2]= sTH; dXeYeZe[5][0] = +sPS * cTH; dXeYeZe[5][1] = -cPS * cTH; dXeYeZe[5][2] = -sTH; // /dZ0 // dXeYeZe[6][0]= cPS*sPH+sPS*sTH*cPH; //bad? dXeYeZe[6][0] = cPS * sPH - sPS * sTH * cPH; //bad? dXeYeZe[6][1] = sPS * sPH + cPS * sTH * cPH; dXeYeZe[6][2] = -cTH * cPH; // /df dXeYeZe[7][0] = 0.0; dXeYeZe[7][1] = 0.0; dXeYeZe[7][2] = 0.0; // /ddist dXeYeZe[8][0] = 0.0; dXeYeZe[8][1] = 0.0; dXeYeZe[8][2] = 1.0; double[][] dPXYmmc = new double[9][2]; //[14]; dPXYmmc[0][0] = PXYmmc[0]; dPXYmmc[0][1] = PXYmmc[1]; //(4) PXmmc =f/Ze* Xe // mm, left from the lens axis intersection with the sensor //dPXmmc/dphi = f/Ze * dXe/dphi - f*Xe/Ze^2 * dZe/dphi dPXYmmc[1][0] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[1][0] - dXeYeZe[0][0] / dXeYeZe[0][2] * dXeYeZe[1][2]); //dPXmmc/dtheta = f/Ze * dXe/dtheta - f*Xe/Ze^2 * dZe/dtheta dPXYmmc[2][0] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[2][0] - dXeYeZe[0][0] / dXeYeZe[0][2] * dXeYeZe[2][2]); //dPXmmc/dpsi = f/Ze * dXe/dpsi - f*Xe/Ze^2 * dZe/dpsi dPXYmmc[3][0] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[3][0] - dXeYeZe[0][0] / dXeYeZe[0][2] * dXeYeZe[3][2]); //dPXmmc/dX0 = f/Ze * dXe/dX0 - f*Xe/Ze^2 * dZe/dX0 dPXYmmc[4][0] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[4][0] - dXeYeZe[0][0] / dXeYeZe[0][2] * dXeYeZe[4][2]); //dPXmmc/dY0 = f/Ze * dXe/dY0 - f*Xe/Ze^2 * dZe/dY0 dPXYmmc[5][0] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[5][0] - dXeYeZe[0][0] / dXeYeZe[0][2] * dXeYeZe[5][2]); //dPXmmc/dZ0 = f/Ze * dXe/dZ0 - f*Xe/Ze^2 * dZe/dZ0 dPXYmmc[6][0] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[6][0] - dXeYeZe[0][0] / dXeYeZe[0][2] * dXeYeZe[6][2]); //bad? //dPXmmc/df = Xe/Ze dPXYmmc[7][0] = dXeYeZe[0][0] / dXeYeZe[0][2]; //dPXmmc/ddist = - f*Xe/Ze^2 dPXYmmc[8][0] = -this.focalLength * dXeYeZe[0][0] / (dXeYeZe[0][2] * dXeYeZe[0][2]); //(5) PYmmc =f/Ze* Ye // mm, up from the lens axis intersection with the sensor //dPYmmc/dphi = f/Ze * dYe/dphi - f*Ye/Ze^2 * dZe/dphi dPXYmmc[1][1] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[1][1] - dXeYeZe[0][1] / dXeYeZe[0][2] * dXeYeZe[1][2]); //dPYmmc/dtheta = f/Ze * dYe/dtheta - f*Ye/Ze^2 * dZe/dtheta dPXYmmc[2][1] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[2][1] - dXeYeZe[0][1] / dXeYeZe[0][2] * dXeYeZe[2][2]); //dPYmmc/dpsi = f/Ze * dYe/dpsi - f*Ye/Ze^2 * dZe/dpsi dPXYmmc[3][1] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[3][1] - dXeYeZe[0][1] / dXeYeZe[0][2] * dXeYeZe[3][2]); //dPYmmc/dX0 = f/Ze * dYe/dX0 - f*Ye/Ze^2 * dZe/dX0 dPXYmmc[4][1] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[4][1] - dXeYeZe[0][1] / dXeYeZe[0][2] * dXeYeZe[4][2]); //dPYmmc/dY0 = f/Ze * dYe/dY0 - f*Ye/Ze^2 * dZe/dY0 dPXYmmc[5][1] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[5][1] - dXeYeZe[0][1] / dXeYeZe[0][2] * dXeYeZe[5][2]); //dPYmmc/dZ0 = f/Ze * dYe/dZ0 - f*Ye/Ze^2 * dZe/dZ0 dPXYmmc[6][1] = this.focalLength / dXeYeZe[0][2] * (dXeYeZe[6][1] - dXeYeZe[0][1] / dXeYeZe[0][2] * dXeYeZe[6][2]); // good? //dPYmmc/df = Ye/Ze dPXYmmc[7][1] = dXeYeZe[0][1] / dXeYeZe[0][2]; //dPYmmc/ddist = - f*Ye/Ze^2 dPXYmmc[8][1] = -this.focalLength * dXeYeZe[0][1] / (dXeYeZe[0][2] * dXeYeZe[0][2]); double[] dr = new double[9]; //(6) r=sqrt(PXmmc^2+PYmmc^2) // distance from the image point to the lens axis intersection with the sensor (pinhole model) dr[0] = r; // Added 3 zeros if (dr[0] < 0.00000001 * this.distortionRadius) dr[0] = 0.00000001 * this.distortionRadius; // avoid 1/0.0 // dr/dphi = (PXmmc*dPXmmc/dphi+PYmmc*dPYmmc/dphi)/r dr[1] = (PXYmmc[0] * dPXYmmc[1][0] + PXYmmc[1] * dPXYmmc[1][1]) / dr[0]; // dr/dtheta = (PXmmc*dPXmmc/dtheta+PYmmc*dPYmmc/dtheta)/r dr[2] = (PXYmmc[0] * dPXYmmc[2][0] + PXYmmc[1] * dPXYmmc[2][1]) / dr[0]; // dr/dpsi = (PXmmc*dPXmmc/dpsi+PYmmc*dPYmmc/dpsi)/r dr[3] = (PXYmmc[0] * dPXYmmc[3][0] + PXYmmc[1] * dPXYmmc[3][1]) / dr[0]; // dr/dX0 = (PXmmc*dPXmmc/dX0+PYmmc*dPYmmc/dX0)/r dr[4] = (PXYmmc[0] * dPXYmmc[4][0] + PXYmmc[1] * dPXYmmc[4][1]) / dr[0]; // dr/dY0 = (PXmmc*dPXmmc/dY0+PYmmc*dPYmmc/dY0)/r dr[5] = (PXYmmc[0] * dPXYmmc[5][0] + PXYmmc[1] * dPXYmmc[5][1]) / dr[0]; // dr/dZ0 = (PXmmc*dPXmmc/dZ0+PYmmc*dPYmmc/dZ0)/r dr[6] = (PXYmmc[0] * dPXYmmc[6][0] + PXYmmc[1] * dPXYmmc[6][1]) / dr[0]; // dr/df = (PXmmc*dPXmmc/df+PYmmc*dPYmmc/df)/r dr[7] = (PXYmmc[0] * dPXYmmc[7][0] + PXYmmc[1] * dPXYmmc[7][1]) / dr[0]; // dr/ddist = (PXmmc*dPXmmc/ddist+PYmmc*dPYmmc/ddist)/r dr[8] = (PXYmmc[0] * dPXYmmc[8][0] + PXYmmc[1] * dPXYmmc[8][1]) / dr[0]; // double[] dkD=new double [12]; double[] dkD = new double[16]; // (7) kD=(Da*(r/r0)^3+Db*(r/r0)^2+Dc*(r/r0)^1+(1-Da-Db-Dc)) correction to the actual distance from the image point to the lens axis due to distortion // (7) kD=(Da5*(r/r0)^4+(Da*(r/r0)^3+Db*(r/r0)^2+Dc*(r/r0)^1+(1-Da-Db-Dc-Da5)) correction to the actual distance from the image point to the lens axis due to distortion dkD[0] = kD; // dkD/dr= Da/r0^3 * 3*r^2 + Db/r0^2*2*r + Dc/r0 // dkD/dr= Da5/r0^4 * 4*r^3 + Da/r0^3 * 3*r^2 + Db/r0^2*2*r + Dc/r0 // dkD/dr= 1/r0*(3*Da*(r/r0)^2 + 2*Db*(r/r0) + Dc) // dkD/dr= 1/r0*(4*Da5*(r/r0)^3 + 3*Da*(r/r0)^2 + 2*Db*(r/r0) + Dc) double dkDdr = 1.0 / this.distortionRadius * ((((((8 * this.distortionA8 * rr + 6 * this.distortionA7) * rr + 5 * this.distortionA6) * rr + 4 * this.distortionA5) * rr + 3 * this.distortionA) * rr + 2 * this.distortionB) * rr + this.distortionC); // dkD/dphi = dkD/dr * dr/dphi dkD[1] = dkDdr * dr[1]; // dkD/dtheta = dkD/dr * dr/dtheta dkD[2] = dkDdr * dr[2]; // dkD/dpsi = dkD/dr * dr/dpsi dkD[3] = dkDdr * dr[3]; // dkD/dX0 = dkD/dr * dr/dX0 dkD[4] = dkDdr * dr[4]; // dkD/dY0 = dkD/dr * dr/dY0 dkD[5] = dkDdr * dr[5]; // dkD/dZ0 = dkD/dr * dr/dZ0 dkD[6] = dkDdr * dr[6]; // dkD/df = dkD/dr * dr/df dkD[7] = dkDdr * dr[7]; // dkD/ddist = dkD/dr * dr/ddist dkD[8] = dkDdr * dr[8]; // dkD/dDa8 = (r/r0)^7 - 1 dkD[9] = rr * rr * rr * rr * rr * rr * rr - 1.0; // dkD/dDa7 = (r/r0)^6 - 1 dkD[10] = rr * rr * rr * rr * rr * rr - 1.0; // dkD/dDa6 = (r/r0)^5 - 1 dkD[11] = rr * rr * rr * rr * rr - 1.0; // dkD/dDa5 = (r/r0)^4 - 1 dkD[12] = rr * rr * rr * rr - 1.0; // dkD/dDa = (r/r0)^3 - 1 dkD[13] = rr * rr * rr - 1.0; // dkD/dDb = (r/r0)^2 - 1 dkD[14] = rr * rr - 1.0; // dkD/dDc = (r/r0) - 1 dkD[15] = rr - 1.0; // double[][] dxyDist=new double [12][2]; double[][] dxyDist = new double[16][2]; // (8) xDist = kD * PXmmc // horisontal distance (mm) from the lens axis on the sensor to the image point, mm (positive - right) // (9) yDist = kD * PYmmc // vertical distance (mm) from the lens axis on the sensor to the image point, mm (positive - up) dxyDist[0][0] = xyDist[0]; dxyDist[0][1] = xyDist[1]; // dxDist/dphi = dkD/dphi*PXmmc + kD*dPXmmc/dphi // dyDist/dphi = dkD/dphi*PYmmc + kD*dPYmmc/dphi dxyDist[1][0] = dkD[1] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[1][0]; dxyDist[1][1] = dkD[1] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[1][1]; // dxDist/dtheta = dkD/dtheta*PXmmc + kD*dPXmmc/dtheta dxyDist[2][0] = dkD[2] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[2][0]; dxyDist[2][1] = dkD[2] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[2][1]; // dxDist/dpsi = dkD/dpsi*PXmmc + kD*dPXmmc/dpsi dxyDist[3][0] = dkD[3] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[3][0]; dxyDist[3][1] = dkD[3] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[3][1]; // dxDist/dX0 = dkD/dX0*PXmmc + kD*dPXmmc/dX0 dxyDist[4][0] = dkD[4] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[4][0]; // large error dxyDist[4][1] = dkD[4] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[4][1]; // dxDist/dY0 = dkD/dY0*PXmmc + kD*dPXmmc/dY0 dxyDist[5][0] = dkD[5] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[5][0]; dxyDist[5][1] = dkD[5] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[5][1]; // dxDist/dZ0 = dkD/dZ0*PXmmc + kD*dPXmmc/dZ0 dxyDist[6][0] = dkD[6] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[6][0]; // large error dxyDist[6][1] = dkD[6] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[6][1]; // dxDist/df = dkD/df*PXmmc + kD*dPXmmc/df dxyDist[7][0] = dkD[7] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[7][0]; dxyDist[7][1] = dkD[7] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[7][1]; // dxDist/ddist = dkD/ddist*PXmmc + kD*dPXmmc/ddist dxyDist[8][0] = dkD[8] * dPXYmmc[0][0] + dkD[0] * dPXYmmc[8][0]; dxyDist[8][1] = dkD[8] * dPXYmmc[0][1] + dkD[0] * dPXYmmc[8][1]; // dxDist/dDa5 = dkD/dDa8*PXmmc dxyDist[9][0] = dkD[9] * dPXYmmc[0][0]; dxyDist[9][1] = dkD[9] * dPXYmmc[0][1]; // dxDist/dDa5 = dkD/dDa7*PXmmc dxyDist[10][0] = dkD[10] * dPXYmmc[0][0]; dxyDist[10][1] = dkD[10] * dPXYmmc[0][1]; // dxDist/dDa5 = dkD/dD6a*PXmmc dxyDist[11][0] = dkD[11] * dPXYmmc[0][0]; dxyDist[11][1] = dkD[11] * dPXYmmc[0][1]; // dxDist/dDa5 = dkD/dDa5*PXmmc` dxyDist[12][0] = dkD[12] * dPXYmmc[0][0]; dxyDist[12][1] = dkD[12] * dPXYmmc[0][1]; // dxDist/dDa = dkD/dDa*PXmmc dxyDist[13][0] = dkD[13] * dPXYmmc[0][0]; dxyDist[13][1] = dkD[13] * dPXYmmc[0][1]; // dxDist/dDb = dkD/dDb*PXmmc dxyDist[14][0] = dkD[14] * dPXYmmc[0][0]; dxyDist[14][1] = dkD[14] * dPXYmmc[0][1]; // dxDist/dDc = dkD/dDc*PXmmc dxyDist[15][0] = dkD[15] * dPXYmmc[0][0]; dxyDist[15][1] = dkD[15] * dPXYmmc[0][1]; double K = Math.PI / 180; // multiply all derivatives my angles // (10) PX = 1000/Psz*( xDist) + PX0 // horizontal pixel of the image (positive - right) // dPX/dphi = 1000/Psz* dxDist/dphi partDeriv[1][0] = K * 1000.0 / this.pixelSize * dxyDist[1][0]; partDeriv[1][1] = -K * 1000.0 / this.pixelSize * dxyDist[1][1]; // dPX/dtheta = 1000/Psz* dxDist/dtheta partDeriv[2][0] = K * 1000.0 / this.pixelSize * dxyDist[2][0]; partDeriv[2][1] = -K * 1000.0 / this.pixelSize * dxyDist[2][1]; // dPX/dpsi = 1000/Psz* dxDist/dpsi partDeriv[3][0] = K * 1000.0 / this.pixelSize * dxyDist[3][0]; partDeriv[3][1] = -K * 1000.0 / this.pixelSize * dxyDist[3][1]; // dPX/dX0 = 1000/Psz* dxDist/dX0 partDeriv[4][0] = 1000.0 / this.pixelSize * dxyDist[4][0]; // large error partDeriv[4][1] = -1000.0 / this.pixelSize * dxyDist[4][1]; // dPX/dY0 = 1000/Psz* dxDist/dY0 partDeriv[5][0] = 1000.0 / this.pixelSize * dxyDist[5][0]; partDeriv[5][1] = -1000.0 / this.pixelSize * dxyDist[5][1]; // dPX/dZ0 = 1000/Psz* dxDist/dZ0 partDeriv[6][0] = 1000.0 / this.pixelSize * dxyDist[6][0]; // large error, including DC (sig -0.8..+0.5, err -0.2..-0.1 partDeriv[6][1] = -1000.0 / this.pixelSize * dxyDist[6][1]; // dPX/df = 1000/Psz* dxDist/df partDeriv[7][0] = 1000.0 / this.pixelSize * dxyDist[7][0]; partDeriv[7][1] = -1000.0 / this.pixelSize * dxyDist[7][1]; // dPX/ddist = 1000/Psz* dxDist/ddist partDeriv[8][0] = 1000.0 / this.pixelSize * dxyDist[8][0]; partDeriv[8][1] = -1000.0 / this.pixelSize * dxyDist[8][1]; // dPX/dDa8 = 1000/Psz* dxDist/dDa5 partDeriv[9][0] = 1000.0 / this.pixelSize * dxyDist[9][0]; partDeriv[9][1] = -1000.0 / this.pixelSize * dxyDist[9][1]; // dPX/dDa7 = 1000/Psz* dxDist/dDa5 partDeriv[10][0] = 1000.0 / this.pixelSize * dxyDist[10][0]; partDeriv[10][1] = -1000.0 / this.pixelSize * dxyDist[10][1]; // dPX/dDa6 = 1000/Psz* dxDist/dDa5 partDeriv[11][0] = 1000.0 / this.pixelSize * dxyDist[11][0]; partDeriv[11][1] = -1000.0 / this.pixelSize * dxyDist[11][1]; // dPX/dDa5 = 1000/Psz* dxDist/dDa5 partDeriv[12][0] = 1000.0 / this.pixelSize * dxyDist[12][0]; partDeriv[12][1] = -1000.0 / this.pixelSize * dxyDist[12][1]; // dPX/dDa = 1000/Psz* dxDist/dDa partDeriv[13][0] = 1000.0 / this.pixelSize * dxyDist[13][0]; partDeriv[13][1] = -1000.0 / this.pixelSize * dxyDist[13][1]; // dPX/dDb = 1000/Psz* dxDist/dDb partDeriv[14][0] = 1000.0 / this.pixelSize * dxyDist[14][0]; partDeriv[14][1] = -1000.0 / this.pixelSize * dxyDist[14][1]; // dPX/dDc = 1000/Psz* dxDist/dDc partDeriv[15][0] = 1000.0 / this.pixelSize * dxyDist[15][0]; partDeriv[15][1] = -1000.0 / this.pixelSize * dxyDist[15][1]; // dPX/dPX0 = 1 // dPY/dPX0 = 0 partDeriv[16][0] = 1.0; partDeriv[16][1] = 0.0; // dPX/dPY0 = 0 // dPY/dPY0 = 1 partDeriv[17][0] = 0.0; partDeriv[17][1] = 1.0; return partDeriv; } public void setProperties(String prefix, Properties properties) { properties.setProperty(prefix + "focalLength", this.focalLength + ""); properties.setProperty(prefix + "pixelSize", this.pixelSize + ""); properties.setProperty(prefix + "distortionRadius", this.distortionRadius + ""); properties.setProperty(prefix + "distortionA8", this.distortionA8 + ""); properties.setProperty(prefix + "distortionA7", this.distortionA7 + ""); properties.setProperty(prefix + "distortionA6", this.distortionA6 + ""); properties.setProperty(prefix + "distortionA5", this.distortionA5 + ""); properties.setProperty(prefix + "distortionA", this.distortionA + ""); properties.setProperty(prefix + "distortionB", this.distortionB + ""); properties.setProperty(prefix + "distortionC", this.distortionC + ""); properties.setProperty(prefix + "yaw", this.yaw + ""); properties.setProperty(prefix + "pitch", this.pitch + ""); properties.setProperty(prefix + "roll", this.roll + ""); properties.setProperty(prefix + "x0", this.x0 + ""); properties.setProperty(prefix + "y0", this.y0 + ""); properties.setProperty(prefix + "z0", this.z0 + ""); properties.setProperty(prefix + "distance", this.distance + ""); properties.setProperty(prefix + "px0", this.px0 + ""); properties.setProperty(prefix + "py0", this.py0 + ""); properties.setProperty(prefix + "flipVertical", this.flipVertical + ""); } public void getProperties(String prefix, Properties properties) { if (properties.getProperty(prefix + "focalLength") != null) this.focalLength = Double.parseDouble(properties.getProperty(prefix + "focalLength")); if (properties.getProperty(prefix + "pixelSize") != null) this.pixelSize = Double.parseDouble(properties.getProperty(prefix + "pixelSize")); if (properties.getProperty(prefix + "distortionRadius") != null) this.distortionRadius = Double.parseDouble(properties.getProperty(prefix + "distortionRadius")); if (properties.getProperty(prefix + "distortionA8") != null) this.distortionA8 = Double.parseDouble(properties.getProperty(prefix + "distortionA8")); if (properties.getProperty(prefix + "distortionA7") != null) this.distortionA7 = Double.parseDouble(properties.getProperty(prefix + "distortionA7")); if (properties.getProperty(prefix + "distortionA6") != null) this.distortionA6 = Double.parseDouble(properties.getProperty(prefix + "distortionA6")); if (properties.getProperty(prefix + "distortionA5") != null) this.distortionA5 = Double.parseDouble(properties.getProperty(prefix + "distortionA5")); if (properties.getProperty(prefix + "distortionA") != null) this.distortionA = Double.parseDouble(properties.getProperty(prefix + "distortionA")); if (properties.getProperty(prefix + "distortionB") != null) this.distortionB = Double.parseDouble(properties.getProperty(prefix + "distortionB")); if (properties.getProperty(prefix + "distortionC") != null) this.distortionC = Double.parseDouble(properties.getProperty(prefix + "distortionC")); if (properties.getProperty(prefix + "yaw") != null) this.yaw = Double.parseDouble(properties.getProperty(prefix + "yaw")); if (properties.getProperty(prefix + "pitch") != null) this.pitch = Double.parseDouble(properties.getProperty(prefix + "pitch")); if (properties.getProperty(prefix + "roll") != null) this.roll = Double.parseDouble(properties.getProperty(prefix + "roll")); if (properties.getProperty(prefix + "x0") != null) this.x0 = Double.parseDouble(properties.getProperty(prefix + "x0")); if (properties.getProperty(prefix + "y0") != null) this.y0 = Double.parseDouble(properties.getProperty(prefix + "y0")); if (properties.getProperty(prefix + "z0") != null) this.z0 = Double.parseDouble(properties.getProperty(prefix + "z0")); if (properties.getProperty(prefix + "distance") != null) this.distance = Double.parseDouble(properties.getProperty(prefix + "distance")); if (properties.getProperty(prefix + "px0") != null) this.px0 = Double.parseDouble(properties.getProperty(prefix + "px0")); if (properties.getProperty(prefix + "py0") != null) this.py0 = Double.parseDouble(properties.getProperty(prefix + "py0")); if (properties.getProperty(prefix + "flipVertical") != null) this.flipVertical = Boolean.parseBoolean(properties.getProperty(prefix + "flipVertical")); recalcCommons(); } public boolean showDialog() { GenericDialog gd = new GenericDialog("Lens distortion, location and orientation"); gd.addNumericField("Lens focal length", this.focalLength, 3, 6, "mm"); gd.addNumericField("Sensor pixel period", this.pixelSize, 3, 6, "um"); gd.addNumericField("Distortion radius (halw width)", this.distortionRadius, 5, 8, "mm"); gd.addNumericField("Distortion A8(r^5)", this.distortionA8, 6, 8, ""); gd.addNumericField("Distortion A7(r^5)", this.distortionA7, 6, 8, ""); gd.addNumericField("Distortion A6(r^5)", this.distortionA6, 6, 8, ""); gd.addNumericField("Distortion A5(r^5)", this.distortionA5, 6, 8, ""); gd.addNumericField("Distortion A (r^4)", this.distortionA, 6, 8, ""); gd.addNumericField("Distortion B (r^3)", this.distortionB, 6, 8, ""); gd.addNumericField("Distortion C (r^2)", this.distortionC, 6, 8, ""); gd.addNumericField("Lens axis from perpendicular to the pattern, positive - clockwise (from top)", this.yaw, 2, 6, "degrees"); gd.addNumericField("Lens axis from perpendicular to the pattern, positive - up", this.pitch, 2, 6, "degrees"); gd.addNumericField("Rotation around lens axis, positive - clockwise (looking to pattern)", this.roll, 2, 6, "degrees"); gd.addNumericField("Lens axis from the pattern center, (to the right)", this.x0, 1, 6, "mm"); gd.addNumericField("Lens axis from the pattern center, (down)", this.y0, 1, 6, "mm"); gd.addNumericField("Lens axis from the pattern center, (away from camera, normally 0.0)", this.z0, 1, 6, "mm"); gd.addNumericField("Distance from the lens input pupil to the pattern plane along the camera axis", this.distance, 1, 6, "mm"); gd.addNumericField("Lens axis on the sensor (horizontal, from left edge)", this.px0, 1, 6, "pixels"); gd.addNumericField("Lens axis on the sensor (vertical, from top edge)", this.py0, 1, 6, "pixels"); gd.addCheckbox("Camera looks through the mirror", this.flipVertical); gd.showDialog(); if (gd.wasCanceled()) return false; this.focalLength = gd.getNextNumber(); this.pixelSize = gd.getNextNumber(); this.distortionRadius = gd.getNextNumber(); this.distortionA8 = gd.getNextNumber(); this.distortionA7 = gd.getNextNumber(); this.distortionA6 = gd.getNextNumber(); this.distortionA5 = gd.getNextNumber(); this.distortionA = gd.getNextNumber(); this.distortionB = gd.getNextNumber(); this.distortionC = gd.getNextNumber(); this.yaw = gd.getNextNumber(); this.pitch = gd.getNextNumber(); this.roll = gd.getNextNumber(); this.x0 = gd.getNextNumber(); this.y0 = gd.getNextNumber(); this.z0 = gd.getNextNumber(); this.distance = gd.getNextNumber(); this.px0 = gd.getNextNumber(); this.py0 = gd.getNextNumber(); this.flipVertical = gd.getNextBoolean(); return true; } /** * 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 */ public void lensCalcInterParamers( // LensDistortionParameters lensDistortionParameters, boolean isTripod, double[][] interParameterDerivatives, //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) if null - just values, no derivatives double[] parVect, boolean[] mask // calculate only selected derivatives (all parVect values are still // boolean calculateDerivatives // calculate this.interParameterDerivatives -derivatives array (false - just this.values) ) { LensDistortionParameters lensDistortionParameters = this; boolean calculateDerivatives = (interParameterDerivatives != null); // calculate this.interParameterDerivatives -derivatives array (false - just this.values) // change meaning of goniometerHorizontal (tripod vertical) and goniometerAxial (tripod horizontal) // boolean isTripod=this.fittingStrategy.distortionCalibrationData.eyesisCameraParameters.isTripod; double azimuth = parVect[0]; double radius = parVect[1]; double height = parVect[2]; double phi = parVect[3]; double theta = parVect[4]; double psi = parVect[5]; double goniometerHorizontal = parVect[6]; double goniometerAxial = parVect[7]; double interAxisDistance = parVect[8]; double interAxisAngle = parVect[9]; double horAxisErrPhi = parVect[10]; double horAxisErrPsi = parVect[11]; double entrancePupilForward = parVect[12]; double centerAboveHorizontal = parVect[13]; double GXYZ0 = parVect[14]; double GXYZ1 = parVect[15]; double GXYZ2 = parVect[16]; double cPS = Math.cos(psi * Math.PI / 180); // subCam.psi double sPS = Math.sin(psi * Math.PI / 180); // subCam.psi double cTH = Math.cos(theta * Math.PI / 180); // subCam.theta double sTH = Math.sin(theta * Math.PI / 180); // subCam.theta double cAZP = Math.cos((azimuth + phi) * Math.PI / 180); //subCam.azimuth+subCam.phi double sAZP = Math.sin((azimuth + phi) * Math.PI / 180); //subCam.azimuth+subCam.phi double cAZ = Math.cos(azimuth * Math.PI / 180); //subCam.azimuth double sAZ = Math.sin(azimuth * Math.PI / 180); //subCam.azimuth double cGA = Math.cos(goniometerAxial * Math.PI / 180); //eyesisCameraParameters.goniometerAxial double sGA = Math.sin(goniometerAxial * Math.PI / 180); //eyesisCameraParameters.goniometerAxial double cGH = Math.cos(goniometerHorizontal * Math.PI / 180); //eyesisCameraParameters.goniometerHorizontal double sGH = Math.sin(goniometerHorizontal * Math.PI / 180); //eyesisCameraParameters.goniometerHorizontal double cGIAA = Math.cos(interAxisAngle * Math.PI / 180); // eyesisCameraParameters.interAxisAngle double sGIAA = Math.sin(interAxisAngle * Math.PI / 180); //eyesisCameraParameters.interAxisAngle double cHAEPH = Math.cos(horAxisErrPhi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPhi double sHAEPH = Math.sin(horAxisErrPhi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPhi double cHAEPS = Math.cos(horAxisErrPsi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPsi double sHAEPS = Math.sin(horAxisErrPsi * Math.PI / 180); //eyesisCameraParameters.horAxisErrPsi\ /* 0) Translate by distance to entrance pupil (lens center) | Xc0 | | 0 | |Xc| | Yc0 | = | 0 | + |Yc| | Zc0 | | entrancePupilForward | |Zc| */ double[][] aT0 = { { 0.0 }, { 0.0 }, { entrancePupilForward } }; Matrix T0 = new Matrix(aT0); /* Converting from the sub-camera coordinates to the target coordinates 1) rotate by -psi around CZ: Vc1= R1*Vc | Xc1 | | cos(psi) sin(psi) 0 | |Xc0| | Yc1 | = |-sin(psi) cos(psi) 0 | * |Yc0| | Zc1 | | 0 0 1 | |Zc0| */ double[][] aR1 = { { cPS, sPS, 0.0 }, { -sPS, cPS, 0.0 }, { 0.0, 0.0, 1.0 } }; Matrix R1 = new Matrix(aR1); /* 2) rotate by - theta around C1X:Vc2= R2*Vc1 | Xc2 | | 1 0 0 | |Xc1| | Yc2 | = | 0 cos(theta) sin(theta) | * |Yc1| | Zc2 | | 0 -sin(theta) cos(theta) | |Zc1| */ double[][] aR2 = { { 1.0, 0.0, 0.0 }, { 0.0, cTH, sTH }, { 0.0, -sTH, cTH } }; Matrix R2 = new Matrix(aR2); /* 3) rotate by -(azimuth+phi) around C2Y:Vc3= R3*Vc2 | Xc3 | | cos(azimuth+phi) 0 sin(azimuth+phi) | |Xc2| | Yc3 | = | 0 1 0 | * |Yc2| | Zc3 | | -sin(azimuth+phi) 0 cos(azimuth+phi) | |Zc2| */ double[][] aR3 = { { cAZP, 0.0, sAZP }, { 0.0, 1.0, 0.0 }, { -sAZP, 0.0, cAZP } }; Matrix R3 = new Matrix(aR3); /* 4) Now axes are aligned, just translate to get to eyesis coordinates: Vey= T1+Vc3 | Xey | | r * sin (azimuth) | |Xc3| | Yey | = | height+centerAboveHorizontal | + |Yc3| | Zey | | r * cos (azimuth) | |Zc3| */ double[][] aT1 = { { radius * sAZ }, { (height + centerAboveHorizontal) }, { radius * cAZ } }; // {{subCam.radius*sAZ},{subCam.height},{subCam.radius*cAZ}}; Matrix T1 = new Matrix(aT1); /** 5) rotate around moving goniometer axis, by (-goniometerAxial) - same as around EY: Vgm1=R4*Vey | Xgm1 | | 1 0 0 | |Xey| | Ygm1 | = | 0 cos(goniometerAxial) sin(goniometerAxial) | * |Yey| | Zgm1 | | 0 -sin(goniometerAxial) cos(goniometerAxial) | |Zey| */ double[][] aR4_tripod = { { 1.0, 0.0, 0.0 }, { 0.0, cGA, sGA }, { 0.0, -sGA, cGA } }; /* 5) rotate around moving goniometer axis, by (-goniometerAxial) - same as around EY: Vgm1=R4*Vey | Xgm1 | | cos(goniometerAxial) 0 sin(goniometerAxial) | |Xey| | Ygm1 | = | 0 1 0 | * |Yey| | Zgm1 | |-sin(goniometerAxial) 0 cos(goniometerAxial) | |Zey| */ double[][] aR4_goniometer = { { cGA, 0.0, sGA }, { 0.0, 1.0, 0.0 }, { -sGA, 0.0, cGA } }; Matrix R4 = new Matrix(isTripod ? aR4_tripod : aR4_goniometer); /* 6) move to the goniometer horizontal axis:Vgm2=T2+Vgm1 | Xgm2 | | 0 | |Xgm1| | Ygm2 | = | 0 | + |Ygm1| | Zgm2 | | interAxisDistance | |Zgm1| */ double[][] aT2 = { { 0.0 }, { 0.0 }, { interAxisDistance } }; //eyesisCameraParameters.interAxisDistance Matrix T2 = new Matrix(aT2); /* 7) rotate around Zgm2 by -interAxisAngle, so Xgm3 axis is the same as horizontal goniometer axis: Vgm3=R5*Vgm2 | Xgm3 | | cos(interAxisAngle) sin(interAxisAngle) 0 | |Xgm2| | Ygm3 | = |-sin(interAxisAngle) cos(interAxisAngle) 0 | * |Ygm2| | Zgm3 | | 0 0 1 | |Zgm2| */ double[][] aR5 = { { cGIAA, sGIAA, 0.0 }, { -sGIAA, cGIAA, 0.0 }, { 0.0, 0.0, 1.0 } }; Matrix R5 = new Matrix(aR5); /** 8) rotate around goniometer horizontal axis (Xgm3) by -goniometerHorizontal: Vgm4=R6*Vgm3 | Xgm4 | | cos(goniometerHorizontal) 0 sin(goniometerHorizontal) | |Xgm3| | Ygm4 | = | 0 1 0 | * |Ygm3| | Zgm4 | | -sin(goniometerHorizontal) 0 cos(goniometerHorizontal) | |Zgm3| */ double[][] aR6_tripod = { { cGH, 0.0, sGH }, { 0.0, 1.0, 0.0 }, { -sGH, 0.0, cGH } }; /* 8) rotate around goniometer horizontal axis (Xgm3) by -goniometerHorizontal: Vgm4=R6*Vgm3 | Xgm4 | | 1 0 0 | |Xgm3| | Ygm4 | = | 0 cos(goniometerHorizontal) sin(goniometerHorizontal) | * |Ygm3| | Zgm4 | | 0 -sin(goniometerHorizontal) cos(goniometerHorizontal) | |Zgm3| */ double[][] aR6_goniometer = { { 1.0, 0.0, 0.0 }, { 0.0, cGH, sGH }, { 0.0, -sGH, cGH } }; Matrix R6 = new Matrix(isTripod ? aR6_tripod : aR6_goniometer); /* 9) Correct roll error of the goniometer horizontal axis - rotate by -horAxisErrPsi around Zgm4: Vgm5=R7*Vgm4 | Xgm5 | | cos(horAxisErrPsi) sin(horAxisErrPsi) 0 | |Xgm4| | Ygm5 | = |-sin(horAxisErrPsi) cos(horAxisErrPsi) 0 | * |Ygm4| | Zgm5 | | 0 0 1 | |Zgm4| */ double[][] aR7 = { { cHAEPS, sHAEPS, 0.0 }, { -sHAEPS, cHAEPS, 0.0 }, { 0.0, 0.0, 1.0 } }; Matrix R7 = new Matrix(aR7); /* 10) Correct azimuth error of the goniometer hoirizontal axis - rotate by -horAxisErrPhi around Ygm5: Vgm6=R8*Vgm5 | Xgm6 | | cos(horAxisErrPhi) 0 sin(horAxisErrPhi) | |Xgm5| | Ygm6 | = | 0 1 0 | * |Ygm5| | Zgm6 | |-sin(horAxisErrPhi) 0 cos(horAxisErrPhi) | |Zgm5| For Tripod - rotate around X-axis (like theta) | Xgm6 | | 1 0 0 | |Xgm5| | Ygm6 | = | 0 cos(horAxisErrPhi) sin(horAxisErrPhi) | * |Ygm5| | Zgm6 | | 0 -sin(horAxisErrPhi) cos(horAxisErrPhi) | |Zgm5| */ double[][] aR8_tripod = { { 1.0, 0.0, 0.0 }, { 0.0, cHAEPH, sHAEPH }, { 0.0, -sHAEPH, cHAEPH } }; double[][] aR8_goniometer = { { cHAEPH, 0.0, sHAEPH }, { 0.0, 1.0, 0.0 }, { -sHAEPH, 0.0, cHAEPH } }; Matrix R8 = new Matrix(isTripod ? aR8_tripod : aR8_goniometer); /* 11) translate to the target zero point: Vt= T3+Vgm6 | Xt | | GXYZ[0] | |Xgm6| | Yt | = |-GXYZ[1] | + |Ygm6| // Y - up positive | Zt | |-GXYZ[2] | |Zgm6| // Z - away positive */ // double [][] aT3={{parVect[12]},{-parVect[13]},{-parVect[14]}};//{{eyesisCameraParameters.GXYZ[0]},{eyesisCameraParameters.GXYZ[1]},{eyesisCameraParameters.GXYZ[2]}}; double[][] aT3 = { { GXYZ0 }, { -GXYZ1 }, { -GXYZ2 } };//{{eyesisCameraParameters.GXYZ[0]},{eyesisCameraParameters.GXYZ[1]},{eyesisCameraParameters.GXYZ[2]}}; // double [][] aT3={{parVect[12]},{ parVect[13]},{-parVect[14]}};//{{eyesisCameraParameters.GXYZ[0]},{eyesisCameraParameters.GXYZ[1]},{eyesisCameraParameters.GXYZ[2]}}; Matrix T3 = new Matrix(aT3); // MA=R8*R7*R6*R5*R4*R3*R2*R1; // MB=T3+(R8*R7*R6*R5*(T2+R4*T1)); - old // MB=T3+(R8*R7*R6*R5*(T2+R4*(T1+R3*R2*R1*T0))); Matrix MA = R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); // Matrix MB=T3.plus(R8.times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1))))))); Matrix MB = T3.plus(R8.times( R7.times(R6.times(R5.times(T2.plus(R4.times(T1.plus(R3.times(R2.times(R1.times(T0))))))))))); if (this.debugLevel > 2) { System.out.println("MA:"); MA.print(10, 5); System.out.println("MB:"); MB.print(10, 5); System.out.println("T3:"); T3.print(10, 5); // System.out.println("interParameterDerivatives[0]="+sprintfArray(interParameterDerivatives[0])); } double[] extrinsicParams = parametersFromMAMB(MA, MB); // all after 6 are 0; lensDistortionParameters.distance = extrinsicParams[2]; lensDistortionParameters.x0 = extrinsicParams[0]; lensDistortionParameters.y0 = extrinsicParams[1]; lensDistortionParameters.z0 = 0.0; // used here lensDistortionParameters.pitch = extrinsicParams[4]; lensDistortionParameters.yaw = extrinsicParams[3]; lensDistortionParameters.roll = extrinsicParams[5]; // lensDistortionParameters.focalLength=parVect[15]; //subCam.focalLength; // lensDistortionParameters.px0=parVect[16]; //subCam.px0; // lensDistortionParameters.py0=parVect[17]; //subCam.py0; // lensDistortionParameters.distortionA5=parVect[18]; //subCam.distortion5; // lensDistortionParameters.distortionA=parVect[19]; //subCam.distortionA; // lensDistortionParameters.distortionB=parVect[20]; //subCam.distortionB; // lensDistortionParameters.distortionC=parVect[21]; //subCam.distortionC; lensDistortionParameters.focalLength = parVect[17]; //subCam.focalLength; lensDistortionParameters.px0 = parVect[18]; //subCam.px0; lensDistortionParameters.py0 = parVect[19]; //subCam.py0; lensDistortionParameters.distortionA8 = parVect[20]; //subCam.distortion5; lensDistortionParameters.distortionA7 = parVect[21]; //subCam.distortion5; lensDistortionParameters.distortionA6 = parVect[22]; //subCam.distortion5; lensDistortionParameters.distortionA5 = parVect[23]; //subCam.distortion5; lensDistortionParameters.distortionA = parVect[24]; //subCam.distortionA; lensDistortionParameters.distortionB = parVect[25]; //subCam.distortionB; lensDistortionParameters.distortionC = parVect[26]; //subCam.distortionC; lensDistortionParameters.recalcCommons(); if (this.debugLevel > 2) { System.out.println("lensDistortionParameters.recalcCommons()"); System.out.println( "lensDistortionParameters.distance=" + IJ.d2s(lensDistortionParameters.distance, 3)); System.out.println("lensDistortionParameters.x0=" + IJ.d2s(lensDistortionParameters.x0, 3)); System.out.println("lensDistortionParameters.y0=" + IJ.d2s(lensDistortionParameters.y0, 3)); System.out.println("lensDistortionParameters.z0=" + IJ.d2s(lensDistortionParameters.z0, 3)); System.out.println("lensDistortionParameters.pitch=" + IJ.d2s(lensDistortionParameters.pitch, 3)); System.out.println("lensDistortionParameters.yaw=" + IJ.d2s(lensDistortionParameters.yaw, 3)); System.out.println("lensDistortionParameters.roll=" + IJ.d2s(lensDistortionParameters.roll, 3)); System.out.println( "lensDistortionParameters.focalLength=" + IJ.d2s(lensDistortionParameters.focalLength, 3)); System.out.println("lensDistortionParameters.px0=" + IJ.d2s(lensDistortionParameters.px0, 3)); System.out.println("lensDistortionParameters.py0=" + IJ.d2s(lensDistortionParameters.py0, 3)); System.out.println("lensDistortionParameters.distortionA8=" + IJ.d2s(lensDistortionParameters.distortionA8, 5)); System.out.println("lensDistortionParameters.distortionA7=" + IJ.d2s(lensDistortionParameters.distortionA7, 5)); System.out.println("lensDistortionParameters.distortionA6=" + IJ.d2s(lensDistortionParameters.distortionA6, 5)); System.out.println("lensDistortionParameters.distortionA5=" + IJ.d2s(lensDistortionParameters.distortionA5, 5)); System.out.println( "lensDistortionParameters.distortionA=" + IJ.d2s(lensDistortionParameters.distortionA, 5)); System.out.println( "lensDistortionParameters.distortionB=" + IJ.d2s(lensDistortionParameters.distortionB, 5)); System.out.println( "lensDistortionParameters.distortionC=" + IJ.d2s(lensDistortionParameters.distortionC, 5)); } if (!calculateDerivatives) return; /* Calculate all derivativs as a matrix. * Input parameters (columns): 0 public double azimuth; // azimuth of the lens entrance pupil center, degrees, clockwise looking from top 1 public double radius; // mm, distance from the rotation axis 2 public double height; // mm, up (was downwards?) - from the origin point 3 public double phi; // degrees, optical axis from azimuth/r vector, clockwise 4 public double theta; // degrees, optical axis from the eyesis horizon, positive - up 5 public double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target 6 public double goniometerHorizontal; // goniometer rotation around "horizontal" axis (tilting from the target - positive) 7 public double goniometerAxial; // goniometer rotation around Eyesis axis (clockwise in plan - positive 8 public double interAxisDistance; // distance in mm between two goniometer axes 9 public double interAxisAngle; // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated 10 public double horAxisErrPhi; // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) 11 public double horAxisErrPsi; // angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) Two new parameters 12 public double entrancePupilForward; // common to all lenses - distance from the sensor to the lens entrance pupil 13 public double centerAboveHorizontal; // camera center distance along camera axis above the closest point to horizontal rotation axis (adds to 14(12) x public double [] GXYZ=null; // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system 15(13) y 16(14) z 17(15) public double focalLength=4.5; 18(16) public double px0=1296.0; // center of the lens on the sensor, pixels 19(17) public double py0=968.0; // center of the lens on the sensor, pixels 20(18) public double distortionA8=0.0; // r^8 (normalized to focal length or to sensor half width?) 21(19) public double distortionA7=0.0; // r^7 (normalized to focal length or to sensor half width?) 22(20) public double distortionA6=0.0; // r^6 (normalized to focal length or to sensor half width?) 23(21) public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 24(22) public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) 25(23) public double distortionB=0.0; // r^3 26(22) public double distortionC=0.0; // r^2 * Output parameters (rows): 0 public double x0=0; // lens axis from pattern center, mm (to the right) 1 public double y0=0; // lens axis from pattern center, mm (down) 2 public double distance=2360; // distance from the lens input pupil to the pattern plane along the camera axis, mm 3 public double yaw=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - clockwise from top 4 public double pitch=0.0; // angle in degrees from perpendicular to the pattern, 0 - towards wall, positive - up 5 public double roll=0.0; // angle in degrees rotation around camera optical axis (perpendicular to pattern if yaw==0, pitch==0), positive - clockwise 6 public double focalLength=4.5; 7 public double px0=1296.0; // center of the lens on the sensor, pixels 8 public double py0=968.0; // center of the lens on the sensor, pixels public double distortionRadius= 2.8512; // mm - half width of the sensor 9 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 10 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 11 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 12 public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) 13 public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) 14 public double distortionB=0.0; // r^3 15 public double distortionC=0.0; // r^2 */ // interParameterDerivatives=new double[getNumInputs()][]; //partial derivative matrix from subcamera-camera-goniometer to single camera (12x21) //calculate dMA_azimuth //calculate dMB_azimuth /* // MA=R8*R7*R6*R5*R4*R3*R2*R1; // MB=T3+(R8*R7*R6*R5*(T2+R4*T1)); - old // MB=T3+(R8*R7*R6*R5*(T2+R4*(T1+R3*R2*R1*T0))); Matrix MA=R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix MB=T3.plus(R8.times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1))))))); old Matrix MB=T3.plus(R8.times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1.plus(R3.times(R2.times(R1.times(T0)))) ))))))); 3) rotate by -(azimuth+phi) around C2Y:Vc3= R3*Vc2 | Xc3 | | cos(azimuth+phi) 0 sin(azimuth+phi) | |Xc2| | Yc3 | = | 0 1 0 | * |Yc2| | Zc3 | | -sin(azimuth+phi) 0 cos(azimuth+phi) | |Zc2| double [][] aR3={{cAZP,0.0,sAZP},{0.0,1.0,0.0},{-sAZP,0.0,cAZP}}; Matrix R3=new Matrix(aR3); 4) Now axes are aligned, just translate to get to eyesis coordinates: Vey= T1+Vc3 | Xey | | r * sin (azimuth) | |Xc3| | Yey | = | height | + |Yc3| | Zey | | r * cos (azimuth) | |Zc3| double [][] aT1={{subCam.radius*sAZ},{subCam.height},{subCam.radius*cAZ}}; Matrix T1=new Matrix(aT1); Matrix // Make a function MA, MB - >parameters (column) - reuse it above and for each interParameterDerivatives row Matrix dMA_azimuth=R8*R7*R6*R5*R4*dR3_azimuth*R2*R1; Matrix dMB_azimuth=T3+(R8*R7*R6*R5*(R4*dT1_azimuth)); Use extrinsicParams=parametersFromMAMB(dMA_azimuth,dMB_azimuth); if (this.debugLevel>3) { System.out.println("calculateFxAndJacobian->calcPartialDerivatives("+IJ.d2s(targetXYZ[fullIndex][0],2)+","+ IJ.d2s(targetXYZ[fullIndex][1],2)+","+ IJ.d2s(targetXYZ[fullIndex][2],2)+" ("+calcJacobian+") -> "+ IJ.d2s(derivatives14[0][0],2)+"/"+IJ.d2s(derivatives14[0][1],2)); } Which parameters affect which matrices R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 || T0 | T1 | T2 | T3 | 0 public double azimuth; // | | + | | | | | || | + | | | 1 public double radius; // | | | | | | | || | + | | | 2 public double height; // | | | | | | | || | + | | | 3 public double phi; // | | + | | | | | || | | | | 4 public double theta; // | + | | | | | | || | | | | 5 public double psi; // + | | | | | | | || | | | | 6 public double goniometerHorizontal; // | | | | | + | | || | | | | 7 public double goniometerAxial; // | | | + | | | | || | | | | 8 public double interAxisDistance; // | | | | | | | || | | + | | 9 public double interAxisAngle; // | | | | + | | | || | | | | 10 public double horAxisErrPhi; // | | | | | | | + || | | | | 11 public double horAxisErrPsi; // | | | | | | + | || | | | | 12 public double entrancePupilForward // | | | | | | | || + | | | | 13 public double centerAboveHorizontal // | | | | | | | || | + | | | 14 x public double [] GXYZ=null; // | | | | | | | || | | | + | 15 y // | | | | | | | || | | | + | 16 z // | | | | | | | || | | | + | */ // Below can be optimized with common intermediate results if (this.debugLevel > 2) { for (int i = 0; i < parVect.length; i++) { System.out.println("calcInterParamers(): parVect[" + i + "]=" + parVect[i]); } } //0 public double azimuth; // azimuth of the lens entrance pupil center, degrees, clockwise looking from top if (mask[0]) { double[][] adR3_azimuth = { { -sAZP, 0.0, cAZP }, { 0.0, 0.0, 0.0 }, { -cAZP, 0.0, -sAZP } }; Matrix dR3_azimuth = new Matrix(adR3_azimuth); // double [][] adT1_azimuth={{radius*cAZ},{height},{-radius*sAZ}}; //{{subCam.radius*cAZ},{subCam.height},{-subCam.radius*sAZ}} double[][] adT1_azimuth = { { radius * cAZ }, { 0.0 }, { -radius * sAZ } }; //{{subCam.radius*cAZ},{subCam.height},{-subCam.radius*sAZ}} Matrix dT1_azimuth = new Matrix(adT1_azimuth); Matrix dMA_azimuth = R8 .times(R7.times(R6.times(R5.times(R4.times(dR3_azimuth.times(R2.times(R1))))))); Matrix dMB0_azimuth = R8.times(R7.times(R6.times(R5.times(R4.times(dT1_azimuth))))); Matrix dMB_azimuth = dMB0_azimuth.plus(dMA_azimuth.times(T0)); // new term interParameterDerivatives[0] = d_parametersFromMAMB(dMA_azimuth, dMB_azimuth, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_azimuth:"); dMA_azimuth.print(10, 5); System.out.println("dMB_azimuth:"); dMB_azimuth.print(10, 5); System.out .println("interParameterDerivatives[0]=" + sprintfArray(interParameterDerivatives[0])); } } else interParameterDerivatives[0] = null; //1 public double radius; // mm, distance from the rotation axis if (mask[1]) { double[][] adT1_radius = { { sAZ }, { 0.0 }, { cAZ } }; //{{subCam.radius*sAZ},{0.0},{subCam.radius*cAZ}} Matrix dT1_radius = new Matrix(adT1_radius); Matrix dMA_radius = new Matrix(3, 3, 0.0); Matrix dMB_radius = R8.times(R7.times(R6.times(R5.times(R4.times(dT1_radius))))); interParameterDerivatives[1] = d_parametersFromMAMB(dMA_radius, dMB_radius, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_radius:"); dMA_radius.print(10, 5); System.out.println("dMB_radius:"); dMB_radius.print(10, 5); System.out .println("interParameterDerivatives[1]=" + sprintfArray(interParameterDerivatives[1])); } } else interParameterDerivatives[1] = null; //2 public double height; // mm, downwards - from the origin point if (mask[2]) { double[][] adT1_height = { { 0.0 }, { 1.0 }, { 0.0 } }; Matrix dT1_height = new Matrix(adT1_height); Matrix dMA_height = new Matrix(3, 3, 0.0); Matrix dMB_height = R8.times(R7.times(R6.times(R5.times(R4.times(dT1_height))))); interParameterDerivatives[2] = d_parametersFromMAMB(dMA_height, dMB_height, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_height:"); dMA_height.print(10, 5); System.out.println("dMB_height:"); dMB_height.print(10, 5); System.out .println("interParameterDerivatives[2]=" + sprintfArray(interParameterDerivatives[2])); } } else interParameterDerivatives[2] = null; //3 public double phi; // degrees, optical axis from azimuth/r vector, clockwise if (mask[3]) { double[][] adR3_phi = { { -sAZP, 0.0, cAZP }, { 0.0, 0.0, 0.0 }, { -cAZP, 0.0, -sAZP } }; // same as adR3_azimuth Matrix dR3_phi = new Matrix(adR3_phi); // same as dR3_azimuth Matrix dMA_phi = R8.times(R7.times(R6.times(R5.times(R4.times(dR3_phi.times(R2.times(R1))))))); //same as dMA_azimuth // Matrix dMB_phi=new Matrix(3,1,0.0); Matrix dMB_phi = dMA_phi.times(T0); // new term interParameterDerivatives[3] = d_parametersFromMAMB(dMA_phi, dMB_phi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_phi:"); dMA_phi.print(10, 5); System.out.println("dMB_phi:"); dMB_phi.print(10, 5); System.out .println("interParameterDerivatives[3]=" + sprintfArray(interParameterDerivatives[3])); } } else interParameterDerivatives[3] = null; //4 public double theta; // degrees, optical axis from the eyesis horizon, positive - up if (mask[4]) { double[][] adR2_theta = { { 0.0, 0.0, 0.0 }, { 0.0, -sTH, cTH }, { 0.0, -cTH, -sTH } }; Matrix dR2_theta = new Matrix(adR2_theta); Matrix dMA_theta = R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(dR2_theta.times(R1))))))); // Matrix dMB_theta=new Matrix(3,1,0.0); Matrix dMB_theta = dMA_theta.times(T0); // new term interParameterDerivatives[4] = d_parametersFromMAMB(dMA_theta, dMB_theta, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_theta:"); dMA_theta.print(10, 5); System.out.println("dMB_theta:"); dMB_theta.print(10, 5); System.out .println("interParameterDerivatives[4]=" + sprintfArray(interParameterDerivatives[4])); } } else interParameterDerivatives[4] = null; //5 public double psi; // degrees, rotation (of the sensor) around the optical axis. Positive if camera is rotated clockwise looking to the target if (mask[5]) { double[][] adR1_psi = { { -sPS, cPS, 0.0 }, { -cPS, -sPS, 0.0 }, { 0.0, 0.0, 0.0 } }; Matrix dR1_psi = new Matrix(adR1_psi); Matrix dMA_psi = R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(dR1_psi))))))); // Matrix dMB_psi=new Matrix(3,1,0.0); Matrix dMB_psi = dMA_psi.times(T0); // new term interParameterDerivatives[5] = d_parametersFromMAMB(dMA_psi, dMB_psi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { /* System.out.print("R1:"); R1.print(10, 5); System.out.print("dR1_psi:"); dR1_psi.print(10, 5); Matrix R82_psi=R8.times(R7.times(R6.times(R5.times(R4.times(R3.times(R2)))))); System.out.print("R82_psi:"); R82_psi.print(10, 5); */ System.out.print("dMA_psi:"); dMA_psi.print(10, 5); System.out.print("dMB_psi:"); dMB_psi.print(10, 5); System.out .println("interParameterDerivatives[5]=" + sprintfArray(interParameterDerivatives[5])); } } else interParameterDerivatives[5] = null; //6 public double goniometerHorizontal; // goniometer rotation around "horizontal" axis (tilting from the target - positive) if (mask[6]) { /* define for isTripod */ double[][] adR6_goniometerHorizontal_tripod = { { -sGH, 0.0, cGH }, { 0.0, 0.0, 0.0 }, { -cGH, 0.0, -sGH } }; double[][] adR6_goniometerHorizontal_goniometer = { { 0.0, 0.0, 0.0 }, { 0.0, -sGH, cGH }, { 0.0, -cGH, -sGH } }; Matrix dR6_goniometerHorizontal = new Matrix( isTripod ? adR6_goniometerHorizontal_tripod : adR6_goniometerHorizontal_goniometer); Matrix dMA_goniometerHorizontal = R8.times( R7.times(dR6_goniometerHorizontal.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_goniometerHorizontal = R8 .times(R7.times(dR6_goniometerHorizontal.times(R5.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[6] = d_parametersFromMAMB(dMA_goniometerHorizontal, dMB_goniometerHorizontal, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_goniometerHorizontal:"); dMA_goniometerHorizontal.print(10, 5); System.out.println("dMB_goniometerHorizontal:"); dMB_goniometerHorizontal.print(10, 5); System.out .println("interParameterDerivatives[6]=" + sprintfArray(interParameterDerivatives[6])); } } else interParameterDerivatives[6] = null; //7 public double goniometerAxial; // goniometer rotation around Eyesis axis (clockwise in plan - positive if (mask[7]) { // define for isTripod double[][] adR4_goniometerAxial_tripod = { { 0.0, 0.0, 0.0 }, { 0.0, -sGA, cGA }, { 0.0, -cGA, -sGA } }; double[][] adR4_goniometerAxial_goniometer = { { -sGA, 0.0, cGA }, { 0.0, 0.0, 0.0 }, { -cGA, 0.0, -sGA } }; Matrix dR4_goniometerAxial = new Matrix( isTripod ? adR4_goniometerAxial_tripod : adR4_goniometerAxial_goniometer); Matrix dMA_goniometerAxial = R8 .times(R7.times(R6.times(R5.times(dR4_goniometerAxial.times(R3.times(R2.times(R1))))))); Matrix dMB_goniometerAxial = R8.times(R7.times(R6.times(R5.times(dR4_goniometerAxial.times(T1))))); interParameterDerivatives[7] = d_parametersFromMAMB(dMA_goniometerAxial, dMB_goniometerAxial, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_goniometerAxial:"); dMA_goniometerAxial.print(10, 5); System.out.println("dMB_goniometerAxial:"); dMB_goniometerAxial.print(10, 5); System.out .println("interParameterDerivatives[7]=" + sprintfArray(interParameterDerivatives[7])); } } else interParameterDerivatives[7] = null; //8 public double interAxisDistance; // distance in mm between two goniometer axes if (mask[8]) { double[][] adT2_interAxisDistance = { { 0.0 }, { 0.0 }, { 1.0 } }; Matrix dT2_interAxisDistance = new Matrix(adT2_interAxisDistance); Matrix dMA_interAxisDistance = new Matrix(3, 3, 0.0); Matrix dMB_interAxisDistance = R8.times(R7.times(R6.times(R5.times(dT2_interAxisDistance)))); interParameterDerivatives[8] = d_parametersFromMAMB(dMA_interAxisDistance, dMB_interAxisDistance, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_interAxisDistance:"); dMA_interAxisDistance.print(10, 5); System.out.println("dMB_interAxisDistance:"); dMB_interAxisDistance.print(10, 5); System.out .println("interParameterDerivatives[8]=" + sprintfArray(interParameterDerivatives[8])); } } else interParameterDerivatives[8] = null; //9 public double interAxisAngle; // angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated if (mask[9]) { double[][] adR5_interAxisAngle = { { -sGIAA, cGIAA, 0.0 }, { -cGIAA, -sGIAA, 0.0 }, { 0.0, 0.0, 0.0 } }; Matrix dR5_interAxisAngle = new Matrix(adR5_interAxisAngle); Matrix dMA_interAxisAngle = R8 .times(R7.times(R6.times(dR5_interAxisAngle.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_interAxisAngle = R8 .times(R7.times(R6.times(dR5_interAxisAngle.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[9] = d_parametersFromMAMB(dMA_interAxisAngle, dMB_interAxisAngle, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_interAxisAngle:"); dMA_interAxisAngle.print(10, 5); System.out.println("dMB_interAxisAngle:"); dMB_interAxisAngle.print(10, 5); System.out .println("interParameterDerivatives[9]=" + sprintfArray(interParameterDerivatives[9])); } } else interParameterDerivatives[9] = null; //10 public double horAxisErrPhi; // angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) // change sHAEPH to rotate like theta /1 0 0 /0 c s/ 0 -s c/ (derivative -/0 0 0 /0 -s c/ 0 -c -s/ if (mask[10]) { double[][] adR8_horAxisErrPhi_tripod = { { 0.0, 0.0, 0.0 }, { 0.0, -sHAEPH, cHAEPH }, { 0.0, -cHAEPH, -sHAEPH } }; double[][] adR8_horAxisErrPhi_goniometer = { { -sHAEPH, 0.0, cHAEPH }, { 0.0, 0.0, 0.0 }, { -cHAEPH, 0.0, -sHAEPH } }; Matrix dR8_horAxisErrPhi = new Matrix( isTripod ? adR8_horAxisErrPhi_tripod : adR8_horAxisErrPhi_goniometer); Matrix dMA_horAxisErrPhi = dR8_horAxisErrPhi .times(R7.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_horAxisErrPhi = dR8_horAxisErrPhi .times(R7.times(R6.times(R5.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[10] = d_parametersFromMAMB(dMA_horAxisErrPhi, dMB_horAxisErrPhi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_horAxisErrPhi:"); dMA_horAxisErrPhi.print(10, 5); System.out.println("dMB_horAxisErrPhi:"); dMB_horAxisErrPhi.print(10, 5); System.out.println( "interParameterDerivatives[10]=" + sprintfArray(interParameterDerivatives[10])); } } else interParameterDerivatives[10] = null; //11 public double horAxisErrPsi; // angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) if (mask[11]) { double[][] adR7_horAxisErrPsi = { { -sHAEPS, cHAEPS, 0.0 }, { -cHAEPS, -sHAEPS, 0.0 }, { 0.0, 0.0, 0.0 } }; Matrix dR7_horAxisErrPsi = new Matrix(adR7_horAxisErrPsi); Matrix dMA_horAxisErrPsi = R8 .times(dR7_horAxisErrPsi.times(R6.times(R5.times(R4.times(R3.times(R2.times(R1))))))); Matrix dMB_horAxisErrPsi = R8 .times(dR7_horAxisErrPsi.times(R6.times(R5.times(T2.plus(R4.times(T1)))))); interParameterDerivatives[11] = d_parametersFromMAMB(dMA_horAxisErrPsi, dMB_horAxisErrPsi, MA, MB, true); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_horAxisErrPsi:"); dMA_horAxisErrPsi.print(10, 5); System.out.println("dMB_horAxisErrPsi:"); dMB_horAxisErrPsi.print(10, 5); System.out.println( "interParameterDerivatives[11]=" + sprintfArray(interParameterDerivatives[11])); } } else interParameterDerivatives[11] = null; // // R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 || T0 | T1 | T2 | T3 | //12 public double entrancePupilForward // | | | | | | | || + | | | | //13 public double centerAboveHorizontal // | | | | | | | || | + | | | if (mask[12]) { double[][] adT0_entrancePupilForward = { { 0.0 }, { 0.0 }, { 1.0 } }; Matrix dT0_entrancePupilForward = new Matrix(adT0_entrancePupilForward); Matrix dMA_entrancePupilForward = new Matrix(3, 3, 0.0); Matrix dMB_entrancePupilForward = MA.times(dT0_entrancePupilForward); interParameterDerivatives[12] = d_parametersFromMAMB(dMA_entrancePupilForward, dMB_entrancePupilForward, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_centerAboveHorizontal:"); dMA_entrancePupilForward.print(10, 5); System.out.println("dMB_centerAboveHorizontal:"); dMB_entrancePupilForward.print(10, 5); System.out.println( "interParameterDerivatives[12]=" + sprintfArray(interParameterDerivatives[12])); } } else interParameterDerivatives[12] = null; if (mask[13]) { double[][] adT1_centerAboveHorizontal = { { 0.0 }, { 1.0 }, { 0.0 } }; Matrix dT1_centerAboveHorizontal = new Matrix(adT1_centerAboveHorizontal); Matrix dMA_centerAboveHorizontal = new Matrix(3, 3, 0.0); Matrix dMB_centerAboveHorizontal = R8 .times(R7.times(R6.times(R5.times(R4.times(dT1_centerAboveHorizontal))))); interParameterDerivatives[13] = d_parametersFromMAMB(dMA_centerAboveHorizontal, dMB_centerAboveHorizontal, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_centerAboveHorizontal:"); dMA_centerAboveHorizontal.print(10, 5); System.out.println("dMB_centerAboveHorizontal:"); dMB_centerAboveHorizontal.print(10, 5); System.out.println( "interParameterDerivatives[13]=" + sprintfArray(interParameterDerivatives[13])); } } else interParameterDerivatives[13] = null; //14 x public double [] GXYZ=null; // coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system if (mask[14]) { double[][] adT3_GXYZ0 = { { 1.0 }, { 0.0 }, { 0.0 } }; Matrix dMA_GXYZ0 = new Matrix(3, 3, 0.0); Matrix dMB_GXYZ0 = new Matrix(adT3_GXYZ0); interParameterDerivatives[14] = d_parametersFromMAMB(dMA_GXYZ0, dMB_GXYZ0, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_GXYZ0:"); dMA_GXYZ0.print(10, 5); System.out.println("dMA_GXYZ0:"); dMB_GXYZ0.print(10, 5); System.out.println( "interParameterDerivatives[14]=" + sprintfArray(interParameterDerivatives[14])); } } else interParameterDerivatives[14] = null; //13 y if (mask[15]) { // double [][] adT3_GXYZ1={{0.0},{1.0},{0.0}}; double[][] adT3_GXYZ1 = { { 0.0 }, { -1.0 }, { 0.0 } }; // up - positive // double [][] adT3_GXYZ1={{0.0},{ 1.0},{0.0}}; // up - positive Matrix dMA_GXYZ1 = new Matrix(3, 3, 0.0); Matrix dMB_GXYZ1 = new Matrix(adT3_GXYZ1); interParameterDerivatives[15] = d_parametersFromMAMB(dMA_GXYZ1, dMB_GXYZ1, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_GXYZ1:"); dMA_GXYZ1.print(10, 5); System.out.println("dMB_GXYZ1:"); dMB_GXYZ1.print(10, 5); System.out.println( "interParameterDerivatives[15]=" + sprintfArray(interParameterDerivatives[15])); } } else interParameterDerivatives[15] = null; //14 z if (mask[16]) { // double [][] adT3_GXYZ2={{0.0},{0.0},{1.0}}; double[][] adT3_GXYZ2 = { { 0.0 }, { 0.0 }, { -1.0 } }; // away - positive Matrix dMA_GXYZ2 = new Matrix(3, 3, 0.0); Matrix dMB_GXYZ2 = new Matrix(adT3_GXYZ2); interParameterDerivatives[16] = d_parametersFromMAMB(dMA_GXYZ2, dMB_GXYZ2, MA, MB, false); // all after 6 are 0; if (this.debugLevel > 2) { System.out.println("dMA_GXYZ2:"); dMA_GXYZ2.print(10, 5); System.out.println("dMB_GXYZ2:"); dMB_GXYZ2.print(10, 5); System.out.println( "interParameterDerivatives[16]=" + sprintfArray(interParameterDerivatives[16])); } } else interParameterDerivatives[16] = null; // now fill the rest, unchanged parameters /* int numInputs=22; //was 21 parameters in subcamera+... int numOutputs=13; //was 12 parameters in a single camera */ //17 (15) public double focalLength=4.5; //18 (16) public double px0=1296.0; // center of the lens on the sensor, pixels //19 (17) public double py0=968.0; // center of the lens on the sensor, pixels //20 (18) public double distortionA8=0.0; // r^5 (normalized to focal length or to sensor half width?) //21 (19) public double distortionA7=0.0; // r^5 (normalized to focal length or to sensor half width?) //22 (20) public double distortionA6=0.0; // r^5 (normalized to focal length or to sensor half width?) //23 (21) public double distortionA5=0.0; // r^5 (normalized to focal length or to sensor half width?) //24 (22) public double distortionA=0.0; // r^4 (normalized to focal length or to sensor half width?) //25 (23) public double distortionB=0.0; // r^3 //26 (24) public double distortionC=0.0; // r^2 // for (int inpPar=15; inpPar<getNumInputs(); inpPar++){ for (int inpPar = 17; inpPar < getNumInputs(); inpPar++) { // OK with A8 if (mask[inpPar]) { interParameterDerivatives[inpPar] = new double[getNumOutputs()]; for (int outPar = 0; outPar < getNumOutputs(); outPar++) { interParameterDerivatives[inpPar][outPar] = ((getNumOutputs() - outPar) == (getNumInputs() - inpPar)) ? 1.0 : 0.0; if (this.debugLevel > 2) { System.out.println("interParameterDerivatives[" + inpPar + "][" + outPar + "]=" + interParameterDerivatives[inpPar][outPar]); } } } else interParameterDerivatives[inpPar] = null; } } } // stores per pattern image camera/subcamera parameters, filenames, ... // saves / restores them from a disk file public static class DistortionCalibrationData { public String pathName = null; public EyesisCameraParameters eyesisCameraParameters; // public int numStations=1; // number of differnt camera tripod/goniometer stations (locations) public int numSubCameras = 1; public int numPointers = 4; // maximal number of pointers to look for public int numMotors = 3; // maximal number of motors to look for public GridImageParameters[] gIP = null; // per-grid image parameters public GridImageSet[] gIS = null; // sets of images with the same timestamp // public String [] paths=null; // private ImagePlus [] gridImages=null; // array of grid images (to be used instead of files) // public double [] timestamps=null; // public int [] channels=null; // keep for now? public double[][] pars = null; // for each defined image: set of (22) parameters // public double [][][] pixelsXY=null; // for each image, each grid node - a pair of {px,py} // public int [][][] pixelsUV=null; // for each image, each grid node - a pair of {gridU, gridV} public double[][] sensorMasks = null; // per-channel (not image) mask //pixelsXY, pixelsUV should match, second dimension is variable public boolean updateStatus = true; public int debugLevel = 2; private showDoubleFloatArrays SDFA_INSTANCE = null; // just for debugging public int getNumStations() { return (eyesisCameraParameters == null) ? 0 : eyesisCameraParameters.getNumStations(); } public class GridImageParameters { public int imgNumber = -1; // index of this image (for pars[][]) private int setNumber = -1; // long overdue - will be some inconsistency GridImageSet gridImageSet = null; public int stationNumber = 0; // changes when camera/goniometer is moved to new position public String path = null; public double[][] laserPixelCoordinates = null; // first index - absolute number of pointer. Each element may be either null or {x,y} pair public int matchedPointers = 0; public int hintedMatch = -1; // -1 - not tried, 0 - no real grid (i.e. double reflection), applied orientation, applied orientation and shift public boolean enabled = true; //false; // to mask out some images from all strategy steps (i.e w/o reliable absolute calibration) public boolean flatFieldAvailable = false; // grid files have flat field data public boolean newEnabled = false; public int[] motors = null; public ImagePlus gridImage = null; public double timestamp = -1; public int channel = -1; public double[] intensityRange = { 255.0, 255.0, 255.0 }; // r,g,b - used to normalize vign* public double[] saturation = { 255.0, 255.0, 255.0 }; // r,g,b - saturation range read from images // public double [] pars=null; // set of (22) parameters public double[][] pixelsXY = null; // for each image, each grid node - a set of of {px,py,contrast,vignR,vignG,vignB} vign* is in the 0..1.0 range public double[] pixelsMask = null; // for each image, each grid node - weight function derived from contrast and 3 parameters public int[][] pixelsUV = null; // for each image, each grid node - a pair of {gridU, gridV} public boolean[] badNodes = null; // if not null, marks node with excessive errors public double[][] pixelsXY_extra = null; // extra data, for nodes that are out of the physical grid (may be needed after re-calibration) public int[][] pixelsUV_extra = null; public double gridPeriod = 0.0; // average grid period, in pixels (to filter out (double-) reflected images public boolean noUsefulPSFKernels = false; // used to mark images w/o good PSF data public double diameter = 0.0; final int contrastIndex = 2; int getSetNumber() { return this.setNumber; } public GridImageParameters(int index) { this.imgNumber = index; } public int getStationNumber() { // TODO: make only a single station number - in GridImageSet? return this.stationNumber; } public void setStationNumber(int stationNumber) { // TODO: make only a single station number - in GridImageSet? this.stationNumber = stationNumber; } public double[] getGridWeight() { return this.pixelsMask; } public void resetMask() { this.pixelsMask = null; } public void resetBadNodes() { this.badNodes = null; } public void setBadNode(int index) { if (this.badNodes == null) { this.badNodes = new boolean[this.pixelsXY.length]; // let it throw if null for (int i = 0; i < this.badNodes.length; i++) this.badNodes[i] = false; } this.badNodes[index] = true; } public boolean isNodeBad(int index) { if (this.badNodes == null) return false; if (index >= this.badNodes.length) { System.out.println( "### isNodeBad(" + index + ") - OOB, as this.badNodes=" + this.badNodes.length); return true; } return this.badNodes[index]; //OOB } public int getNumContrastNodes(double minContrast) { int num = 0; for (int i = 0; i < this.pixelsXY.length; i++) if (this.pixelsXY[i][contrastIndex] >= minContrast) num++; return num; } /** * Calculate "diameter" of the image to be used for image weight * @param xc image center pixel X * @param yc image center pixel Y * @param r0 reference diameter * @param minContrast minimal contrast to count the node */ public void setImageDiameter( // need to get image center px,py. Maybe r0 - use to normalize result diameter double xc, double yc, double r0, double minContrast, int dbgImgNum //>=0 - debug print with image number ) { boolean debug = (dbgImgNum >= 0); // find the farthest point from the center double maxR2 = -1; int firstIndex = 0; for (int i = 0; i < this.pixelsXY.length; i++) if (this.pixelsXY[i][contrastIndex] >= minContrast) { double dx = this.pixelsXY[i][0] - xc; double dy = this.pixelsXY[i][1] - yc; double r2 = dx * dx + dy * dy; if (r2 > maxR2) { maxR2 = r2; firstIndex = i; } } if (maxR2 <= 0) { this.diameter = 0.0; return; } double maxDx = this.pixelsXY[firstIndex][0] - xc; double maxDy = this.pixelsXY[firstIndex][1] - yc; if (debug) System.out.print("setImageDiameter(" + IJ.d2s(xc, 2) + "," + IJ.d2s(yc, 2) + "," + IJ.d2s(r0, 2) + "," + IJ.d2s(minContrast, 4) + "," + dbgImgNum + ") ---- > "); if (debug) System.out.print(" maxR2=" + IJ.d2s(maxR2, 2) + " maxDx=" + IJ.d2s(maxDx, 2) + " maxDy=" + IJ.d2s(maxDy, 2)); double maxAamb = 0; double dbgDx = 0.0, dbgDy = 0.0; for (int i = 0; i < this.pixelsXY.length; i++) if (this.pixelsXY[i][contrastIndex] >= minContrast) { // double dx=maxDx-this.pixelsXY[i][0]; // double dy=maxDy-this.pixelsXY[i][1]; double dx = this.pixelsXY[firstIndex][0] - this.pixelsXY[i][0]; double dy = this.pixelsXY[firstIndex][1] - this.pixelsXY[i][1]; double aAmb = dx * maxDx + dy * maxDy; if (aAmb > maxAamb) { maxAamb = aAmb; dbgDx = this.pixelsXY[i][0]; // debug only ! dbgDy = this.pixelsXY[i][1]; // debug only ! } } this.diameter = maxAamb / Math.sqrt(maxR2) / r0; if (debug) System.out.println(" maxAamb=" + IJ.d2s(maxAamb, 2) + " dbgDx=" + IJ.d2s(dbgDx, 2) + " dbgDy=" + IJ.d2s(dbgDy, 2) + " --> " + IJ.d2s(this.diameter, 2)); } /** * Uzses data calculated by setImageDiameter(); * @return detected grid diameter (along the radius) to be uses as image weight (in r0 units) */ public double getGridDiameter() { return this.diameter; } public void calculateMask(double minContrast, double shrinkBlurSigma, double shrinkBlurLevel) { if (this.pixelsMask != null) return; // need to reset ro re-calculate if (this.pixelsUV == null) { this.pixelsMask = null; return; } if (this.pixelsUV.length == 0) { this.pixelsMask = new double[0]; return; } this.pixelsMask = new double[this.pixelsUV.length]; if (shrinkBlurSigma <= 0) { for (int i = 0; i < this.pixelsUV.length; i++) { this.pixelsMask[i] = (this.pixelsXY[i][contrastIndex] >= minContrast) ? 1.0 : 0.0; } return; } int minU = this.pixelsUV[0][0], minV = this.pixelsUV[0][1]; int maxU = minU, maxV = minV; int margin = (int) (2 * shrinkBlurSigma); for (int i = 0; i < this.pixelsUV.length; i++) { if (this.pixelsUV[i][0] > maxU) maxU = this.pixelsUV[i][0]; if (this.pixelsUV[i][0] < minU) minU = this.pixelsUV[i][0]; if (this.pixelsUV[i][1] > maxV) maxV = this.pixelsUV[i][1]; if (this.pixelsUV[i][1] < minV) minV = this.pixelsUV[i][1]; } int U0 = minU - margin; int V0 = minV - margin; int width = (maxU - minU + 1 + 2 * margin); int height = (maxV - minV + 1 + 2 * margin); double[] mask = new double[width * height]; for (int i = 0; i < mask.length; i++) mask[i] = -1.0; for (int i = 0; i < this.pixelsUV.length; i++) { int index = (this.pixelsUV[i][0] - U0) + width * (this.pixelsUV[i][1] - V0); mask[index] = (this.pixelsXY[i][contrastIndex] >= minContrast) ? 1.0 : -1.0; // java.lang.ArrayIndexOutOfBoundsException: 2230 } (new DoubleGaussianBlur()).blurDouble(mask, width, height, shrinkBlurSigma, shrinkBlurSigma, 0.01); double k = 1.0 / (1.0 - shrinkBlurLevel); double dbgMax = 0.0; for (int i = 0; i < this.pixelsUV.length; i++) { int index = (this.pixelsUV[i][0] - U0) + width * (this.pixelsUV[i][1] - V0); double d = k * (mask[index] - shrinkBlurLevel); this.pixelsMask[i] = (d > 0.0) ? (d * d) : 0.0; if (this.pixelsMask[i] > dbgMax) dbgMax = this.pixelsMask[i]; } // System.out.print(" "+IJ.d2s(dbgMax,2)+" "); } } public class GridImageSet { private int numPars = 27; private int thisParsStartIndex = 6; public int stationNumber = 0; // changes when camera/goniometer is moved to new position public GridImageParameters[] imageSet = null; // public GridImageParameters firstImage=null; // first non-null image in the sert (update to have current parameters?) public double timeStamp; public int[] motors = null; public double goniometerAxial = Double.NaN; public double goniometerTilt = Double.NaN; public double interAxisDistance; // 8 distance in mm between two goniometer axes public double interAxisAngle; // 9 angle in degrees between two goniometer axes minus 90. negative if "vertical" axis is rotated public double horAxisErrPhi; //10 angle in degrees "horizontal" goniometer axis is rotated around target Y axis from target X axis (CW) public double horAxisErrPsi; //11 angle in degrees "horizontal" goniometer axis is rotated around moving X axis (up) public double entrancePupilForward; //12 common to all lenses - distance from the sensor to the lens entrance pupil public double centerAboveHorizontal;//13 camera center distance along camera axis above the closest point to horizontal rotation axis (adds to height of each public double[] GXYZ = new double[3]; //14 (12) coordinates (in mm) of the goniometer horizontal axis closest to the moving one in target system // this.GXYZ[stationNumber][1], //15 (13) y // this.GXYZ[stationNumber][2], //16 (14) z public boolean orientationEstimated = true; // orientation is estimated from other stes, notr adjusted by LMA public double setWeight = 0.0; // weight of this set when calculating errors public void setEstimatedFromNonNaN() { this.orientationEstimated = Double.isNaN(this.goniometerTilt) || Double.isNaN(this.goniometerAxial); } public int getMinIndex() { return this.thisParsStartIndex; } public int getMaxIndexPlusOne() { return this.thisParsStartIndex + getSetVector().length; } public double[] getSetVector() { double[] sv = { this.goniometerTilt, this.goniometerAxial, this.interAxisDistance, this.interAxisAngle, this.horAxisErrPhi, this.horAxisErrPsi, this.entrancePupilForward, this.centerAboveHorizontal, this.GXYZ[0], this.GXYZ[1], this.GXYZ[2] }; return sv; } public void setSetVector(double[] vector) { if (vector.length != getSetVector().length) { String msg = "Wrong parameter vector length - got:" + vector.length + ", expected: " + getSetVector().length; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.goniometerTilt = vector[0]; this.goniometerAxial = vector[1]; this.interAxisDistance = vector[2]; this.interAxisAngle = vector[3]; this.horAxisErrPhi = vector[4]; this.horAxisErrPsi = vector[5]; this.entrancePupilForward = vector[6]; this.centerAboveHorizontal = vector[7]; this.GXYZ[0] = vector[8]; this.GXYZ[1] = vector[9]; this.GXYZ[2] = vector[10]; } public double getParameterValue(int index) { int thisIndex = index - this.thisParsStartIndex; double[] sv = getSetVector(); if ((thisIndex < 0) || (index > sv.length)) return Double.NaN; return sv[thisIndex]; } public void setParameterValue(int index, double value, boolean updateEstimated) { int thisIndex = index - this.thisParsStartIndex; switch (thisIndex) { case 0: this.goniometerTilt = value; setEstimatedFromNonNaN(); break; case 1: this.goniometerAxial = value; setEstimatedFromNonNaN(); break; case 2: this.interAxisDistance = value; break; case 3: this.interAxisAngle = value; break; case 4: this.horAxisErrPhi = value; break; case 5: this.horAxisErrPsi = value; break; case 6: this.entrancePupilForward = value; break; case 7: this.centerAboveHorizontal = value; break; case 8: this.GXYZ[0] = value; break; case 9: this.GXYZ[1] = value; break; case 10: this.GXYZ[2] = value; break; } } public double[] updateParameterVectorFromSet(double[] vector) { if (vector == null) { vector = new double[this.numPars]; for (int i = 0; i < vector.length; i++) vector[i] = Double.NaN; } if (vector.length != this.numPars) { String msg = "Wrong parameter vector length - got:" + vector.length + ", expected: " + this.numPars; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } double[] sv = getSetVector(); for (int i = 0; i < sv.length; i++) if (!Double.isNaN(sv[i])) vector[i + this.thisParsStartIndex] = sv[i]; return vector; } public double[] updateParameterVectorFromSet(double[] vector, boolean[] mask) { if (vector == null) { vector = new double[this.numPars]; for (int i = 0; i < vector.length; i++) vector[i] = Double.NaN; } if (vector.length != this.numPars) { String msg = "Wrong parameter vector length - got:" + vector.length + ", expected: " + this.numPars; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } double[] sv = getSetVector(); for (int i = 0; i < sv.length; i++) if (!Double.isNaN(sv[i]) && mask[this.thisParsStartIndex + i]) vector[i + this.thisParsStartIndex] = sv[i]; return vector; } public void updateSetFromParameterVector(double[] vector) { if (vector.length != this.numPars) { String msg = "Wrong parameter vector length - got:" + vector.length + ", expected: " + this.numPars; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.goniometerTilt = vector[this.thisParsStartIndex + 0]; this.goniometerAxial = vector[this.thisParsStartIndex + 1]; this.interAxisDistance = vector[this.thisParsStartIndex + 2]; this.interAxisAngle = vector[this.thisParsStartIndex + 3]; this.horAxisErrPhi = vector[this.thisParsStartIndex + 4]; this.horAxisErrPsi = vector[this.thisParsStartIndex + 5]; this.entrancePupilForward = vector[this.thisParsStartIndex + 6]; this.centerAboveHorizontal = vector[this.thisParsStartIndex + 7]; this.GXYZ[0] = vector[this.thisParsStartIndex + 8]; this.GXYZ[1] = vector[this.thisParsStartIndex + 9]; this.GXYZ[2] = vector[this.thisParsStartIndex + 10]; } public void updateSetFromParameterVector(double[] vector, boolean[] mask) { if (vector.length != this.numPars) { String msg = "Wrong parameter vector length - got:" + vector.length + ", expected: " + this.numPars; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (mask[this.thisParsStartIndex + 0]) this.goniometerTilt = vector[this.thisParsStartIndex + 0]; if (mask[this.thisParsStartIndex + 1]) this.goniometerAxial = vector[this.thisParsStartIndex + 1]; if (mask[this.thisParsStartIndex + 2]) this.interAxisDistance = vector[this.thisParsStartIndex + 2]; if (mask[this.thisParsStartIndex + 3]) this.interAxisAngle = vector[this.thisParsStartIndex + 3]; if (mask[this.thisParsStartIndex + 4]) this.horAxisErrPhi = vector[this.thisParsStartIndex + 4]; if (mask[this.thisParsStartIndex + 5]) this.horAxisErrPsi = vector[this.thisParsStartIndex + 5]; if (mask[this.thisParsStartIndex + 6]) this.entrancePupilForward = vector[this.thisParsStartIndex + 6]; if (mask[this.thisParsStartIndex + 7]) this.centerAboveHorizontal = vector[this.thisParsStartIndex + 7]; if (mask[this.thisParsStartIndex + 8]) this.GXYZ[0] = vector[this.thisParsStartIndex + 8]; if (mask[this.thisParsStartIndex + 9]) this.GXYZ[1] = vector[this.thisParsStartIndex + 9]; if (mask[this.thisParsStartIndex + 10]) this.GXYZ[2] = vector[this.thisParsStartIndex + 10]; } public double getSetWeight() { return this.setWeight; } public int getStationNumber() { // TODO: make only a single station number - in GridImageSet? return this.stationNumber; } public void setStationNumber(int stationNumber) { // TODO: make only a single station number - in GridImageSet? this.stationNumber = stationNumber; } } public String[][] parameterDescriptions = { { "subcamAzimuth", "Subcamera azimuth, clockwise looking from top", "degrees", "S", "E" }, // 0 { "subcamDistance", "Subcamera distance from the axis", "mm", "S", "E" }, // 1 { "subcamHeight", "Subcamera height from the 'equator'", "mm", "S", "E" }, // 2 { "subcamHeading", "Optical axis heading (relative to azimuth)", "degrees", "S", "E" }, // 3 { "subcamElevation", "Optical axis elevation (up from equator)", "degrees", "S", "E" }, // 4 { "subcamRoll", "Subcamera roll, positive CW looking to the target", "degrees", "S", "E" }, // 5 { "goniometerHorizontal", "Goniometer rotation around 'horizontal' axis (tilting from the target - positive)", "degrees", "R", "E" }, // 6 { "goniometerAxial", "Rotation around Eyesis main axis (clockwise in plan - positive)", "degrees", "R", "E" }, // 7 { "interAxisDistance", "Distance between goniometer axes", "mm", "C", "E" }, // 8 { "interAxisAngle", "Angle error between goniometer axes (<0 if vertical axis rotated CW )", "degrees", "C", "E" }, // 9 { "horAxisErrPhi", "Horizontal axis azimuth error (CW in plan)", "degrees", "C", "E" }, //10 { "horAxisErrPsi", "Horizontal axis roll error (CW looking to target)", "degrees", "C", "E" }, //11 { "entrancePupilForward", "Distance from the sensor to the lens entrance pupil", "mm", "C", "E" }, //12 { "centerAboveHorizontal", "CenterAboveHorizontal", "mm", "C", "E" }, //13 { "GXYZ0", "Goniometer reference point position X (target coordinates, left)", "mm", "T", "E" }, //14 (12) { "GXYZ1", "Goniometer reference point position Y (target coordinates, up)", "mm", "T", "E" }, //15 (13) { "GXYZ2", "Goniometer reference point position Z (target coordinates, away)", "mm", "T", "E" }, //16 (14) { "subcamFocalLength", "Lens focal length", "mm", "S", "I" }, //17 (15) { "subcamPX0", "Lens axis on the sensor (horizontal, from left edge)", "pixels", "S", "I" }, //18 (16) { "subcamPY0", "Lens axis on the sensor (vertical, from top edge)", "pixels", "S", "I" }, //19 (17) { "subcamDistortionA8", "Distortion A8(r^5)", "relative", "S", "I" }, //20 (18) { "subcamDistortionA7", "Distortion A7(r^5)", "relative", "S", "I" }, //21 (19) { "subcamDistortionA6", "Distortion A6(r^5)", "relative", "S", "I" }, //22 (20) { "subcamDistortionA5", "Distortion A5(r^5)", "relative", "S", "I" }, //23 (21) { "subcamDistortionA", "Distortion A (r^4)", "relative", "S", "I" }, //24 (22) { "subcamDistortionB", "Distortion B (r^3)", "relative", "S", "I" }, //25 (23) { "subcamDistortionC", "Distortion C (r^2)", "relative", "S", "I" } //26 (24) }; public String[] channelSuffixes = { // natural order (same as array indices, may be modified to camera/subcamera "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29" }; public int getParameterIndexByName(String name) { for (int i = 0; i < this.parameterDescriptions.length; i++) if (this.parameterDescriptions[i][0].equals(name)) { return i; } return -1; } /** * Initialize data from scratch using filenames "grid-<timestamp-seconds>_<timestamp-microseconds>-<channel-number>.tiff * @param filenames List of grid filenames (2-slice TIFFs) */ public DistortionCalibrationData(String[] filenames, PatternParameters patternParameters, EyesisCameraParameters eyesisCameraParameters) { String[][] stationFilenames = { filenames }; setupDistortionCalibrationData(stationFilenames, patternParameters, eyesisCameraParameters // debugLevel ); } public DistortionCalibrationData(String[] filenames, PatternParameters patternParameters, EyesisCameraParameters eyesisCameraParameters, int debugLevel) { this.debugLevel = debugLevel; String[][] stationFilenames = { filenames }; setupDistortionCalibrationData(stationFilenames, patternParameters, eyesisCameraParameters // debugLevel ); } public DistortionCalibrationData(String[][] stationFilenames, PatternParameters patternParameters, EyesisCameraParameters eyesisCameraParameters, int debugLevel) { this.debugLevel = debugLevel; setupDistortionCalibrationData(stationFilenames, patternParameters, eyesisCameraParameters // debugLevel ); } public void setupDistortionCalibrationData(String[][] stationFilenames, PatternParameters patternParameters, EyesisCameraParameters eyesisCameraParameters) { this.eyesisCameraParameters = eyesisCameraParameters; int numSubCameras = (eyesisCameraParameters == null) ? 1 : eyesisCameraParameters.eyesisSubCameras[0].length; this.numSubCameras = numSubCameras; this.eyesisCameraParameters.numStations = stationFilenames.length; int numFiles = 0; for (int i = 0; i < stationFilenames.length; i++) numFiles += stationFilenames[i].length; this.gIP = new GridImageParameters[numFiles]; // this.paths=new String [filenames.length]; // this.timestamps=new double [filenames.length]; // this.channels= new int [filenames.length]; int numFile = 0; for (int numStation = 0; numStation < stationFilenames.length; numStation++) { for (int index = 0; index < stationFilenames[numStation].length; index++) { System.out.println(numFile + " (" + numStation + ":" + index + "): " + stationFilenames[numStation][index]); this.gIP[numFile] = new GridImageParameters(numFile); this.gIP[numFile].path = stationFilenames[numStation][index]; //Exception in thread "Run$_AWT-EventQueue-0" java.lang.NullPointerException at Distortions$DistortionCalibrationData.<init>(Distortions.java:5987) this.gIP[numFile].setStationNumber(numStation); int i1 = stationFilenames[numStation][index].indexOf('-', stationFilenames[numStation][index].lastIndexOf(Prefs.getFileSeparator())); int i2 = stationFilenames[numStation][index].indexOf('-', i1 + 1); int i3 = stationFilenames[numStation][index].indexOf('.', i2 + 1); // Extract timestamp from the filename if ((i1 < 0) || (i2 < 0)) { String msg = "invalid file format - '" + stationFilenames[numStation][index] + "', should be '<timestamp-seconds>_<timestamp-microseconds>-<channel-number>.tiff'"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } // Extract channel number from the filename this.gIP[numFile].timestamp = Double.parseDouble( stationFilenames[numStation][index].substring(i1 + 1, i2).replace('_', '.')); String channelSuffix = stationFilenames[numStation][index].substring(i2 + 1, i3); this.gIP[numFile].channel = -1; for (int j = 0; j < this.channelSuffixes.length; j++) if (channelSuffix.equals(this.channelSuffixes[j])) { this.gIP[numFile].channel = j; break; } if (this.gIP[numFile].channel < 0) { String msg = "invalid file format (channel suffix not recognized) - '" + stationFilenames[numStation][index] + "', should be '<timestamp-seconds>_<timestamp-microseconds>-<channel-number>.tiff'"; msg += "\nThis channel suffix is " + channelSuffix + ", available channel suffixes are:\n"; for (int j = 0; j < this.channelSuffixes.length; j++) msg += this.channelSuffixes[j] + ", "; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } numFile++; } } // Create parameters array // this.pars=new double[this.gIP.length][parameterDescriptions.length]; initPars(this.gIP.length, parameterDescriptions.length); if (this.debugLevel > 1) System.out.println("setupDistortionCalibrationData(): Resetting this.gIS"); this.gIS = null; // so it will be initialized in readAllGrids() readAllGrids(patternParameters); // prepare grid parameters for LMA // no orientation } // return (Integer) this.images[sensorNum].getProperty("POINTERS"); public DistortionCalibrationData(EyesisCameraParameters eyesisCameraParameters) { int numSubCameras = (eyesisCameraParameters == null) ? 1 : eyesisCameraParameters.eyesisSubCameras[0].length; this.numSubCameras = numSubCameras; this.eyesisCameraParameters = eyesisCameraParameters; } public DistortionCalibrationData(ImagePlus[] images, // images in the memory PatternParameters patternParameters, EyesisCameraParameters eyesisCameraParameters) { int numSubCameras = (eyesisCameraParameters == null) ? 1 : eyesisCameraParameters.eyesisSubCameras[0].length; this.numSubCameras = numSubCameras; this.eyesisCameraParameters = eyesisCameraParameters; setImages(images, patternParameters); } public int get_gIS_index(int numImg) { if (this.gIS == null) return -1; for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].imageSet != null) for (int j = 0; j < this.gIS[i].imageSet.length; j++) if ((this.gIS[i].imageSet[j] != null) && (this.gIS[i].imageSet[j].imgNumber == numImg)) return i; return -1; } public void listCameraParameters() { int numSubCameras = getNumSubCameras(); if (this.gIP != null) { int maxChn = 0; for (int i = 0; i < this.gIP.length; i++) if ((this.gIP[i] != null) && (this.gIP[i].channel > maxChn)) { maxChn = this.gIP[i].channel; } numSubCameras = maxChn + 1; } String header = "Name\tUnits"; StringBuffer sb = new StringBuffer(); for (int i = 0; i < numSubCameras; i++) header += "\t" + i; for (int stationNumber = 0; stationNumber < this.eyesisCameraParameters.numStations; stationNumber++) { if (this.eyesisCameraParameters.numStations > 1) { sb.append("Station " + stationNumber + " W=" + (100 * this.eyesisCameraParameters.stationWeight[stationNumber]) + "%"); for (int i = -1; i < numSubCameras; i++) sb.append("\t==="); sb.append("\n"); } double[][] cameraPars = new double[numSubCameras][]; for (int i = 0; i < numSubCameras; i++) cameraPars[i] = eyesisCameraParameters.getParametersVector(stationNumber, i); // parameters same order as in this for (int n = 0; n < cameraPars[0].length; n++) if (isSubcameraParameter(n) && isIntrinsicParameter(n)) { sb.append(getParameterName(n) + "\t" + getParameterUnits(n)); for (int i = 0; i < numSubCameras; i++) sb.append("\t" + IJ.d2s(cameraPars[i][n], 3)); sb.append("\n"); } sb.append("---"); for (int i = -1; i < numSubCameras; i++) sb.append("\t"); sb.append("\n"); for (int n = 0; n < cameraPars[0].length; n++) if (isSubcameraParameter(n) && !isIntrinsicParameter(n)) { sb.append(getParameterName(n) + "\t" + getParameterUnits(n)); for (int i = 0; i < numSubCameras; i++) sb.append("\t" + IJ.d2s(cameraPars[i][n], 3)); sb.append("\n"); } sb.append("---"); for (int i = -1; i < numSubCameras; i++) sb.append("\t"); sb.append("\n"); for (int n = 0; n < cameraPars[0].length; n++) if (!isSubcameraParameter(n) && !isLocationParameter(n) && !isOrientationParameter(n)) { sb.append(getParameterName(n) + "\t" + getParameterUnits(n)); sb.append("\t" + IJ.d2s(cameraPars[0][n], 3)); for (int i = 1; i < numSubCameras; i++) sb.append("\t---"); sb.append("\n"); } sb.append("---"); for (int i = -1; i < numSubCameras; i++) sb.append("\t"); sb.append("\n"); for (int n = 0; n < cameraPars[0].length; n++) if (isLocationParameter(n)) { sb.append(getParameterName(n) + "\t" + getParameterUnits(n)); sb.append("\t" + IJ.d2s(cameraPars[0][n], 3)); for (int i = 1; i < numSubCameras; i++) sb.append("\t---"); sb.append("\n"); } sb.append("---"); for (int i = -1; i < numSubCameras; i++) sb.append("\t"); sb.append("\n"); for (int n = 0; n < cameraPars[0].length; n++) if (isOrientationParameter(n)) { sb.append(getParameterName(n) + "\t" + getParameterUnits(n)); sb.append("\t" + IJ.d2s(cameraPars[0][n], 3)); for (int i = 1; i < numSubCameras; i++) sb.append("\t---"); sb.append("\n"); } } new TextWindow("Camera parameters", header, sb.toString(), 85 * (numSubCameras + 3), 600); } public void setImages(ImagePlus[] images, // images in the memory PatternParameters patternParameters) { this.gIP = new GridImageParameters[images.length]; for (int i = 0; i < images.length; i++) { this.gIP[i] = new GridImageParameters(i); this.gIP[i].path = images[i].getTitle(); // not real path? this.gIP[i].timestamp = getImageTimestamp(images[i]); System.out.println(i + ": " + this.gIP[i].path + " - timestamp=" + this.gIP[i].timestamp); this.gIP[i].channel = getImageChannel(images[i]); this.gIP[i].gridImage = images[i]; // free later? } // Create parameters array // this.pars=new double[images.length][parameterDescriptions.length]; initPars(this.gIP.length, parameterDescriptions.length); this.gIS = null; // so it will be created in readAllGrids() readAllGrids(patternParameters); // prepare grid parameters for LMA // no orientation } public void listImageSet() { listImageSet(null, null, null); } public void listImageSet(int[] numPoints, double[] setRMS, boolean[] hasNaNInSet) { if ((this.gIS == null) || (this.gIS.length == 0)) { return; } String header = "#\ttimestamp"; if (this.eyesisCameraParameters.numStations > 1) header += "\tStation"; header += "\tAxial\tTilt\thorPhi\thorPsi\tX\tY\tZ\tMotor2\tMotor3"; if (numPoints != null) header += "\tNumPoints"; header += "\tEnabled\tMatched"; if (setRMS != null) header += "\tRMS\tWeight"; for (int n = 0; n < this.gIS[0].imageSet.length; n++) header += "\t" + n; StringBuffer sb = new StringBuffer(); for (int i = 0; i < this.gIS.length; i++) { double firstHorAxisErrPhi = Double.NaN; double firstHorAxisErrPsi = Double.NaN; double firstGXYZ0 = Double.NaN; double firstGXYZ1 = Double.NaN; double firstGXYZ2 = Double.NaN; // int index=-1; // for (int ni=0;ni<this.gIS[i].imageSet.length;ni++) if (this.gIS[i].imageSet[ni]!=null){ // index=this.gIS[i].imageSet[ni].imgNumber; // break; // } // int horAxisErrPhiIndex=getParameterIndexByName("horAxisErrPhi"); // int horAxisErrPsiIndex=getParameterIndexByName("horAxisErrPsi"); // if ((index>=0) && (horAxisErrPhiIndex>=0)) { // firstHorAxisErrPhi=this.pars[index][horAxisErrPhiIndex]; firstHorAxisErrPhi = this.gIS[i].horAxisErrPhi; // } // if ((index>=0) && (horAxisErrPsiIndex>=0)) { // firstHorAxisErrPsi=this.pars[index][horAxisErrPsiIndex]; firstHorAxisErrPsi = this.gIS[i].horAxisErrPsi; // } // int GXYZ0Index=getParameterIndexByName("GXYZ0"); // if ((index>=0) && (GXYZ0Index>=0)) { // firstGXYZ0=this.pars[index][GXYZ0Index]; firstGXYZ0 = this.gIS[i].GXYZ[0]; // } // int GXYZ1Index=getParameterIndexByName("GXYZ1"); // if ((index>=0) && (GXYZ1Index>=0)) { // firstGXYZ1=this.pars[index][GXYZ1Index]; firstGXYZ1 = this.gIS[i].GXYZ[1]; // } // int GXYZ2Index=getParameterIndexByName("GXYZ2"); // if ((index>=0) && (GXYZ2Index>=0)) { // firstGXYZ2=this.pars[index][GXYZ2Index]; firstGXYZ2 = this.gIS[i].GXYZ[2]; // } sb.append(i + "\t" + IJ.d2s(this.gIS[i].timeStamp, 6)); if (this.eyesisCameraParameters.numStations > 1) sb.append(i + "\t" + this.gIS[i].getStationNumber()); sb.append( "\t" + (Double.isNaN(this.gIS[i].goniometerAxial) ? "---" : ((this.gIS[i].orientationEstimated ? "(" : "") + IJ.d2s(this.gIS[i].goniometerAxial, 3) + (this.gIS[i].orientationEstimated ? ")" : "")))); sb.append( "\t" + (Double.isNaN(this.gIS[i].goniometerTilt) ? "---" : ((this.gIS[i].orientationEstimated ? "(" : "") + IJ.d2s(this.gIS[i].goniometerTilt, 3) + (this.gIS[i].orientationEstimated ? ")" : "")))); sb.append("\t" + (Double.isNaN(firstHorAxisErrPhi) ? "---" : IJ.d2s(firstHorAxisErrPhi, 3))); sb.append("\t" + (Double.isNaN(firstHorAxisErrPsi) ? "---" : IJ.d2s(firstHorAxisErrPsi, 3))); sb.append("\t" + (Double.isNaN(firstGXYZ0) ? "---" : IJ.d2s(firstGXYZ0, 3))); sb.append("\t" + (Double.isNaN(firstGXYZ1) ? "---" : IJ.d2s(firstGXYZ1, 3))); sb.append("\t" + (Double.isNaN(firstGXYZ2) ? "---" : IJ.d2s(firstGXYZ2, 3))); if (this.gIS[i].motors == null) { sb.append("\t" + "bug" + "\t" + "bug"); } else { sb.append("\t" + this.gIS[i].motors[1] + "\t" + this.gIS[i].motors[2]); // null pointer here???? } if (numPoints != null) sb.append("\t" + numPoints[i]); int numEnImages = 0; for (int n = 0; n < this.gIS[i].imageSet.length; n++) if (this.gIS[i].imageSet[n] != null) { if (this.gIS[i].imageSet[n].enabled) numEnImages++; } sb.append("\t" + numEnImages); int matchedPointersInSet = 0; for (int n = 0; n < this.gIS[i].imageSet.length; n++) { if (this.gIS[i].imageSet[n] != null) { matchedPointersInSet += this.gIS[i].imageSet[n].matchedPointers; } } sb.append("\t" + matchedPointersInSet); if (setRMS != null) { sb.append("\t" + (((hasNaNInSet != null) && hasNaNInSet[i]) ? "*" : "") + IJ.d2s(setRMS[i], 3)); sb.append("\t" + IJ.d2s(this.gIS[i].setWeight, 3)); } for (int n = 0; n < this.gIS[i].imageSet.length; n++) { sb.append("\t"); if (this.gIS[i].imageSet[n] != null) { int numPointers = 0; // count number of laser pointers if (this.gIS[i].imageSet[n].laserPixelCoordinates != null) { for (int j = 0; j < this.gIS[i].imageSet[n].laserPixelCoordinates.length; j++) if (this.gIS[i].imageSet[n].laserPixelCoordinates[j] != null) numPointers++; } if (!this.gIS[i].imageSet[n].enabled) sb.append("("); sb.append(numPointers + "(" + this.gIS[i].imageSet[n].matchedPointers + "):" + this.gIS[i].imageSet[n].hintedMatch + " " + IJ.d2s(this.gIS[i].imageSet[n].gridPeriod, 1)); if (!this.gIS[i].imageSet[n].enabled) sb.append(")"); } } sb.append("\n"); } new TextWindow("Image calibration state (pointers/hinted state)", header, sb.toString(), 900, 1400); } /** * crete list of image indices per image set * @return array of image indices for each image set */ public int[][] listImages(boolean enabledOnly) { int[][] imageSets = new int[this.gIS.length][]; for (int i = 0; i < this.gIS.length; i++) { int setSize = 0; for (int n = 0; n < this.gIS[i].imageSet.length; n++) if ((this.gIS[i].imageSet[n] != null) && (this.gIS[i].imageSet[n].imgNumber >= 0) && (!enabledOnly || this.gIS[i].imageSet[n].enabled)) setSize++; imageSets[i] = new int[setSize]; } for (int i = 0; i < this.gIS.length; i++) { int index = 0; for (int n = 0; n < this.gIS[i].imageSet.length; n++) if ((this.gIS[i].imageSet[n] != null) && (this.gIS[i].imageSet[n].imgNumber >= 0) && (!enabledOnly || this.gIS[i].imageSet[n].enabled)) imageSets[i][index++] = this.gIS[i].imageSet[n].imgNumber; } return imageSets; } /** * Filter images (grids) by calibration status with laser pointers and "hinted" from the camera orientation * buildImageSets may be needed to be re-ran (if it was ran with all=false) * @param resetHinted - if true - reset status of "hinted" calibration to undefined * @param minPointers minimal number of laser pointers considered to be enough (usually 2, as mirror/non-mirror is apriori known * @parame minGridPeriod - minimal detected grid period as a fraction of the maximal (filtering reflected grids) * @return number of enabled images */ public int[] filterImages(boolean resetHinted, int minPointers, double minGridPeriodFraction, boolean disableNoVignetting, int minGridNodes) { int notEnoughNodes = 0; int numEnabled = 0; int newEnabled = 0; int maxPeriod = 100; int periodSubdivide = 10; int numBins = maxPeriod * periodSubdivide; double[] periodHistogram = new double[numBins]; double[] medianGridPeriod = new double[this.eyesisCameraParameters.numStations]; double[] maxGridPeriod = new double[this.eyesisCameraParameters.numStations]; double[] minGridPeriod = new double[this.eyesisCameraParameters.numStations]; for (int stationNumber = 0; stationNumber < this.eyesisCameraParameters.numStations; stationNumber++) { for (int i = 0; i < numBins; i++) periodHistogram[i] = 0.0; int numSamples = 0; for (int i = 0; i < this.gIP.length; i++) if (this.gIP[i].getStationNumber() == stationNumber) { if (!Double.isNaN(this.gIP[i].gridPeriod)) { int iPeriod = (int) Math.round(this.gIP[i].gridPeriod * periodSubdivide); if (iPeriod >= numBins) iPeriod = numBins - 1; else if (iPeriod < 0) iPeriod = 0; // does not count NaN if (iPeriod > 0) { periodHistogram[iPeriod]++; numSamples++; } } } int sumLess = 0; medianGridPeriod[stationNumber] = 0.0; for (int i = 0; i < numBins; i++) { sumLess += periodHistogram[i]; if (sumLess > (numSamples / 2)) { medianGridPeriod[stationNumber] = (1.0 * i) / periodSubdivide; break; } } maxGridPeriod[stationNumber] = 0.0; for (int i = 0; i < this.gIP.length; i++) if (this.gIP[i].getStationNumber() == stationNumber) { if (this.gIP[i].gridPeriod > maxGridPeriod[stationNumber]) maxGridPeriod[stationNumber] = this.gIP[i].gridPeriod; } minGridPeriod[stationNumber] = medianGridPeriod[stationNumber] * minGridPeriodFraction; System.out.print("Station " + stationNumber + ": maximal grid period=" + maxGridPeriod[stationNumber] + " minimal grid period=" + minGridPeriod[stationNumber] + " median grid period=" + medianGridPeriod[stationNumber] + " numSamples=" + numSamples); if (minGridPeriodFraction > 0.0) maxGridPeriod[stationNumber] = medianGridPeriod[stationNumber] / minGridPeriodFraction; System.out.println(" new maximal grid period=" + maxGridPeriod[stationNumber]); } // set which image set each image belongs int[] gIS_index = new int[this.gIP.length]; for (int i = 0; i < gIS_index.length; i++) gIS_index[i] = -1; if (this.gIS != null) { for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].imageSet != null) for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) { gIS_index[this.gIS[i].imageSet[j].imgNumber] = i; } } int numNoVignetting = 0; for (int i = 0; i < this.gIP.length; i++) { int stationNumber = this.gIP[i].getStationNumber(); boolean wasEnabled = this.gIP[i].enabled; if (resetHinted) this.gIP[i].hintedMatch = -1; // undefined if (Double.isNaN(this.gIP[i].gridPeriod) || ((minGridPeriodFraction > 0) && ((this.gIP[i].gridPeriod < minGridPeriod[stationNumber]) || (this.gIP[i].gridPeriod > maxGridPeriod[stationNumber])))) { this.gIP[i].hintedMatch = 0; // is it needed? this.gIP[i].enabled = false; // failed against minimal grid period (too far) - probably double reflection in the windows } if (this.gIP[i].hintedMatch == 0) this.gIP[i].enabled = false; // failed against predicted grid else { if ((this.gIP[i].matchedPointers >= minPointers) || ((this.gIP[i].matchedPointers > 0) && (this.gIP[i].hintedMatch > 0)) || // orientation and one pointer (this.gIP[i].hintedMatch > 1)) { // before enabling - copy orientation from gIS if (!this.gIP[i].enabled && (gIS_index[i] >= 0)) { if (!Double.isNaN(this.gIS[gIS_index[i]].goniometerTilt)) setGH(i, this.gIS[gIS_index[i]].goniometerTilt); if (!Double.isNaN(this.gIS[gIS_index[i]].goniometerAxial)) setGA(i, this.gIS[gIS_index[i]].goniometerAxial); } this.gIP[i].enabled = true; } else this.gIP[i].enabled = false; } if (disableNoVignetting) { if (this.gIP[i].enabled & !this.gIP[i].flatFieldAvailable) numNoVignetting++; this.gIP[i].enabled &= this.gIP[i].flatFieldAvailable; } if (this.gIP[i].motors == null) this.gIP[i].enabled = false; // got some no-motor images made without scanning /* Disable no-pointer, new, number of points less than required */ if (this.gIP[i].enabled && !wasEnabled && (this.gIP[i].matchedPointers == 0) && (this.gIP[i].pixelsXY.length < minGridNodes)) { this.gIP[i].enabled = false; notEnoughNodes++; } if (this.gIP[i].enabled) numEnabled++; this.gIP[i].newEnabled = this.gIP[i].enabled && !wasEnabled; if (this.gIP[i].newEnabled) newEnabled++; } // may need buildImageSets int[] result = { numEnabled, newEnabled, numNoVignetting, notEnoughNodes }; return result; } // TODO: // 1 - Filter by lasers/hint state // 2 - recalculate hinted // connect "enabled" to strategies (not done yet) // applyHintedGrids90 - moved to the parent class /** * Create array of image sets ("panoramas"), sorted by timestamps * @return number of sets */ public int buildImageSets(boolean preserveSet) { if (this.debugLevel > 0) { System.out.println("buildImageSets(" + preserveSet + ")"); } if (!preserveSet) { List<Double> timeStampList = new ArrayList<Double>(this.gIP.length); int numChannels = 0; for (int i = 0; i < this.gIP.length; i++) { if (this.gIP[i].channel > numChannels) numChannels = this.gIP[i].channel; int j = 0; Double ts = this.gIP[i].timestamp; if (!timeStampList.contains(ts)) { for (; (j < timeStampList.size()) && (ts > timeStampList.get(j)); j++) ; timeStampList.add(j, ts); } } numChannels++; this.gIS = new GridImageSet[timeStampList.size()]; for (int i = 0; i < this.gIS.length; i++) { this.gIS[i] = new GridImageSet(); this.gIS[i].timeStamp = timeStampList.get(i); this.gIS[i].imageSet = new GridImageParameters[numChannels]; for (int j = 0; j < numChannels; j++) this.gIS[i].imageSet[j] = null; } for (int i = 0; i < this.gIP.length; i++) { Double ts = this.gIP[i].timestamp; int iIS = timeStampList.indexOf(ts); this.gIS[iIS].setStationNumber(this.gIP[i].getStationNumber()); this.gIS[iIS].imageSet[this.gIP[i].channel] = this.gIP[i]; // if (this.gIP[i].motors!=null) this.gIS[iIS].motors=this.gIP[i].motors; this.gIP[i].setNumber = iIS; this.gIP[i].gridImageSet = this.gIS[iIS]; } // verify that station number is the same for the same timestamp for (int i = 0; i < this.gIP.length; i++) { Double ts = this.gIP[i].timestamp; int iIS = timeStampList.indexOf(ts); if (this.gIS[iIS].getStationNumber() != this.gIP[i].getStationNumber()) { String msg = "Inconsistent station number for timestamp " + ts + ": this.gIS[iIS].getStationNumber()=" + this.gIS[iIS].getStationNumber() + " this.gIP[i].getStationNumber()=" + this.gIP[i].getStationNumber() + ", using " + this.gIS[iIS].getStationNumber(); System.out.println(msg); IJ.showMessage("Error:", msg); this.gIP[i].setStationNumber(this.gIS[iIS].getStationNumber()); } } } for (int i = 0; i < this.gIP.length; i++) { int iIS = this.gIP[i].setNumber; if (this.gIP[i].motors != null) this.gIS[iIS].motors = this.gIP[i].motors.clone(); } return this.gIS.length; } /** * Create array of image sets ("panoramas"), sorted by timestamps * @param all // use all images (false - only enabled) * @return number of sets */ public int buildImageSetsOld(boolean all) { List<Double> timeStampList = new ArrayList<Double>(this.gIP.length); int numChannels = 0; for (int i = 0; i < this.gIP.length; i++) if (all || this.gIP[i].enabled) { if (this.gIP[i].channel > numChannels) numChannels = this.gIP[i].channel; int j = 0; Double ts = this.gIP[i].timestamp; if (!timeStampList.contains(ts)) { for (; (j < timeStampList.size()) && (ts > timeStampList.get(j)); j++) ; timeStampList.add(j, ts); } } numChannels++; this.gIS = new GridImageSet[timeStampList.size()]; for (int i = 0; i < this.gIS.length; i++) { this.gIS[i] = new GridImageSet(); this.gIS[i].timeStamp = timeStampList.get(i); this.gIS[i].imageSet = new GridImageParameters[numChannels]; for (int j = 0; j < numChannels; j++) this.gIS[i].imageSet[j] = null; } for (int i = 0; i < this.gIP.length; i++) if (all || this.gIP[i].enabled) { Double ts = this.gIP[i].timestamp; int iIS = timeStampList.indexOf(ts); this.gIS[iIS].imageSet[this.gIP[i].channel] = this.gIP[i]; if (this.gIP[i].motors != null) this.gIS[iIS].motors = this.gIP[i].motors; } return this.gIS.length; } /** * Set goniometer initial orientation from the image with maximal number of laser pointers (make averaging later?) * Needed before LMA to have some reasonable initial orientation * @param overwriteAll if true, overwrite orientation data even if it is alredy not NaN, false -skipp those that have orientation set */ public void setInitialOrientation(boolean overwriteAll) { if (this.debugLevel > 0) { System.out.println("setInitialOrientation(" + overwriteAll + "), debugLevel= " + this.debugLevel); } for (int i = 0; i < this.gIS.length; i++) { int stationNumber = this.gIS[i].getStationNumber(); int bestRating = -1; int bestChannel = -1; for (int j = 0; j < this.gIS[i].imageSet.length; j++) if ((this.gIS[i].imageSet[j] != null) && this.gIS[i].imageSet[j].enabled) { int thisRating = this.gIS[i].imageSet[j].matchedPointers + ((this.gIS[i].imageSet[j].hintedMatch > 0) ? 1 : 0); // rate hintedMatch 2 higher? if (thisRating > bestRating) { bestRating = thisRating; bestChannel = j; } } if (bestRating > 0) { if (overwriteAll || Double.isNaN(this.gIS[i].goniometerAxial)) { // System.out.println("setInitialOrientation("+overwriteAll+"), Double.isNaN(this.gIS["+i+"].goniometerAxial)="+Double.isNaN(this.gIS[i].goniometerAxial)); this.gIS[i].goniometerAxial = -this.eyesisCameraParameters.eyesisSubCameras[stationNumber][bestChannel].azimuth; for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) setGA(this.gIS[i].imageSet[j].imgNumber, this.gIS[i].goniometerAxial); this.gIS[i].orientationEstimated = true; if (this.debugLevel > 1) { System.out.println("Setting goniometerAxial for the image set #" + i + " (" + this.gIS[i].timeStamp + ") to " + this.gIS[i].goniometerAxial + " +++++ orientationEstimated==true +++++"); } } if (overwriteAll || Double.isNaN(this.gIS[i].goniometerTilt)) { // System.out.println("setInitialOrientation("+overwriteAll+"), Double.isNaN(this.gIS["+i+"].goniometerTilt)="+Double.isNaN(this.gIS[i].goniometerTilt)); this.gIS[i].goniometerTilt = -this.eyesisCameraParameters.eyesisSubCameras[stationNumber][bestChannel].theta; for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) setGH(this.gIS[i].imageSet[j].imgNumber, this.gIS[i].goniometerTilt); this.gIS[i].orientationEstimated = true; if (this.debugLevel > 1) { System.out.println("Setting goniometerTilt for the image set #" + i + " (" + this.gIS[i].timeStamp + ") to " + this.gIS[i].goniometerTilt + " ===== orientationEstimated==true ====="); } } } } } /** * update image set (panorama, set of simultaneous images) goniometer orientation from the image parameters, do after running LMA * @param selectedImages boolean array of selected images (in current strategy) or null (all selected) */ // TODO: potential problem here if only some images were enabled in the strategy -- FIXED // TODO: Add other extrinsic parameters here to sets? /** * Updated version - only flag as orientationEstimated if no enabled images exist in the set or any of the angles is NaN * Temporarily duplicate image parameters from those of the set (should not be needed) * selectedImages will not be used */ public void updateSetOrientation(boolean[] selectedImages) { // if selectedImages[] is not null will set orientationEstimated for unselected images if (this.gIS == null) { String msg = "Image set is not initilaized"; System.out.println(msg); IJ.showMessage(msg); } for (int i = 0; i < this.gIS.length; i++) { this.gIS[i].orientationEstimated = true; if (!Double.isNaN(this.gIS[i].goniometerAxial) && !Double.isNaN(this.gIS[i].goniometerTilt)) { for (int j = 0; j < this.gIS[i].imageSet.length; j++) if ((this.gIS[i].imageSet[j] != null) && this.gIS[i].imageSet[j].enabled) { if ((selectedImages == null) || selectedImages[this.gIS[i].imageSet[j].imgNumber]) { this.gIS[i].goniometerAxial -= 360.0 * Math.floor((this.gIS[i].goniometerAxial + 180.0) / 360.0); this.gIS[i].orientationEstimated = false; break; // set from the first non-null, enabled image } } } if (!this.gIS[i].orientationEstimated) { // now fill that data to all disabled images of the same set (just for listing RMS errors and debugging) for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) { // fill even those that are enabled setGA(this.gIS[i].imageSet[j].imgNumber, this.gIS[i].goniometerAxial); setGH(this.gIS[i].imageSet[j].imgNumber, this.gIS[i].goniometerTilt); } } else { this.gIS[i].goniometerAxial = Double.NaN; this.gIS[i].goniometerTilt = Double.NaN; System.out.println("updateSetOrientation(): imageSet " + i + " orientationEstimated == true"); } } } public void updateSetOrientationOld(boolean[] selectedImages) { if (this.gIS == null) { String msg = "Image set is not initilaized"; System.out.println(msg); IJ.showMessage(msg); } for (int i = 0; i < this.gIS.length; i++) { if (selectedImages == null) { // if all selected - remove orientation if there are no enabled images (i.e. after removeOutlayers) this.gIS[i].goniometerAxial = Double.NaN; this.gIS[i].goniometerTilt = Double.NaN; this.gIS[i].orientationEstimated = true; } for (int j = 0; j < this.gIS[i].imageSet.length; j++) if ((this.gIS[i].imageSet[j] != null) && this.gIS[i].imageSet[j].enabled) { if ((selectedImages == null) || selectedImages[this.gIS[i].imageSet[j].imgNumber]) { this.gIS[i].goniometerAxial = getGA(this.gIS[i].imageSet[j].imgNumber); //update - most likely will do nothing (if set has non-NaN) this.gIS[i].goniometerTilt = getGH(this.gIS[i].imageSet[j].imgNumber); this.gIS[i].goniometerAxial -= 360.0 * Math.floor((this.gIS[i].goniometerAxial + 180.0) / 360.0); this.gIS[i].orientationEstimated = false; break; // set from the first non-null, enabled image } } // now fill that data to all disabled images of the same set (just for listing RMS errors and debugging) if (!Double.isNaN(this.gIS[i].goniometerAxial) && !Double.isNaN(this.gIS[i].goniometerTilt)) { for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) { // fill even those that are enabled setGA(this.gIS[i].imageSet[j].imgNumber, this.gIS[i].goniometerAxial); setGH(this.gIS[i].imageSet[j].imgNumber, this.gIS[i].goniometerTilt); } } } } public boolean isEstimated(int imgNum) { if (this.gIS == null) { String msg = "Image sets are not initialized"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (this.gIP[imgNum].gridImageSet != null) return this.gIP[imgNum].gridImageSet.orientationEstimated; // should not get here System.out.println("FIXME: isEstimated(" + imgNum + "): this.gIP[" + imgNum + "].gridImageSet==null"); for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].imageSet != null) { for (int j = 0; j < this.gIS[i].imageSet.length; j++) if ((this.gIS[i].imageSet[j] != null) && (this.gIS[i].imageSet[j].imgNumber == imgNum)) { return this.gIS[i].orientationEstimated; } } String msg = "Image with index " + imgNum + " is not in the image set"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } public boolean isEstimatedOld(int imgNum) { if (this.gIS == null) { String msg = "Image sets are not initialized"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].imageSet != null) { for (int j = 0; j < this.gIS[i].imageSet.length; j++) if ((this.gIS[i].imageSet[j] != null) && (this.gIS[i].imageSet[j].imgNumber == imgNum)) { return this.gIS[i].orientationEstimated; } } String msg = "Image with index " + imgNum + " is not in the image set"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } int getNumberOfEstimated(boolean enabledOnly) { int numEstimated = 0; if (this.gIS == null) return 0; for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].imageSet != null) { for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) { if ((!enabledOnly || this.gIS[i].imageSet[j].enabled) && this.gIS[i].orientationEstimated) numEstimated++; } } return numEstimated; } int[] getNumberOfEstimatedPerStation(boolean enabledOnly) { int[] numEstimated = new int[this.eyesisCameraParameters.numStations]; for (int i = 0; i < numEstimated.length; i++) numEstimated[i] = 0; if (this.gIS != null) { for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].imageSet != null) { for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) { if ((!enabledOnly || this.gIS[i].imageSet[j].enabled) && this.gIS[i].orientationEstimated) numEstimated[this.gIS[i].getStationNumber()]++; } } } return numEstimated; } int getNumEnabled() { int num = 0; for (int i = 0; i < this.gIP.length; i++) if ((this.gIP[i] != null) && this.gIP[i].enabled) num++; return num; } int getNumNewEnabled() { int num = 0; for (int i = 0; i < this.gIP.length; i++) if ((this.gIP[i] != null) && this.gIP[i].enabled && this.gIP[i].newEnabled) num++; return num; } int[] getNumNewEnabledPerStation() { int[] numEnabled = new int[this.eyesisCameraParameters.numStations]; for (int i = 0; i < numEnabled.length; i++) numEnabled[i] = 0; for (int i = 0; i < this.gIP.length; i++) if ((this.gIP[i] != null) && this.gIP[i].enabled && this.gIP[i].newEnabled) numEnabled[this.gIP[i].getStationNumber()]++;// OOB 837 return numEnabled; } boolean[] selectNewEnabled() { boolean[] newEnabled = new boolean[this.gIP.length]; for (int i = 0; i < this.gIP.length; i++) newEnabled[i] = (this.gIP[i] != null) && this.gIP[i].enabled && this.gIP[i].newEnabled; return newEnabled; } boolean[] selectEstimated(boolean enabledOnly) { if (this.gIS == null) { String msg = "Image sets are not initialized"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } boolean[] estimated = new boolean[getNumImages()]; for (int i = 0; i < estimated.length; i++) estimated[i] = false; for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].imageSet != null) { for (int j = 0; j < this.gIS[i].imageSet.length; j++) if (this.gIS[i].imageSet[j] != null) { if ((!enabledOnly || this.gIS[i].imageSet[j].enabled)) estimated[this.gIS[i].imageSet[j].imgNumber] = this.gIS[i].orientationEstimated; } } return estimated; } public void enableSelected(boolean[] selected) { for (int i = 0; i < this.gIP.length; i++) if (this.gIP[i] != null) { int i1 = i; if (i1 >= selected.length) i1 = selected.length - 1; this.gIP[i].enabled = selected[i1]; } } /** * Calculate goniometer orientation for one of the "known" images/grids * @param imgNum grid image number * @return pair of {goniometerHorizontal, goniometerAxial} (in angular degrees) */ public double[] getImagesetTiltAxial(int imgNum) { return getImagesetTiltAxial(this.gIP[imgNum].timestamp); } /** * Return pair of {goniometerHorizontal, goniometerAxial} for the specified timestamp * updateSetOrientation() should be called after LMA or other updates to camera parameters * @param timeStamp - double timestamp identifying imageset (image does not need to be a part of selected grid files) * @return null if no images set has the specified timestamp, may contain Double.NaN if the orientation was not set. */ public double[] getImagesetTiltAxial(double timeStamp) { int mAxial = 1; // m2 int mHorizontal = 2;// m3 // this is probably already set for (int i = 0; i < this.gIS.length; i++) { if ((this.gIS[i].imageSet != null) && (this.gIS[i].imageSet.length > 0) && (this.gIS[i].imageSet[0] != null)) this.gIS[i].setStationNumber(this.gIS[i].imageSet[0].getStationNumber()); } for (int i = 0; i < this.gIS.length; i++) if (this.gIS[i].timeStamp == timeStamp) { int iBest = i; if (Double.isNaN(this.gIS[i].goniometerTilt) || Double.isNaN(this.gIS[i].goniometerAxial)) { // find the closest one (by motors) if (this.gIS[i].motors == null) { if (this.debugLevel > 0) System.out.println("getImagesetTiltAxial(" + timeStamp + "): No motor data"); return null; } // Maybe later use both motors, for now - just the axial. It seems to have <0.5 degree error (but accumulates gradually as there are friction rollers involved). int thisMotorHorizontal = this.gIS[i].motors[mHorizontal]; int thisMotorAxial = this.gIS[i].motors[mAxial]; int stationNumber = this.gIS[i].getStationNumber(); ArrayList<Integer> setList = new ArrayList<Integer>(100); for (int j = 0; j < this.gIS.length; j++) { if (this.gIS[j] == null) { System.out.println( "BUG?: getImagesetTiltAxial(" + timeStamp + "): this.gIS[" + j + "]==null"); continue; } if (this.gIS[j].motors == null) { System.out.println("BUG?: getImagesetTiltAxial(" + timeStamp + "): this.gIS[" + j + "].motors==null"); continue; } if ( // (j!=i) && // not needed - this set does not have orientation (this.gIS[j].getStationNumber() == stationNumber) && (this.gIS[j].motors[mHorizontal] == thisMotorHorizontal) && !Double.isNaN(this.gIS[j].goniometerTilt) && !Double.isNaN(this.gIS[j].goniometerAxial)) { setList.add(new Integer(j)); } } if (setList.size() >= 2) { if (this.debugLevel > 2) System.out.println( "getImagesetTiltAxial(" + timeStamp + "): estimating orientation for set # " + i + ": this.debugLevel=" + this.debugLevel); // find the closest one int indexClosest = setList.get(0); double dClosest = Math.abs(this.gIS[indexClosest].motors[mAxial] - thisMotorAxial); for (int j = 1; j < setList.size(); j++) if (Math.abs(this.gIS[setList.get(j)].motors[mAxial] - thisMotorAxial) < dClosest) { indexClosest = setList.get(j); dClosest = Math.abs(this.gIS[indexClosest].motors[mAxial] - thisMotorAxial); } // try to get the second on the other side than the closest first int indexSecond = -1; for (int j = 0; j < setList.size(); j++) { if (((this.gIS[indexClosest].motors[mAxial] - thisMotorAxial) * (this.gIS[setList.get(j)].motors[mAxial] - thisMotorAxial) < 0) && // different side ((indexSecond < 0) || (Math.abs(this.gIS[setList.get(j)].motors[mAxial] - thisMotorAxial) < dClosest))) { indexSecond = setList.get(j); dClosest = Math.abs(this.gIS[indexSecond].motors[mAxial] - thisMotorAxial); } } if (this.debugLevel > 2) System.out.println("indexSecond=" + indexSecond); if (indexSecond < 0) { // no sets on the opposite side from the indexClosest, use second closest on the same side as indexClosest for (int j = 0; j < setList.size(); j++) { if ((setList.get(j) != indexClosest) && ((indexSecond < 0) || (Math.abs(this.gIS[setList.get(j)].motors[mAxial] - thisMotorAxial) < dClosest))) { indexSecond = setList.get(j); dClosest = Math.abs(this.gIS[indexSecond].motors[mAxial] - thisMotorAxial); } } } if (indexSecond < 0) { // no second sets at all System.out.println("getImagesetTiltAxial(" + timeStamp + ") - this is a BUG "); } else { // now linear interpolate axail between theses two sets: indexClosest and indexSecond. (resolve/ guess crossing 360 double axialClosest = this.gIS[indexClosest].goniometerAxial; double axialSecond = this.gIS[indexSecond].goniometerAxial; axialClosest -= 360.0 * Math.floor((axialClosest + 180.0) / 360.0); axialSecond -= 360.0 * Math.floor((axialSecond + 180.0) / 360.0); if (this.debugLevel > 2) System.out.println("getImagesetTiltAxial(" + timeStamp + "):" + " same tilt - " + setList.size() + " axialClosest=" + axialClosest + " axialSecond=" + axialSecond + " motor closest=" + this.gIS[indexClosest].motors[mAxial] + " motor second=" + this.gIS[indexSecond].motors[mAxial]); // axial motor has the same sign/direction as the axial angle if (this.gIS[indexSecond].motors[mAxial] > this.gIS[indexClosest].motors[mAxial]) { if (axialSecond < axialClosest) axialSecond += 360.0; } else { if (axialSecond > axialClosest) axialClosest += 360.0; } this.gIS[i].goniometerAxial = axialClosest + (axialSecond - axialClosest) * (thisMotorAxial - this.gIS[indexClosest].motors[mAxial]) / (this.gIS[indexSecond].motors[mAxial] - this.gIS[indexClosest].motors[mAxial]); this.gIS[i].goniometerTilt = this.gIS[indexClosest].goniometerTilt + (this.gIS[indexSecond].goniometerTilt - this.gIS[indexClosest].goniometerTilt) * (thisMotorAxial - this.gIS[indexClosest].motors[mAxial]) / (this.gIS[indexSecond].motors[mAxial] - this.gIS[indexClosest].motors[mAxial]); } } else { // old way double d2Min = -1; for (int j = 0; j < this.gIS.length; j++) if ((j != i) && (this.gIS[j].motors != null) && !Double.isNaN(this.gIS[j].goniometerTilt) && !Double.isNaN(this.gIS[j].goniometerAxial)) { double d2 = 0; for (int k = 0; k < this.gIS[j].motors.length; k++) { d2 += 1.0 * (this.gIS[j].motors[k] - this.gIS[i].motors[k]) * (this.gIS[j].motors[k] - this.gIS[i].motors[k]); } if ((d2Min < 0) || (d2Min > d2)) { d2Min = d2; iBest = j; } } } } double[] result = { this.gIS[iBest].goniometerTilt, this.gIS[iBest].goniometerAxial }; if (iBest != i) { if (this.debugLevel > 0) System.out.println("Orientation for set # " + i + " timestamp " + IJ.d2s(this.gIS[i].timeStamp, 6) + ") is not defined, using # " + iBest + " (timestamp " + IJ.d2s(this.gIS[iBest].timeStamp, 6) + ")"); this.gIS[i].orientationEstimated = true; this.gIS[i].goniometerTilt = this.gIS[iBest].goniometerTilt; this.gIS[i].goniometerAxial = this.gIS[iBest].goniometerAxial; } return result; // may have Double.NaN } return null; } public double getImageTimestamp(ImagePlus image) { if ((image.getProperty("timestamp") == null) || (((String) image.getProperty("timestamp")).length() == 0)) { (new JP46_Reader_camera(false)).decodeProperiesFromInfo(image); } return Double.parseDouble((String) image.getProperty("timestamp")); } public int getImageChannel(ImagePlus image) { if ((image.getProperty("channel") == null) || (((String) image.getProperty("channel")).length() == 0)) { (new JP46_Reader_camera(false)).decodeProperiesFromInfo(image); } String channelSuffix = (String) image.getProperty("channel"); int channel = -1; for (int j = 0; j < this.channelSuffixes.length; j++) { // System.out.println("== j="+j); // System.out.println("channelSuffix="+channelSuffix); // System.out.println("this.channelSuffixes[j]="+this.channelSuffixes[j]); if (channelSuffix.equals(this.channelSuffixes[j])) { channel = j; break; } } if (channel < 0) { String msg = "Channel not recognized) - this channel suffix is " + channelSuffix + ", available channel suffixes are:\n"; for (int j = 0; j < this.channelSuffixes.length; j++) msg += this.channelSuffixes[j] + ", "; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return channel; } /** * initialize image data with camera defaults * @param distortionCalibrationData grid distortionCalibrationData * @param eyesisCameraParameters deafault camera parameters * @return */ // Used in Goniometer public void initImageSet(EyesisCameraParameters eyesisCameraParameters) { for (int i = 0; i < this.getNumImages(); i++) { int subCam = this.getImageSubcamera(i); int stationNumber = this.getImageStation(i); this.setParameters(eyesisCameraParameters.getParametersVector(stationNumber, subCam), i); } } // constructor from XML file public DistortionCalibrationData(boolean smart, String defaultPath, PatternParameters patternParameters, EyesisCameraParameters eyesisCameraParameters, ImagePlus[] gridImages) { // null - use specified files String[] extensions = { ".dcal-xml", "-distcal.xml" }; CalibrationFileManagement.MultipleExtensionsFileFilter parFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter( "", extensions, "Distortion calibration *.dcal-xml files"); String pathname = CalibrationFileManagement.selectFile(smart, false, "Restore Calibration Parameters", "Restore", parFilter, defaultPath); //String defaultPath if ((pathname == null) || (pathname == "")) return; // setGridImages(gridImages); //TODO: these images will be overwritten by setFromXML !!!!!!!!! this.gIS = null; // So readAllGrids will create it setFromXML(pathname, eyesisCameraParameters); if (gridImages != null) { // this.pathName=""; // modified, keep the path anyway // overwrite saved paths with the provided images, number of images{ should match if (this.gIP.length != gridImages.length) { String msg = "Number of provided images (" + gridImages.length + ") does not match parameters restored from the " + pathname + " (" + this.gIP.length + ")"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } for (int i = 0; i < this.gIP.length; i++) { this.gIP[i].gridImage = gridImages[i]; this.gIP[i].path = null; // not needed, just in case this.gIP[i].enabled = true;// enable all (actually just one) acquired images } // setGridImages(gridImages); } readAllGrids(patternParameters); // prepare grid parameters for LMA updateSetOrientation(null); // update orientation of image sets (built in readAllGrids() UPDATE - not anymore) } /* public DistortionCalibrationData( String pathname, PatternParameters patternParameters, EyesisCameraParameters eyesisCameraParameters) { setFromXML( pathname, eyesisCameraParameters); System.out.println("DistortionCalibrationData("+pathname+",eyesisCameraParameters) 1 -> this.gIS.length="+((this.gIS==null)?"null":this.gIS.length)); readAllGrids(patternParameters); // prepare grid parameters for LMA (now will preserve this.gIS if it is non-null) System.out.println("DistortionCalibrationData("+pathname+",eyesisCameraParameters) 2 -> this.gIS.length="+((this.gIS==null)?"null":this.gIS.length)); updateSetOrientation(null); // update orientation of image sets (built in readAllGrids()) } */ public void setFromXML(String pathname, EyesisCameraParameters eyesisCameraParameters) { this.eyesisCameraParameters = eyesisCameraParameters; XMLConfiguration hConfig = null; try { hConfig = new XMLConfiguration(pathname); } catch (ConfigurationException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.numSubCameras = Integer.parseInt(hConfig.getString("subcameras", "1")); System.out.println("Number of subcameras is " + this.numSubCameras); int num = hConfig.getMaxIndex("file"); num++; this.gIP = new GridImageParameters[num]; this.pars = new double[num][parameterDescriptions.length]; System.out.println("Number of pattern grid images in " + pathname + " is " + num); int numSets = hConfig.getMaxIndex("set") + 1; // see if it returns -1 for none System.out.println("Number of image sets in " + pathname + " is " + numSets); if (numSets > 0) { this.gIS = new GridImageSet[numSets]; for (int i = 0; i < numSets; i++) { HierarchicalConfiguration sub = hConfig.configurationAt("set(" + i + ")"); int index = Integer.parseInt(sub.getString("index")); this.gIS[index] = new GridImageSet(); this.gIS[index].timeStamp = Double.parseDouble(sub.getString("timestamp")); this.gIS[index].stationNumber = Integer.parseInt(sub.getString("stationNumber")); int minIndex = this.gIS[index].getMinIndex(); int maxIndexPlusOne = this.gIS[index].getMaxIndexPlusOne(); for (int j = minIndex; j < maxIndexPlusOne; j++) if (sub.getString(parameterDescriptions[j][0]) != null) { this.gIS[index].setParameterValue(j, Double.parseDouble((sub.getString(parameterDescriptions[j][0]))), false); } if (sub.getString("orientationEstimated") != null) { this.gIS[i].orientationEstimated = Boolean .parseBoolean(sub.getString("orientationEstimated")); /* System.out.println(i+": restored orientationEstimated="+this.gIS[i].orientationEstimated+ " tilt="+this.gIS[i].goniometerTilt+ " axial="+this.gIS[i].goniometerAxial);*/ } else { this.gIS[i].setEstimatedFromNonNaN(); /* System.out.println(i+": guessed. orientationEstimated="+this.gIS[i].orientationEstimated+ " tilt="+this.gIS[i].goniometerTilt+ " axial="+this.gIS[i].goniometerAxial);*/ } } } else { this.gIS = null; // has to be build later } for (int i = 0; i < num; i++) { this.gIP[i] = new GridImageParameters(i); HierarchicalConfiguration sub = hConfig.configurationAt("file(" + i + ")"); this.gIP[i].imgNumber = i; this.gIP[i].path = sub.getString("name"); this.gIP[i].timestamp = Double.parseDouble(sub.getString("timestamp")); this.gIP[i].channel = Integer.parseInt(sub.getString("channel")); if (sub.getString("stationNumber") != null) this.gIP[i].setStationNumber(Integer.parseInt(sub.getString("stationNumber"))); else this.gIP[i].setStationNumber(0); if (sub.getString("enabled") != null) this.gIP[i].enabled = Boolean.parseBoolean(sub.getString("enabled")); if (sub.getString("noUsefulPSFKernels") != null) this.gIP[i].noUsefulPSFKernels = Boolean.parseBoolean(sub.getString("noUsefulPSFKernels")); if (sub.getString("setNumber") != null) { this.gIP[i].setNumber = Integer.parseInt(sub.getString("setNumber")); } else { this.gIP[i].setNumber = -1; } for (int j = 0; j < this.parameterDescriptions.length; j++) { if (sub.getString(parameterDescriptions[j][0]) != null) this.pars[i][j] = Double.parseDouble(sub.getString(parameterDescriptions[j][0])); else this.pars[i][j] = Double.NaN; } } if (this.gIS != null) { System.out.println("Using stored image set data"); for (int is = 0; is < this.gIS.length; is++) { this.gIS[is].imageSet = new GridImageParameters[this.numSubCameras]; for (int j = 0; j < this.numSubCameras; j++) this.gIS[is].imageSet[j] = null; } for (int ip = 0; ip < this.gIP.length; ip++) if (this.gIP[ip].setNumber >= 0) { this.gIS[this.gIP[ip].setNumber].imageSet[this.gIP[ip].channel] = this.gIP[ip]; this.gIP[ip].gridImageSet = this.gIS[this.gIP[ip].setNumber]; //this.gIP[i].channel } } else { System.out.println("Re-creating image set data from individual images (old format)"); System.out.println("WARNING: Some parameters may get from unused images and so have wrong values"); buildImageSets(false); // from scratch // copying only parameters that have the same values for all images in a set for (int is = 0; is < this.gIS.length; is++) { int minIndex = this.gIS[is].getMinIndex(); int maxIndexPlusOne = this.gIS[is].getMaxIndexPlusOne(); for (int pi = minIndex; pi < maxIndexPlusOne; pi++) { double parVal = Double.NaN; boolean differs = false; for (int j = 0; j < this.gIS[is].imageSet.length; j++) if (this.gIS[is].imageSet[j] != null) { int imgNum = this.gIS[is].imageSet[j].imgNumber; if (!Double.isNaN(this.pars[imgNum][pi])) { if (!Double.isNaN(parVal) && (parVal != this.pars[imgNum][pi])) { differs = true; break; } else { parVal = this.pars[imgNum][pi]; } } if (!differs && !Double.isNaN(parVal)) { this.gIS[is].setParameterValue(pi, parVal, false); } if (differs) { System.out.println("ImageSet #" + is + ": " + parameterDescriptions[j][0] + " has different values for individual images, skipping"); } } } this.gIS[is].setEstimatedFromNonNaN(); //orientationEstimated System.out.println(is + ": tilt=" + this.gIS[is].goniometerTilt + " axial=" + this.gIS[is].goniometerAxial + " estimated=" + this.gIS[is].orientationEstimated); } System.out.println("setFromXML(" + pathname + ",eyesisCameraParameters) 1 -> this.gIS.length=" + this.gIS.length); } // System.out.println("setFromXML("+pathname+",eyesisCameraParameters) 2 -> this.gIS.length="+((this.gIS==null)?"null":this.gIS.length)); this.pathName = pathname; // where this instance was created from } //http://commons.apache.org/configuration/userguide/howto_xml.html public String getPath() { return this.pathName; } public String selectAndSaveToXML(boolean smart, String defaultPath) { return selectAndSaveToXML(smart, defaultPath, null); } public String selectAndSaveToXML(boolean smart, String defaultPath, String comment) { String[] extensions = { ".dcal-xml", "-distcal.xml" }; CalibrationFileManagement.MultipleExtensionsFileFilter parFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter( "", extensions, "Distortion calibration *.dcal-xml files"); String pathname = CalibrationFileManagement.selectFile(smart, true, "Save Calibration Parameters", "Save", parFilter, (defaultPath == null) ? this.pathName : defaultPath); //String defaultPath if (pathname != null) saveToXML(pathname, comment); return pathname; } public boolean saveTimestampedToXML(String pathname, String comment) { return saveToXML( pathname + "_" + IJ.d2s(0.000001 * (System.nanoTime() / 1000), 6).replace('.', '_') + ".dcal-xml", // full path or null null); } public boolean saveToXML(String pathname) { return saveToXML(pathname, null); } public boolean saveToXML(String pathname, String comment) { XMLConfiguration hConfig = new XMLConfiguration(); if (comment != null) hConfig.addProperty("comment", comment); hConfig.setRootElementName("distortionCalibrationParameters"); hConfig.addProperty("subcameras", this.numSubCameras); for (int i = 0; i < this.gIP.length; i++) { hConfig.addProperty("file", ""); hConfig.addProperty("file.setNumber", this.gIP[i].setNumber); hConfig.addProperty("file.name", this.gIP[i].path); hConfig.addProperty("file.enabled", this.gIP[i].enabled); hConfig.addProperty("file.timestamp", IJ.d2s(this.gIP[i].timestamp, 6)); hConfig.addProperty("file.channel", this.gIP[i].channel); hConfig.addProperty("file.stationNumber", this.gIP[i].getStationNumber()); hConfig.addProperty("file.noUsefulPSFKernels", this.gIP[i].noUsefulPSFKernels); for (int j = 0; j < this.parameterDescriptions.length; j++) { hConfig.addProperty("file." + parameterDescriptions[j][0], this.pars[i][j]); } } // save image sets for (int i = 0; i < this.gIS.length; i++) { hConfig.addProperty("set", ""); hConfig.addProperty("set.index", i); hConfig.addProperty("set.stationNumber", this.gIS[i].stationNumber); hConfig.addProperty("set.timestamp", IJ.d2s(this.gIS[i].timeStamp, 6)); hConfig.addProperty("set.orientationEstimated", this.gIS[i].orientationEstimated); double[] vector = this.gIS[i].updateParameterVectorFromSet(null); // unused parameters will be NaN for (int j = 0; j < vector.length; j++) if (!Double.isNaN(vector[j])) { hConfig.addProperty("set." + parameterDescriptions[j][0], vector[j]); } } // hConfig.addProperty("grids",""); File file = new File(pathname); BufferedWriter writer; try { writer = new BufferedWriter(new FileWriter(file)); hConfig.save(writer); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ConfigurationException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.pathName = pathname; return true; } // public double gridPeriod=0.0; // average grid period, in pixels (to filter out (double-) reflected images public double calcGridPeriod(int fileNumber) { if ((this.gIP[fileNumber].pixelsXY == null) || (this.gIP[fileNumber].pixelsXY.length < 3)) { this.gIP[fileNumber].gridPeriod = Double.NaN; } else { double[][][] data = new double[this.gIP[fileNumber].pixelsXY.length][2][2]; // U(x,y), v(x,y) for (int i = 0; i < data.length; i++) { data[i][0][0] = this.gIP[fileNumber].pixelsXY[i][0]; data[i][0][1] = this.gIP[fileNumber].pixelsXY[i][1]; data[i][1][0] = this.gIP[fileNumber].pixelsUV[i][0]; data[i][1][1] = this.gIP[fileNumber].pixelsUV[i][1]; } if (this.debugLevel > 3) { System.out.println("calcGridPeriod(" + fileNumber + "), debugLevel=" + this.debugLevel + ":"); for (int i = 0; i < data.length; i++) System.out.println(i + ": {{" + data[i][0][0] + "," + data[i][0][1] + "},{" + data[i][1][0] + "," + data[i][1][1] + "}}"); } double[][] coeff = new PolynomialApproximation(this.debugLevel).quadraticApproximation(data, true); // force linear if (coeff != null) { this.gIP[fileNumber].gridPeriod = 2.0 / Math.sqrt(coeff[0][0] * coeff[0][0] + coeff[0][1] * coeff[0][1] + coeff[1][0] * coeff[1][0] + coeff[1][1] * coeff[1][1]); if (this.debugLevel > 3) { System.out.println("coeff[][]={{" + coeff[0][0] + "," + coeff[0][1] + "},{" + coeff[1][0] + "," + coeff[1][1] + "}}"); } } else { this.gIP[fileNumber].gridPeriod = Double.NaN; } } if (this.debugLevel > 3) { System.out.println("calcGridPeriod(" + fileNumber + ") => " + this.gIP[fileNumber].gridPeriod); } return this.gIP[fileNumber].gridPeriod; } public boolean readAllGrids(PatternParameters patternParameters) { boolean disableNoFlatfield = false; // true only for processing transitional images - mixture of ff/ no-ff System.out.println("readAllGrids(), this.debugLevel=" + this.debugLevel + " this.gIS is " + ((this.gIS == null) ? "null" : "not null")); int numImages = getNumImages(); // this.pixelsXY=new double[numImages][][]; // this.pixelsUV=new int[numImages][][]; Opener opener = new Opener(); JP46_Reader_camera jp4_reader = new JP46_Reader_camera(false); ImagePlus imp_grid = null; ImageStack stack; int numOfGridNodes = 0; int numOfGridNodes_extra = 0; for (int fileNumber = 0; fileNumber < numImages; fileNumber++) { if (this.gIP[fileNumber].gridImage != null) { // use in-memory grid images instead of the files int numGridImg = fileNumber; if (numGridImg >= this.gIP.length) numGridImg = this.gIP.length - 1; if (this.updateStatus) IJ.showStatus("Using in-memory grid image " + (fileNumber + 1) + " (of " + (numImages) + "): " + this.gIP[numGridImg].gridImage.getTitle()); if (this.debugLevel > 1) System.out.print((fileNumber + 1) + ": " + this.gIP[numGridImg].gridImage.getTitle()); imp_grid = this.gIP[numGridImg].gridImage; } else { if (this.updateStatus) IJ.showStatus("Reading grid file " + (fileNumber + 1) + " (of " + (numImages) + "): " + this.gIP[fileNumber].path); if (this.debugLevel > 1) System.out.print(fileNumber + " (" + this.gIP[fileNumber].getStationNumber() + "): " + this.gIP[fileNumber].path); imp_grid = opener.openImage("", this.gIP[fileNumber].path); // or (path+filenames[nFile]) if (imp_grid == null) { String msg = "Failed to read grid file " + this.gIP[fileNumber].path; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } // TODO: here - need to decode properties jp4_reader.decodeProperiesFromInfo(imp_grid); } this.gIP[fileNumber].laserPixelCoordinates = getPointersXY(imp_grid, this.numPointers); this.gIP[fileNumber].motors = getMotorPositions(imp_grid, this.numMotors); this.gIP[fileNumber].matchedPointers = getUsedPonters(imp_grid); // this.gIP[fileNumber].enabled=true; // will filter separately this.gIP[fileNumber].hintedMatch = -1; // unknown yet double[] saturations = new double[4]; for (int i = 0; i < saturations.length; i++) { saturations[i] = Double.NaN; if (imp_grid.getProperty("saturation_" + i) != null) saturations[i] = Double.parseDouble((String) imp_grid.getProperty("saturation_" + i)); } if (!Double.isNaN(saturations[1])) this.gIP[fileNumber].saturation[0] = saturations[1]; if (!Double.isNaN(saturations[2])) this.gIP[fileNumber].saturation[2] = saturations[2]; if (!Double.isNaN(saturations[0]) && !Double.isNaN(saturations[3])) this.gIP[fileNumber].saturation[1] = 0.5 * (saturations[0] + saturations[3]); else { if (!Double.isNaN(saturations[0])) this.gIP[fileNumber].saturation[1] = saturations[0]; if (!Double.isNaN(saturations[3])) this.gIP[fileNumber].saturation[1] = saturations[3]; } stack = imp_grid.getStack(); if ((stack == null) || (stack.getSize() < 4)) { String msg = "Expected a 8-slice stack in " + this.gIP[fileNumber].path; 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 if (this.eyesisCameraParameters.badNodeThreshold > 0.0) { boolean thisDebug = false; // thisDebug|= (fileNumber== 720); // chn 25 // thisDebug|= (fileNumber== 793); // chn 10 // thisDebug|= (fileNumber== 895); // chn 15 // thisDebug|= (fileNumber==1359); // chn 0 // thisDebug|= (fileNumber==1029); // chn 2 // thisDebug|= (fileNumber==1081); // chn 14 // int maxBadNeighb=1; // 7 of 8 shold be good int numBadNodes = fixBadGridNodes(pixels, stack.getWidth(), this.eyesisCameraParameters.badNodeThreshold, this.eyesisCameraParameters.maxBadNeighb, this.debugLevel + (thisDebug ? 3 : 0), thisDebug ? ("fixBad-" + fileNumber) : null); if (this.debugLevel > 1) { if (numBadNodes > 0) System.out.print(" -- replaced " + numBadNodes + " bad grid nodes"); System.out.println(); } } int sensorWidth = this.eyesisCameraParameters.getSensorWidth(this.gIP[fileNumber].channel); int sensorHeight = this.eyesisCameraParameters.getSensorHeight(this.gIP[fileNumber].channel); int station = this.gIP[fileNumber].getStationNumber(); int size = 0; int size_extra = 0; for (int i = 0; i < pixels[0].length; i++) if ((pixels[0][i] >= 0) && (pixels[1][i] >= 0) && (pixels[0][i] < sensorWidth) && (pixels[1][i] < sensorHeight)) { int u = (int) Math.round(pixels[2][i]); int v = (int) Math.round(pixels[3][i]); // if (patternParameters.getXYZM(u,v,this.debugLevel>1)!=null) size++; if (patternParameters.getXYZM(u, v, false, station) != null) size++; else size_extra++; } this.gIP[fileNumber].resetMask(); this.gIP[fileNumber].pixelsXY = new double[size][6]; this.gIP[fileNumber].pixelsUV = new int[size][2]; this.gIP[fileNumber].pixelsXY_extra = new double[size_extra][6]; this.gIP[fileNumber].pixelsUV_extra = new int[size_extra][2]; numOfGridNodes += size; numOfGridNodes_extra += size_extra; int index = 0; int index_extra = 0; // boolean vignettingAvailable=pixels.length>=8; this.gIP[fileNumber].flatFieldAvailable = pixels.length >= 8; if (disableNoFlatfield && !this.gIP[fileNumber].flatFieldAvailable) this.gIP[fileNumber].enabled = false; // just to use old mixed data for (int i = 0; i < pixels[0].length; i++) if ((pixels[0][i] >= 0) && (pixels[1][i] >= 0) && (pixels[0][i] < sensorWidth) && (pixels[1][i] < sensorHeight)) { int u = (int) Math.round(pixels[2][i]); int v = (int) Math.round(pixels[3][i]); // if (patternParameters.getXYZM(u,v,this.debugLevel>1)!=null) { if (patternParameters.getXYZM(u, v, false, station) != null) { this.gIP[fileNumber].pixelsXY[index][0] = pixels[0][i]; this.gIP[fileNumber].pixelsXY[index][1] = pixels[1][i]; this.gIP[fileNumber].pixelsUV[index][0] = u; this.gIP[fileNumber].pixelsUV[index][1] = v; if (this.gIP[fileNumber].flatFieldAvailable) { this.gIP[fileNumber].pixelsXY[index][2] = pixels[4][i]; for (int n = 0; n < 3; n++) this.gIP[fileNumber].pixelsXY[index][n + 3] = pixels[n + 5][i] / this.gIP[fileNumber].intensityRange[n]; } else { for (int n = 0; n < 4; n++) this.gIP[fileNumber].pixelsXY[index][n + 2] = 1.0; } index++; } else { this.gIP[fileNumber].pixelsXY_extra[index_extra][0] = pixels[0][i]; this.gIP[fileNumber].pixelsXY_extra[index_extra][1] = pixels[1][i]; this.gIP[fileNumber].pixelsUV_extra[index_extra][0] = u; this.gIP[fileNumber].pixelsUV_extra[index_extra][1] = v; if (this.gIP[fileNumber].flatFieldAvailable) { this.gIP[fileNumber].pixelsXY_extra[index_extra][2] = pixels[4][i]; for (int n = 0; n < 3; n++) { this.gIP[fileNumber].pixelsXY_extra[index_extra][n + 3] = pixels[n + 5][i] / this.gIP[fileNumber].intensityRange[n]; } } else { for (int n = 0; n < 4; n++) this.gIP[fileNumber].pixelsXY_extra[index_extra][n + 2] = 1.0; } index_extra++; } } calcGridPeriod(fileNumber); // will be used to filter out reflections //System.out.println ("pixelsXY["+fileNumber+"]length="+pixelsXY[fileNumber].length); } if (this.debugLevel > 3) { System.out.println("readAllGrids(), numImages=" + numImages); for (int n = 0; n < this.gIP.length; n++) { System.out.println(n + ": length=" + this.gIP[n].pixelsXY.length); System.out.println("pixelsUV[][][0]/pixelsUV[][][1] pixelsXY[][][0]/pixelsXY[][][1]"); for (int i = 0; i < this.gIP[n].pixelsXY.length; i++) { System.out.println(n + ":" + i + " " + this.gIP[n].pixelsUV[i][0] + "/" + this.gIP[n].pixelsUV[1][1] + " " + IJ.d2s(this.gIP[n].pixelsXY[i][0], 2) + "/" + IJ.d2s(this.gIP[n].pixelsXY[i][1], 2)); } } } if (this.debugLevel > 0) { System.out.println("readAllGrids(), numImages=" + numImages + ", total number of grid nodes=" + numOfGridNodes + ", unused nodes " + numOfGridNodes_extra); } // probably - do not need to verify that this.gIS is null - should do that anyway. UPDATE: no, now reading config file creates gIS /* if (this.gIS!=null){ System.out.println("readAllGrids() 1: "); for (int is=0;is<this.gIS.length;is++){ System.out.println("readAllGrids() 1: "+is+": tilt="+this.gIS[is].goniometerTilt+" axial="+this.gIS[is].goniometerAxial+" estimated="+this.gIS[is].orientationEstimated); } } */ buildImageSets(this.gIS != null); /* if (this.gIS!=null){ System.out.println("readAllGrids() 2: "); for (int is=0;is<this.gIS.length;is++){ System.out.println("readAllGrids() 2: "+is+": tilt="+this.gIS[is].goniometerTilt+" axial="+this.gIS[is].goniometerAxial+" estimated="+this.gIS[is].orientationEstimated); } } */ return true; } /** * Sometimes "Process grid files" generates outlayers (by 0.1..5 pixels) TODO: find the bug * This program replaces the "bad" ones with predicted by 8 neighbors using 2-nd order interpolation * @param fPixels stack of pX,pY,target-U,target-V,contrast (some bad pixels have low contrast), red,green,blue * @param width grid width * @param tolerance maximal tolerated difference between the predicted by 8 neigbors and center pixels * @parame maxBadNeighb - maximal number of bad cells among 8 neighbors * @parame gebugLevel debug level * @return number of fixed nodes * Neighbors of bad pixels can be reported bad, so they have to be re-tried with the worst removed */ public int fixBadGridNodes(float[][] fpixels, int width, double tolerance, int maxBadNeighb, int debugLevel, String dbgTitle) { int debugThreshold = 3; double tolerance2 = tolerance * tolerance; double tolerance2Final = 10.0 * tolerance2; // final pass - fix even if the surronding are not that good int[][] dirs8 = { { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 } }; int[] dirs8Index = { 1, width + 1, width, width - 1, -1, -width - 1, -width, -width + 1 }; double[] diffs2 = new double[fpixels[0].length]; int height = diffs2.length / width; for (int i = 0; i < diffs2.length; i++) diffs2[i] = -1.0; // no nodes double[][][] data = new double[8][3][]; for (int i = 0; i < data.length; i++) { data[i][0] = new double[2]; data[i][1] = new double[2]; data[i][2] = new double[1]; } PolynomialApproximation polynomialApproximation = new PolynomialApproximation(0); // do not report linear double maxDiff2 = 0.0; for (int y = 1; y < (height - 1); y++) for (int x = 1; x < (width - 1); x++) { int index = y * width + x; if (fpixels[0][index] >= 0.0) { int numNonZero = 0; for (int iDir = 0; iDir < dirs8.length; iDir++) { int index1 = index + dirs8[iDir][1] * width + dirs8[iDir][0]; data[iDir][0][0] = dirs8[iDir][0]; data[iDir][0][1] = dirs8[iDir][1]; data[iDir][1][0] = fpixels[0][index1]; data[iDir][1][1] = fpixels[1][index1]; if ((fpixels[0][index1] < 0) || (fpixels[1][index1] < 0)) { data[iDir][2][0] = 0.0; } else { data[iDir][2][0] = 1.0; numNonZero++; } } if (numNonZero < 8) continue; // should all be defined double[][] coeff = polynomialApproximation.quadraticApproximation(data, false); // boolean forceLinear // use linear approximation if (coeff != null) { if ((coeff[0].length < 6) || (coeff[1].length < 6)) { if (debugLevel > 0) { System.out.println( "fixBadGridNodes() linear interpolate for x=" + x + ", y=" + y); for (int j = 0; j < data.length; j++) { System.out.println(j + " " + data[j][0][0] + "/" + data[j][0][1] + " - " + data[j][1][0] + "/" + data[j][1][1] + " : " + data[j][2][0]); } } } double dx = coeff[0][coeff[0].length - 1] - fpixels[0][index]; double dy = coeff[1][coeff[1].length - 1] - fpixels[1][index]; diffs2[index] = dx * dx + dy * dy; if (diffs2[index] > maxDiff2) maxDiff2 = diffs2[index]; } else { if (debugLevel > 0) { System.out.println("fixBadGridNodes() failed for x=" + x + ", y=" + y); } } } } if (maxDiff2 <= tolerance2) return 0; // nothing to fix // here - first debug show? boolean[] localWorst = new boolean[diffs2.length]; int numBad = 0; for (int i = 0; i < localWorst.length; i++) { if (diffs2[i] < tolerance2) { localWorst[i] = false; } else { localWorst[i] = true; for (int iDir = 0; iDir < dirs8Index.length; iDir++) if (diffs2[i + dirs8Index[iDir]] > diffs2[i]) { localWorst[i] = false; break; } if (localWorst[i]) numBad++; } } if (numBad == 0) { System.out.println("fixBadGridNodes() BUG - should not get here."); return 0; // should not get here - } double[][] dbgData = null; if (debugLevel > debugThreshold) { dbgData = new double[9][]; dbgData[0] = diffs2.clone(); dbgData[2] = dbgData[0].clone(); for (int i = 0; i < dbgData[2].length; i++) if (!localWorst[i]) dbgData[2][i] = -1.0; // (new showDoubleFloatArrays()).showArrays(diffs2, width, height, "diffs2"); } // Trying to eliminate all non local worst (may that is just extra as there anot too many bad nodes) int numStillBad = 0; for (int i = 0; i < localWorst.length; i++) if (localWorst[i]) { for (int iDir0 = 0; iDir0 < dirs8Index.length; iDir0++) if (diffs2[i + dirs8Index[iDir0]] > tolerance2) { // don't bother with not-so-bad int index = i + dirs8Index[iDir0]; // will never be on the border as diffs2 is <=0.0 there int numNonZero = 0; for (int iDir = 0; iDir < dirs8.length; iDir++) { int index1 = index + dirs8[iDir][1] * width + dirs8[iDir][0]; data[iDir][0][0] = dirs8[iDir][0]; data[iDir][0][1] = dirs8[iDir][1]; data[iDir][1][0] = fpixels[0][index1]; data[iDir][1][1] = fpixels[1][index1]; if ((data[iDir][1][0] < 0) || (data[iDir][1][1] < 0) || localWorst[index1]) { data[iDir][2][0] = 0.0; } else { data[iDir][2][0] = 1.0; numNonZero++; } } if (debugLevel > 3) { System.out.print("+++ fixBadGridNodes() trying to fix for x=" + (index % width) + ", y=" + (index / width) + ", iDir0=" + iDir0 + " numNonZero=" + numNonZero + " maxBadNeighb=" + maxBadNeighb); } if (numNonZero < (data.length - maxBadNeighb - 1)) continue; double[][] coeff = polynomialApproximation.quadraticApproximation(data, false); // boolean forceLinear // use linear approximation if (coeff != null) { double dx = coeff[0][coeff[0].length - 1] - fpixels[0][index]; double dy = coeff[1][coeff[1].length - 1] - fpixels[1][index]; if (debugLevel > 3) { System.out .print("fixBadGridNodes() old diffs2[" + index + "]=" + diffs2[index]); } diffs2[index] = dx * dx + dy * dy; // updated value if (debugLevel > 3) { System.out.print(" new diffs2[" + index + "]=" + diffs2[index]); } if (diffs2[index] > tolerance2) { numStillBad++; if (debugLevel > 3) { System.out.print(" --- BAD"); } } else if (debugLevel > 3) { System.out.print(" --- GOOD"); } if ((coeff[0].length < 6) || (coeff[1].length < 6)) { if (debugLevel > 3) { System.out.print("fixBadGridNodes() 2 linear interpolate for x=" + (index % width) + ", y=" + (index / width)); for (int j = 0; j < data.length; j++) { System.out.println(j + " " + data[j][0][0] + "/" + data[j][0][1] + " - " + data[j][1][0] + "/" + data[j][1][1] + " : " + data[j][2][0]); } } } } else { if (debugLevel > 3) { System.out.println("fixBadGridNodes() failed for x=" + (index % width) + ", y=" + (index / width) + ", iDir0=" + iDir0); } } if (debugLevel > 3) System.out.println(); } } if (numStillBad > 0) { if (debugLevel > 3) { System.out.println("fixBadGridNodes(): numStillBad=" + numStillBad + " > 0 - probably near the border, just make sure OK."); } } if (debugLevel > debugThreshold) { dbgData[1] = diffs2.clone(); for (int i = 0; i < dbgData[1].length; i++) if (localWorst[i]) dbgData[1][i] = 0.0; dbgData[3] = new double[dbgData[0].length]; for (int i = 0; i < dbgData[3].length; i++) dbgData[3][i] = 0.0; dbgData[4] = dbgData[3].clone(); dbgData[5] = dbgData[3].clone(); dbgData[6] = dbgData[3].clone(); dbgData[7] = dbgData[3].clone(); dbgData[8] = dbgData[3].clone(); for (int i = 0; i < dbgData[3].length; i++) { dbgData[3][i] = fpixels[0][i]; dbgData[4][i] = fpixels[1][i]; } } // TODO - try to fix some around pixels first? // Actually patching locally worst nodes for (int index = 0; index < localWorst.length; index++) if (localWorst[index]) { int numNonZero = 0; for (int iDir = 0; iDir < dirs8.length; iDir++) { int index1 = index + dirs8[iDir][1] * width + dirs8[iDir][0]; data[iDir][0][0] = dirs8[iDir][0]; data[iDir][0][1] = dirs8[iDir][1]; data[iDir][1][0] = fpixels[0][index1]; data[iDir][1][1] = fpixels[1][index1]; if (diffs2[index1] > tolerance2Final) { // increased tolerance for the final correction data[iDir][2][0] = 0.0; // do not count neighbors who are bad themselves } else { data[iDir][2][0] = 1.0; numNonZero++; } } if (numNonZero < (data.length - maxBadNeighb)) { if (debugLevel > 3) { System.out.println("fixBadGridNodes() failed x=" + (index % width) + ", y=" + (index / width) + ", number of good neighbors=" + numNonZero); } continue; // do not fix anything } double[][] coeff = polynomialApproximation.quadraticApproximation(data, false); // boolean forceLinear // use linear approximation if (coeff != null) { if ((coeff[0].length < 6) || (coeff[1].length < 6)) { if (debugLevel > 3) { System.out.println("fixBadGridNodes() linear interpolate for x=" + (index % width) + ", y=" + (index / width)); for (int j = 0; j < data.length; j++) { System.out.println(j + " " + data[j][0][0] + "/" + data[j][0][1] + " - " + data[j][1][0] + "/" + data[j][1][1] + " : " + data[j][2][0]); } for (int n = 0; n < coeff.length; n++) { for (int j = 0; j < coeff[n].length; j++) { System.out.print(coeff[n][j] + " "); } System.out.println(); } } } else if (debugLevel > 3) { System.out.println("fixBadGridNodes() qudratic interpolate for x=" + (index % width) + ", y=" + (index / width)); for (int j = 0; j < data.length; j++) { System.out.println(j + " " + data[j][0][0] + "/" + data[j][0][1] + " - " + data[j][1][0] + "/" + data[j][1][1] + " : " + data[j][2][0]); } for (int n = 0; n < coeff.length; n++) { for (int j = 0; j < coeff[n].length; j++) { System.out.print(coeff[n][j] + " "); } System.out.println(); } if (((index % width) == 19) && ((index / width) == 57)) { coeff = (new PolynomialApproximation(4)).quadraticApproximation(data, false); } } fpixels[0][index] = (float) coeff[0][coeff[0].length - 1]; fpixels[1][index] = (float) coeff[1][coeff[1].length - 1]; } else { if (debugLevel > 3) { System.out.println("fixBadGridNodes() failed for x=" + (index % width) + ", y=" + (index / width) + ", last pass"); } } } if (debugLevel > debugThreshold) { for (int i = 0; i < dbgData[3].length; i++) { dbgData[5][i] = fpixels[0][i]; dbgData[6][i] = fpixels[1][i]; dbgData[7][i] = dbgData[3][i] - fpixels[0][i]; dbgData[8][i] = dbgData[4][i] - fpixels[1][i]; } String[] dbgTitles = { "diff20", "diff2Mod", "localWorst", "old-X", "old-Y", "new-X", "new-Y", "old-new-X", "old-new-Y" }; if (dbgTitle != null) (new showDoubleFloatArrays()).showArrays(dbgData, width, height, true, dbgTitle, dbgTitles); } return numBad; } // TODO: Move all custom image properties (including encode/decode from JP4_reader_camera) to a separate class. // below is a duplicatie from MatchSimulatedPattern public double[][] getPointersXY(ImagePlus imp, int numPointers) { // read image info to properties (if it was not done yet - should it? if ((imp.getProperty("timestamp") == null) || (((String) imp.getProperty("timestamp")).length() == 0)) { JP46_Reader_camera jp4_instance = new JP46_Reader_camera(false); jp4_instance.decodeProperiesFromInfo(imp); } double[][] pointersXY = new double[numPointers][]; int numPointerDetected = 0; for (int i = 0; i < pointersXY.length; i++) { pointersXY[i] = null; if ((imp.getProperty("POINTER_X_" + i) != null) && (imp.getProperty("POINTER_Y_" + i) != null)) { pointersXY[i] = new double[2]; pointersXY[i][0] = Double.parseDouble((String) imp.getProperty("POINTER_X_" + i)); pointersXY[i][1] = Double.parseDouble((String) imp.getProperty("POINTER_Y_" + i)); numPointerDetected++; } } if (numPointerDetected > 0) return pointersXY; else return null; } public int[] getMotorPositions(ImagePlus imp, int numMotors) { // read image info to properties (if it was not done yet - should it? if ((imp.getProperty("timestamp") == null) || (((String) imp.getProperty("timestamp")).length() == 0)) { JP46_Reader_camera jp4_instance = new JP46_Reader_camera(false); jp4_instance.decodeProperiesFromInfo(imp); } int[] motorPos = new int[numMotors]; int numMotorsDetected = 0; for (int i = 0; i < motorPos.length; i++) { motorPos[i] = 0; if (imp.getProperty("MOTOR" + (i + 1)) != null) { motorPos[i] = Integer.parseInt((String) imp.getProperty("MOTOR" + (i + 1))); numMotorsDetected++; } } if (numMotorsDetected > 0) return motorPos; else return null; } public int getUsedPonters(ImagePlus imp) { // read image info to properties (if it was not done yet - should it? if ((imp.getProperty("timestamp") == null) || (((String) imp.getProperty("timestamp")).length() == 0)) { JP46_Reader_camera jp4_instance = new JP46_Reader_camera(false); jp4_instance.decodeProperiesFromInfo(imp); } if (imp.getProperty("USED_POINTERS") != null) { return Integer.parseInt((String) imp.getProperty("USED_POINTERS")); } return 0; } public int getImageNumPoints(int numImg) { return this.gIP[numImg].pixelsUV.length; } public void initPars(int numImages, int numPars) { this.pars = new double[numImages][numPars]; for (int i = 0; i < numImages; i++) for (int j = 0; j < numPars; j++) this.pars[i][j] = Double.NaN; } public double getParameterValue(int numImg, int numPar) { if ((numImg < 0) || (numImg >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + numImg; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if ((numPar < 0) || (numPar >= this.pars[numImg].length)) { String msg = "There are only " + this.pars[numImg].length + " parameters defined, requested #" + numPar; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } double par = (this.gIP[numImg].gridImageSet != null) ? this.gIP[numImg].gridImageSet.getParameterValue(numPar) : Double.NaN; if (Double.isNaN(par)) par = this.pars[numImg][numPar]; return par; } public void setParameterValue(int numImg, int numPar, double value, boolean updateEstimated) { if ((numImg < 0) || (numImg >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + numImg; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if ((numPar < 0) || (numPar >= this.pars[numImg].length)) { String msg = "There are only " + this.pars[numImg].length + " parameters defined, requested #" + numPar; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.pars[numImg][numPar] = value; if (this.gIP[numImg].gridImageSet != null) this.gIP[numImg].gridImageSet.setParameterValue(numPar, value, updateEstimated); } public void setParameters(double[] parameters, int numImg) { if ((numImg < 0) || (numImg >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + numImg; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.pars[numImg] = parameters.clone(); if (this.gIP[numImg].gridImageSet != null) this.gIP[numImg].gridImageSet.updateSetFromParameterVector(parameters); } public int getParametersLength(int numImg) { return this.pars[numImg].length; } public double[] getParameters(int numImg) { if ((numImg < 0) || (numImg >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + numImg; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } double[] parameters = this.pars[numImg].clone(); if (this.gIP[numImg].gridImageSet != null) this.gIP[numImg].gridImageSet.updateParameterVectorFromSet(parameters); return parameters; } public double[] getAzEl(int imgNum) { // get sensor azimuth and elevation DANGEROUS - absolute indices of parameters if ((imgNum < 0) || (imgNum >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + imgNum; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } double[] azel = { this.pars[imgNum][0], this.pars[imgNum][4] }; return azel; } // set goniometer horizontal axis angle and goniometer axial angles in all images public void setGHGA(double gh, double ga) { for (int imgNum = 0; imgNum < this.pars.length; imgNum++) setGHGA(imgNum, gh, ga); } public void setGHGA(int imgNum, double gh, double ga) { setGH(imgNum, gh); setGA(imgNum, ga); } public void setGH(int numImg, double gh) { this.pars[numImg][6] = gh; if (this.gIP[numImg].gridImageSet != null) this.gIP[numImg].gridImageSet.goniometerTilt = gh; } public void setGA(int numImg, double ga) { this.pars[numImg][7] = ga; if (this.gIP[numImg].gridImageSet != null) this.gIP[numImg].gridImageSet.goniometerAxial = ga; } public double getGH(int numImg) { if (this.gIP[numImg].gridImageSet != null) return this.gIP[numImg].gridImageSet.goniometerTilt; return this.pars[numImg][6]; } public double getGA(int numImg) { if (this.gIP[numImg].gridImageSet != null) return this.gIP[numImg].gridImageSet.goniometerAxial; return this.pars[numImg][7]; } public void setParameters(double[] parameters, int numImg, boolean[] mask) { if ((numImg < 0) || (numImg >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + numImg; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if ((this.pars[numImg].length != parameters.length) || (this.pars[numImg].length != mask.length)) { String msg = "Vector lengths for image #" + numImg + " mismatch: this.pars[" + numImg + "].length=" + this.pars[numImg].length + " parameters.length=" + parameters.length + " mask.length=" + mask.length; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } for (int i = 0; i < mask.length; i++) if (mask[i]) this.pars[numImg][i] = parameters[i]; if (this.gIP[numImg].gridImageSet != null) this.gIP[numImg].gridImageSet.updateSetFromParameterVector(parameters, mask); } public void setIntrinsicParameters(double[] parameters, int num) { if ((num < 0) || (num >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (this.pars[num].length != parameters.length) { String msg = "Vector lengths for image #" + num + " mismatch: this.pars[" + num + "].length=" + this.pars[num].length + " parameters.length=" + parameters.length; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } for (int i = 0; i < parameters.length; i++) if (isIntrinsicParameter(i)) this.pars[num][i] = parameters[i]; // no need to update image sets } public void setSubcameraParameters(double[] parameters, int num) { if ((num < 0) || (num >= this.pars.length)) { String msg = "There are only " + this.pars.length + " images defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (this.pars[num].length != parameters.length) { String msg = "Vector lengths for image #" + num + " mismatch: this.pars[" + num + "].length=" + this.pars[num].length + " parameters.length=" + parameters.length; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } for (int i = 0; i < parameters.length; i++) if (isSubcameraParameter(i)) this.pars[num][i] = parameters[i]; // no need to update image sets } public String getParameterName(int num) { if ((num < 0) || (num >= this.parameterDescriptions.length)) { String msg = "There are only " + this.parameterDescriptions.length + " parameters defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return parameterDescriptions[num][0]; } public String getParameterDescription(int num) { if ((num < 0) || (num >= this.parameterDescriptions.length)) { String msg = "There are only " + this.parameterDescriptions.length + " parameters defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.parameterDescriptions[num][1]; } public String getParameterUnits(int num) { if ((num < 0) || (num >= this.parameterDescriptions.length)) { String msg = "There are only " + this.parameterDescriptions.length + " parameters defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.parameterDescriptions[num][2]; } public boolean isSubcameraParameter(int num) { if ((num < 0) || (num >= this.parameterDescriptions.length)) { String msg = "There are only " + this.parameterDescriptions.length + " parameters defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return (this.parameterDescriptions[num][3].equals("S")); } public boolean isLocationParameter(int num) { //X,Y or Z location of the camera if ((num < 0) || (num >= this.parameterDescriptions.length)) { String msg = "There are only " + this.parameterDescriptions.length + " parameters defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return (this.parameterDescriptions[num][3].equals("T")); } public boolean isOrientationParameter(int num) { //one of the 2 goniometer orientation angles if ((num < 0) || (num >= this.parameterDescriptions.length)) { String msg = "There are only " + this.parameterDescriptions.length + " parameters defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return (this.parameterDescriptions[num][3].equals("R")); } public boolean isIntrinsicParameter(int num) { // updated from image calibration file if ((num < 0) || (num >= this.parameterDescriptions.length)) { String msg = "There are only " + this.parameterDescriptions.length + " parameters defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return (this.parameterDescriptions[num][4].equals("I")); } public String getImagePath(int num) { if ((num < 0) || (num >= this.gIP.length)) { String msg = "There are only " + this.gIP.length + " images defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.gIP[num].path; } public int getImageSubcamera(int num) { if ((num < 0) || (num >= this.gIP.length)) { String msg = "There are only " + this.gIP.length + " images defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.gIP[num].channel; } public int getImageStation(int num) { if ((num < 0) || (num >= this.gIP.length)) { String msg = "There are only " + this.gIP.length + " images defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.gIP[num].getStationNumber(); } public double getImageTimestamp(int num) { if ((num < 0) || (num >= this.gIP.length)) { String msg = "There are only " + this.gIP.length + " images defined, requested #" + num; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } return this.gIP[num].timestamp; } public int getNumImages() { return this.gIP.length; } public int getNumParameters() { return this.parameterDescriptions.length; } public int getNumSubCameras() { return this.numSubCameras; } /** * * @param imgNumber number of grid image to edit parameters (location, distortion) for * @return <2 - canceled, -1 - done, els - number of the next image to edit */ public int editImageParameters(int imgNumber) { if ((this.gIP == null) || (imgNumber < 0) || (imgNumber >= this.gIP.length)) return -3; int sub = getImageSubcamera(imgNumber); String sTS = IJ.d2s(getImageTimestamp(imgNumber), 6); GenericDialog gd = new GenericDialog("Manually editing per-image parameters, timestamp=" + sTS + ", subchannel-" + sub + " " + getImagePath(imgNumber)); for (int i = 0; i < getNumParameters(); i++) { gd.addNumericField( i + ": " + getParameterDescription(i) + "[" + getParameterName(i) + "] " + (isSubcameraParameter(i) ? "S " : " "), // this.pars[imgNumber][i],5,10, getParameterUnits(i)); this.getParameterValue(imgNumber, i), 5, 10, getParameterUnits(i)); } gd.addNumericField("Next image to edit (0.." + this.pars.length + ", -1 - none) ", imgNumber + 1, 0); gd.enableYesNoCancel("OK", "Done"); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return -2; for (int i = 0; i < getNumParameters(); i++) { // this.pars[imgNumber][i]= gd.getNextNumber(); this.setParameterValue(imgNumber, i, gd.getNextNumber(), true); } imgNumber = (int) gd.getNextNumber(); if ((imgNumber < 0) || (imgNumber >= getNumImages())) return -1; if (!gd.wasOKed()) return -1; // pressed Done (no need to ask for the next number) return imgNumber; } public void setMaskFromImageStack(String path) { Opener opener = new Opener(); if (this.debugLevel > 1) System.out.println("Opening " + path + " as a stack of sensor masks"); ImagePlus imp = opener.openImage("", path); if (imp == null) { String msg = "Failed to read sensors mask file " + path; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } (new JP46_Reader_camera(false)).decodeProperiesFromInfo(imp); if (imp.getProperty("shrinkGridForMask") != null) eyesisCameraParameters.shrinkGridForMask = Integer .parseInt((String) imp.getProperty("shrinkGridForMask")); if (imp.getProperty("maskBlurSigma") != null) eyesisCameraParameters.maskBlurSigma = Double .parseDouble((String) imp.getProperty("maskBlurSigma")); if (imp.getProperty("decimateMasks") != null) eyesisCameraParameters.decimateMasks = Integer.parseInt((String) imp.getProperty("decimateMasks")); if (imp.getProperty("sensorWidth") != null) eyesisCameraParameters.sensorWidth = Integer.parseInt((String) imp.getProperty("sensorWidth")); if (imp.getProperty("sensorHeight") != null) eyesisCameraParameters.sensorHeight = Integer.parseInt((String) imp.getProperty("sensorHeight")); setMaskFromImageStack(imp); } /** * Find number of channels in this camera * @return maximal number of channel used plus one */ public int getNumChannels() { int nChn = -1; for (int i = 0; i < this.gIP.length; i++) if (this.gIP[i].channel > nChn) nChn = this.gIP[i].channel; return nChn + 1; } public double getMask(int chnNum, double px, double py) { int width = eyesisCameraParameters.sensorWidth / eyesisCameraParameters.decimateMasks; int height = eyesisCameraParameters.sensorHeight / eyesisCameraParameters.decimateMasks; int iPX = ((int) Math.round(px)) / eyesisCameraParameters.decimateMasks; int iPY = ((int) Math.round(py)) / eyesisCameraParameters.decimateMasks; if ((iPX < 0) || (iPY < 0) || (iPX >= width) || (iPY >= height)) return 0.0; if ((this.sensorMasks == null) || (this.sensorMasks[chnNum] == null)) return 1.0; return this.sensorMasks[chnNum][iPY * width + iPX]; } public double getMask(double[] mask, double px, double py) { if (mask == null) return 0; int width = eyesisCameraParameters.sensorWidth / eyesisCameraParameters.decimateMasks; int height = eyesisCameraParameters.sensorHeight / eyesisCameraParameters.decimateMasks; int iPX = ((int) Math.round(px)) / eyesisCameraParameters.decimateMasks; int iPY = ((int) Math.round(py)) / eyesisCameraParameters.decimateMasks; if ((iPX < 0) || (iPY < 0) || (iPX >= width) || (iPY >= height)) return 0.0; return mask[iPY * width + iPX]; // null ponter } public void setMaskFromImageStack(ImagePlus imp) { if (imp == null) { String msg = "sensors mask image is null"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (imp.getProperty("decimateMasks") != null) eyesisCameraParameters.decimateMasks = Integer.parseInt((String) imp.getProperty("decimateMasks")); eyesisCameraParameters.sensorWidth = imp.getWidth() * eyesisCameraParameters.decimateMasks; eyesisCameraParameters.sensorHeight = imp.getHeight() * eyesisCameraParameters.decimateMasks; if (imp.getProperty("sensorWidth") != null) eyesisCameraParameters.sensorWidth = Integer.parseInt((String) imp.getProperty("sensorWidth")); if (imp.getProperty("sensorHeight") != null) eyesisCameraParameters.sensorHeight = Integer.parseInt((String) imp.getProperty("sensorHeight")); if (this.sensorMasks == null) { this.sensorMasks = new double[getNumChannels()][]; for (int i = 0; i < this.sensorMasks.length; i++) this.sensorMasks[i] = null; } int numChannels = imp.getStackSize(); float[][] pixels = new float[numChannels][]; if (numChannels == 1) { pixels[0] = (float[]) imp.getProcessor().getPixels(); } else { ImageStack stack = imp.getStack(); if (stack == null) { String msg = "Expected a image stack with masks"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } for (int i = 0; i < numChannels; i++) pixels[i] = (float[]) stack.getPixels(i + 1); } for (int numChn = 0; (numChn < numChannels) && (numChn < this.sensorMasks.length); numChn++) { //Make shure masks contain non-zero (>0.0) pixels, otherwise skip those boolean defined = false; for (int i = 0; i < pixels[numChn].length; i++) if (pixels[numChn][i] > 0.0) { defined = true; break; } if (defined) { this.sensorMasks[numChn] = new double[pixels[numChn].length]; for (int i = 0; i < this.sensorMasks[numChn].length; i++) this.sensorMasks[numChn][i] = pixels[numChn][i]; } } } public ImagePlus saveMaskAsImageStack(String title, String path) { ImagePlus imp = getMaskAsImageStack(title); if (imp == null) return null; FileSaver fs = new FileSaver(imp); if (updateStatus) IJ.showStatus("Saving masks " + path); if (this.debugLevel > 0) System.out.println("Saving masks " + path); if (imp.getStackSize() > 1) fs.saveAsTiffStack(path); else fs.saveAsTiff(path); return imp; } public ImagePlus getMaskAsImageStack(String title) { if (this.sensorMasks == null) { String msg = "Sensor mask array does not exist, nothing to convert"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } int width = eyesisCameraParameters.sensorWidth / eyesisCameraParameters.decimateMasks; int height = eyesisCameraParameters.sensorHeight / eyesisCameraParameters.decimateMasks; float[][] pixels = new float[getNumChannels()][width * height]; ImagePlus imp = null; for (int numChn = 0; numChn < getNumChannels(); numChn++) { if (this.sensorMasks[numChn] == null) for (int i = 0; i < pixels[numChn].length; i++) pixels[numChn][i] = 0.0F; else for (int i = 0; i < pixels[numChn].length; i++) pixels[numChn][i] = (float) this.sensorMasks[numChn][i]; } if (this.sensorMasks.length > 0) { ImageStack stack = new ImageStack(width, height); for (int numChn = 0; numChn < pixels.length; numChn++) stack.addSlice("chn-" + numChn, pixels[numChn]); imp = new ImagePlus(title, stack); } else { ImageProcessor ip = new FloatProcessor(width, height); ip.setPixels(pixels[0]); imp = new ImagePlus(title, ip); } // TODO: add more properties here (MAC+channel)? preserve other properties? imp.setProperty("sensorWidth", "" + eyesisCameraParameters.sensorWidth); imp.setProperty("sensorHeight", "" + eyesisCameraParameters.sensorHeight); imp.setProperty("shrinkGridForMask", "" + eyesisCameraParameters.shrinkGridForMask); imp.setProperty("maskBlurSigma", "" + eyesisCameraParameters.maskBlurSigma); imp.setProperty("decimateMasks", "" + eyesisCameraParameters.decimateMasks); (new JP46_Reader_camera(false)).encodeProperiesToInfo(imp); imp.getProcessor().resetMinAndMax(); return imp; } /** * Generate low-vignetting sensor mask for flat-field calculation * @param sensorMask sensor mask, decimated array * @param width sensor width, pixels * @param height sensor height, pixels * @param shrink 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 */ public double[] nonVignettedMask(double[] sensorMask, int width, int height, double x0, // lens center X (sensor, non-decimated pix) double y0, // lens center Y (sensor, non-decimated pix) double shrink, double radius, double minimalAlpha) { int decimate = (int) Math.round(Math.sqrt(width * height / sensorMask.length)); int dcmWidth = width / decimate; int dcmHeight = height / decimate; double[] mask = sensorMask.clone(); if (shrink > 0) { (new DoubleGaussianBlur()).blurDouble(mask, dcmWidth, dcmHeight, shrink / decimate, shrink / decimate, 0.01); for (int i = 0; i < mask.length; i++) { double d = 2 * (mask[i] - 0.5); mask[i] = (d > 0) ? (d * d) : (0.0); } } if (radius > 0.0) { int index = 0; for (int iy = 0; iy < dcmHeight; iy++) for (int ix = 0; ix < dcmWidth; ix++) { double r = Math.sqrt((iy * decimate - y0) * (iy * decimate - y0) + (ix * decimate - x0) * (ix * decimate - x0)) / radius; double k = (r > 1.0) ? 0.0 : (0.5 * (Math.cos(Math.PI * r) + 1.0)); mask[index++] *= k; } } if (minimalAlpha > 0.0) for (int i = 0; i < mask.length; i++) if (mask[i] < minimalAlpha) mask[i] = 0.0; return mask; } public double[][] calculateSensorMasks() { return calculateSensorMasks(eyesisCameraParameters.decimateMasks, eyesisCameraParameters.sensorWidth, eyesisCameraParameters.sensorHeight, eyesisCameraParameters.shrinkGridForMask, eyesisCameraParameters.maskBlurSigma); } /** * * @param width image width, in pixels (pixel X coordinates are between 0 and width-1, inclusive) * @param height image height, in pixels (pixel Y coordinates are between 0 and height-1, inclusive) * @param shrinkGridForMask shrink detected grids by this number of nodes in each direction before bluring * @param sigmaUV Gaussian sigma fro bluring of the sensor mask (if negative - in grid inter-node distances) * @return array of pixel arrays (or nulls) for each camera subchannel (also keeps it in the class instance) */ public double[][] calculateSensorMasks(int decimate, int width, int height, int shrinkGridForMask, double sigmaUV) { int dWidth = (width - 1) / decimate + 1; int dHeight = (height - 1) / decimate + 1; int numChannels = getNumChannels(); this.sensorMasks = new double[numChannels][]; DoubleGaussianBlur gb = new DoubleGaussianBlur(); if ((this.debugLevel > 1) && (SDFA_INSTANCE == null)) SDFA_INSTANCE = new showDoubleFloatArrays(); if (this.debugLevel > 2) System.out.println("calculateSensorMasks(" + width + "," + height + "," + shrinkGridForMask + "," + sigmaUV + ")"); for (int chNum = 0; chNum < numChannels; chNum++) { this.sensorMasks[chNum] = new double[dWidth * dHeight]; for (int i = 0; i < this.sensorMasks[chNum].length; i++) this.sensorMasks[chNum][i] = 0.0; double rAverage = 0.0; double rAverageNum = 0.0; for (int imgNum = 0; imgNum < this.gIP.length; imgNum++) if (this.gIP[imgNum].channel == chNum) { // image is for this this channel double[][] preMask = preCalculateSingleImageMask(imgNum, decimate, width, height, shrinkGridForMask); if (preMask == null) continue; //nothing in this channel rAverage += preMask[0][0]; rAverageNum += preMask[0][1]; for (int i = 0; i < this.sensorMasks[chNum].length; i++) if (preMask[1][i] > 0.0) this.sensorMasks[chNum][i] = 1.0; } if (rAverageNum == 0.0) continue; // nothing to blur/process for this channel rAverage /= rAverageNum; // average distance to the fartherst node from the current double sigma = sigmaUV; if (sigma < 0) sigma *= -rAverage; gb.blurDouble(this.sensorMasks[chNum], dWidth, dHeight, sigma / decimate, sigma / decimate, 0.01); // this.sensorMasks[chNum] now contains 0.0/1.0 mask. Blur it // gb.blurDouble(pointedBayer[bayerR], halfWidth, halfHeight, this.lowpassSigma, this.lowpassSigma, 0.01); // if (debugLevel>2) sdfra_instance.showArrays(pointedBayer[bayerR].clone(), halfWidth, halfHeight, title+"-smooth"); } return this.sensorMasks; } public double[] calculateImageGridMask(int imgNum) { return calculateImageGridMask(imgNum, eyesisCameraParameters.decimateMasks, eyesisCameraParameters.sensorWidth, eyesisCameraParameters.sensorHeight, eyesisCameraParameters.shrinkGridForMask, eyesisCameraParameters.maskBlurSigma); } public double[] calculateImageGridMask(int imgNum, int decimate, int width, int height, int shrinkGridForMask, double sigmaUV) { int dWidth = (width - 1) / decimate + 1; int dHeight = (height - 1) / decimate + 1; DoubleGaussianBlur gb = new DoubleGaussianBlur(); if ((this.debugLevel > 1) && (SDFA_INSTANCE == null)) SDFA_INSTANCE = new showDoubleFloatArrays(); if (this.debugLevel > 2) System.out.println("calculateSensorMasks(" + width + "," + height + "," + shrinkGridForMask + "," + sigmaUV + ")"); double[][] preMask = preCalculateSingleImageMask(imgNum, decimate, width, height, shrinkGridForMask); if (preMask == null) return null; //nothing in this channel double rAverage = preMask[0][0]; double rAverageNum = preMask[0][1]; if (rAverageNum == 0.0) return null; // nothing to blur/process for this channel rAverage /= rAverageNum; // average distance to the fartherst node from the current double sigma = sigmaUV; if (sigma < 0) sigma *= -rAverage; // old version, trying new - will influence all sensor masks!! // gb.blurDouble(preMask[1], dWidth, dHeight, sigma/decimate, sigma/decimate, 0.01); double[] mask0 = preMask[1].clone(); gb.blurDouble(preMask[1], dWidth, dHeight, sigma / decimate, sigma / decimate, 0.01); for (int i = 0; i < preMask[1].length; i++) { double d = 2.0 * (preMask[1][i] - 0.5); preMask[1][i] = ((mask0[i] > 0) && (d > 0)) ? (d * d) : 0.0; } return preMask[1]; } /** * * @param imgNum number of image to process * @param decimate - reduce image resolution for the mask * @param width - image width (actual will be divided by decimate * @param height- image height (actual will be divided by decimate * @param shrinkGridForMask shrink defined grid before bluring * @return array of 2 rows - [0] has just rAverage and rAverageNum for the average radius of the grid [1] - mask (1.0/0.0) * or null if there are no grid nodes at all; */ public double[][] preCalculateSingleImageMask(int imgNum, int decimate, int width, int height, int shrinkGridForMask) { if (!this.gIP[imgNum].enabled) return null; // this image is disabled, ignore it int[][] dirs = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; double rAverage = 0.0; double rAverageNum = 0.0; int i; int dWidth = (width - 1) / decimate + 1; int dHeight = (height - 1) / decimate + 1; double[] mask = new double[dWidth * dHeight]; boolean hasGrid = false; for (i = 0; i < this.gIP[imgNum].pixelsXY.length; i++) if ((this.gIP[imgNum].pixelsXY[i] != null) && (this.gIP[imgNum].pixelsXY[i][0] >= 0)) { hasGrid = true; break; } if (!hasGrid) return null; // image has no grid nodes int minU = this.gIP[imgNum].pixelsUV[i][0]; int minV = this.gIP[imgNum].pixelsUV[i][1]; int maxU = minU; int maxV = minV; for (; i < this.gIP[imgNum].pixelsXY.length; i++) if ((this.gIP[imgNum].pixelsXY[i] != null) && (this.gIP[imgNum].pixelsXY[i][0] >= 0)) { if (this.gIP[imgNum].pixelsUV[i][0] < minU) minU = this.gIP[imgNum].pixelsUV[i][0]; if (this.gIP[imgNum].pixelsUV[i][1] < minV) minV = this.gIP[imgNum].pixelsUV[i][1]; if (this.gIP[imgNum].pixelsUV[i][0] > maxU) maxU = this.gIP[imgNum].pixelsUV[i][0]; if (this.gIP[imgNum].pixelsUV[i][1] > maxV) maxV = this.gIP[imgNum].pixelsUV[i][1]; } if (this.debugLevel > 2) System.out.println("calculateSensorMasks, imgNum=" + imgNum + ", minU=" + minU + ", maxU=" + maxU + ", minV=" + minV + ", maxV=" + maxV); // restore the grid rectangle for u,v ->pixel-x,pixel-y double[][][] pXY = new double[maxV - minV + 1][maxU - minU + 1][2]; int[][] iMask = new int[pXY.length][pXY[0].length]; for (int v = 0; v < pXY.length; v++) for (int u = 0; u < pXY[0].length; u++) { pXY[v][u][0] = -1; iMask[v][u] = 0; } if (this.debugLevel > 2) System.out.println( "calculateSensorMasks, pXY.length=" + pXY.length + ", pXY[0].length=" + pXY[0].length); for (i = 0; i < this.gIP[imgNum].pixelsXY.length; i++) if ((this.gIP[imgNum].pixelsXY != null) && (this.gIP[imgNum].pixelsXY[i][0] >= 0)) { int v = this.gIP[imgNum].pixelsUV[i][1] - minV; int u = this.gIP[imgNum].pixelsUV[i][0] - minU; pXY[v][u][0] = this.gIP[imgNum].pixelsXY[i][0]; // out of bounds 22 pXY[v][u][1] = this.gIP[imgNum].pixelsXY[i][1]; // if (this.debugLevel>2)System.out.println("calculateSensorMasks, i="+i+", pXY["+v+"]["+u+"]={"+pXY[v][u][0]+","+pXY[v][u][1]+"}"); iMask[v][u] = 1; } if (this.debugLevel > 3) { double[][] testArray = new double[3][pXY.length * pXY[0].length]; int index = 0; for (int v = 0; v < iMask.length; v++) for (int u = 0; u < iMask[0].length; u++) { testArray[0][index] = pXY[v][u][0]; testArray[1][index] = pXY[v][u][1]; testArray[2][index++] = iMask[v][u]; } String[] dbgTitles = { "X", "Y", "iMask" }; this.SDFA_INSTANCE.showArrays(testArray, pXY[0].length, pXY.length, true, "original", dbgTitles); } // shrink the grid int vMax = iMask.length - 1; int uMax = iMask[0].length - 1; if (this.debugLevel > 2) System.out.println("calculateSensorMasks, uMax=" + uMax + ", vMax=" + vMax); for (int n = 0; n < shrinkGridForMask; n++) { for (int v = 0; v < iMask.length; v++) for (int u = 0; u < iMask[0].length; u++) if (iMask[v][u] > 0) { if ((v == 0) || (v == vMax) || (u == 0) || (u == uMax) || (iMask[v - 1][u] == -n) || (iMask[v + 1][u] == -n) || (iMask[v][u - 1] == -n) || (iMask[v][u + 1] == -n)) { iMask[v][u] = -n - 1; } } } if (this.debugLevel > 3) { double[][] testArray1 = new double[3][pXY.length * pXY[0].length]; int index = 0; for (int v = 0; v < iMask.length; v++) for (int u = 0; u < iMask[0].length; u++) { testArray1[0][index] = pXY[v][u][0]; testArray1[1][index] = pXY[v][u][1]; testArray1[2][index++] = iMask[v][u]; } String[] dbgTitles = { "X", "Y", "iMask" }; this.SDFA_INSTANCE.showArrays(testArray1, pXY[0].length, pXY.length, true, "shrank", dbgTitles); } // now in remaining grid nodes iMask[v][u]>0 (0 and negative - no grid) // accumulate pixels around the grid points for (int v = 0; v < iMask.length; v++) for (int u = 0; u < iMask[0].length; u++) if (iMask[v][u] > 0) { // find the radius - distance to the fartherst of the 4 (existent) neighbors (if none exist - disregard the node) double r2Max = 0; for (int d = 0; d < dirs.length; d++) { int u1 = u + dirs[d][0]; int v1 = v + dirs[d][1]; double r2; if ((v1 >= 0) && (v1 <= vMax) && (u1 >= 0) && (u1 <= uMax)) { r2 = (pXY[v1][u][0] - pXY[v][u][0]) * (pXY[v1][u][0] - pXY[v][u][0]) + (pXY[v][u1][0] - pXY[v][u][0]) * (pXY[v][u1][0] - pXY[v][u][0]); if (r2Max < r2) r2Max = r2; } } if (r2Max == 0.0) continue; // nothing around - skip this node // calculate average radius (for bluring) double r = Math.sqrt(r2Max); rAverage += r; rAverageNum++; int iR = (int) Math.round(r); int iX0 = (int) Math.round(pXY[v][u][0]); int iY0 = (int) Math.round(pXY[v][u][1]); int xLowLim = iX0 - iR; int xHighLim = iX0 + iR; int yLowLim = iY0 - iR; int yHighLim = iY0 + iR; if (xLowLim < 0) xLowLim = 0; // decimation apply below if (xHighLim >= width) xHighLim = width - 1; if (yLowLim < 0) yLowLim = 0; if (yHighLim >= height) yHighLim = height - 1; for (int iY = yLowLim; iY <= yHighLim; iY += decimate) for (int iX = xLowLim; iX <= xHighLim; iX += decimate) { double r2 = (iX - pXY[v][u][0]) * (iX - pXY[v][u][0]) + (iY - pXY[v][u][1]) * (iY - pXY[v][u][1]); if (r2 <= r2Max) { if (decimate == 1) mask[iY * width + iX] = 1.0; else mask[(iY / decimate) * dWidth + (iX / decimate)] = 1.0; } } } double[][] result = { { rAverage, rAverageNum }, mask }; return result; } } /** * * Specifies images to process and parameters to adjust * Each parameter cab be: * 0 - "fixed" - use individual, per-image parameters, do not modify them * 1 - "common" - parameters are common for all selected images. * When saving - save to all selected images. * When loading (if different) use "master image" (or closest to it in time) * 2 - "super common" - same as common, but save to all images, not just selected * 3 - "individual" * 4 - "per group" * 5 - "per-station" * 6 - "per-station" save to all (super) * 7 - "weak common" - like common, but enable small individual variations (for a price) - not yet implemented, will have separate weight fixed/floating * 8 - "weak station" - like per-station, but enable individual (for a price) * +====================+===========+===========+ | | Same TS | Diff TS | | +-----+-----+-----+-----+ | | C | I | C | I | +============+=======+=====+=====+=====+=====+ | Same |Eyesis | X | X | C | I | | Subcamera +-------+-----+-----+-----+-----+ | |Subcam | X | X | C | I* | +============+=======+=====+=====+=====+=====+ | Different |Eyesis | C | C | C | I | | Subcameras +-------+-----+-----+-----+-----+ | |Subcam | I | I | I | I | +============+=======+=====+=====+=====+=====+ I* - special case when the subcamera is being adjusted/replaced. How to deal with it? * */ public static class FittingStrategy { public String pathName = null; // path to XML file this instance was created from public DistortionCalibrationData distortionCalibrationData = null;// per-image parameters private boolean[][] selectedImages = null; // images selected for each step (will be masked with enabled images) private boolean[][] selectedValidImages = null; // images selected for each step same as selected, but only if number of weight>0.0 nodes > threshold public boolean[] stopAfterThis = null; final public int modeFixed = 0, modeCommon = 1, modeSupercommon = 2, modeIndividual = 3, modeGroup = 4; final public int modeStation = 5, modeSuperStation = 6, modeWeakCommon = 7, modeWeakStation = 8, modeTiltEqualize = 9; public String[] definedModes = { "fixed", // modeFixed=0 "common", // modeCommon=1 "common, save to all", // modeSupercommon=2 "individual", // modeIndividual=3 "per-group", // modeGroup=4 "per-station", // modeStation=5 "per-station, save to all", // modeSuperStation=6 "weak common, save to all", // modeWeakCommon=7 "weak per-station, save to all" // modeWeakStation=8 }; public String[] definedModesNoWeak = { // show for parameters that can not use weak "fixed", // modeFixed=0 "common", // modeCommon=1 "common, save to all", // modeSupercommon=2 "individual", // modeIndividual=3 "per-group", // modeGroup=4 "per-station", // modeStation=5 "per-station, save to all" // modeSuperStation=6 // "weak common", // modeWeakCommon=7 // "weak per-station", // modeWeakStation=8 }; public String[] definedModesTiltEq = { // show for parameters that can not use weak "fixed", // modeFixed=0 "common", // modeCommon=1 "common, save to all", // modeSupercommon=2 "individual", // modeIndividual=3 "per-group", // modeGroup=4 "per-station", // modeStation=5 "per-station, save to all", // modeSuperStation=6 "weak common, save to all", // modeWeakCommon=7 "weak per-station, save to all", // modeWeakStation=8 "tilt equalize" // modeTiltEqualize }; public String[] definedModesAll = definedModesTiltEq; public int[][] parameterMode = null; // per series, per-parameter public int[][][] parameterGroups = null; // per series, per-parameter - null or array of group numbers (1 element per image) public int[][] zGroups = null; public boolean saveUnusedGroups = false; // purge groups for parameters when saving to XML, preserve if true public double[] lambdas = null; // LMA initial lambda for each step public double defaultLambda = 0.001; public int[][] parameterList = null; // list of all parameters in the system, each has subcamera number and parameters index public boolean[] parameterEnable = null; // select which parameters (of some 320 to display in selector) public int[] masterImages = null; // number of image no take "common" parameters from if they are different public double defaultStepDone = 1.0E-6; public double[] stepDone = null;// delta_error to error ratio to consider series finished // public double [] parameterVector; public int[][] parameterMap = null;// ** valid for one strategy sequence *** for each parameter vector element hold image number/parameter number public int[][] reverseParameterMap = null; // reversed map - for each [imageNumber][parameterNumber] -> vector index (-1 fixed) public int currentSeriesNumber = -1; // currently selected strategy step (for which parameterMap and reverseParameterMap) public int debugLevel = 2; public int[] varianceModes = null; // per-series: 0 - disabled, 1 - constant imageSets weight, 2 - variable imageSets weight final public int varianceModeDisabled = 0, varianceModeSameWeight = 1, varianceModeVariableWeight = 2; public String[] definedVarianceModes = { "disabled", "same weight", "variable weight", }; // next arrays will be initialized at buildVariancesMaps only if at least some parameters use variances, otherwise they will be null public double[] variationsAverages = null; // holds per extrinsic parameter or per parameter/per station average values public int[] averageCellIndex = null; // for each element in the parameters vector holds index in variationsAverages array public double[] weightOnAverage = null; // how variation of this parameter influences average (for all or for currenrt station public double[] weightVariance = null; // weight for LMA over variance of parameters from average public double[] varianceErrorsSquared = null; // weighted squared errors public FittingStrategy(DistortionCalibrationData distortionCalibrationData// per-image parameters ) { this.distortionCalibrationData = distortionCalibrationData; setDflt(0); } public FittingStrategy(DistortionCalibrationData distortionCalibrationData, // per-image parameters int numSeries // number of iteration series ) { this.distortionCalibrationData = distortionCalibrationData; this.selectedImages = new boolean[numSeries][this.distortionCalibrationData.getNumImages()]; this.selectedValidImages = new boolean[numSeries][this.distortionCalibrationData.getNumImages()]; setDflt(numSeries); } public FittingStrategy(boolean smart, String defaultPath, DistortionCalibrationData distortionCalibrationData // per-image parameters ) { String[] extensions = { ".stg-xml", "-strategy.xml" }; CalibrationFileManagement.MultipleExtensionsFileFilter parFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter( "", extensions, "*.stg-xml files"); String pathname = CalibrationFileManagement.selectFile(smart, false, "Restore Fitting Strategy", "Restore", parFilter, defaultPath); //String defaultPath if ((pathname == null) || (pathname == "")) return; setFromXML(distortionCalibrationData, // per-image parameters pathname); System.out.println("Restored fitting strategy from " + pathname); } /** * Reads FittingStrategy from an XML file * @param distortionCalibrationData should be defined before this class! * @param pathname pathe to the saved data */ public FittingStrategy(DistortionCalibrationData distortionCalibrationData, // per-image parameters String pathname) { setFromXML(distortionCalibrationData, // per-image parameters pathname); } public void setFromXML(DistortionCalibrationData distortionCalibrationData, // per-image parameters String pathname) { this.distortionCalibrationData = distortionCalibrationData; XMLConfiguration hConfig = null; try { hConfig = new XMLConfiguration(pathname); } catch (ConfigurationException e) { // TODO Auto-generated catch block e.printStackTrace(); } hConfig.setThrowExceptionOnMissing(false); // default value, will return null on missing // read parameterList int len = Integer.parseInt(hConfig.getString("parameterMap.length", "0")); if (len <= 0) { String msg = "No parameterMap specified in " + pathname; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } this.parameterList = new int[len][2]; this.parameterEnable = new boolean[len]; for (int i = 0; i < this.parameterList.length; i++) { this.parameterList[i][0] = Integer .parseInt(hConfig.getString("parameterMap.par_" + i + ".subcamera")); this.parameterList[i][1] = Integer.parseInt(hConfig.getString("parameterMap.par_" + i + ".index")); this.parameterEnable[i] = (Integer .parseInt(hConfig.getString("parameterMap.par_" + i + ".visible")) > 0); } int nSer = Integer.parseInt(hConfig.getString("series.number", "0")); if (nSer == 0) return; // arrays will just be null this.selectedImages = new boolean[nSer][]; this.selectedValidImages = new boolean[nSer][]; this.masterImages = new int[nSer]; this.stopAfterThis = new boolean[nSer]; for (int i = 0; i < nSer; i++) this.stopAfterThis[i] = true; // older configuration files did not have it this.parameterMode = new int[nSer][len]; this.varianceModes = new int[nSer]; this.zGroups = new int[nSer][]; this.parameterGroups = new int[nSer][len][]; this.lambdas = new double[nSer]; this.stepDone = new double[nSer]; for (int i = 0; i < nSer; i++) { // iterate through series //this.stopAfterThis // read selected images (no check here that it matches to the distortionCalibrationData! String sSeries = "series.series_" + i; String fs = hConfig.getString(sSeries + ".selectedImages"); this.selectedImages[i] = new boolean[fs.length()]; this.selectedValidImages[i] = null; for (int j = 0; j < this.selectedImages[i].length; j++) this.selectedImages[i][j] = (fs.charAt(j) == '+'); this.masterImages[i] = Integer.parseInt(hConfig.getString(sSeries + ".masterImage")); if (hConfig.getString(sSeries + ".varianceModes") != null) { this.varianceModes[i] = Integer.parseInt(hConfig.getString(sSeries + ".varianceModes")); } else { this.varianceModes[i] = varianceModeDisabled; } for (int j = 0; j < this.parameterList.length; j++) { int nPar = this.parameterList[j][1]; int nSub = this.parameterList[j][0]; String hconfigName = sSeries + ".parameterMode." + this.distortionCalibrationData.parameterDescriptions[nPar][0] + (this.distortionCalibrationData.isSubcameraParameter(nPar) ? ("_sub" + nSub) : ""); // System.out.println("Setting this.parameterMode["+i+"]["+j+"] from " +hconfigName); if (hConfig.getString(hconfigName) != null) this.parameterMode[i][j] = Integer.parseInt(hConfig.getString(hconfigName)); else System.out.println( "Failed to set this.parameterMode[" + i + "][" + j + "] from " + hconfigName + " - maybe it is a new parameter not present in the file " + pathname); this.parameterGroups[i][j] = null; //Try to read series for (int ni = 0; ni < this.selectedImages.length; ni++) { String sGroup = hConfig.getString(sSeries + ".parameterMode." + this.distortionCalibrationData.parameterDescriptions[nPar][0] + (this.distortionCalibrationData.isSubcameraParameter(nPar) ? ("_sub" + nSub) : "") + "_group" + ni); if (sGroup != null) { if (this.parameterGroups[i][j] == null) { this.parameterGroups[i][j] = new int[this.selectedImages.length]; for (int ni1 = 0; ni1 < this.selectedImages.length; ni1++) this.parameterGroups[i][j][ni1] = 0; } this.parameterGroups[i][j][ni] = Integer.parseInt(sGroup); } } } this.lambdas[i] = Double.parseDouble(hConfig.getString(sSeries + ".lambdas")); this.stepDone[i] = Double.parseDouble(hConfig.getString(sSeries + ".stepDone")); if (hConfig.getString(sSeries + ".stopAfterThis") != null) { this.stopAfterThis[i] = Boolean.parseBoolean(hConfig.getString(sSeries + ".stopAfterThis")); } String zGroupsName = sSeries + ".zGroups"; if (hConfig.getString(zGroupsName) != null) { int numZgroups = Integer.parseInt(hConfig.getString(zGroupsName)); this.zGroups[i] = new int[numZgroups]; for (int nZGroup = 0; nZGroup < numZgroups; nZGroup++) { this.zGroups[i][nZGroup] = Integer .parseInt(hConfig.getString(sSeries + ".zGroup_" + nZGroup)); } } else { this.zGroups[i] = null; } } // if (hConfig.getString("variances")!=null){ // if (!hConfig.configurationAt("variances").isEmpty()){ if (hConfig.configurationsAt("variances").size() > 0) { // System.out.println("hConfig.configurationAt(\"variances\").isEmpty()=false"); this.distortionCalibrationData.eyesisCameraParameters.getCostsPropertiesXML("variances.", hConfig); } this.pathName = pathname; // distortionCalibrationData.readAllGrids(); } /** * Adjust selectedImages and parameterGroups after number of images may change, old number of images should be >0 * If the new number is larger, the first images attributes will be copied, the extra ones - use those of the last of the old ones * @param newNumberOfImages new number of images used by this series */ public void adjustNumberOfImages(int newNumberOfImages) { if ((this.selectedImages == null) || (this.selectedImages[0].length == 0)) { String msg = "selectedImages array is " + ((this.selectedImages == null) ? "null" : "empty"); IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } if (this.selectedImages[0].length == newNumberOfImages) return; for (int nSer = 0; nSer < this.selectedImages.length; nSer++) { boolean[] tmp = selectedImages[nSer].clone(); this.selectedImages[nSer] = new boolean[newNumberOfImages]; this.selectedValidImages[nSer] = null; // just invalidate for (int i = 0; i < newNumberOfImages; i++) this.selectedImages[nSer][i] = (i < tmp.length) ? tmp[i] : tmp[tmp.length - 1]; for (int nPar = 0; nPar < this.parameterList.length; nPar++) { if (this.parameterGroups[nSer][nPar] != null) { int[] tmpGroup = this.parameterGroups[nSer][nPar].clone(); this.parameterGroups[nSer][nPar] = new int[newNumberOfImages]; for (int i = 0; i < newNumberOfImages; i++) this.parameterGroups[nSer][nPar][i] = (i < tmpGroup.length) ? tmpGroup[i] : tmpGroup[tmpGroup.length - 1]; } } } } public String selectAndSaveToXML(boolean smart, String defaultPath) { String[] extensions = { ".stg-xml", "-strategy.xml" }; CalibrationFileManagement.MultipleExtensionsFileFilter parFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter( "", extensions, "*.stg-xml files"); if ((defaultPath == null) || (defaultPath.length() == 0)) { defaultPath = this.pathName; } String pathname = CalibrationFileManagement.selectFile(smart, true, "Save Fitting Strategy", "Save", parFilter, defaultPath); //String defaultPath if (pathname != null) saveToXML(pathname); return pathname; } //http://commons.apache.org/configuration/userguide/howto_xml.html public boolean saveToXML(String pathname) { if (pathname == null) return false; XMLConfiguration hConfig = new XMLConfiguration(); hConfig.setRootElementName("FittingStrategy"); // write which of the overall parameter correspond to each global/subcamera one, and which parameters are hidden/shown hConfig.addProperty("parameterMap", ""); hConfig.addProperty("parameterMap.length", this.parameterList.length); for (int i = 0; i < this.parameterList.length; i++) { hConfig.addProperty("parameterMap.par_" + i, ""); hConfig.addProperty("parameterMap.par_" + i + ".subcamera", this.parameterList[i][0]); hConfig.addProperty("parameterMap.par_" + i + ".index", this.parameterList[i][1]); hConfig.addProperty("parameterMap.par_" + i + ".visible", this.parameterEnable[i] ? "1" : "0"); } hConfig.addProperty("series", ""); hConfig.addProperty("series.number", this.selectedImages.length); for (int i = 0; i < this.selectedImages.length; i++) { // iterate through series String sSeries = "series.series_" + i; hConfig.addProperty(sSeries, ""); String fs = ""; for (int j = 0; j < this.selectedImages[i].length; j++) fs += this.selectedImages[i][j] ? "+" : "-"; hConfig.addProperty(sSeries + ".selectedImages", fs); hConfig.addProperty(sSeries + ".masterImage", this.masterImages[i]); if (this.varianceModes != null) hConfig.addProperty(sSeries + ".varianceModes", this.varianceModes[i]); hConfig.addProperty(sSeries + ".parameterMode", ""); for (int j = 0; j < this.parameterList.length; j++) { int nPar = this.parameterList[j][1]; int nSub = this.parameterList[j][0]; hConfig.addProperty(sSeries + ".parameterMode." + this.distortionCalibrationData.parameterDescriptions[nPar][0] + (this.distortionCalibrationData.isSubcameraParameter(nPar) ? ("_sub" + nSub) : ""), this.parameterMode[i][j]); // cleaning up output - removing unused groups (may disable if (((this.parameterMode[i][j] == this.modeGroup) || this.saveUnusedGroups) && (this.parameterGroups[i][j] != null)) { for (int ni = 0; ni < this.parameterGroups[i][j].length; ni++) { hConfig.addProperty(sSeries + ".parameterMode." + this.distortionCalibrationData.parameterDescriptions[nPar][0] + (this.distortionCalibrationData.isSubcameraParameter(nPar) ? ("_sub" + nSub) : "") + "_group" + ni, this.parameterGroups[i][j][ni]); } } } if (this.zGroups[i] != null) { hConfig.addProperty(sSeries + ".zGroups", this.zGroups[i].length); for (int nZGroup = 0; nZGroup < this.zGroups[i].length; nZGroup++) { hConfig.addProperty(sSeries + ".zGroup_" + nZGroup, this.zGroups[i][nZGroup]); } } hConfig.addProperty(sSeries + ".lambdas", lambdas[i]); hConfig.addProperty(sSeries + ".stepDone", stepDone[i]); hConfig.addProperty(sSeries + ".stopAfterThis", this.stopAfterThis[i]); } hConfig.addProperty("variances", ""); this.distortionCalibrationData.eyesisCameraParameters.setCostsPropertiesXML("variances.", hConfig); File file = new File(pathname); BufferedWriter writer; try { writer = new BufferedWriter(new FileWriter(file)); hConfig.save(writer); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ConfigurationException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.pathName = pathname; return true; } /** * Verifies that series exists and is not empty (valid for LMA) * @param num - series to verify * @return true if series is valid */ public boolean isSeriesValid(int num) { if ((num < 0) || (num >= this.selectedImages.length)) return false; boolean someImagesSelected = false; for (int i = 0; i < this.selectedImages[num].length; i++) if (this.selectedImages[num][i]) { someImagesSelected = true; break; } if (!someImagesSelected) { if (this.debugLevel > 0) System.out.println("isSeriesValid(" + num + "): no images selected"); return false; } boolean someParametersSelected = false; for (int i = 0; i < this.parameterMode[num].length; i++) if (this.parameterMode[num][i] > 0) { someParametersSelected = true; break; } if (!someParametersSelected) { if (this.debugLevel > 0) System.out.println("isSeriesValid(" + num + "): no parameters selected"); return false; } return true; } /** * Determins if LMA should stop after this series (either flag is set or next is invalid) * @param num number of series to test * @return true if LMA should stop */ public boolean isLastSeries(int num) { if (this.stopAfterThis[num]) return true; if (!isSeriesValid(num + 1)) return true; return false; } /** * * Specifies images to process and parameters to adjust * Each parameter cab be: * 0 - "fixed" - use individual, per-image parameters, do not modify them * 1 - "common" - parameters are common for all selected images. * When saving - save to all selected images. * When loading (if different) use "master image" (or closest to it in time) * 2 - "super common" - same as common, but save to all images, not just selected * 3 - "individual" +====================+===========+===========+ | | Same TS | Diff TS | | +-----+-----+-----+-----+ | | C | I | C | I | +============+=======+=====+=====+=====+=====+ | Same |Eyesis | X | X | C | I | | Subcamera +-------+-----+-----+-----+-----+ | |Subcam | X | X | C | I* | +============+=======+=====+=====+=====+=====+ | Different |Eyesis | C | C | C | I | | Subcameras +-------+-----+-----+-----+-----+ | |Subcam | X | X | X | X | subcameras for different subcameras are individual parameters +============+=======+=====+=====+=====+=====+ I* - special case when the subcamera is being adjusted/replaced. How to deal with it? * */ // public int [][] reverseParameterMap=null; // reversed map - for each [imageNumber][parameterNumber] -> vector index (-1 fixed)\ // selects all enabled images in the specified series (modifies strategy series!) /** * selects all enabled images in the specified series (modifies strategy series!) * @param ser number of fitting strategy series to set * @return old value for series images selection */ public boolean[] selectAllImages(int ser) { // this.selectedValidImages[ser]=null; // just invalidate - do nothing, so restore would work boolean[] oldSelection = this.selectedImages[ser].clone(); for (int i = 0; i < this.selectedImages[ser].length; i++) this.selectedImages[ser][i] = true; // select all\ return oldSelection; } /** * Sets image selection. Can be used to restore saved selection after selectAllImages(int ser) * @param ser number of fitting strategy series to set * @param selection array specifying selected images */ public void setImageSelection(int ser, boolean[] selection) { this.selectedImages[ser] = selection.clone(); } public boolean[] selectedAllImages() { // if (this.currentSeriesNumber<0) return null; return selectedAllImages(this.currentSeriesNumber); } public boolean[] selectedAllImages(int ser) { if ((ser < 0) || (ser >= this.selectedImages.length)) { boolean[] allImages = new boolean[this.selectedImages[0].length]; for (int i = 0; i < allImages.length; i++) allImages[i] = true; // select all return allImages; } else { return this.selectedImages[ser]; } } public boolean[] selectedImages() { // if (this.currentSeriesNumber<0) return null; return selectedImages(this.currentSeriesNumber); } public boolean[] selectedImagesNoBadKernels(int ser) { boolean[] selected = selectedImages(ser); for (int i = 0; i < selected.length; i++) selected[i] &= !this.distortionCalibrationData.gIP[i].noUsefulPSFKernels; return selected; } public void setNoUsefulPSFKernels(int i, boolean noUsefulPSFKernels) { this.distortionCalibrationData.gIP[i].noUsefulPSFKernels = noUsefulPSFKernels; } public void invalidateSelectedImages(int ser) { this.selectedValidImages[ser] = null; } public void initSelectedValidImages(int ser) { this.selectedValidImages[ser] = this.selectedImages[ser].clone(); } public void invalidateSelectedImage(int ser, int nImg) { this.selectedValidImages[ser][nImg] = false; } //this.selectedValidImages public boolean[] selectedImages(int ser) { return selectedImages(ser, false); } public boolean[] selectedImages(int ser, boolean userSelection) { boolean[] selectedMasked; if ((ser < 0) || (ser >= this.selectedImages.length)) { selectedMasked = new boolean[this.selectedImages[0].length]; for (int i = 0; i < selectedMasked.length; i++) selectedMasked[i] = true; // select all } else { selectedMasked = this.selectedImages[ser].clone(); } for (int i = 0; i < selectedMasked.length; i++) selectedMasked[i] &= this.distortionCalibrationData.gIP[i].enabled; // unselect empty // TODO: add minimal number of nodes? if ((ser >= 0) && (this.selectedValidImages[ser] != null) && !userSelection) { for (int i = 0; i < selectedMasked.length; i++) selectedMasked[i] &= this.selectedValidImages[ser][i]; } else { for (int i = 0; i < selectedMasked.length; i++) selectedMasked[i] &= (this.distortionCalibrationData.gIP[i].pixelsXY.length > 0); } return selectedMasked; //this.selectedImages[ser]; } public int getNumSeries() { return this.selectedImages.length; } /** * 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 * @param numSeries number of fitting strategy series * @return this.parameterMap */ public int buildParameterMap(int numSeries) { int numPars = this.distortionCalibrationData.getNumParameters(); int numImg = this.distortionCalibrationData.getNumImages(); int numTPars = this.parameterMode[numSeries].length; this.reverseParameterMap = new int[numImg][numPars]; // boolean [] selectedEnabledImagesAll=selectedImages(numSeries,true); // strictly as in series, including no valid points ones boolean[] selectedEnabledImages = selectedImages(numSeries); // set defaults - -1 - "fixed", use individual parameter from this image for (int i = 0; i < numImg; i++) for (int j = 0; j < numPars; j++) this.reverseParameterMap[i][j] = -1; int vectorIndex = 0; // int [][] tmpMap=new int[numTPars][3]; // temporary array parameterMap[][] (will be truncated) int[][] tmpMap = new int[numPars * numImg][3]; // temporary array parameterMap[][] (will be truncated) double masterTS = this.distortionCalibrationData.getImageTimestamp(this.masterImages[numSeries]); // timestamp of the master image // iterate through all global/subcamera parameters for (int numTPar = 0; numTPar < numTPars; numTPar++) if (this.parameterMode[numSeries][numTPar] != this.modeFixed) { // skip "fixed" boolean isCommon = (this.parameterMode[numSeries][numTPar] == this.modeCommon) || (this.parameterMode[numSeries][numTPar] == this.modeSupercommon); boolean isStation = (this.parameterMode[numSeries][numTPar] == this.modeStation) || (this.parameterMode[numSeries][numTPar] == this.modeSuperStation); boolean isGroup = (this.parameterMode[numSeries][numTPar] == this.modeGroup); int numSub = this.parameterList[numTPar][0]; // number of sub-camera for this total parameter index int numPar = this.parameterList[numTPar][1]; // number of per-image parameter for this total parameter index boolean isSubCamera = this.distortionCalibrationData.isSubcameraParameter(numPar); if (this.debugLevel > 2) System.out.println("numTPar=" + numTPar + " numSub=" + numSub + " numPar=" + numPar); // iterate through available images for (int numThisImg = 0; numThisImg < numImg; numThisImg++) { if ((selectedEnabledImages[numThisImg]) && (!isSubCamera || (numSub == this.distortionCalibrationData.getImageSubcamera(numThisImg))) && (this.reverseParameterMap[numThisImg][numPar] < 0)) { // image used, this cell is not (yet) defined if (this.debugLevel > 2) { System.out.println("buildParameterMap(" + numSeries + "): numThisImg=" + numThisImg + ", numPar=" + numPar + ", vectorIndex=" + vectorIndex); } // assign it a new parameter this.reverseParameterMap[numThisImg][numPar] = vectorIndex; double thisTS = this.distortionCalibrationData.getImageTimestamp(numThisImg); int thisStation = this.distortionCalibrationData.gIP[numThisImg].getStationNumber(); // set pointer to this first image tmpMap[vectorIndex][0] = numThisImg; // vectorindex==22 > tmpMap.length? tmpMap[vectorIndex][1] = numPar; tmpMap[vectorIndex][2] = this.parameterMode[numSeries][numTPar]; double minDist = Math .abs(this.distortionCalibrationData.getImageTimestamp(numThisImg) - masterTS); if (this.debugLevel > 2) System.out.println("vectorIndex=" + vectorIndex + " numThisImg=" + numThisImg); // see if same parameter in some other image(s) is shared for (int numOtherImg = numThisImg + 1; numOtherImg < numImg; numOtherImg++) if ((selectedEnabledImages[numOtherImg]) && // OOB 1 (!isSubCamera || (numSub == this.distortionCalibrationData .getImageSubcamera(numOtherImg))) && (this.reverseParameterMap[numOtherImg][numPar] < 0)) { // image used, this cell is not (yet) defined if ((this.distortionCalibrationData.getImageTimestamp(numOtherImg) == thisTS) || // same parameter same timestamp - same group even if is set differently (isStation && (this.distortionCalibrationData.gIP[numOtherImg] .getStationNumber() == thisStation)) || // new isCommon || (isGroup && (this.parameterGroups[numSeries][numTPar][numThisImg] == this.parameterGroups[numSeries][numTPar][numOtherImg]))) { // assign it a the same parameter this.reverseParameterMap[numOtherImg][numPar] = vectorIndex; double thisDist = Math .abs(this.distortionCalibrationData.getImageTimestamp(numThisImg) - masterTS); if (thisDist < minDist) { minDist = thisDist; tmpMap[vectorIndex][0] = numOtherImg; } } } vectorIndex++; } } } // reverseParameterMap built, vectorIndex equals to the total number of parameters needed for fitting //truncate tmpMap into this.parameterMap[][] this.parameterMap = new int[vectorIndex][]; for (int i = 0; i < vectorIndex; i++) this.parameterMap[i] = tmpMap[i]; this.currentSeriesNumber = numSeries; if (this.debugLevel > 2) System.out.println("this.parameterMap.length=" + this.parameterMap.length); return this.parameterMap.length; } /** * Prepare data for calculating additional LMA terms for parameter variances * @param numSeries fitting series to use * @return number of parameters for which variance is considered */ public int buildVariancesMaps(int numSeries) { if (this.debugLevel > 0) { System.out.println("buildVariancesMaps(" + numSeries + ")"); } if ((this.varianceModes == null) || (this.varianceModes[numSeries] == varianceModeDisabled)) { this.averageCellIndex = null; this.variationsAverages = null; this.weightVariance = null; this.varianceErrorsSquared = null; return 0; } int debugThreshold = 2; boolean useSetWeights = (this.varianceModes[numSeries] == varianceModeVariableWeight); int numPars = this.distortionCalibrationData.getNumParameters(); int numStations = this.distortionCalibrationData.eyesisCameraParameters.getNumStations(); // int numTPars=this.parameterMode[numSeries].length; int numSeriesPars = this.parameterMap.length; // count tilt positions per station List<Integer> tiltList = new ArrayList<Integer>(1000); Integer iStationTilt; int tiltMotorIndex = 2; for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { if (this.parameterMap[numSPar] == null) { System.out.println("buildVariancesMaps() BUG - ,this.parameterMap[" + numSPar + "]==null, numSeriesPars=" + numSeriesPars); continue; } int imgNumber = this.parameterMap[numSPar][0]; // null pointer for triclops after adding variances for tilt int setNumber = this.distortionCalibrationData.gIP[imgNumber].getSetNumber(); int station = this.distortionCalibrationData.gIP[imgNumber].getStationNumber(); if (this.distortionCalibrationData.gIS[setNumber] == null) { System.out.println("buildVariancesMaps() BUG - ,this.distortionCalibrationData.gIS[" + setNumber + "]==null, numSeriesPars=" + numSeriesPars + " numSPar=" + numSPar + " imgNumber=" + imgNumber + " station=" + station); continue; } if (this.distortionCalibrationData.gIS[setNumber].motors == null) { System.out.println("buildVariancesMaps() BUG - ,this.distortionCalibrationData.gIS[" + setNumber + "].motors==null, numSeriesPars=" + numSeriesPars + " numSPar=" + numSPar + " imgNumber=" + imgNumber + " station=" + station); continue; } int tiltMotor = this.distortionCalibrationData.gIS[setNumber].motors[tiltMotorIndex];// null pointer for triclops after adding variances for tilt iStationTilt = station + numStations * tiltMotor; if (!tiltList.contains(iStationTilt)) tiltList.add(iStationTilt); } int numStationTilts = tiltList.size(); int[] numGroups = new int[numPars]; for (int i = 0; i < numGroups.length; i++) numGroups[i] = 0; for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { int parIndex = this.parameterMap[numSPar][1]; // if ((numGroups[parIndex]==0) && (this.distortionCalibrationData.eyesisCameraParameters.isVarianceCostSet(parIndex))){ if (numGroups[parIndex] == 0) { boolean isSet = this.distortionCalibrationData.eyesisCameraParameters .isVarianceCostSet(parIndex); switch (this.parameterMap[numSPar][2]) { case modeWeakCommon: if (isSet) numGroups[parIndex] = 1; if (this.debugLevel > debugThreshold) System.out.println(">>>1 " + numSPar + ":" + parIndex + " - " + numGroups[parIndex] + " isSet=" + isSet); break; case modeWeakStation: if (isSet) numGroups[parIndex] = numStations; if (this.debugLevel > debugThreshold) System.out.println(">>>2 " + numSPar + ":" + parIndex + " - " + numGroups[parIndex] + " isSet=" + isSet); break; case modeTiltEqualize: if (isSet) numGroups[parIndex] = numStationTilts; if (this.debugLevel > debugThreshold) System.out.println(">>>3 " + numSPar + ":" + parIndex + " - " + numGroups[parIndex] + " isSet=" + isSet); break; } } } if (this.debugLevel > debugThreshold) { System.out.println("buildVariancesMaps() numGroups:"); for (int i = 0; i < numGroups.length; i++) if (numGroups[i] > 0) System.out.println("--- " + i + ":" + numGroups[i]); } int startIndex = 0; for (int i = 0; i < numGroups.length; i++) { if (numGroups[i] > 0) { int num = numGroups[i]; numGroups[i] = startIndex; startIndex += num; } else { numGroups[i] = -1; } } if (this.debugLevel > debugThreshold) { System.out.println("buildVariancesMaps() start indices:"); for (int i = 0; i < numGroups.length; i++) if (numGroups[i] >= 0) System.out.println("--- " + i + ":" + numGroups[i]); } if (this.debugLevel > debugThreshold) { System.out.println("buildVariancesMaps() startIndex=" + startIndex); } if (startIndex == 0) { // no variance parameters defined this.averageCellIndex = null; this.variationsAverages = null; this.weightVariance = null; this.varianceErrorsSquared = null; return 0; } // Assign average cell indices for each vector element, for each paramter it can be just 1 for global, numStations or number of tilt positions this.averageCellIndex = new int[numSeriesPars]; for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { this.averageCellIndex[numSPar] = -1; // not applicable int parIndex = this.parameterMap[numSPar][1]; // for (int numTPar=0;numTPar<numTPars;numTPar++) { // int parIndex=this.parameterList[numTPar][1]; if ((numGroups[parIndex] >= 0) && (this.distortionCalibrationData.eyesisCameraParameters.isVarianceCostSet(parIndex))) { int imgNumber = this.parameterMap[numSPar][0]; int setNumber = this.distortionCalibrationData.gIP[imgNumber].getSetNumber(); int station = this.distortionCalibrationData.gIP[imgNumber].getStationNumber(); switch (this.parameterMap[numSPar][2]) { case modeWeakCommon: this.averageCellIndex[numSPar] = numGroups[parIndex]; break; case modeWeakStation: this.averageCellIndex[numSPar] = numGroups[parIndex] + station; break; case modeTiltEqualize: int tiltMotor = this.distortionCalibrationData.gIS[setNumber].motors[tiltMotorIndex]; iStationTilt = station + numStations * tiltMotor; this.averageCellIndex[numSPar] = numGroups[parIndex] + tiltList.indexOf(iStationTilt); break; } } } if (this.debugLevel > 1) { System.out.println("buildVariancesMaps() numStationTilts=" + numStationTilts + " numSeriesPars=" + numSeriesPars + " useSetWeights=" + useSetWeights + " number of averages=" + startIndex); } this.variationsAverages = new double[startIndex]; // total number of different averages int[] numContributors = new int[startIndex]; for (int i = 0; i < this.variationsAverages.length; i++) { this.variationsAverages[i] = 0.0; numContributors[i] = 0; } // Calculate total weights for averages and number of contributors (to remove single-contributor items) for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { int avIndex = this.averageCellIndex[numSPar]; if (avIndex >= 0) { double w; if (useSetWeights) { int imgNumber = this.parameterMap[numSPar][0]; int setNumber = this.distortionCalibrationData.gIP[imgNumber].getSetNumber(); w = this.distortionCalibrationData.gIS[setNumber].getSetWeight(); } else { w = 1.0; } if (w > 0.0) { // do not count zero-contributors (are they possible?) this.variationsAverages[avIndex] += w; numContributors[avIndex]++; } } } if (this.debugLevel > debugThreshold) { System.out.println("buildVariancesMaps() numContributors:"); for (int i = 0; i < numContributors.length; i++) if (numContributors[i] > 0) System.out.println("--- " + i + ":" + numContributors[i]); } // calculate each parameter share of the average TODO: disable if share =100% (single contributor) int numVariancePars = 0; this.weightOnAverage = new double[numSeriesPars]; this.weightVariance = new double[numSeriesPars]; this.varianceErrorsSquared = new double[numSeriesPars]; for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { this.weightOnAverage[numSPar] = 0.0; this.weightVariance[numSPar] = 0.0; this.varianceErrorsSquared[numSPar] = Double.NaN; int avIndex = this.averageCellIndex[numSPar]; if (avIndex >= 0) { if (numContributors[avIndex] < 2) { this.averageCellIndex[numSPar] = -1; // single element - impossible to use if (this.debugLevel > 0) System.out.println("buildVariancesMaps(): removed parameter #" + numSPar + " - single contributor to average"); } else { numVariancePars++; double w; if (useSetWeights) { int imgNumber = this.parameterMap[numSPar][0]; int setNumber = this.distortionCalibrationData.gIP[imgNumber].getSetNumber(); w = this.distortionCalibrationData.gIS[setNumber].getSetWeight(); } else w = 1.0; this.weightOnAverage[numSPar] = w / this.variationsAverages[avIndex]; this.weightVariance[numSPar] = w; } } } if (this.debugLevel > debugThreshold) { System.out.println("average indices/shares:"); for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { int avIndex = this.averageCellIndex[numSPar]; if (avIndex >= 0) { int parIndex = this.parameterMap[numSPar][1]; System.out.println("===" + numSPar + " parIndex=" + parIndex + " avIndex=" + avIndex + " weightOnAverage=" + this.weightOnAverage[numSPar] + " weightVariance=" + this.weightVariance[numSPar]); } } } if (numVariancePars == 0) { this.averageCellIndex = null; this.variationsAverages = null; this.weightVariance = null; this.varianceErrorsSquared = null; return 0; } // TODO this.variationsAverages needs to be reset to all zeros each calculations return numVariancePars; } /** * Ammend LMA arrays to pull individual values of the extrinsic parameters to their averages * Averages may be global, per-station or per same tilt motor position (same station) * The function to be minimized is * f(x) Scale*(1/p*Xeff+(1-1/p)*pow(Xeff,p) * Xeff=x/X0*(1-shareInAverage) * and x is difference between the poarameter and it's average over the specified set (all/station/tiltStation) * @param numSeries fitting series number * @param vector - parameters vector * @param jTByJ Jacobian transposed multiplied by Jacobian - some diagonal elements may be modified (weighted) or null (will add to old values) * @param jTByDiff Jacobian transposed multiplied by difference vector (weighted) or null (will add to old values) * @return true if parameter variances are applicable to the fitting series */ public boolean addVarianceToLMA(int numSeries, final double[] vector, double[][] jTByJ, // jacobian multiplied by Jacobian transposed double[] jTByDiff) { // jacobian multiplied difference vector if (this.averageCellIndex == null) return false; // parameter varainces are not used // int numTPars=this.parameterMode[numSeries].length; int numSeriesPars = this.parameterMap.length; int debugThreshold = 2; // double [] fX= new double [numTPars]; // double [] diagJ=new double [numTPars]; // diagonal of Jacobian (here parameters only influence themselves) double[] weights = new double[this.variationsAverages.length]; for (int i = 0; i < this.variationsAverages.length; i++) { this.variationsAverages[i] = 0.0; weights[i] = 0.0; } // calculate appropriate averages for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { this.varianceErrorsSquared[numSPar] = 0.0; int avIndex = this.averageCellIndex[numSPar]; if (avIndex >= 0) { this.variationsAverages[avIndex] += this.weightOnAverage[numSPar] * vector[numSPar]; } } // now fX and diagJ int numModded = 0; for (int numSPar = 0; numSPar < numSeriesPars; numSPar++) { int avIndex = this.averageCellIndex[numSPar]; int parIndex = this.parameterMap[numSPar][1]; // if ((avIndex>=0) && (this.distortionCalibrationData.eyesisCameraParameters.isVarianceCostSet(parIndex))) { if (avIndex >= 0) { double scale = this.distortionCalibrationData.eyesisCameraParameters .varianceCostScale(parIndex); double x0 = this.distortionCalibrationData.eyesisCameraParameters .varianceCostVariationAbs(parIndex); double exp = this.distortionCalibrationData.eyesisCameraParameters .varianceCostVariationExponent(parIndex); double xEff = (vector[numSPar] - this.variationsAverages[avIndex]) * (1.0 - this.weightOnAverage[numSPar]) / x0; //OOB-1 double axEff = Math.abs(xEff); double sxEff = Math.signum(xEff); double k = 1.0 / exp; double fX = sxEff * scale * (k * axEff + (1.0 - k) * Math.pow(axEff, exp)); double diagJ = scale * ((1.0 - this.weightOnAverage[numSPar]) / x0) * (k + (exp - 1) * Math.pow(axEff, exp - 1.0)); this.varianceErrorsSquared[numSPar] = this.weightVariance[numSPar] * fX * fX; if (jTByJ != null) jTByJ[numSPar][numSPar] += this.weightVariance[numSPar] * diagJ * diagJ; if (jTByDiff != null) jTByDiff[numSPar] -= this.weightVariance[numSPar] * fX * diagJ; if (this.debugLevel > debugThreshold) { System.out.print(numSPar + " " + parIndex + " " + avIndex + ": " + " scale=" + scale); System.out.print(" vector[" + numSPar + "]=" + vector[numSPar] + " (" + this.variationsAverages[avIndex] + ") xEff=" + xEff + " fX=" + fX + " k=" + k + " exp=" + exp); System.out.print(" diagJ=" + diagJ + " " + xEff + " jTByJ+" + (this.weightVariance[numSPar] * diagJ * diagJ)); System.out.print(" jTByDiff+" + (-this.weightVariance[numSPar] * fX * diagJ) + " ves=" + (this.weightVariance[numSPar] * fX * fX)); System.out.println(" weight=" + this.weightVariance[numSPar]); } if (this.weightVariance[numSPar] != 0.0) numModded++; } } if (this.debugLevel > 1) System.out.println("addVarianceToLMA() - modified " + numModded + " elements"); return true; } public double[] getVarianceError2() { return (this.averageCellIndex == null) ? null : this.varianceErrorsSquared; } public double[] getWeights() { return (this.averageCellIndex == null) ? null : this.weightVariance; } /** * * @return number of the current fitting strategy series */ public int getCurrentSeries() { return this.currentSeriesNumber; } /** * Calculate vector of the parameters used in LMA algorithm, extracted from the * individual data, using parameter map (calculated once after changing series) * @return vector of parameters used for the LMA */ public double[] getSeriesVector() { if (this.parameterMap == null) { String msg = "Series parameters map is not calculated"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } double[] vector = new double[this.parameterMap.length]; for (int i = 0; i < vector.length; i++) { // vector[i]=this.distortionCalibrationData.pars[this.parameterMap[i][0]][this.parameterMap[i][1]]; vector[i] = this.distortionCalibrationData.getParameterValue(this.parameterMap[i][0], this.parameterMap[i][1]); // (numImg, numPar) } return vector; } /** * Saves data from the parameter vector to that of the images * @param vector vector of parameters (after LMA fitting) */ // TODO: Update the temporarily disabled images also, when possible (modify buildParameterMap also? public void saveSeriesVector(double[] vector) { if ((this.parameterMap == null) || (vector == null) || (vector.length != this.parameterMap.length)) { String msg = "Vector length does not match parameters length"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } // save for "individual" and "common" - and "station" (week - same as individual here) boolean[] selectedEnabledImages = selectedImages(this.currentSeriesNumber); for (int numImg = 0; numImg < this.reverseParameterMap.length; numImg++) if (selectedEnabledImages[numImg]) { for (int nPar = 0; nPar < this.reverseParameterMap[numImg].length; nPar++) if (this.reverseParameterMap[numImg][nPar] >= 0) { // this.distortionCalibrationData.pars[numImg][nPar]=vector[this.reverseParameterMap[numImg][nPar]]; this.distortionCalibrationData.setParameterValue(numImg, nPar, vector[this.reverseParameterMap[numImg][nPar]], true); if (this.debugLevel > 2) { System.out.println(" Updated image " + numImg + " " + distortionCalibrationData.getParameterName(nPar) + " = " + // this.distortionCalibrationData.pars[numImg][nPar]) ; //vector[this.reverseParameterMap[numImg][nPar]]); this.distortionCalibrationData.getParameterValue(numImg, nPar)); } } } // propagate to other (unselected) images for "super common" parameters for (int vPar = 0; vPar < this.parameterMap.length; vPar++) { if ((this.parameterMap[vPar][2] == modeSupercommon) || (this.parameterMap[vPar][2] == modeSuperStation)) { // "super common" No "weak" here! // int nSub=this.parameterMap[vPar][0]; // that's an image number, not a subcamera number int nSub = this.distortionCalibrationData.gIP[this.parameterMap[vPar][0]].channel; int nStation = this.distortionCalibrationData.gIP[this.parameterMap[vPar][0]] .getStationNumber(); boolean superCommon = (this.parameterMap[vPar][2] == modeSupercommon); int nPar = this.parameterMap[vPar][1]; //this.distortionCalibrationData.channels[i] boolean isSubCamera = this.distortionCalibrationData.isSubcameraParameter(nPar); for (int numImg = 0; numImg < this.reverseParameterMap.length; numImg++) { if (superCommon || (this.distortionCalibrationData.gIP[numImg].getStationNumber() == nStation)) { if (this.debugLevel > 2) { System.out.println("saveSeriesVector(): numImg=" + numImg + " nPar=" + nPar + " isSubCamera=" + isSubCamera + " nSub=" + nSub + " distortionCalibrationData.getImageSubcamera(" + numImg + ")=" + distortionCalibrationData.getImageSubcamera(numImg) + " vector[" + vPar + "]=" + vector[vPar]); } if ((!isSubCamera) || (nSub == this.distortionCalibrationData.getImageSubcamera(numImg))) { // this.distortionCalibrationData.pars[numImg][nPar]=vector[vPar]; this.distortionCalibrationData.setParameterValue(numImg, nPar, vector[vPar], true); } } } } } } /** * 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) UPDATE: works with null parameterVector * @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 double[] getImageParametersVector(int numImg, double[] parameterVector) { // double [] vector = this.distortionCalibrationData.pars[numImg].clone(); double[] vector = this.distortionCalibrationData.getParameters(numImg); // returns a copy, no clone() is needed if ((parameterVector != null) && (this.reverseParameterMap != null) && (this.reverseParameterMap[numImg] != null)) { for (int i = 0; i < vector.length; i++) { if (this.reverseParameterMap[numImg][i] >= 0) vector[i] = parameterVector[this.reverseParameterMap[numImg][i]]; } } return vector; } /** * Calculates which of all parameters for the particular sensor are to be adjusted (to reduce calcualtions) * @param numImg number of image * @param vector parameters vector * @return mask vector to be used with the results of getImageParametersVector() */ public boolean[] getImageParametersVectorMask(int numImg) { if ((this.reverseParameterMap == null) || (this.reverseParameterMap[numImg] == null)) return null; // boolean [] mask =new boolean [this.distortionCalibrationData.pars[numImg].length]; boolean[] mask = new boolean[this.distortionCalibrationData.getParametersLength(numImg)]; for (int i = 0; i < mask.length; i++) { mask[i] = (this.reverseParameterMap[numImg][i] >= 0); } return mask; } /** * Calculates index in the parameter vector corresponding to each image parameter vector element * @param numImg number of image * @param vector parameters vector * @return mask vector to be used with the results of getImageParametersVector() */ public int[] getImageParametersVectorReverseMap(int numImg) { if ((this.reverseParameterMap == null) || (this.reverseParameterMap[numImg] == null)) return null; // int [] map =new int [this.distortionCalibrationData.pars[numImg].length]; int[] map = new int[this.distortionCalibrationData.getParametersLength(numImg)]; for (int i = 0; i < map.length; i++) { map[i] = this.reverseParameterMap[numImg][i]; } return map; } /** * Opens a text window wityh a table that shows map from each element of the parameters vector * to the image number used as a source of this parameter * @param title Window title */ public void showCurrentParameterMap(String title) { String header = "#\tMode\tName\tSubcamera\tImage Number\tMode"; StringBuffer sb = new StringBuffer(); for (int i = 0; i < this.parameterMap.length; i++) { sb.append(i + "\t" + this.definedModesAll[this.parameterMap[i][2]] + "\t" + this.distortionCalibrationData.getParameterName(this.parameterMap[i][1]) + "\t" + this.distortionCalibrationData.getImageSubcamera(this.parameterMap[i][0]) + "\t" + this.parameterMap[i][0] + "\t" + this.definedModesAll[this.parameterMap[i][2]] + "\n"); } new TextWindow(title, header, sb.toString(), 800, 600); } /** * Opens window with a table mapping each image (column) parameter (row) to parameter vector element * "-" (-1 in teh table) - fixed parameter, use one from the individual image * @param title Window title */ public void showCurrentReverseParameterMap(String title) { String header = "Image"; boolean[] selectedEnabledImages = selectedImages(this.currentSeriesNumber); for (int i = 0; i < this.reverseParameterMap.length; i++) if (selectedEnabledImages[i]) { header += "\t" + i; } StringBuffer sb = new StringBuffer(); sb.append("timestmps"); for (int i = 0; i < this.reverseParameterMap.length; i++) if (selectedEnabledImages[i]) { sb.append("\t" + IJ.d2s(this.distortionCalibrationData.getImageTimestamp(i), 6)); } sb.append("\n"); sb.append("subcamera"); for (int i = 0; i < this.reverseParameterMap.length; i++) if (selectedEnabledImages[i]) { sb.append("\t" + this.distortionCalibrationData.getImageSubcamera(i)); } sb.append("\n"); for (int k = 0; k < this.reverseParameterMap[0].length; k++) { // boolean isSubCamera=this.distortionCalibrationData.isSubcameraParameter(k); // sb.append(this.distortionCalibrationData.getParameterName(this.parameterMap[k][1])); // name of parameter sb.append(this.distortionCalibrationData.getParameterName(k)); // name of parameter for (int i = 0; i < this.reverseParameterMap.length; i++) if (selectedEnabledImages[i]) { int mode = this.reverseParameterMap[i][k]; sb.append("\t" + ((mode >= 0) ? mode : "-")); } sb.append("\n"); } new TextWindow(title, header, sb.toString(), 800, 600); } /** * Add/reduce number of series in this fitting strategy * @param numSeries new number of series */ public void setLength(int numSeries) { if (numSeries <= 0) { setDflt(0); return; } if ((this.selectedImages != null) && (numSeries == this.selectedImages.length)) return; if ((this.selectedImages == null) && (numSeries == 0)) return; boolean[][] oldSelectedImages = null; // images selected for each step int[][] oldParameterMode = null; int[] oldVarianceModes = null; int[][] oldZGroups = null; int[][][] oldParameterGroups = null; double[] oldLambdas = null; // LMA initial lambda for each step int[] oldMasterImages = null; double[] oldStepDone = null; boolean[] oldStopAfterThis = null; int oldNumSeries = (this.selectedImages == null) ? 0 : this.selectedImages.length; int oldLength = 0; if (this.selectedImages != null) { // deep clone this.* to old*; oldSelectedImages = new boolean[oldNumSeries][]; oldParameterMode = new int[oldNumSeries][]; oldParameterGroups = new int[oldNumSeries][][]; oldZGroups = new int[oldNumSeries][]; for (int i = 0; i < oldNumSeries; i++) { oldSelectedImages[i] = this.selectedImages[i].clone(); oldParameterMode[i] = this.parameterMode[i].clone(); // out of bound - 2 oldParameterGroups[i] = new int[this.parameterGroups[i].length][]; for (int j = 0; j < oldParameterGroups[i].length; j++) oldParameterGroups[i][j] = (parameterGroups[i][j] == null) ? null : parameterGroups[i][j].clone(); if (this.zGroups[i] != null) oldZGroups[i] = this.zGroups[i].clone(); else this.zGroups[i] = oldZGroups[i]; } oldVarianceModes = (this.varianceModes == null) ? null : this.varianceModes.clone(); oldLambdas = this.lambdas.clone(); oldLength = this.selectedImages.length; oldMasterImages = this.masterImages.clone(); oldStepDone = this.stepDone.clone(); oldStopAfterThis = this.stopAfterThis.clone(); } setDflt(numSeries); if (this.selectedImages != null) for (int i = 0; (i < this.selectedImages.length) && (i < oldLength); i++) { this.selectedImages[i] = oldSelectedImages[i]; this.parameterMode[i] = oldParameterMode[i]; this.parameterGroups[i] = oldParameterGroups[i]; this.lambdas[i] = oldLambdas[i]; this.masterImages[i] = oldMasterImages[i]; this.stepDone[i] = oldStepDone[i]; this.stopAfterThis[i] = oldStopAfterThis[i]; this.varianceModes[i] = oldVarianceModes[i]; this.zGroups[i] = (oldZGroups[i] != null) ? oldZGroups[i].clone() : null; } } public void updateNumberOfSubcameras() { int numPars = this.distortionCalibrationData.getNumParameters(); int numSubCams = this.distortionCalibrationData.getNumSubCameras(); initParameterList(); int numSubPars = 0; int[] subParIndex = new int[numPars]; for (int i = 0; i < numPars; i++) { if (this.distortionCalibrationData.isSubcameraParameter(i)) subParIndex[numSubPars++] = i; } int totalNumPars = numPars + (numSubCams - 1) * numSubPars; // maximal number of parametes per timestamp? int oldTotalPars = this.parameterEnable.length; if (oldTotalPars == totalNumPars) return; if (oldTotalPars > numPars) { // more than one subcamera, so last numSubPars are subcamera parameters for (int i = 0; i < numSubPars; i++) subParIndex[i] = (oldTotalPars - numSubPars) + i; } // public boolean isSubcameraParameter(int num){ int numSeries = this.parameterMode.length; if (oldTotalPars < totalNumPars) { // grow, repeat last subcamera if (this.debugLevel > 1) System.out.println("Increasing total number of parameters in fitting strategy: " + oldTotalPars + " -> " + totalNumPars); for (int nSer = 0; nSer < numSeries; nSer++) { int[] parameterMode_tmp = this.parameterMode[nSer]; //.clone(); int[][] parameterGroups_tmp = this.parameterGroups[nSer]; //.clone(); this.parameterMode[nSer] = new int[totalNumPars]; this.parameterGroups[nSer] = new int[totalNumPars][]; for (int nTPar = 0; nTPar < oldTotalPars; nTPar++) { this.parameterMode[nSer][nTPar] = parameterMode_tmp[nTPar]; this.parameterGroups[nSer][nTPar] = parameterGroups_tmp[nTPar]; } for (int nTPar = oldTotalPars; nTPar < totalNumPars; nTPar++) { int index = subParIndex[(nTPar - oldTotalPars) % numSubPars]; this.parameterMode[nSer][nTPar] = parameterMode_tmp[index]; this.parameterGroups[nSer][nTPar] = (parameterGroups_tmp[index] == null) ? null : parameterGroups_tmp[index].clone(); } } boolean[] parameterEnable_tmp = this.parameterEnable; //.clone(); this.parameterEnable = new boolean[totalNumPars]; for (int nTPar = 0; nTPar < oldTotalPars; nTPar++) { this.parameterEnable[nTPar] = parameterEnable_tmp[nTPar]; } for (int nTPar = oldTotalPars; nTPar < totalNumPars; nTPar++) { int index = subParIndex[(nTPar - oldTotalPars) % numSubPars]; this.parameterEnable[nTPar] = parameterEnable_tmp[index]; } } else { // shrink if (this.debugLevel > 1) System.out.println("Reducing total number of parameters in fitting strategy: " + oldTotalPars + " -> " + totalNumPars); for (int nSer = 0; nSer < numSeries; nSer++) { int[] parameterMode_tmp = this.parameterMode[nSer]; //.clone(); int[][] parameterGroups_tmp = this.parameterGroups[nSer]; //.clone(); this.parameterMode[nSer] = new int[totalNumPars]; this.parameterGroups[nSer] = new int[totalNumPars][]; for (int nTPar = 0; nTPar < totalNumPars; nTPar++) { this.parameterMode[nSer][nTPar] = parameterMode_tmp[nTPar]; this.parameterGroups[nSer][nTPar] = parameterGroups_tmp[nTPar]; } } boolean[] parameterEnable_tmp = this.parameterEnable; //.clone(); this.parameterEnable = new boolean[totalNumPars]; for (int nTPar = 0; nTPar < totalNumPars; nTPar++) { this.parameterEnable[nTPar] = parameterEnable_tmp[nTPar]; } } } private void setDflt(int numSeries) { if (numSeries == 0) { this.selectedImages = null; this.lambdas = null; this.stepDone = null; this.parameterMode = null; this.parameterGroups = null; this.varianceModes = null; this.zGroups = null; return; } this.stopAfterThis = new boolean[numSeries]; for (int i = 0; i < numSeries; i++) this.stopAfterThis[i] = true; // calculate total (potential) number of parameters in the system //TODO: modify for groups int numPars = this.distortionCalibrationData.getNumParameters(); int numSubCams = this.distortionCalibrationData.getNumSubCameras(); int numSubPars = 0; for (int i = 0; i < numPars; i++) { if (this.distortionCalibrationData.isSubcameraParameter(i)) numSubPars++; } int totalNumPars = numPars + (numSubCams - 1) * numSubPars; // maximal number of parametes per timestamp? // number of parameters to adjust may be greater, if they were changing between the images this.selectedImages = new boolean[numSeries][this.distortionCalibrationData.getNumImages()];// this.selectedValidImages = new boolean[numSeries][this.distortionCalibrationData.getNumImages()];// this.masterImages = new int[numSeries]; this.lambdas = new double[numSeries]; this.stepDone = new double[numSeries]; this.parameterMode = new int[numSeries][totalNumPars]; this.varianceModes = new int[numSeries]; this.parameterGroups = new int[numSeries][totalNumPars][]; this.parameterEnable = new boolean[totalNumPars]; this.zGroups = new int[numSeries][]; for (int i = 0; i < this.selectedImages.length; i++) { invalidateSelectedImages(i); for (int j = 0; j < this.selectedImages[i].length; j++) { this.selectedImages[i][j] = false; } for (int j = 0; j < this.parameterMode[i].length; j++) { this.parameterMode[i][j] = this.modeFixed; // fixed this.parameterGroups[i][j] = null; // no groups yet defined } this.lambdas[i] = defaultLambda; this.stepDone[i] = defaultStepDone; this.masterImages[i] = 0; // first image, supposingly the earliest timestamp this.varianceModes[i] = varianceModeDisabled; this.zGroups[i] = null; } initParameterList(); for (int i = 0; i < numPars; i++) { this.parameterEnable[i] = true; // initially enable all parameters for the first camera } for (int i = 1; i < numSubCams; i++) { int i1 = numPars + numSubPars * (i - 1); int j1 = 0; for (int j = 0; j < numPars; j++) if (this.distortionCalibrationData.isSubcameraParameter(j)) { this.parameterEnable[i1 + j1] = false; // initially disable all other subcamera parameters j1++; } } } private void initParameterList() { int numPars = this.distortionCalibrationData.getNumParameters(); int numSubCams = this.distortionCalibrationData.getNumSubCameras(); int numSubPars = 0; for (int i = 0; i < numPars; i++) { if (this.distortionCalibrationData.isSubcameraParameter(i)) numSubPars++; } int totalNumPars = numPars + (numSubCams - 1) * numSubPars; // maximal number of parametes per timestamp? this.parameterList = new int[totalNumPars][2]; for (int i = 0; i < numPars; i++) { this.parameterList[i][0] = 0; this.parameterList[i][1] = i; // this.parameterEnable[i]=true; // initially enable all parameters for the first camera } for (int i = 1; i < numSubCams; i++) { int i1 = numPars + numSubPars * (i - 1); int j1 = 0; for (int j = 0; j < numPars; j++) if (this.distortionCalibrationData.isSubcameraParameter(j)) { this.parameterList[i1 + j1][0] = i; this.parameterList[i1 + j1][1] = j; // this.parameterEnable[i1+j1]=false; // initially disable all other subcamera parameters j1++; } } } /** * Find parameter number from subcamera and index * @param nSub number of subcamera (channel) * @param index index of parameter * @return number of parameter that has specified sub-camera and index. -1 if such does not exist */ private int getParameterNumber(int nSub, int index) { for (int i = 0; i < this.parameterList.length; i++) if ((this.parameterList[i][0] == nSub) && (this.parameterList[i][1] == index)) return i; return -1; } /** * * @param numSeries Number of series to edit * @param useImages Select images for this series * @param fromToImages - limit number of checkboxes, otherwise window does not show the bottom ones * @param useParameters Select parameters for this series * @param askNextSeries Ask for next series number * @param zeroAndOther use 2 channels 0 and "other", propagete settings for channel 1 to all the rest * @return -2 - cancel, -1, done, otherwise - number of step to edit */ public int selectStrategyStep(int numSeries, boolean useImages, int[] fromToImages, boolean allImages, boolean useParameters, boolean askLambdas, boolean askNextSeries, boolean zeroAndOther) { boolean showDirectMap = false; boolean showReverseMap = false; // if current series is not valid (probably just started a new one) - look for the last valid (if any) // and copy it; int numEstimated = this.distortionCalibrationData.getNumberOfEstimated(true); //(boolean enabledOnly int[] numEstimatedPerStation = this.distortionCalibrationData.getNumberOfEstimatedPerStation(true); //(boolean enabledOnly String sNumEstimatedPerStation = ""; for (int i = 0; i < numEstimatedPerStation.length; i++) { if (i > 0) sNumEstimatedPerStation += ", "; sNumEstimatedPerStation += numEstimatedPerStation[i]; } int numNewEnabled = this.distortionCalibrationData.getNumNewEnabled(); int[] numNewEnabledPerStation = this.distortionCalibrationData.getNumNewEnabledPerStation(); String sNumNewEnabledPerStation = ""; for (int i = 0; i < numNewEnabledPerStation.length; i++) { if (i > 0) sNumNewEnabledPerStation += ", "; sNumNewEnabledPerStation += numNewEnabledPerStation[i]; } if (!isSeriesValid(numSeries)) { int sourceSeries = -1; for (int i = numSeries - 1; i >= 0; i--) if (isSeriesValid(i)) { sourceSeries = i; break; } if (sourceSeries >= 0) { invalidateSelectedImages(numSeries); // just in case -= will be recalculated for (int i = 0; i < this.distortionCalibrationData.getNumImages(); i++) { this.selectedImages[numSeries][i] = this.selectedImages[sourceSeries][i]; // copy for all, not only enabled } for (int i = 0; i < this.parameterEnable.length; i++) if (this.parameterEnable[i]) { this.parameterMode[numSeries][i] = this.parameterMode[sourceSeries][i]; this.parameterGroups[numSeries][i] = (this.parameterGroups[sourceSeries][i] == null) ? null : this.parameterGroups[sourceSeries][i].clone(); } this.masterImages[numSeries] = this.masterImages[sourceSeries]; this.lambdas[numSeries] = this.lambdas[sourceSeries]; this.stepDone[numSeries] = this.stepDone[sourceSeries]; this.stopAfterThis[numSeries] = this.stopAfterThis[sourceSeries]; this.varianceModes[numSeries] = this.varianceModes[sourceSeries]; this.zGroups[numSeries] = (this.zGroups[sourceSeries] != null) ? this.zGroups[sourceSeries].clone() : null; } } GenericDialog gd = new GenericDialog("Fitting Strategy Step Configuration, step " + numSeries + " number of enabled images=" + this.distortionCalibrationData.getNumEnabled()); gd.addCheckbox("Copy all from previous series (ignore all other fields)", false); gd.addCheckbox("Remove all (but first) images, reopen dialog", false); // remove all will be invalid, copied from the previous gd.addCheckbox("Select all images, reopen dialog", false); if (numEstimated > 0) { gd.addMessage("There are " + numEstimated + " (" + sNumEstimatedPerStation + ") enabled images that have estimated orientation"); gd.addCheckbox("Select them and only them (and re-open dialog)", false); } else { gd.addMessage("There are no enabled images with estimated (from neighbors) orientation"); } if (numNewEnabled > 0) { gd.addMessage( "There are " + numNewEnabled + " (" + sNumNewEnabledPerStation + " new enabled images"); gd.addCheckbox("Select them and only them (and re-open dialog)", false); } else { gd.addMessage("There are no new enabled images"); } int numStations = this.distortionCalibrationData.eyesisCameraParameters.getNumStations(); boolean[] constrainByStation = new boolean[numStations]; for (int i = 0; i < constrainByStation.length; i++) constrainByStation[i] = true; if (this.distortionCalibrationData.eyesisCameraParameters.numStations > 1) { gd.addMessage("Constrain by stations"); // gd.addCheckbox("Remove images of unselected stations below", true); for (int i = 0; i < this.distortionCalibrationData.eyesisCameraParameters.numStations; i++) gd.addCheckbox("Station " + i, constrainByStation[i]); } if (useImages) { gd.addNumericField("Image sepection range, from", fromToImages[0], 0); gd.addNumericField("Image sepection range, up to (inclufing)", fromToImages[1], 0); gd.addMessage("Select files to include"); for (int i = 0; i < this.distortionCalibrationData.getNumImages(); i++) if ((allImages || this.distortionCalibrationData.gIP[i].enabled) && (i >= fromToImages[0]) && (i <= fromToImages[1])) { int hm = this.distortionCalibrationData.gIP[i].hintedMatch; gd.addCheckbox( i + " - " + (this.distortionCalibrationData.gIP[i].enabled ? "" : "(disabled) ") + IJ.d2s(this.distortionCalibrationData.gIP[i].timestamp, 6) + ": " + this.distortionCalibrationData.gIP[i].channel + " matched " + this.distortionCalibrationData.gIP[i].matchedPointers + " pointers" + ", hinted state: " + ((hm < 0) ? "undefined" : ((hm == 0) ? "failed" : ((hm == 1) ? "orientation" : "orientation and translation"))), this.selectedImages[numSeries][i]); } if (allImages) gd.addCheckbox("Enable selected, disable deselected images", false); gd.addNumericField("The 'master' (used for common parameters)", this.masterImages[numSeries], 0); } if (useParameters) { gd.addMessage("Select parameters to fit"); for (int i = 0; i < this.parameterEnable.length; i++) if (this.parameterEnable[i] && (!zeroAndOther || (this.parameterList[i][0] <= 1) || (this.parameterList[i][0] == 24))) { // in "zeroAndOther" mode do not show other subcameras int parIndex = this.parameterList[i][1]; int subCam = this.parameterList[i][0]; boolean isSub = this.distortionCalibrationData.isSubcameraParameter(parIndex); boolean defined = false; double min = 0.0, max = 0.0; for (int imgNumber = 0; imgNumber < this.distortionCalibrationData .getNumImages(); imgNumber++) if (this.selectedImages[numSeries][imgNumber] && this.distortionCalibrationData.gIP[imgNumber].enabled) { int sub = this.distortionCalibrationData.gIP[imgNumber].channel; if (!isSub || (sub == subCam) || // global or same subcamera (zeroAndOther && (subCam >= 1) && (subCam < 24) && (sub >= 1) && (sub < 24)) || // both head "other" (zeroAndOther && (subCam >= 24) && (sub >= 24))) { // both subcameras are "other" subcameras double parValue = this.distortionCalibrationData.getParameterValue(imgNumber, parIndex); if (!defined) { // min=this.distortionCalibrationData.pars[imgNumber][parIndex]; min = parValue; max = min; defined = true; } // if (this.distortionCalibrationData.pars[imgNumber][parIndex]<min) min=this.distortionCalibrationData.pars[imgNumber][parIndex]; // if (this.distortionCalibrationData.pars[imgNumber][parIndex]>max) max=this.distortionCalibrationData.pars[imgNumber][parIndex]; if (parValue < min) min = parValue; if (parValue > max) max = parValue; } } // System.out.println(i+": "+parIndex+":"+subCam+"defined="+defined+" min="+min+" max="+max); // undefined, min, max String sValue = (defined) ? ((min == max) ? (min + "") : (min + "..." + max)) : "undefined"; String sChn = (zeroAndOther && (subCam >= 1) && (subCam < 24)) ? "-head-other" : ((zeroAndOther && (subCam >= 24)) ? "-bottom" : ("-" + subCam)); boolean noWeak = !this.distortionCalibrationData.eyesisCameraParameters .isExtrinsic(parIndex); boolean isTilt = this.distortionCalibrationData.eyesisCameraParameters.isTilt(parIndex); gd.addChoice( // ArrayIndexOutOfBoundsException: 9 this.distortionCalibrationData.getParameterName(parIndex) + " (" + sValue + " " + this.distortionCalibrationData.getParameterUnits(parIndex) + ")" + (this.distortionCalibrationData.isSubcameraParameter(parIndex) ? (" sub" + sChn) : "com "), (isTilt ? this.definedModesTiltEq : (noWeak ? this.definedModesNoWeak : this.definedModes)), this.definedModesAll[this.parameterMode[numSeries][i]]); // definedModesAll - includes all others } } if (askLambdas) { gd.addNumericField("Initial lambda for the L-M algorithm", this.lambdas[numSeries], 6, 8, ""); gd.addStringField("Relative decrese in error to error ratio to consider series finished", "" + this.stepDone[numSeries], 8); gd.addCheckbox("Stop after this series", this.stopAfterThis[numSeries]); } if (this.varianceModes != null) gd.addChoice("Processing of selected parameters variances", this.definedVarianceModes, this.definedVarianceModes[this.varianceModes[numSeries]]); gd.addCheckbox("Edit variances costs", false); if (numStations > 1) { int oldZGroupsLength = (this.zGroups[numSeries] != null) ? this.zGroups[numSeries].length : 0; int[] oldZGroups = {}; if (oldZGroupsLength > 0) oldZGroups = this.zGroups[numSeries].clone(); this.zGroups[numSeries] = new int[numStations]; int nextZGroup = -1; for (int i = 0; i < numStations; i++) { if (i < oldZGroupsLength) { this.zGroups[numSeries][i] = oldZGroups[i]; if (this.zGroups[numSeries][i] > nextZGroup) nextZGroup = this.zGroups[numSeries][i]; } else { this.zGroups[numSeries][i] = ++nextZGroup; } } String[] zGroupsChoices = new String[numStations + 1]; zGroupsChoices[0] = "Not used"; for (int i = 0; i < numStations; i++) zGroupsChoices[i + 1] = "Group " + i; gd.addMessage( "Select groups of same pattern for different stations (pattern did not move between measurements). Used for pattern correction command"); for (int i = 0; i < numStations; i++) { int zG = (this.zGroups[numSeries][i] < 0) ? 0 : (this.zGroups[numSeries][i] + 1); gd.addChoice( // ArrayIndexOutOfBoundsException: 9 "Same target group for station " + i, zGroupsChoices, zGroupsChoices[zG]); // definedModesAll - includes all others } } else { this.zGroups[numSeries] = new int[1]; this.zGroups[numSeries][0] = 0; } if (askNextSeries) { gd.addCheckbox("Rebuild/Show parameter Map", showDirectMap); gd.addCheckbox("Rebuild/Show reverse parameter Map", showReverseMap); gd.addNumericField("Next series to edit (<0 - done)", numSeries + 1, 0); } if (askNextSeries) gd.enableYesNoCancel("OK", "Done"); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return -2; boolean copyFromPrevious = gd.getNextBoolean(); boolean removeAllImages = gd.getNextBoolean(); boolean selectAllImages = gd.getNextBoolean(); boolean selectEstimated = false; if (numEstimated > 0) selectEstimated = gd.getNextBoolean(); boolean selectNewEnabled = false; if (numNewEnabled > 0) selectNewEnabled = gd.getNextBoolean(); if (selectNewEnabled) { this.selectedImages[numSeries] = this.distortionCalibrationData.selectNewEnabled(); return numSeries; // caller will repeat with the same series } if (this.distortionCalibrationData.eyesisCameraParameters.numStations > 1) { // boolean removeUnselectedStations=gd.getNextBoolean(); for (int i = 0; i < constrainByStation.length; i++) constrainByStation[i] = gd.getNextBoolean(); } if (selectEstimated) { this.selectedImages[numSeries] = this.distortionCalibrationData.selectEstimated(true); //(boolean enabledOnly for (int i = 0; i < this.distortionCalibrationData.getNumImages(); i++) { this.selectedImages[numSeries][i] &= constrainByStation[this.distortionCalibrationData.gIP[i] .getStationNumber()]; } return numSeries; // caller will repeat with the same series } if (copyFromPrevious || removeAllImages || selectAllImages) { for (int i = 0; i < this.distortionCalibrationData.getNumImages(); i++) { // this.selectedImages[numSeries][i]=false; // invalidate - all, regardless of .enabled this.selectedImages[numSeries][i] = selectAllImages || ((i == 0) && removeAllImages); // invalidate - all, regardless of .enabled this.selectedImages[numSeries][i] &= constrainByStation[this.distortionCalibrationData.gIP[i] .getStationNumber()]; } return numSeries; // caller will repeat with the same series } boolean enableDisableSelected = false; if (useImages) { fromToImages[0] = (int) gd.getNextNumber(); fromToImages[1] = (int) gd.getNextNumber(); for (int i = 0; i < this.distortionCalibrationData.getNumImages(); i++) if ((allImages || this.distortionCalibrationData.gIP[i].enabled) && (i >= fromToImages[0]) && (i <= fromToImages[1])) { this.selectedImages[numSeries][i] = gd.getNextBoolean(); } if (allImages) enableDisableSelected = gd.getNextBoolean(); this.masterImages[numSeries] = (int) gd.getNextNumber(); if (this.masterImages[numSeries] < 0) this.masterImages[numSeries] = 0; if (this.masterImages[numSeries] >= this.selectedImages[numSeries].length) this.masterImages[numSeries] = this.selectedImages[numSeries].length; } if (useParameters) { int[] lastGroups = null; for (int i = 0; i < this.parameterEnable.length; i++) if (this.parameterEnable[i] && (!zeroAndOther || (this.parameterList[i][0] <= 1) || (this.parameterList[i][0] == 24))) { // in "zeroAndOther" mode do not show other subcameras // for (int i =0; i<this.parameterEnable.length;i++) if (this.parameterEnable[i]){ this.parameterMode[numSeries][i] = gd.getNextChoiceIndex(); if (this.parameterMode[numSeries][i] == this.modeGroup) { if (this.parameterGroups[numSeries][i] != null) lastGroups = this.parameterGroups[numSeries][i]; // default groups else if (lastGroups != null) this.parameterGroups[numSeries][i] = lastGroups.clone(); // may be null selectGroups(numSeries, i); } } if (zeroAndOther) { for (int i = 0; i < this.parameterEnable.length; i++) { if ((this.parameterList[i][0] > 1) && (this.parameterList[i][0] != 24)) { // "other" subchannels - copy from subchannel1 int refChannel = (this.parameterList[i][0] < 24) ? 1 : 24; int iSub1 = getParameterNumber(refChannel, this.parameterList[i][1]); if (this.parameterEnable[iSub1]) { // System.out.println( "parameter number="+i+" this.parameterList[i][0]="+this.parameterList[i][0]+" this.parameterList[i][1]="+this.parameterList[i][1]+" iSub1="+iSub1); this.parameterMode[numSeries][i] = this.parameterMode[numSeries][iSub1]; if (this.parameterMode[numSeries][i] == this.modeGroup) { // copy groups from channel 1 if (this.parameterGroups[numSeries][i] != null) this.parameterGroups[numSeries][i] = this.parameterGroups[numSeries][iSub1] .clone(); // may be null else this.parameterGroups[numSeries][i] = null; } } } } } } if (askLambdas) { this.lambdas[numSeries] = gd.getNextNumber(); this.stepDone[numSeries] = Double.parseDouble(gd.getNextString()); this.stopAfterThis[numSeries] = gd.getNextBoolean(); } if (enableDisableSelected) { this.distortionCalibrationData.enableSelected(this.selectedImages[numSeries]); } if (this.varianceModes != null) this.varianceModes[numSeries] = gd.getNextChoiceIndex(); boolean editVariancesCosts = gd.getNextBoolean(); if (editVariancesCosts) { for (int i = 0; i < this.parameterList.length; i++) { int parIndex = this.parameterList[i][1]; if ((this.parameterMode[numSeries][i] == modeWeakCommon) || (this.parameterMode[numSeries][i] == modeWeakStation) || (this.parameterMode[numSeries][i] == modeTiltEqualize)) { if (!this.distortionCalibrationData.eyesisCameraParameters.isExtrinsic(parIndex)) { System.out.println("BUG: this.parameterMode[" + numSeries + "][" + i + "]=" + this.parameterMode[numSeries][i] + ", but this parameter (" + this.distortionCalibrationData.getParameterName(parIndex) + " is not valid for variances"); continue; } this.distortionCalibrationData.eyesisCameraParameters.editCostProperties(parIndex, this.distortionCalibrationData.getParameterName(parIndex), this.distortionCalibrationData.getParameterDescription(parIndex), this.distortionCalibrationData.getParameterUnits(parIndex)); } } } if (numStations > 1) { for (int i = 0; i < numStations; i++) { this.zGroups[numSeries][i] = gd.getNextChoiceIndex() - 1; } } if (!gd.wasOKed()) return -1; // pressed Done (no need to ask for the next number) if (askNextSeries) { showDirectMap = gd.getNextBoolean(); showReverseMap = gd.getNextBoolean(); if (showDirectMap || showReverseMap) { buildParameterMap(numSeries); if (showDirectMap) showCurrentParameterMap("Parameter map"); if (showReverseMap) showCurrentReverseParameterMap("Reverse parameter map"); } int nextSeries = (int) gd.getNextNumber(); if (nextSeries < -1) nextSeries = -1; return nextSeries; } return -1; } private boolean selectGroups(int numSeries, int numPar) { // if (this.debugLevel>1){ // System.out.println("selectGroups("+numSeries+", "+numPar+")"); // } int parIndex = this.parameterList[numPar][1]; int subCam = this.parameterList[numPar][0]; String name = this.distortionCalibrationData.getParameterName(parIndex) + (this.distortionCalibrationData.isSubcameraParameter(parIndex) ? (" s" + subCam) : "com "); GenericDialog gd = new GenericDialog("Select image groups for " + name); gd.addMessage("Select which images share the same value of " + name); if (this.parameterGroups[numSeries][numPar] == null) { this.parameterGroups[numSeries][numPar] = new int[this.distortionCalibrationData.getNumImages()]; for (int i = 0; i < this.parameterGroups[numSeries][numPar].length; i++) this.parameterGroups[numSeries][numPar][i] = 0; } String[] choices = organizeGroups(this.parameterGroups[numSeries][numPar], false); // preserve group numbers for (int i = 0; i < this.distortionCalibrationData.getNumImages(); i++) if (this.selectedImages[numSeries][i]) { gd.addChoice( i + " - " + IJ.d2s(this.distortionCalibrationData.gIP[i].timestamp, 6) + ": " + this.distortionCalibrationData.gIP[i].channel, choices, choices[this.parameterGroups[numSeries][numPar][i]]); } WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return false; for (int i = 0; i < this.distortionCalibrationData.getNumImages(); i++) if (this.selectedImages[numSeries][i]) { this.parameterGroups[numSeries][numPar][i] = gd.getNextChoiceIndex(); } return true; } /** * Organizes list of groups and creates a list of selection choices. If all members fit in the range * of 0 (length-1) and (force==false), group numbers are preserved, otherwise they are renumbered * @param groups array of integers - group numbers * @param force force renumbering groups * @return list of selection choices */ private String[] organizeGroups(int[] groups, boolean force) { if ((groups == null) || (groups.length == 0)) { String msg = "Cannot organize mull or empty group"; IJ.showMessage("Error", msg); throw new IllegalArgumentException(msg); } // See if 0.. length-1 groups is not enough to represent all numbers used for (int i = 0; !force && (i < groups.length); i++) if ((groups[i] < 0) || (groups[i] >= (groups.length - 1))) force = true; if (force) { int[] tmp = groups.clone(); for (int i = 0; i < groups.length; i++) groups[i] = -1; int groupNumber = 0; int max = tmp[0]; for (int i = 0; i < tmp.length; i++) if (max < tmp[i]) max = tmp[i]; for (boolean organized = false; !organized;) { int min = max + 1; for (int i = 0; i < tmp.length; i++) if ((groups[i] < 0) && (min > tmp[i])) min = tmp[i]; if (min > max) organized = true; else { for (int i = 0; i < tmp.length; i++) if ((groups[i] < 0) && (min == tmp[i])) { groups[i] = groupNumber; } groupNumber++; } } } String[] rslt = new String[groups.length]; for (int i = 0; i < rslt.length; i++) rslt[i] = "Group " + (i + 1); return rslt; } public boolean selectStrategy(int startSerNumber) { int defaultLength = 20; boolean selectImages = false; boolean allImages = false; boolean selectParameters = true; boolean askNextSeries = true; boolean askLambdas = true; boolean askParameterMask = false; boolean zeroAndOther = true; int[] fromToImages = { 0, 500 }; int numSeries = startSerNumber; int oldLength = (this.selectedImages == null) ? 0 : this.selectedImages.length; GenericDialog gd = new GenericDialog("Fitting Strategy Step Configuration"); if (oldLength <= 0) gd.addNumericField("Number of series in this strategy", defaultLength, 0); gd.addNumericField("Number of series to edit (<0 - none)", numSeries, 0); // gd.addNumericField("Number of series in this strategy", (oldLength>0)?oldLength:1, 0); // gd.addCheckbox("Select images", selectImages); gd.addNumericField("Show image checkboxes from", fromToImages[0], 0); gd.addNumericField("Show image checkboxes up to (inclufing)", fromToImages[1], 0); gd.addCheckbox("Select from all images (false - only enabled)", allImages); gd.addCheckbox("Select parameters", selectParameters); gd.addCheckbox("Ask for initial lambda", askLambdas); gd.addCheckbox("Ask for parameter mask", askParameterMask); gd.addCheckbox("Use only channel 0 and \"all other channels\"", zeroAndOther); gd.addCheckbox("Ask for next series", askNextSeries); if (oldLength > 0) gd.addNumericField("Increase number of series in this strategy", oldLength, 0); gd.enableYesNoCancel("OK", "Done"); gd.showDialog(); if (gd.wasCanceled()) return false; int numberOfSeries = 0; if (oldLength <= 0) numberOfSeries = (int) gd.getNextNumber(); numSeries = (int) gd.getNextNumber(); selectImages = gd.getNextBoolean(); fromToImages[0] = (int) gd.getNextNumber(); fromToImages[1] = (int) gd.getNextNumber(); allImages = gd.getNextBoolean(); selectParameters = gd.getNextBoolean(); askLambdas = gd.getNextBoolean(); askParameterMask = gd.getNextBoolean(); zeroAndOther = gd.getNextBoolean(); askNextSeries = gd.getNextBoolean(); if (oldLength > 0) numberOfSeries = (int) gd.getNextNumber(); if (numberOfSeries != oldLength) setLength(numberOfSeries); if (!gd.wasOKed()) return true; if (askParameterMask) setParameterSelectionMask(zeroAndOther); while ((numSeries >= 0) && (numSeries < this.selectedImages.length)) { numSeries = selectStrategyStep(numSeries, selectImages, fromToImages, allImages, selectParameters, askLambdas, askNextSeries, zeroAndOther); } return true; } public boolean setParameterSelectionMask(boolean zeroAndOther) { GenericDialog gd = new GenericDialog("Set Parameter Selection Mask"); gd.addMessage("Common parameters and sub-camera 0 parameters"); int subCam = 0; boolean showParameter = true; for (int i = 0; i < this.parameterList.length; i++) { if (this.parameterList[i][0] != subCam) { subCam = this.parameterList[i][0]; if (zeroAndOther) { showParameter = true; if (subCam == 1) gd.addMessage("Other sub-cameras parameters"); else if (subCam == 24) gd.addMessage("Bottom sub-cameras parameters"); // else break; else { showParameter = false; // continue; } } else { gd.addMessage("Sub-camera " + subCam + " parameters"); } } if (showParameter) gd.addCheckbox(this.distortionCalibrationData.getParameterName(parameterList[i][1]), this.parameterEnable[i]); } WindowTools.addScrollBars(gd); // gd.setBackground(Color.white); gd.showDialog(); if (gd.wasCanceled()) return false; subCam = 0; showParameter = true; //May add disablong all other subcameras, but try keeping it for later running with zeroAndOther==false for (int i = 0; i < this.parameterList.length; i++) { if (this.parameterList[i][0] != subCam) { subCam = this.parameterList[i][0]; showParameter = true; // if (subCam>1) break; if ((subCam > 1) && (subCam != 24)) showParameter = false; } if (showParameter) this.parameterEnable[i] = gd.getNextBoolean(); } return true; } //this.currentSeriesNumber public double getLambda() { return getLambda(this.currentSeriesNumber); } public double getLambda(int numSeries) { return this.lambdas[numSeries]; } public double getStepDone() { return getStepDone(this.currentSeriesNumber); } public double getStepDone(int numSeries) { return this.stepDone[numSeries]; } } public static class DistortionProcessConfiguration { public String sourceDirectory = ""; public String gridDirectory = ""; public boolean useLaserPonters = true; public boolean useNoPonters = true; // use images that do not have any lasre pointers public boolean showAcquiredImages = true; public boolean saveAcquiredImages = true; public boolean selectSourceFiles = true; public boolean removeOutOfGridPointers = true; public boolean showGridImages = false; public boolean saveGridImages = true; public boolean overwriteResultFiles = false; public int debugLevel = 1; public String selectSourceDirectory(boolean smart, String defaultPath, boolean newAllowed) { String dir = CalibrationFileManagement.selectDirectory(smart, newAllowed, // save "Source (acquired from the camera) image directory", // title "Select source directory", // button null, // filter defaultPath); // this.sourceDirectory); if (dir != null) this.sourceDirectory = dir; return dir; } public String selectGridFileDirectory(boolean smart, String defaultPath, boolean newAllowed) { String dir = CalibrationFileManagement.selectDirectory(smart, newAllowed, // save "Grid files directory (grid patterns extracted from the images)", // title "Select grid files directory", // button null, // filter defaultPath); //this.sourceDirectory); if (dir != null) this.gridDirectory = dir; return dir; } public void setProperties(String prefix, Properties properties) { properties.setProperty(prefix + "sourceDirectory", this.sourceDirectory); properties.setProperty(prefix + "gridDirectory", this.gridDirectory); properties.setProperty(prefix + "useLaserPonters", this.useLaserPonters + ""); properties.setProperty(prefix + "useNoPonters", this.useNoPonters + ""); properties.setProperty(prefix + "showAcquiredImages", this.showAcquiredImages + ""); properties.setProperty(prefix + "saveAcquiredImages", this.saveAcquiredImages + ""); properties.setProperty(prefix + "selectSourceFiles", this.selectSourceFiles + ""); properties.setProperty(prefix + "removeOutOfGridPointers", this.removeOutOfGridPointers + ""); properties.setProperty(prefix + "showGridImages", this.showGridImages + ""); properties.setProperty(prefix + "saveGridImages", this.saveGridImages + ""); properties.setProperty(prefix + "overwriteResultFiles", this.overwriteResultFiles + ""); properties.setProperty(prefix + "debugLevel", this.debugLevel + ""); } public void getProperties(String prefix, Properties properties) { if (properties.getProperty(prefix + "sourceDirectory") != null) this.sourceDirectory = properties.getProperty(prefix + "sourceDirectory"); if (properties.getProperty(prefix + "gridDirectory") != null) this.gridDirectory = properties.getProperty(prefix + "gridDirectory"); if (properties.getProperty(prefix + "useLaserPonters") != null) this.useLaserPonters = Boolean.parseBoolean(properties.getProperty(prefix + "useLaserPonters")); if (properties.getProperty(prefix + "useNoPonters") != null) this.useNoPonters = Boolean.parseBoolean(properties.getProperty(prefix + "useNoPonters")); if (properties.getProperty(prefix + "showAcquiredImages") != null) this.showAcquiredImages = Boolean .parseBoolean(properties.getProperty(prefix + "showAcquiredImages")); if (properties.getProperty(prefix + "saveAcquiredImages") != null) this.saveAcquiredImages = Boolean .parseBoolean(properties.getProperty(prefix + "saveAcquiredImages")); if (properties.getProperty(prefix + "selectSourceFiles") != null) this.selectSourceFiles = Boolean.parseBoolean(properties.getProperty(prefix + "selectSourceFiles")); if (properties.getProperty(prefix + "removeOutOfGridPointers") != null) this.removeOutOfGridPointers = Boolean .parseBoolean(properties.getProperty(prefix + "removeOutOfGridPointers")); if (properties.getProperty(prefix + "showGridImages") != null) this.showGridImages = Boolean.parseBoolean(properties.getProperty(prefix + "showGridImages")); if (properties.getProperty(prefix + "saveGridImages") != null) this.saveGridImages = Boolean.parseBoolean(properties.getProperty(prefix + "saveGridImages")); if (properties.getProperty(prefix + "overwriteResultFiles") != null) this.overwriteResultFiles = Boolean .parseBoolean(properties.getProperty(prefix + "overwriteResultFiles")); if (properties.getProperty(prefix + "debugLevel") != null) this.debugLevel = Integer.parseInt(properties.getProperty(prefix + "debugLevel")); } public boolean showDialog(String title) { GenericDialog gd = new GenericDialog(title); gd.addStringField("Source (acquired from the camera) image directory, blank will open selection window", this.sourceDirectory, 40); gd.addStringField( "Grid files directory (grid patterns extracted from the images, blank will open selection window)", this.gridDirectory, 40); gd.addCheckbox("Locate laser pointers for each image", this.useLaserPonters); gd.addCheckbox("Use images that do not contain laser pointers", this.useLaserPonters); gd.addCheckbox("Show images after acquisition", this.showAcquiredImages); gd.addCheckbox("Save acquired images", this.saveAcquiredImages); gd.addCheckbox("Individually select source image (false use all directory)", this.selectSourceFiles); gd.addCheckbox("Remove detected laser pointers if they are outside of the grid", this.removeOutOfGridPointers); gd.addCheckbox("Show grid files as images", this.showGridImages); gd.addCheckbox("Save grid files", this.saveGridImages); gd.addCheckbox("Overwrite existing result files", this.overwriteResultFiles); gd.addNumericField("Debug level", this.debugLevel, 0); WindowTools.addScrollBars(gd); gd.showDialog(); if (gd.wasCanceled()) return false; String newSourceDirectory = gd.getNextString(); String newGridDirectory = gd.getNextString(); this.useLaserPonters = gd.getNextBoolean(); this.useLaserPonters = gd.getNextBoolean(); this.showAcquiredImages = gd.getNextBoolean(); this.saveAcquiredImages = gd.getNextBoolean(); this.selectSourceFiles = gd.getNextBoolean(); this.removeOutOfGridPointers = gd.getNextBoolean(); this.showGridImages = gd.getNextBoolean(); this.saveGridImages = gd.getNextBoolean(); this.overwriteResultFiles = gd.getNextBoolean(); this.debugLevel = (int) gd.getNextNumber(); System.out.println("1.newSourceDirectory = " + newSourceDirectory); System.out.println("1.newGridDirectory = " + newGridDirectory); if ((newSourceDirectory.length() == 0) || (newSourceDirectory.indexOf('?') >= 0)) newSourceDirectory = selectSourceDirectory(false, this.sourceDirectory, true); else newSourceDirectory = selectSourceDirectory(true, newSourceDirectory, true); // if matches, no dialog if (newSourceDirectory != null) this.sourceDirectory = newSourceDirectory; if ((newGridDirectory.length() == 0) || (newGridDirectory.indexOf('?') >= 0)) newGridDirectory = selectGridFileDirectory(false, this.gridDirectory, true); else newGridDirectory = selectGridFileDirectory(true, newGridDirectory, true); if (newGridDirectory != null) this.gridDirectory = newGridDirectory; // System.out.println("2.newSourceDirectory = "+newSourceDirectory); // System.out.println("2.newGridDirectory = "+ newGridDirectory); // System.out.println("this.sourceDirectory = "+this.sourceDirectory); // System.out.println("this.gridDirectory = "+ this.gridDirectory); return true; } public String[] selectSourceFiles() { return selectSourceFiles(!this.selectSourceFiles); } public String[] selectSourceFiles(boolean allFiles) { String[] extensions = { ".tif", ".tiff" }; if (this.sourceDirectory.length() == 0) { String newSourceDirectory = selectSourceDirectory(true, this.sourceDirectory, true); if (newSourceDirectory != null) this.sourceDirectory = newSourceDirectory; } String[] defaultPaths = { this.sourceDirectory + Prefs.getFileSeparator() }; if (this.sourceDirectory.length() == 0) { defaultPaths[0] = ""; } CalibrationFileManagement.MultipleExtensionsFileFilter sourceFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter( "", extensions, "Source files"); String[] sourceFiles = null; if (allFiles) { File dir = new File(this.sourceDirectory); if (this.debugLevel > 1) System.out.println("selectSourceFiles, dir=" + this.sourceDirectory); if (!dir.exists()) { String error = "Source directory " + this.sourceDirectory + " does not exist."; IJ.showMessage("No files selected"); if (this.debugLevel > 1) System.out.println("selectSourceFiles() ERROR:" + error); return null; } File[] fileList = dir.listFiles(sourceFilter); if (this.debugLevel > 1) System.out.println( "Source directory " + this.sourceDirectory + " has " + fileList.length + " files."); sourceFiles = new String[fileList.length]; for (int i = 0; i < sourceFiles.length; i++) sourceFiles[i] = fileList[i].getPath(); } else { new CalibrationFileManagement.MultipleExtensionsFileFilter("", extensions, "Source files"); sourceFiles = CalibrationFileManagement.selectFiles(false, "Select Source files, saved as TIFF", "Select", sourceFilter, defaultPaths); // String [] defaultPaths); //this.sourceDirectory // null if ((sourceFiles == null) || (sourceFiles.length == 0)) { if (this.debugLevel > 1) System.out.println("selectSourceFiles() ERROR: No files selected"); IJ.showMessage("No files selected"); return null; } } return sourceFiles; } } }