FocusingField.java Source code

Java tutorial

Introduction

Here is the source code for FocusingField.java

Source

/**
**
** FocusingField.jave - save/restore/process sagittal/tangential PSF width
** over FOV, together with related data
**
** Copyright (C) 2014 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** CalibrationHardwareInterface.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.IJ;
import ij.gui.GenericDialog;
import ij.text.TextWindow;

import java.awt.Point;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;

//import Distortions.LMAArrays; // may still reuse?
import Jama.LUDecomposition;
import Jama.Matrix;

public class FocusingField {
    //    public String path;
    // restored from properties   
    FieldFitting fieldFitting = null;
    QualBOptimize qualBOptimize = new QualBOptimize();
    public double pX0_distortions;
    public double pY0_distortions;
    public double currentPX0;
    public double currentPY0;
    boolean sagittalMaster; // center data is the same, when true sagittal fitting only may change r=0 coefficients,
    boolean parallelOnly; // only process measurements for parallel moves
    boolean filterInput;
    double filterInputMotorDiff;
    double filterInputDiff; // um
    boolean filterInputFirstLast;
    boolean filterInputTooFar; // filter samples that are too far from the "center of mass" of other samples 
    double filterInputFarRatio; // remove samples that are farther than this ration of average distance
    boolean filterInputConcave;
    double filterInputConcaveSigma;
    boolean filterInputConcaveRemoveFew;
    int filterInputConcaveMinSeries;
    double filterInputConcaveScale;
    boolean filterZ; // (adjustment mode)filter samples by Z
    boolean filterTiltedZ; // remove tilted measurements using Z range determined by non-tilted LMA
    double filterByValueScale;
    double filterTiltedByValueScale; // filter tilted measurement samples if the spot FWHM is higher than scaled best FWHM
    boolean filterByScanValue; // filter adjustment samples if fwhm exceeds maximal used in focal scan mode
    boolean filterTiltedByScanValue; // filter tilted samples if fwhm exceeds maximal used in focal scan mode
    int filterByNeib; // remove samples having less neighbors (same channel) that this during adjustment
    int filterCalibByNeib; // remove samples having less neighbors (same channel) that this during calibration
    double filterSetsByRMS; // remove complete sets (same timestamp) with RMS greater than scaled average
    boolean filterSetsByRMSTiltOnly; // only remove non-scan sets
    int minLeftSamples; // minimal number of samples (channel/dir/location) for adjustment
    int minBestLeftSamples; // minimal number of samples of the best channel/dir for adjustment
    int minCenterSamplesBest; // minimal number of samples (channel/dir/location) for adjustment in the center, best channel
    int minCenterSamplesTotal; // minimal number of samples (channel/dir/location) for adjustment in the center, all channels total
    int centerSamples; // number of center samples to consider for minLeftCenterSamples

    double maxRMS; // maximal (pure) RMS allowed during adjustment
    double zMin;
    double zMax;
    double zStep;
    double tMin;
    double tMax;
    double tStep;

    double targetRelFocalShift; // target focal shift relative to best composite "sharpness" 
    double targetRelTiltX; // target tilt Horizontal 
    double targetRelTiltY; // target tilt Vertical

    public double avgTx; // average absolute tilt X (optionally used when finding Z of the glued SFE)
    public double avgTy; // average absolute tilt Y (optionally used when finding Z of the glued SFE)
    public int[] zTxTyAdjustMode; //[3] - 0 - keep, 1 adjust common, 2 - adjust individual
    public boolean updateAverageTilts; // update avgTx, avgTy after averaging measurements (in "Focus Average" and "Temp. Scan")
    public int recalculateAverageTilts; // recalculate tilts during averaging, same values as in zTxTyAdjustMode

    // when false - tangential is master
    double[] minMeas; // pixels
    double[] maxMeas; // pixels
    double[] thresholdMax; // pixels
    boolean useMinMeas;
    boolean useMaxMeas;
    boolean useThresholdMax;
    int weightMode; // 0; // 0 - same weight, 1 - linear threshold difference, 2 - quadratic thershold difference
    double weightRadius; //2.0; // Gaussian sigma in mm
    private double k_red;
    private double k_blue;
    private double k_sag;
    private double k_tan;
    private double k_qualBFractionPeripheral; // relative weight of peripheral areas when optimizing qualB
    private double k_qualBFractionHor; // reduce weight of peripheral areas when optimizing qualB (linear, reaching k_qualBFractionPeripheral at the sensor margins)
    private double k_qualBFractionVert; // reduce weight of peripheral areas when optimizing qualB (linear, reaching k_qualBFractionPeripheral at the sensor margins)
    private boolean qualBRemoveBadSamples;
    public int qualBOptimizeMode; // 0 - none, +1 - optimize Zc, +2 - optimize Tx, +4 - optimize Ty
    public double[] qualBOptimizationResults = null; // not saved, re-calculated when needed

    private double qb_scan_below; // um
    private double qb_scan_above; // um
    private double qb_scan_step; // um
    private boolean qb_use_corrected;
    private boolean qb_invert;
    private boolean z_relative; // focal distance relative to center greeen
    private boolean rslt_show_z_axial;
    private boolean rslt_show_z_smooth;
    private boolean rslt_show_z_individual;
    private boolean rslt_show_f_axial;
    private boolean rslt_show_f_smooth;
    private boolean rslt_show_f_individual;
    private double rslt_show_smooth_sigma;
    private double rslt_scan_below;
    private double rslt_scan_above;
    private double rslt_scan_step;
    private boolean rslt_mtf50_mode;
    private boolean rslt_solve; // find z for minimum of f, if false - use parameter z0
    private boolean[] rslt_show_chn;

    // not saved/restored
    private double[] z0_estimates = null; // initial estimates for z0 from the lowest value on data
    private double lambdaStepUp; // multiply lambda by this if result is worse
    private double lambdaStepDown; // multiply lambda by this if result is better
    private double thresholdFinish; // (copied from series) stop iterations if 2 last steps had less improvement (but not worsening )
    private int numIterations; // maximal number of iterations
    private double maxLambda; // max lambda to fail
    private double lambda; // copied from series
    private double adjustmentInitialLambda;
    private String strategyComment;
    private boolean lastInSeries;
    private int currentStrategyStep; // -1 do not read from strategies
    private boolean stopEachStep; // open dialog after each fitting step
    private boolean stopEachSeries; // stop after each series
    private boolean stopOnFailure; // open dialog when fitting series failed
    private boolean showParams; // show modified parameters
    private boolean showDisabledParams;
    private boolean showCorrectionParams;
    private boolean keepCorrectionParameters;
    private boolean resetVariableParameters; // reset all SFE-dependent parameters before running LMA
    private boolean resetCenter; // use distortion center
    private boolean saveSeries; // just for the dialog
    private boolean showMotors;
    private boolean[] showMeasCalc;
    private boolean[] showColors;
    private boolean[] showDirs;
    private boolean[] showSamples;
    private boolean showAllSamples;
    private boolean showIgnoredData;
    private boolean showRad;
    private boolean correct_measurement_ST;
    private boolean updateWeightWhileFitting;
    private int debugPoint;
    private int debugParameter;
    // not reset to defaults
    private boolean[][][][][] sampleMask = null;
    public int debugLevel;
    public boolean debugDerivatives;
    public boolean debugDerivativesFxDxDy;
    private Properties savedProperties = null; // to-be applied
    private String propertiesPrefix = null;
    public double fwhm_to_mtf50 = 2 * Math.log(2.0) / Math.PI * 1000; //pi/0.004
    public boolean updateStatus = true;
    public String[] debugParameterNames = null;
    private double[] lastImprovements = { -1.0, -1.0 }; // {last improvement, previous improvement}. If both >0 and < thresholdFinish - done
    private int iterationStepNumber = 0;
    private long startTime = 0;
    private AtomicInteger stopRequested = null; // 1 - stop now, 2 - when convenient
    public static final String sep = " ";
    public static final String regSep = "\\s";
    public String serialNumber;
    public String lensSerial; // if null - do not add average
    public String comment;
    public String historyPath = null;
    public int sensorWidth;
    public int sensorHeight;
    //   public static final double PIXEL_SIZE=0.0022; // mm
    public double PIXEL_SIZE; // mm

    public double[][][] sampleCoord;
    public ArrayList<FocusingFieldMeasurement> measurements;
    double[] weightReference = null; // calculated per-channel (6) array of maximal PSF FWHM after applying min/max correction
    MeasuredSample[] dataVector;
    double[][][] zRanges; // min/max "reliable" z for each channel/sample - will be used during adjustment
    boolean[] prevEnable; // used in adjustment mode to save previous result of filterByZRanges()
    //   boolean changedEnable; // used in adjustment mode to signal if new result of filterByZRanges() differes from the previous one
    double[] dataValues;
    double[] dataWeights;
    //   int [][][] dataIndex=null; // [measurement][channel][sample] - index in dataValues (and  dataWeights) or -1
    //    double sumWeights=0.0;
    double[][] jacobian = null; // rows - parameters, columns - samples
    double[] currentVector = null;
    double[] nextVector = null;
    double[] savedVector = null;
    boolean[][] goodCalibratedSamples = null;

    private LMAArrays lMAArrays = null;
    private LMAArrays savedLMAArrays = null;
    // temporarily changing visibility of currentfX
    double[] currentfX = null; // array of "f(x)" - simulated data for all images, combining pixel-X and pixel-Y (odd/even)
    private double[] nextfX = null; // array of "f(x)" - simulated data for all images, combining pixel-X and pixel-Y (odd/even)
    private double currentRMS = -1.0; // calculated RMS for the currentVector->currentfX
    private double currentRMSPure = -1.0; // calculated RMS for the currentVector->currentfX
    private double nextRMS = -1.0; // calculated RMS for the nextVector->nextfX
    private double nextRMSPure = -1.0; // calculated RMS for the nextVector->nextfX

    private double firstRMS = -1.0; // RMS before current series of LMA started
    private double firstRMSPure = -1.0; // RMS before current series of LMA started

    public int threadsMax = 100; // 0 - old code
    private boolean multiJacobian = true; // to try multithreaded mode

    public void setThreads(int num) {
        this.threadsMax = num;
    }

    public void setDefaults() {
        goodCalibratedSamples = null;
        sensorWidth = 2592;
        sensorHeight = 1936;
        PIXEL_SIZE = 0.0022; // mm
        pX0_distortions = Double.NaN;
        pY0_distortions = Double.NaN;
        zRanges = null;
        prevEnable = null;
        //       changedEnable=true;
        z0_estimates = null;
        sagittalMaster = false; // center data is the same, when true sagittal fitting only may change r=0 coefficients,
        parallelOnly = true; // only process measurements for parallel moves
        filterInput = true;
        filterInputMotorDiff = 500.0;
        filterInputDiff = 2.0; // um
        filterInputFirstLast = true;
        filterInputTooFar = true; // filter samples that are too far from the "center of mass" of other samples 
        filterInputFarRatio = 3.0; // remove samples that are farther than this ration of average distance
        filterInputConcave = true; //um
        filterInputConcaveSigma = 8.0; //um
        filterInputConcaveRemoveFew = true;
        filterInputConcaveMinSeries = 5;
        filterInputConcaveScale = 0.9;
        filterZ = true; // (adjustment mode)filter samples by Z
        filterTiltedZ = true;
        filterByValueScale = 1.5; // (adjustment mode)filter samples by value - remove higher than scaled best FWHM
        filterTiltedByValueScale = 1.5;
        filterByScanValue = true; // filter adjustment samples if fwhm exceeds maximal used in focal scan mode
        filterTiltedByScanValue = true; // filter tilted samples if fwhm exceeds maximal used in focal scan mode
        filterByNeib = 3; // remove samples having less neighbors (same channel) that this during adjustment
        filterCalibByNeib = 3; // remove samples having less neighbors (same channel) that this during calibration
        filterSetsByRMS = 0.0; // remove complete sets (same timestamp) with RMS greater than scaled average
        filterSetsByRMSTiltOnly = true; // only remove non-scan sets

        minLeftSamples = 10; // minimal number of samples (channel/dir/location) for adjustment
        minBestLeftSamples = 5; // minimal number of samples of the best channel/dir for adjustment (FIXME: still may fail if colinear and tilt free) 

        minCenterSamplesBest = 4; // minimal number of samples (channel/dir/location) for adjustment in the center, best channel
        minCenterSamplesTotal = 0;// minimal number of samples (channel/dir/location) for adjustment in the center, all channels total
        centerSamples = 8; // there should remain at least  of centerSamples closest to r==0
        maxRMS = 0.5; // maximal (pure) RMS allowed during adjustment

        zMin = -40.0;
        zMax = 40.0;
        zStep = 2.0;
        tMin = -10.0;
        tMax = 10.0;
        tStep = 2.0;

        targetRelFocalShift = 0.0; // target focal shift relative to best composite "sharpness"
        targetRelTiltX = 0.0; // target tilt Horizontal 
        targetRelTiltY = 0.0; // target tilt Vertical 

        avgTx = 0.0; // average absolute tilt X (optionally used when finding Z of the glued SFE)
        avgTy = 0.0; // average absolute tilt Y (optionally used when finding Z of the glued SFE)
        zTxTyAdjustMode = new int[3]; //[3] - 0 - keep, 1 adjust common, 2 - adjust individual
        zTxTyAdjustMode[0] = 1;
        zTxTyAdjustMode[1] = 1;
        zTxTyAdjustMode[2] = 1;
        updateAverageTilts = true;
        recalculateAverageTilts = 1; // recalculate, common

        // when false - tangential is master
        double[] minMeasDflt = { 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 }; // pixels
        minMeas = minMeasDflt; // pixels
        double[] maxMeasDflt = { 4.5, 4.5, 4.5, 4.5, 4.5, 4.5 }; // pixels
        maxMeas = maxMeasDflt; // pixels
        //       double [] thresholdMaxDflt= {2.4,3.0,2.6,3.0,3.1,3.0}; // pixels
        double[] thresholdMaxDflt = { 3.5, 3.5, 3.5, 3.5, 3.5, 3.5 }; // pixels
        thresholdMax = thresholdMaxDflt; // pixels
        useMinMeas = true;
        useMaxMeas = true;
        useThresholdMax = true;
        weightMode = 2; // 1; // 0 - same weight, 1 - linear threshold difference, >1 - power of PSF radius
        weightRadius = 0.0; //2.0; // Gaussian sigma in mm
        k_red = 0.7;
        k_blue = 0.3;
        k_sag = 1.0;
        k_tan = 1.0;
        k_qualBFractionPeripheral = 0.5; // relative weight of peripheral areas when optimizing qualB
        k_qualBFractionHor = 0.8; // reduce weight of peripheral areas when optimizing qualB (linear, reaching k_qualBFractionPeripheral at the sensor margins)
        k_qualBFractionVert = 0.8; // reduce weight of peripheral areas when optimizing qualB (linear, reaching k_qualBFractionPeripheral at the sensor margins)

        qualBRemoveBadSamples = true;
        qualBOptimizeMode = 7; // 0 - none, +1 - optimize Zc, +2 - optimize Tx, +4 - optimize Ty

        qb_scan_below = -40.0; // um
        qb_scan_above = 80.0; // um
        qb_scan_step = 0.5; // um
        qb_use_corrected = true;
        qb_invert = true;
        z_relative = true; // focal distance relative to best overall (false - center green )
        rslt_show_z_axial = true;
        rslt_show_z_smooth = false;
        rslt_show_z_individual = true;
        rslt_show_f_axial = true;
        rslt_show_f_smooth = false;
        rslt_show_f_individual = true;
        rslt_show_smooth_sigma = 0.3; // mm
        rslt_scan_below = -10.0;
        rslt_scan_above = 10.0;
        rslt_scan_step = 5.0;
        rslt_mtf50_mode = true;
        rslt_solve = false;
        boolean[] rslt_show_chnDflt = { true, true, true, true, true, true };
        rslt_show_chn = rslt_show_chnDflt.clone();
        // not saved/restored
        adjustmentInitialLambda = 0.001;
        lambdaStepUp = 8.0; // multiply lambda by this if result is worse
        lambdaStepDown = 0.5; // multiply lambda by this if result is better
        thresholdFinish = 0.001; // (copied from series) stop iterations if 2 last steps had less improvement (but not worsening )
        numIterations = 100; // maximal number of iterations
        maxLambda = 100.0; // max lambda to fail
        lambda = 0.001; // copied from series
        stopEachStep = true; // open dialog after each fitting step
        stopEachSeries = false;
        stopOnFailure = true; // open dialog when fitting series failed
        strategyComment = "";
        lastInSeries = true;
        currentStrategyStep = -1; // -1 do not read from strategies

        showParams = false; // show modified parameters
        showDisabledParams = false;
        showCorrectionParams = false;
        keepCorrectionParameters = true;
        resetVariableParameters = false;

        resetCenter = false;
        saveSeries = false; // just for the dialog

        showMotors = true;
        boolean[] showMeasCalcDflt = { true, true, true };
        showMeasCalc = showMeasCalcDflt.clone();
        boolean[] showColorsDflt = { true, true, true };
        showColors = showColorsDflt.clone();
        boolean[] showDirsDflt = { true, true };
        showDirs = showDirsDflt.clone();
        showSamples = null;
        showAllSamples = true;
        showIgnoredData = false;
        showRad = true;
        sampleMask = null;

        correct_measurement_ST = true;
        updateWeightWhileFitting = false;
        debugPoint = -1;
        debugParameter = -1;

        currentPX0 = pX0_distortions;
        currentPY0 = pY0_distortions;

    }

    public void setProperties(String prefix, Properties properties) {
        if (debugLevel > 1)
            System.out.println("FocusingField: setProperties()");
        if (fieldFitting == null) {
            System.out.println("fieldFitting is not initialized, nothing to save");
            return;
        }
        boolean select = (properties.getProperty("selected") != null);
        boolean select_fieldFitting = !select;
        boolean select_FOCUSING_FIELD = !select;
        if (select) {
            GenericDialog gd = new GenericDialog("Select FocusingField parameters to save");
            gd.addCheckbox("FieldFitting parameter class", select_fieldFitting);
            gd.addCheckbox("FocusingField local parameters", select_FOCUSING_FIELD);
            gd.showDialog();
            if (gd.wasCanceled())
                return;
            select_fieldFitting = gd.getNextBoolean();
            select_FOCUSING_FIELD = gd.getNextBoolean();
        }
        if (select_fieldFitting)
            fieldFitting.setProperties(prefix + "fieldFitting.", properties);
        if (select_FOCUSING_FIELD) {
            properties.setProperty(prefix + "pX0_distortions", pX0_distortions + "");
            properties.setProperty(prefix + "pY0_distortions", pY0_distortions + "");
            properties.setProperty(prefix + "currentPX0", currentPX0 + "");
            properties.setProperty(prefix + "currentPY0", currentPY0 + "");
            properties.setProperty(prefix + "sagittalMaster", sagittalMaster + "");
            properties.setProperty(prefix + "parallelOnly", parallelOnly + "");
            properties.setProperty(prefix + "filterInput", filterInput + "");
            properties.setProperty(prefix + "filterInputMotorDiff", filterInputMotorDiff + "");
            properties.setProperty(prefix + "filterInputDiff", filterInputDiff + "");
            properties.setProperty(prefix + "filterInputFirstLast", filterInputFirstLast + "");
            properties.setProperty(prefix + "filterInputTooFar", filterInputTooFar + "");
            properties.setProperty(prefix + "filterInputFarRatio", filterInputFarRatio + "");
            properties.setProperty(prefix + "filterInputConcave", filterInputConcave + "");
            properties.setProperty(prefix + "filterInputConcaveSigma", filterInputConcaveSigma + "");
            properties.setProperty(prefix + "filterInputConcaveRemoveFew", filterInputConcaveRemoveFew + "");
            properties.setProperty(prefix + "filterInputConcaveMinSeries", filterInputConcaveMinSeries + "");
            properties.setProperty(prefix + "filterInputConcaveScale", filterInputConcaveScale + "");
            properties.setProperty(prefix + "filterZ", filterZ + "");
            properties.setProperty(prefix + "filterTiltedZ", filterTiltedZ + "");
            properties.setProperty(prefix + "filterByValueScale", filterByValueScale + "");
            properties.setProperty(prefix + "filterTiltedByValueScale", filterTiltedByValueScale + "");
            properties.setProperty(prefix + "filterByScanValue", filterByScanValue + "");
            properties.setProperty(prefix + "filterTiltedByScanValue", filterTiltedByScanValue + "");
            properties.setProperty(prefix + "filterByNeib", filterByNeib + "");
            properties.setProperty(prefix + "filterCalibByNeib", filterCalibByNeib + "");
            properties.setProperty(prefix + "filterSetsByRMS", filterSetsByRMS + "");
            properties.setProperty(prefix + "filterSetsByRMSTiltOnly", filterSetsByRMSTiltOnly + "");
            properties.setProperty(prefix + "minLeftSamples", minLeftSamples + "");
            properties.setProperty(prefix + "minBestLeftSamples", minBestLeftSamples + "");

            properties.setProperty(prefix + "minCenterSamplesBest", minCenterSamplesBest + "");
            properties.setProperty(prefix + "minCenterSamplesTotal", minCenterSamplesTotal + "");
            properties.setProperty(prefix + "centerSamples", centerSamples + "");
            properties.setProperty(prefix + "maxRMS", maxRMS + "");
            properties.setProperty(prefix + "zMin", zMin + "");
            properties.setProperty(prefix + "zMax", zMax + "");
            properties.setProperty(prefix + "zStep", zStep + "");
            properties.setProperty(prefix + "tMin", tMin + "");
            properties.setProperty(prefix + "tMax", tMax + "");
            properties.setProperty(prefix + "tStep", tStep + "");

            properties.setProperty(prefix + "targetRelFocalShift", targetRelFocalShift + "");

            properties.setProperty(prefix + "targetRelTiltX", targetRelTiltX + "");
            properties.setProperty(prefix + "targetRelTiltY", targetRelTiltY + "");

            properties.setProperty(prefix + "avgTx", avgTx + "");
            properties.setProperty(prefix + "avgTy", avgTy + "");
            for (int i = 0; i < zTxTyAdjustMode.length; i++)
                properties.setProperty(prefix + "zTxTyAdjustMode_" + i, zTxTyAdjustMode[i] + "");
            properties.setProperty(prefix + "updateAverageTilts", updateAverageTilts + "");
            properties.setProperty(prefix + "recalculateAverageTilts", recalculateAverageTilts + "");
            for (int chn = 0; chn < minMeas.length; chn++)
                properties.setProperty(prefix + "minMeas_" + chn, minMeas[chn] + "");
            for (int chn = 0; chn < maxMeas.length; chn++)
                properties.setProperty(prefix + "maxMeas_" + chn, maxMeas[chn] + "");
            for (int chn = 0; chn < thresholdMax.length; chn++)
                properties.setProperty(prefix + "thresholdMax_" + chn, thresholdMax[chn] + "");
            properties.setProperty(prefix + "useMinMeas", useMinMeas + "");
            properties.setProperty(prefix + "useMaxMeas", useMaxMeas + "");
            properties.setProperty(prefix + "useThresholdMax", useThresholdMax + "");
            properties.setProperty(prefix + "weightMode", weightMode + "");
            properties.setProperty(prefix + "weightRadius", weightRadius + "");
            properties.setProperty(prefix + "k_red", k_red + "");
            properties.setProperty(prefix + "k_blue", k_blue + "");
            properties.setProperty(prefix + "k_sag", k_sag + "");
            properties.setProperty(prefix + "k_tan", k_tan + "");
            properties.setProperty(prefix + "k_qualBFractionPeripheral", k_qualBFractionPeripheral + "");
            properties.setProperty(prefix + "k_qualBFractionHor", k_qualBFractionHor + "");
            properties.setProperty(prefix + "k_qualBFractionVert", k_qualBFractionVert + "");
            properties.setProperty(prefix + "qualBRemoveBadSamples", qualBRemoveBadSamples + "");
            properties.setProperty(prefix + "qualBOptimizeMode", qualBOptimizeMode + "");
            properties.setProperty(prefix + "qb_scan_below", qb_scan_below + "");
            properties.setProperty(prefix + "qb_scan_above", qb_scan_above + "");
            properties.setProperty(prefix + "qb_scan_step", qb_scan_step + "");
            properties.setProperty(prefix + "qb_use_corrected", qb_use_corrected + "");
            properties.setProperty(prefix + "qb_invert", qb_invert + "");
            properties.setProperty(prefix + "z_relative", z_relative + "");
            properties.setProperty(prefix + "rslt_show_z_axial", rslt_show_z_axial + "");
            properties.setProperty(prefix + "rslt_show_z_smooth", rslt_show_z_smooth + "");
            properties.setProperty(prefix + "rslt_show_z_individual", rslt_show_z_individual + "");
            properties.setProperty(prefix + "rslt_show_f_axial", rslt_show_f_axial + "");
            properties.setProperty(prefix + "rslt_show_f_smooth", rslt_show_f_smooth + "");
            properties.setProperty(prefix + "rslt_show_f_individual", rslt_show_f_individual + "");
            properties.setProperty(prefix + "rslt_show_smooth_sigma", rslt_show_smooth_sigma + "");
            properties.setProperty(prefix + "rslt_scan_below", rslt_scan_below + "");
            properties.setProperty(prefix + "rslt_scan_above", rslt_scan_above + "");
            properties.setProperty(prefix + "rslt_scan_step", rslt_scan_step + "");
            properties.setProperty(prefix + "rslt_mtf50_mode", rslt_mtf50_mode + "");
            properties.setProperty(prefix + "rslt_solve", rslt_solve + "");
            for (int chn = 0; chn < rslt_show_chn.length; chn++)
                properties.setProperty(prefix + "rslt_show_chn_" + chn, rslt_show_chn[chn] + "");
            // always re-calculate here? - only in calibration mode or restore calibration mode? No, only in LMA in calibration mode      
            //      zRanges=calcZRanges(dataWeightsToBoolean());
            if (zRanges != null) {
                properties.setProperty(prefix + "zRanges_length", zRanges.length + "");
                for (int chn = 0; chn < zRanges.length; chn++)
                    if (zRanges[chn] != null) {
                        properties.setProperty(prefix + "zRanges_" + chn + "_length", zRanges[chn].length + "");
                        for (int sample = 0; sample < zRanges[chn].length; sample++)
                            if (zRanges[chn][sample] != null) {
                                properties.setProperty(prefix + "zRanges_" + chn + "_" + sample,
                                        zRanges[chn][sample][0] + "," + zRanges[chn][sample][1] + ","
                                                + zRanges[chn][sample][2]);
                            }
                    }
            }
            if (goodCalibratedSamples != null) {
                properties.setProperty(prefix + "goodCalibratedSamples_length", goodCalibratedSamples.length + "");
                for (int chn = 0; chn < goodCalibratedSamples.length; chn++) {
                    String s = "";
                    if (goodCalibratedSamples[chn] != null)
                        for (int j = 0; j < goodCalibratedSamples[chn].length; j++)
                            s += goodCalibratedSamples[chn][j] ? "+" : "-";
                    properties.setProperty(prefix + "goodCalibratedSamples_" + chn, s);
                }

            }
        }
    }

    /**
     * Set parameters from properties
     * @param prefix property name prefix 
     * @param properties properties
     * @param keepFromHistory keep distortion center read from the history (file or structure)
     */
    public void getProperties(String prefix, Properties properties, boolean keepFromHistory) {

        savedProperties = properties;
        propertiesPrefix = prefix;
        if (debugLevel > 1)
            System.out.println("FocusingField: getProperties()");
        if (fieldFitting == null) {
            System.out.println("fieldFitting is not initialized, will apply properties later");
            return; //fieldFitting=new FieldFitting();
        }
        fieldFitting.getProperties(prefix + "fieldFitting.", properties);
        if (!keepFromHistory || Double.isNaN(pX0_distortions)) {
            if (properties.getProperty(prefix + "pX0_distortions") != null)
                pX0_distortions = Double.parseDouble(properties.getProperty(prefix + "pX0_distortions"));
        }
        if (!keepFromHistory || Double.isNaN(pY0_distortions)) {
            if (properties.getProperty(prefix + "pY0_distortions") != null)
                pY0_distortions = Double.parseDouble(properties.getProperty(prefix + "pY0_distortions"));
        }
        if (properties.getProperty(prefix + "currentPX0") != null)
            currentPX0 = Double.parseDouble(properties.getProperty(prefix + "currentPX0"));
        if (properties.getProperty(prefix + "currentPY0") != null)
            currentPY0 = Double.parseDouble(properties.getProperty(prefix + "currentPY0"));
        if (properties.getProperty(prefix + "sagittalMaster") != null)
            sagittalMaster = Boolean.parseBoolean(properties.getProperty(prefix + "sagittalMaster"));
        if (properties.getProperty(prefix + "parallelOnly") != null)
            parallelOnly = Boolean.parseBoolean(properties.getProperty(prefix + "parallelOnly"));
        if (properties.getProperty(prefix + "filterInput") != null)
            filterInput = Boolean.parseBoolean(properties.getProperty(prefix + "filterInput"));
        if (properties.getProperty(prefix + "filterInputMotorDiff") != null)
            filterInputMotorDiff = Double.parseDouble(properties.getProperty(prefix + "filterInputMotorDiff"));
        if (properties.getProperty(prefix + "filterInputDiff") != null)
            filterInputDiff = Double.parseDouble(properties.getProperty(prefix + "filterInputDiff"));
        if (properties.getProperty(prefix + "filterInputFirstLast") != null)
            filterInputFirstLast = Boolean.parseBoolean(properties.getProperty(prefix + "filterInputFirstLast"));
        if (properties.getProperty(prefix + "filterInputTooFar") != null)
            filterInputTooFar = Boolean.parseBoolean(properties.getProperty(prefix + "filterInputTooFar"));
        if (properties.getProperty(prefix + "filterInputFarRatio") != null)
            filterInputFarRatio = Double.parseDouble(properties.getProperty(prefix + "filterInputFarRatio"));

        if (properties.getProperty(prefix + "filterInputConcave") != null)
            filterInputConcave = Boolean.parseBoolean(properties.getProperty(prefix + "filterInputConcave"));
        if (properties.getProperty(prefix + "filterInputConcaveSigma") != null)
            filterInputConcaveSigma = Double
                    .parseDouble(properties.getProperty(prefix + "filterInputConcaveSigma"));

        if (properties.getProperty(prefix + "filterInputConcaveRemoveFew") != null)
            filterInputConcaveRemoveFew = Boolean
                    .parseBoolean(properties.getProperty(prefix + "filterInputConcaveRemoveFew"));
        if (properties.getProperty(prefix + "filterInputConcaveMinSeries") != null)
            filterInputConcaveMinSeries = Integer
                    .parseInt(properties.getProperty(prefix + "filterInputConcaveMinSeries"));
        if (properties.getProperty(prefix + "filterInputConcaveScale") != null)
            filterInputConcaveScale = Double
                    .parseDouble(properties.getProperty(prefix + "filterInputConcaveScale"));
        if (properties.getProperty(prefix + "filterZ") != null)
            filterZ = Boolean.parseBoolean(properties.getProperty(prefix + "filterZ"));
        if (properties.getProperty(prefix + "filterTiltedZ") != null)
            filterTiltedZ = Boolean.parseBoolean(properties.getProperty(prefix + "filterTiltedZ"));
        if (properties.getProperty(prefix + "filterByValueScale") != null)
            filterByValueScale = Double.parseDouble(properties.getProperty(prefix + "filterByValueScale"));
        if (properties.getProperty(prefix + "filterTiltedByValueScale") != null)
            filterTiltedByValueScale = Double
                    .parseDouble(properties.getProperty(prefix + "filterTiltedByValueScale"));
        if (properties.getProperty(prefix + "filterByScanValue") != null)
            filterByScanValue = Boolean.parseBoolean(properties.getProperty(prefix + "filterByScanValue"));
        if (properties.getProperty(prefix + "filterTiltedByScanValue") != null)
            filterTiltedByScanValue = Boolean
                    .parseBoolean(properties.getProperty(prefix + "filterTiltedByScanValue"));
        if (properties.getProperty(prefix + "filterByNeib") != null)
            filterByNeib = Integer.parseInt(properties.getProperty(prefix + "filterByNeib"));
        if (properties.getProperty(prefix + "filterCalibByNeib") != null)
            filterCalibByNeib = Integer.parseInt(properties.getProperty(prefix + "filterCalibByNeib"));
        if (properties.getProperty(prefix + "filterSetsByRMS") != null)
            filterSetsByRMS = Double.parseDouble(properties.getProperty(prefix + "filterSetsByRMS"));
        if (properties.getProperty(prefix + "filterSetsByRMSTiltOnly") != null)
            filterSetsByRMSTiltOnly = Boolean
                    .parseBoolean(properties.getProperty(prefix + "filterSetsByRMSTiltOnly"));
        if (properties.getProperty(prefix + "minLeftSamples") != null)
            minLeftSamples = Integer.parseInt(properties.getProperty(prefix + "minLeftSamples"));

        if (properties.getProperty(prefix + "minBestLeftSamples") != null)
            minBestLeftSamples = Integer.parseInt(properties.getProperty(prefix + "minBestLeftSamples"));

        if (properties.getProperty(prefix + "minCenterSamplesBest") != null)
            minCenterSamplesBest = Integer.parseInt(properties.getProperty(prefix + "minCenterSamplesBest"));
        if (properties.getProperty(prefix + "minCenterSamplesTotal") != null)
            minCenterSamplesTotal = Integer.parseInt(properties.getProperty(prefix + "minCenterSamplesTotal"));
        if (properties.getProperty(prefix + "centerSamples") != null)
            centerSamples = Integer.parseInt(properties.getProperty(prefix + "centerSamples"));
        if (properties.getProperty(prefix + "maxRMS") != null)
            maxRMS = Double.parseDouble(properties.getProperty(prefix + "maxRMS"));
        if (properties.getProperty(prefix + "zMin") != null)
            zMin = Double.parseDouble(properties.getProperty(prefix + "zMin"));
        if (properties.getProperty(prefix + "zMax") != null)
            zMax = Double.parseDouble(properties.getProperty(prefix + "zMax"));
        if (properties.getProperty(prefix + "zStep") != null)
            zStep = Double.parseDouble(properties.getProperty(prefix + "zStep"));
        if (properties.getProperty(prefix + "tMin") != null)
            tMin = Double.parseDouble(properties.getProperty(prefix + "tMin"));
        if (properties.getProperty(prefix + "tMax") != null)
            tMax = Double.parseDouble(properties.getProperty(prefix + "tMax"));
        if (properties.getProperty(prefix + "tStep") != null)
            tStep = Double.parseDouble(properties.getProperty(prefix + "tStep"));
        if (properties.getProperty(prefix + "targetRelFocalShift") != null)
            targetRelFocalShift = Double.parseDouble(properties.getProperty(prefix + "targetRelFocalShift"));

        if (properties.getProperty(prefix + "targetRelTiltX") != null)
            targetRelTiltX = Double.parseDouble(properties.getProperty(prefix + "targetRelTiltX"));
        if (properties.getProperty(prefix + "targetRelTiltY") != null)
            targetRelTiltY = Double.parseDouble(properties.getProperty(prefix + "targetRelTiltY"));

        if (properties.getProperty(prefix + "avgTx") != null)
            avgTx = Double.parseDouble(properties.getProperty(prefix + "avgTx"));
        if (properties.getProperty(prefix + "avgTy") != null)
            avgTy = Double.parseDouble(properties.getProperty(prefix + "avgTy"));
        for (int i = 0; i < zTxTyAdjustMode.length; i++) {
            if (properties.getProperty(prefix + "zTxTyAdjustMode_" + i) != null)
                zTxTyAdjustMode[i] = Integer.parseInt(properties.getProperty(prefix + "zTxTyAdjustMode_" + i));
        }
        if (properties.getProperty(prefix + "updateAverageTilts") != null)
            updateAverageTilts = Boolean.parseBoolean(properties.getProperty(prefix + "updateAverageTilts"));
        if (properties.getProperty(prefix + "recalculateAverageTilts") != null)
            recalculateAverageTilts = Integer.parseInt(properties.getProperty(prefix + "recalculateAverageTilts"));
        for (int chn = 0; chn < minMeas.length; chn++)
            if (properties.getProperty(prefix + "minMeas_" + chn) != null)
                minMeas[chn] = Double.parseDouble(properties.getProperty(prefix + "minMeas_" + chn));
        for (int chn = 0; chn < maxMeas.length; chn++)
            if (properties.getProperty(prefix + "maxMeas_" + chn) != null)
                maxMeas[chn] = Double.parseDouble(properties.getProperty(prefix + "maxMeas_" + chn));
        for (int chn = 0; chn < thresholdMax.length; chn++)
            if (properties.getProperty(prefix + "thresholdMax_" + chn) != null)
                thresholdMax[chn] = Double.parseDouble(properties.getProperty(prefix + "thresholdMax_" + chn));
        if (properties.getProperty(prefix + "useMinMeas") != null)
            useMinMeas = Boolean.parseBoolean(properties.getProperty(prefix + "useMinMeas"));
        if (properties.getProperty(prefix + "useMaxMeas") != null)
            useMaxMeas = Boolean.parseBoolean(properties.getProperty(prefix + "useMaxMeas"));
        if (properties.getProperty(prefix + "useThresholdMax") != null)
            useThresholdMax = Boolean.parseBoolean(properties.getProperty(prefix + "useThresholdMax"));
        if (properties.getProperty(prefix + "weightMode") != null)
            weightMode = Integer.parseInt(properties.getProperty(prefix + "weightMode"));
        if (properties.getProperty(prefix + "weightRadius") != null)
            weightRadius = Double.parseDouble(properties.getProperty(prefix + "weightRadius"));
        if (properties.getProperty(prefix + "k_red") != null)
            k_red = Double.parseDouble(properties.getProperty(prefix + "k_red"));
        if (properties.getProperty(prefix + "k_blue") != null)
            k_blue = Double.parseDouble(properties.getProperty(prefix + "k_blue"));
        if (properties.getProperty(prefix + "k_sag") != null)
            k_sag = Double.parseDouble(properties.getProperty(prefix + "k_sag"));
        if (properties.getProperty(prefix + "k_tan") != null)
            k_tan = Double.parseDouble(properties.getProperty(prefix + "k_tan"));

        if (properties.getProperty(prefix + "k_qualBFractionPeripheral") != null)
            k_qualBFractionPeripheral = Double
                    .parseDouble(properties.getProperty(prefix + "k_qualBFractionPeripheral"));
        if (properties.getProperty(prefix + "k_qualBFractionHor") != null)
            k_qualBFractionHor = Double.parseDouble(properties.getProperty(prefix + "k_qualBFractionHor"));
        if (properties.getProperty(prefix + "k_qualBFractionVert") != null)
            k_qualBFractionVert = Double.parseDouble(properties.getProperty(prefix + "k_qualBFractionVert"));
        if (properties.getProperty(prefix + "qualBRemoveBadSamples") != null)
            qualBRemoveBadSamples = Boolean.parseBoolean(properties.getProperty(prefix + "qualBRemoveBadSamples"));
        if (properties.getProperty(prefix + "qualBOptimizeMode") != null)
            qualBOptimizeMode = Integer.parseInt(properties.getProperty(prefix + "qualBOptimizeMode"));
        if (properties.getProperty(prefix + "qb_scan_below") != null)
            qb_scan_below = Double.parseDouble(properties.getProperty(prefix + "qb_scan_below"));
        if (properties.getProperty(prefix + "qb_scan_above") != null)
            qb_scan_above = Double.parseDouble(properties.getProperty(prefix + "qb_scan_above"));
        if (properties.getProperty(prefix + "qb_scan_step") != null)
            qb_scan_step = Double.parseDouble(properties.getProperty(prefix + "qb_scan_step"));
        if (properties.getProperty(prefix + "qb_use_corrected") != null)
            qb_use_corrected = Boolean.parseBoolean(properties.getProperty(prefix + "qb_use_corrected"));
        if (properties.getProperty(prefix + "qb_invert") != null)
            qb_invert = Boolean.parseBoolean(properties.getProperty(prefix + "qb_invert"));
        if (properties.getProperty(prefix + "z_relative") != null)
            z_relative = Boolean.parseBoolean(properties.getProperty(prefix + "z_relative"));
        if (properties.getProperty(prefix + "rslt_show_z_axial") != null)
            rslt_show_z_axial = Boolean.parseBoolean(properties.getProperty(prefix + "rslt_show_z_axial"));
        if (properties.getProperty(prefix + "rslt_show_z_smooth") != null)
            rslt_show_z_smooth = Boolean.parseBoolean(properties.getProperty(prefix + "rslt_show_z_smooth"));
        if (properties.getProperty(prefix + "rslt_show_z_individual") != null)
            rslt_show_z_individual = Boolean
                    .parseBoolean(properties.getProperty(prefix + "rslt_show_z_individual"));
        if (properties.getProperty(prefix + "rslt_show_f_axial") != null)
            rslt_show_f_axial = Boolean.parseBoolean(properties.getProperty(prefix + "rslt_show_f_axial"));
        if (properties.getProperty(prefix + "rslt_show_f_smooth") != null)
            rslt_show_f_smooth = Boolean.parseBoolean(properties.getProperty(prefix + "rslt_show_f_smooth"));
        if (properties.getProperty(prefix + "rslt_show_f_individual") != null)
            rslt_show_f_individual = Boolean
                    .parseBoolean(properties.getProperty(prefix + "rslt_show_f_individual"));
        if (properties.getProperty(prefix + "rslt_show_smooth_sigma") != null)
            rslt_show_smooth_sigma = Double.parseDouble(properties.getProperty(prefix + "rslt_show_smooth_sigma"));
        if (properties.getProperty(prefix + "rslt_scan_below") != null)
            rslt_scan_below = Double.parseDouble(properties.getProperty(prefix + "rslt_scan_below"));
        if (properties.getProperty(prefix + "rslt_scan_above") != null)
            rslt_scan_above = Double.parseDouble(properties.getProperty(prefix + "rslt_scan_above"));
        if (properties.getProperty(prefix + "rslt_scan_step") != null)
            rslt_scan_step = Double.parseDouble(properties.getProperty(prefix + "rslt_scan_step"));
        if (properties.getProperty(prefix + "rslt_mtf50_mode") != null)
            rslt_mtf50_mode = Boolean.parseBoolean(properties.getProperty(prefix + "rslt_mtf50_mode"));
        if (properties.getProperty(prefix + "rslt_solve") != null)
            rslt_solve = Boolean.parseBoolean(properties.getProperty(prefix + "rslt_solve"));
        for (int chn = 0; chn < rslt_show_chn.length; chn++)
            if (properties.getProperty(prefix + "rslt_show_chn_" + chn) != null)
                rslt_show_chn[chn] = Boolean.parseBoolean(properties.getProperty(prefix + "rslt_show_chn_" + chn));
        zRanges = null;
        if (properties.getProperty(prefix + "zRanges_length") != null) {
            zRanges = new double[Integer.parseInt(properties.getProperty(prefix + "zRanges_length"))][][];
            for (int chn = 0; chn < zRanges.length; chn++) {
                zRanges[chn] = null;
                if (properties.getProperty(prefix + "zRanges_" + chn + "_length") != null) {
                    zRanges[chn] = new double[Integer
                            .parseInt(properties.getProperty(prefix + "zRanges_" + chn + "_length"))][];
                    for (int sample = 0; sample < zRanges[chn].length; sample++) {
                        zRanges[chn][sample] = null;
                        String s = properties.getProperty(prefix + "zRanges_" + chn + "_" + sample);
                        if (s != null) {
                            zRanges[chn][sample] = new double[3];
                            String[] ss = s.split(",");
                            zRanges[chn][sample][0] = Double.parseDouble(ss[0]);
                            zRanges[chn][sample][1] = Double.parseDouble(ss[1]);
                            if (ss.length > 2)
                                zRanges[chn][sample][2] = Double.parseDouble(ss[2]);
                            else
                                zRanges[chn][sample][2] = 0.0;
                        }
                    }
                }
            }
        }
        if (properties.getProperty(prefix + "goodCalibratedSamples_length") != null) {
            goodCalibratedSamples = new boolean[Integer
                    .parseInt(properties.getProperty(prefix + "goodCalibratedSamples_length"))][];
            for (int chn = 0; chn < goodCalibratedSamples.length; chn++) {
                String s = properties.getProperty(prefix + "goodCalibratedSamples_" + chn);
                if ((s == null) || (s.length() == 0)) {
                    goodCalibratedSamples[chn] = null;
                } else {
                    goodCalibratedSamples[chn] = new boolean[s.length()];
                    for (int i = 0; i < goodCalibratedSamples[chn].length; i++) {
                        goodCalibratedSamples[chn][i] = s.charAt(i) == '+';
                    }
                }
            }
        }
    }

    public void setDebugLevel(int debugLevel) {
        this.debugLevel = debugLevel;
    }

    public void setAdjustMode(boolean mode, int[] zTxTyMode) { //0 - do not adjust, 1 - commonn adjust, 2 - individual adjust
        if ((fieldFitting != null) && (fieldFitting.mechanicalFocusingModel != null)) {
            fieldFitting.mechanicalFocusingModel.setAdjustMode(mode, zTxTyMode);
        }
    }

    public class LMAArrays { // reuse from Distortions?
        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 enum MECH_PAR {
        K0, // Average motor center travel","um/step","0.0124"},
        KD1, // M1 and M2 travel disbalance","um/step","0.0"},
        KD3, // M3 to average of M1 and M2 travel disbalance","um/step","0.0"},
        sM1, // M1: sin component amplitude, relative to tread pitch","","0.0"},
        cM1, // M1: cos component amplitude, relative to tread pitch","","0.0"},
        sM2, // M2: sin component amplitude, relative to tread pitch","","0.0"},
        cM2, // M2: cos component amplitude, relative to tread pitch","","0.0"},
        sM3, // M3: sin component amplitude, relative to tread pitch","","0.0"},
        cM3, // M3: cos component amplitude, relative to tread pitch","","0.0"},
        Lx, // Half horizontal distance between M3 and and M2 supports", "mm","21.0"},
        Ly, // Half vertical distance between M1 and M2 supports", "mm","10.0"},
        mpX0, // pixel X coordinate of mechanical center","px","1296.0"},
        mpY0, // pixel Y coordinate of mechanical center","px","968.0"},
        z0, // center shift, positive away form the lens","um","0.0"},
        tx, // horizontal tilt", "um/pix","0.0"},
        ty // vertical tilt", "um/pix","0.0"}};
    }

    //public static double getPixelMM(){return PIXEL_SIZE;}
    //public static double getPixelUM(){return PIXEL_SIZE*1000;}
    public double getPixelMM() {
        return PIXEL_SIZE;
    }

    public double getPixelUM() {
        return PIXEL_SIZE * 1000;
    }

    public int flattenIndex(int i, int j) {
        return j + i * sampleCoord[0].length;
    }

    public int getNumSamples() {
        return sampleCoord.length * sampleCoord[0].length;
    }

    public int getNumChannels() {
        return 6;
    }

    public int getSampleWidth() {
        return sampleCoord[0].length;
    }

    public double[][] flattenSampleCoord() {
        ///sampleCoord
        double[][] flatSampleCoord = new double[sampleCoord.length * sampleCoord[0].length][2];
        int index = 0;
        for (int i = 0; i < sampleCoord.length; i++)
            for (int j = 0; j < sampleCoord[0].length; j++)
                flatSampleCoord[index++] = sampleCoord[i][j];
        return flatSampleCoord; // last dimension is not cloned
    }

    public double[][][] getSampleCoord() {
        return this.sampleCoord;
    }

    public class MeasuredSample {
        public int[] motors = new int[3];
        public String timestamp;
        public double px;
        public double py;
        public int sampleIndex = 0;
        public int measurementIndex = -1;
        public int channel;
        public double value;
        public double[] dPxyc = new double[2]; // derivative of the value by optical (aberration) center pixel X,Y
        public boolean scan = false; // sample belongs to focal distance scanning series

        //     public double weight;
        //     public MeasuredSample(){}
        public MeasuredSample(int[] motors, String timestamp, double px, double py, int sampleIndex,
                int measurementIndex, int channel, double value, double dPxc, double dPyc, boolean scan) {
            this.motors = motors;
            this.timestamp = timestamp;
            this.px = px;
            this.py = py;
            this.channel = channel;
            this.value = value;
            this.dPxyc[0] = dPxc;
            this.dPxyc[1] = dPyc;
            this.sampleIndex = sampleIndex;
            this.measurementIndex = measurementIndex;
            this.scan = scan;
        }
    }

    public boolean configureDataVector(boolean silent, String title, boolean forcenew, boolean enableReset) {
        if ((fieldFitting == null) && !forcenew) {
            forcenew = true;
        }
        boolean setupMasks = silent ? false : true;
        boolean setupParameters = silent ? false : true;
        boolean showDisabled = silent ? false : true;
        FieldFitting tmpFieldFitting = fieldFitting;
        if (tmpFieldFitting == null)
            tmpFieldFitting = new FieldFitting(); // just to get field description
        int[] numCurvPars = tmpFieldFitting.getNumCurvars();
        if (!silent) {
            GenericDialog gd = new GenericDialog(title + (forcenew ? " RESETTING DATA" : ""));
            gd.addCheckbox("Only use measurements acquired during parallel moves (false - use all)", parallelOnly);

            gd.addCheckbox("Remove \"crazy\" input data (small motor move causing large variations of FWHM)",
                    filterInput);
            gd.addNumericField("Maximal motor move to be considered small", filterInputMotorDiff, 0, 5,
                    "steps (~90um/step)");
            gd.addNumericField("Maximal allowed PSF FWHM variations fro the move above", filterInputDiff, 3, 5,
                    "um");
            gd.addCheckbox("Remove first/last in a series of measuremnts separated by small (see above) steps",
                    filterInputFirstLast);
            gd.addCheckbox("Remove measurements taken too far from the rest for the same channel/sample",
                    filterInputTooFar);
            gd.addNumericField("\"Too far\" ratio to the average distance to the center of measurements",
                    filterInputFarRatio, 3, 5, "um");

            gd.addCheckbox("Filter non-concave areas from best focus for each sample", filterInputConcave);
            gd.addNumericField("Concave filter sigma", filterInputConcaveSigma, 3, 5, "um");
            gd.addCheckbox("Remove small series ", filterInputConcaveRemoveFew);
            gd.addNumericField("Minimal number of samples (to remove / apply concave filter) ",
                    filterInputConcaveMinSeries, 3, 5, "samples");
            gd.addNumericField("Concave filter scale", filterInputConcaveScale, 3, 5, "<=1.0");

            gd.addCheckbox("Filter tilted samples/channels by Z", filterTiltedZ);
            gd.addCheckbox("Filter tilted samples by value (leave lower than maximal fwhm used in focal scan mode)",
                    filterTiltedByScanValue);
            gd.addNumericField(
                    "Filter tilted samples by value (remove samples above scaled best FWHM for channel/location)",
                    filterTiltedByValueScale, 2, 5, "x");

            gd.addNumericField("Remove samples having less neighbors (same channel) than this", filterCalibByNeib,
                    0, 1, "");

            gd.addNumericField("Remove complete sets (same timestamp) with RMS greater than scaled average RMS",
                    filterSetsByRMS, 3, 5, "x");
            gd.addCheckbox("Only remove sets of tilt calibration, keep focus scanning ones",
                    filterSetsByRMSTiltOnly);

            gd.addCheckbox("Sagittal channels are master channels (false - tangential are masters)",
                    sagittalMaster);
            gd.addMessage("=== Setting minimal measured PSF radius for different colors/directions ===");

            for (int i = 0; i < minMeas.length; i++) {
                gd.addNumericField(tmpFieldFitting.getDescription(i), this.minMeas[i], 3, 5, "pix");
            }
            gd.addCheckbox("Use minimal radius", useMinMeas);
            gd.addMessage("=== Setting maximal measured PSF radius for different colors/directions ===");
            for (int i = 0; i < maxMeas.length; i++) {
                gd.addNumericField(tmpFieldFitting.getDescription(i), this.maxMeas[i], 3, 5, "pix");
            }
            gd.addCheckbox("Use maximal radius", useMaxMeas);
            gd.addMessage("=== Setting maximal usable PSF radius for different colors/directions ===");
            for (int i = 0; i < thresholdMax.length; i++) {
                gd.addNumericField(tmpFieldFitting.getDescription(i), this.thresholdMax[i], 3, 5, "pix");
            }
            gd.addCheckbox("Discard measurements with PSF radius above specified above threshold", useThresholdMax);
            if (forcenew) {
                gd.addMessage("=== Setting number of parameters for approximation of the PSF dimensions ===");
                gd.addNumericField("Number of parameters for psf(z) approximation (>=3)", numCurvPars[0], 0);
                gd.addNumericField("Number of parameters for radial dependence of PSF curves (>=1)", numCurvPars[1],
                        0);
            }
            gd.addMessage("");
            gd.addNumericField(
                    "Data weight mode (0 - equal mode, 1 -linear treshold diff, 2 - quadratic threshold diff)",
                    weightMode, 0);
            gd.addNumericField("Data weight radius (multiply weight by Gaussian), 0 - no dependence on radius",
                    weightRadius, 3, 5, "mm");
            gd.addCheckbox("Setup parameter masks?", setupMasks);
            gd.addCheckbox("Setup parameter values?", setupParameters);
            gd.addCheckbox("Show/modify disabled for auto-adjustment parameters?", showDisabled);
            gd.addCheckbox("Debug feature: update measurements and /dxc, /dyc if center is being fitted",
                    correct_measurement_ST);
            gd.addCheckbox("Debug feature: update sample weights during fitting", updateWeightWhileFitting);
            if (enableReset)
                gd.enableYesNoCancel("OK", "Reset to defaults, re-open"); // default OK (on enter) - "Apply"
            WindowTools.addScrollBars(gd);
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            if (!gd.wasOKed()) {
                savedProperties = null;
                setDefaults();
                if (!configureDataVector(false, title, true, false))
                    return false;
                return true;
            }

            // boolean configureDataVector(String title, boolean forcenew, boolean moreset)   

            parallelOnly = gd.getNextBoolean();
            filterInput = gd.getNextBoolean();
            filterInputMotorDiff = gd.getNextNumber();
            filterInputDiff = gd.getNextNumber();

            filterInputFirstLast = gd.getNextBoolean();
            filterInputTooFar = gd.getNextBoolean();
            filterInputFarRatio = gd.getNextNumber();
            filterInputConcave = gd.getNextBoolean();
            filterInputConcaveSigma = gd.getNextNumber();
            filterInputConcaveRemoveFew = gd.getNextBoolean();
            filterInputConcaveMinSeries = (int) gd.getNextNumber();
            filterInputConcaveScale = gd.getNextNumber();

            filterTiltedZ = gd.getNextBoolean();
            filterTiltedByScanValue = gd.getNextBoolean();
            filterTiltedByValueScale = gd.getNextNumber();
            filterCalibByNeib = (int) gd.getNextNumber();
            filterSetsByRMS = gd.getNextNumber();
            filterSetsByRMSTiltOnly = gd.getNextBoolean();

            sagittalMaster = gd.getNextBoolean();
            for (int i = 0; i < minMeas.length; i++)
                this.minMeas[i] = gd.getNextNumber();
            useMinMeas = gd.getNextBoolean();
            for (int i = 0; i < maxMeas.length; i++)
                this.maxMeas[i] = gd.getNextNumber();
            useMaxMeas = gd.getNextBoolean();
            for (int i = 0; i < thresholdMax.length; i++)
                this.thresholdMax[i] = gd.getNextNumber();
            useThresholdMax = gd.getNextBoolean();
            if (forcenew) {
                numCurvPars[0] = (int) gd.getNextNumber();
                numCurvPars[1] = (int) gd.getNextNumber();
            }
            weightMode = (int) gd.getNextNumber();
            weightRadius = gd.getNextNumber();

            setupMasks = gd.getNextBoolean();
            setupParameters = gd.getNextBoolean();
            showDisabled = gd.getNextBoolean();
            correct_measurement_ST = gd.getNextBoolean();
            updateWeightWhileFitting = gd.getNextBoolean();
        }
        if (forcenew) {
            this.fieldFitting = new FieldFitting(currentPX0, currentPY0, numCurvPars[0], numCurvPars[1]);
            if (savedProperties != null) {
                if (debugLevel > 0)
                    System.out.println("configureDataVector(): Applying properties");
                getProperties(propertiesPrefix, savedProperties, true); // overwrites parallelOnly! and distortions center
            }
        }
        fieldFitting.setCenterXY(currentPX0, currentPY0);
        if (setupMasks) {
            if (!fieldFitting.maskSetDialog("Setup parameter masks"))
                return false;
        } else {
            fieldFitting.initSampleCorrChnParIndex(flattenSampleCoord());
        }
        if (setupParameters) {
            if (!fieldFitting.showModifyParameterValues("Setup parameter values", showDisabled))
                return false;
        }
        double[] centerXY = fieldFitting.getCenterXY();
        currentPX0 = centerXY[0];
        currentPY0 = centerXY[1];
        this.savedVector = fieldFitting.createParameterVector(sagittalMaster);
        //     initialVector     
        return true;
    }

    private boolean[] dataWeightsToBoolean() {
        boolean[] enable = new boolean[dataVector.length];
        for (int i = 0; i < enable.length; i++) {
            enable[i] = dataWeights[i] > 0.0;
        }
        return enable;
    }

    public int numEnabled(boolean[] en) {
        int num = 0;
        if (en != null)
            for (boolean e : en)
                if (e)
                    num++;
        return num;
    }

    private void maskDataWeights(boolean[] enable) {
        for (int i = 0; i < enable.length; i++) {
            if (!enable[i])
                dataWeights[i] = 0.0;
        }
    }

    public double[][] getSeriesWeights() {
        double[][] seriesWeights = new double[getNumChannels()][getNumSamples()];
        for (int chn = 0; chn < seriesWeights.length; chn++)
            for (int sample = 0; sample < seriesWeights[chn].length; sample++)
                seriesWeights[chn][sample] = 0.0;
        for (int index = 0; index < dataVector.length; index++)
            if (dataWeights[index] > 0.0) {
                seriesWeights[dataVector[index].channel][dataVector[index].sampleIndex] += dataWeights[index];
            }
        if (debugLevel > 1) {
            System.out.println("==== getSeriesWeights():");
            for (int chn = 0; chn < seriesWeights.length; chn++)
                for (int sample = 0; sample < seriesWeights[chn].length; sample++) {
                    System.out.println("chn=" + chn + " sample=" + sample + " weight="
                            + IJ.d2s(seriesWeights[chn][sample], 3));
                }
        }
        return seriesWeights;
    }

    private double[][][] calcZRanges(boolean scanOnly, boolean[] enable) {
        double[][][] zRanges = new double[getNumChannels()][getNumSamples()][];
        for (int chn = 0; chn < zRanges.length; chn++)
            for (int sample = 0; sample < zRanges[chn].length; sample++)
                zRanges[chn][sample] = null;
        double[][] sCoord = flattenSampleCoord();

        for (int index = 0; index < dataVector.length; index++)
            if ((!scanOnly || dataVector[index].scan) && ((index >= enable.length) || enable[index])) {
                int chn = dataVector[index].channel;
                int sample = dataVector[index].sampleIndex;
                double z = fieldFitting.getMotorsZ(dataVector[index].motors, // 3 motor coordinates
                        sCoord[sample][0], // pixel x
                        sCoord[sample][1]); // pixel y
                double fwhm = dataVector[index].value;
                if (zRanges[chn][sample] == null) {
                    zRanges[chn][sample] = new double[3];
                    zRanges[chn][sample][0] = z; // low limit
                    zRanges[chn][sample][1] = z; // high limit
                    zRanges[chn][sample][2] = 0.0; // maximal used value
                } else {
                    if (z < zRanges[chn][sample][0])
                        zRanges[chn][sample][0] = z;
                    if (z > zRanges[chn][sample][1])
                        zRanges[chn][sample][1] = z;
                    if (fwhm > zRanges[chn][sample][2])
                        zRanges[chn][sample][2] = fwhm;
                }
            }
        if (debugLevel > 0)
            System.out.println("***** calcZRanges() *****");
        return zRanges;
    }

    private boolean[] filterByZRanges(double[][][] zRanges, boolean[] enable_in, boolean[] scanMask) {
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        //   if (scanMask==null) {
        //      scanMask=new boolean [dataVector.length];
        //      for (int i=0;i<scanMask.length;i++) scanMask[i]=true;
        //   }
        boolean[] enable_masked = enable_in.clone();
        if (scanMask != null) {
            for (int i = 0; i < enable_masked.length; i++)
                if ((i < scanMask.length) && scanMask[i])
                    enable_masked[i] = false;
        }
        boolean[] enable_out = enable_masked.clone();
        //   boolean [] enable_out=enable_in.clone();
        double[][] sCoord = flattenSampleCoord();
        int numFiltered = 0;

        if (zRanges != null) {
            for (int index = 0; index < dataVector.length; index++)
                if ((index >= enable_masked.length) || enable_masked[index]) {
                    int chn = dataVector[index].channel;
                    int sample = dataVector[index].sampleIndex;
                    double z = fieldFitting.getMotorsZ(dataVector[index].motors, // 3 motor coordinates
                            sCoord[sample][0], // pixel x
                            sCoord[sample][1]); // pixel y
                    if ((zRanges[chn] != null) && (zRanges[chn][sample] != null)) {
                        if ((z < zRanges[chn][sample][0]) || (z > zRanges[chn][sample][1])) {
                            enable_out[index] = false;
                            numFiltered++;
                            //            } else {
                            //               numLeft++;
                        }
                    }
                }
        }
        // restore masked out data
        if (scanMask != null) {
            for (int i = 0; i < enable_out.length; i++)
                if ((i < scanMask.length) && scanMask[i] && enable_in[i])
                    enable_out[i] = true;
        }
        if ((debugLevel + ((scanMask != null) ? 1 : 0)) > 1) {
            int numLeft = 0;
            for (int i = 0; i < enable_out.length; i++)
                if (enable_out[i])
                    numLeft++;
            System.out.println(
                    "filterByZRanges(): Filtered " + numFiltered + " samples, left " + numLeft + " samples");
        }
        return enable_out;
    }

    private boolean[] filterByScanValues(double[][][] zRanges, boolean[] enable_in, boolean[] scanMask) {
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        boolean[] enable_masked = enable_in.clone();
        if (scanMask != null) {
            for (int i = 0; i < enable_masked.length; i++)
                if ((i < scanMask.length) && scanMask[i])
                    enable_masked[i] = false;
        }
        boolean[] enable_out = enable_masked.clone();
        int numFiltered = 0;

        if (zRanges != null) {
            for (int index = 0; index < dataVector.length; index++)
                if ((index >= enable_masked.length) || enable_masked[index]) {
                    int chn = dataVector[index].channel;
                    int sample = dataVector[index].sampleIndex;
                    double fwhm = dataVector[index].value;
                    if ((zRanges[chn] != null) && (zRanges[chn][sample] != null)) {
                        if (fwhm > zRanges[chn][sample][2]) {
                            enable_out[index] = false;
                            numFiltered++;
                        }
                    }
                }
        }
        // restore masked out data
        if (scanMask != null) {
            for (int i = 0; i < enable_out.length; i++)
                if ((i < scanMask.length) && scanMask[i] && enable_in[i])
                    enable_out[i] = true;
        }
        if ((debugLevel + ((scanMask != null) ? 1 : 0)) > 1) {
            int numLeft = 0;
            for (int i = 0; i < enable_out.length; i++)
                if (enable_out[i])
                    numLeft++;
            System.out.println(
                    "filterByScanValues(): Filtered " + numFiltered + " samples, left " + numLeft + " samples");
        }
        return enable_out;
    }

    private boolean[] filterByValue(double scale, // scale to best FWHM - larger are ignored
            boolean[] enable_in, boolean[] scanMask) {
        //   boolean [] enable_out=enable_in.clone();
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        //   if (scanMask==null) {
        //      scanMask=new boolean [dataVector.length];
        //      for (int i=0;i<scanMask.length;i++) scanMask[i]=true;
        //   }
        boolean[] enable_masked = enable_in.clone();
        if (scanMask != null) {
            for (int i = 0; i < enable_masked.length; i++)
                if ((i < scanMask.length) && scanMask[i])
                    enable_masked[i] = false;
        }
        boolean[] enable_out = enable_masked.clone();

        double[][] fwhm = fieldFitting.getFWHM(true, // boolean corrected,
                true //boolean allChannels 
        );
        if (scale > 1.0) {
            for (int chn = 0; chn < fwhm.length; chn++)
                for (int sample = 0; sample < fwhm[chn].length; sample++) {
                    fwhm[chn][sample] *= scale;
                }
        } else {
            if (zRanges == null) {
                System.out.println("filterByValue(): scale <=1.0 and zRanges==null -> nothing filtered");
                return enable_in.clone();
            }
            for (int chn = 0; chn < fwhm.length; chn++)
                for (int sample = 0; sample < fwhm[chn].length; sample++) {
                    if ((zRanges[chn] != null) && (zRanges[chn][sample] != null)) {
                        fwhm[chn][sample] += scale * (zRanges[chn][sample][2] - fwhm[chn][sample]); // based on worst accepted during calibration
                    }
                }
        }
        int numFiltered = 0;
        //   int numLeft=0;
        if (scale > 0.0) {
            for (int index = 0; index < dataVector.length; index++)
                if ((index >= enable_masked.length) || enable_masked[index]) {
                    int chn = dataVector[index].channel;
                    int sample = dataVector[index].sampleIndex;
                    if (dataVector[index].value > fwhm[chn][sample]) {
                        enable_out[index] = false;
                        numFiltered++;
                    }
                }
        }
        // restore masked out data
        if (scanMask != null) {
            for (int i = 0; i < enable_out.length; i++)
                if ((i < scanMask.length) && scanMask[i] && enable_in[i])
                    enable_out[i] = true;
        }

        if ((debugLevel + ((scanMask != null) ? 1 : 0)) > 1) {
            int numLeft = 0;
            for (int i = 0; i < enable_out.length; i++)
                if (enable_out[i])
                    numLeft++;
            System.out
                    .println("filterByValue(): Filtered " + numFiltered + " samples, left " + numLeft + " samples");
        }
        return enable_out;
    }

    private boolean[] filterNotEnoughSamples(boolean[] centerSamples, boolean[] enable_in,
            int minTotalCenterSamples, int minBestChannelCenterSamples, int minTotalSamples,
            int minBestChannelSamples) {
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        boolean[] enable_out = enable_in.clone();
        int numFiltered = 0;
        int lastIndex;
        int firstIndex;
        int nextIndex = 0;
        String lastTimestamp = "";
        while (nextIndex < dataVector.length) {
            // find first enabled sample
            for (firstIndex = nextIndex; (firstIndex < dataVector.length)
                    && ((firstIndex < enable_in.length) && !enable_in[firstIndex]); firstIndex++)
                ;
            lastTimestamp = dataVector[firstIndex].timestamp;
            lastIndex = firstIndex;
            for (nextIndex = firstIndex; nextIndex < dataVector.length; nextIndex++)
                if ((nextIndex >= enable_in.length) || enable_in[nextIndex]) {
                    if (dataVector[nextIndex].timestamp.equals(lastTimestamp))
                        lastIndex = nextIndex;
                    else
                        break;
                }

            int[] numCenterSamples = new int[getNumChannels()];
            int[] numSamples = new int[getNumChannels()];
            for (int chn = 0; chn < getNumChannels(); chn++) {
                numCenterSamples[chn] = 0;
                numSamples[chn] = 0;
            }
            for (int index = firstIndex; index <= lastIndex; index++)
                if ((index >= enable_in.length) || enable_in[index]) {
                    int chn = dataVector[index].channel;
                    int sample = dataVector[index].sampleIndex;
                    numSamples[chn]++;
                    if (centerSamples[sample])
                        numCenterSamples[chn]++;
                }
            int numTotalCenterSamples = 0;
            int numBestChannelCenterSamples = 0;
            int numTotalSamples = 0;
            int numBestChannelSamples = 0;
            for (int chn = 0; chn < getNumChannels(); chn++) {
                numTotalSamples += numSamples[chn];
                numTotalCenterSamples += numCenterSamples[chn];
                if (numBestChannelSamples < numSamples[chn])
                    numBestChannelSamples = numSamples[chn];
                if (numBestChannelCenterSamples < numCenterSamples[chn])
                    numBestChannelCenterSamples = numCenterSamples[chn];
            }
            if ((numTotalCenterSamples < minTotalCenterSamples)
                    || (numBestChannelCenterSamples < minBestChannelCenterSamples)
                    || (numTotalSamples < minTotalSamples) || (numBestChannelSamples < minBestChannelSamples)) {
                // Remove all samples in this measurement
                for (int index = firstIndex; index <= lastIndex; index++)
                    if ((index >= enable_in.length) || enable_in[index]) {
                        numFiltered++;
                        enable_out[index] = false;
                    }
            }
        }
        if (debugLevel > 1) {
            int numLeft = 0;
            for (int i = 0; i < enable_out.length; i++)
                if (enable_out[i])
                    numLeft++;
            System.out.println("filterNotEnoughSamples(" + minTotalCenterSamples + "," + minBestChannelCenterSamples
                    + "," + minTotalSamples + "," + minBestChannelSamples + "): Filtered " + numFiltered
                    + " samples, left " + numLeft + " samples");
        }
        return enable_out;
    }
    /*
    public boolean checkEnoughCenter(
          boolean [] centerSamples,
          boolean [] enable_in,
          int minTotalSamples,
          int minBestChannelSamples){
       int [] numSamples=getNumCenterSamples(
     centerSamples,
     enable_in);
       int total=0;
       boolean bestOK=false;
       for (int num:numSamples){
          total+=num;
          if (num>=minBestChannelSamples) bestOK=true;
       }
       return bestOK && (total>=minTotalSamples);
    }
    public int [] getNumCenterSamples( // per channel (disabled channels are already removed in enable_in)
          boolean [] centerSamples,
          boolean [] enable_in){
       int [] numSamples=new int [getNumChannels()];
       for (int i=0;i<numSamples.length;i++) numSamples[i]=0;
       for (int index=0;index<dataVector.length;index++) if ((index>=enable_in.length) || enable_in[index]){
          if (centerSamples[dataVector[index].sampleIndex]) numSamples[dataVector[index].channel]++;
       }
       return numSamples;
    }
    */

    public boolean[] getCenterSamples(int num) {
        double[] sampleCorrRadiuses = fieldFitting.getSampleRadiuses();
        if (num > sampleCorrRadiuses.length)
            num = sampleCorrRadiuses.length;
        boolean[] centerMask = new boolean[sampleCorrRadiuses.length];
        for (int i = 0; i < centerMask.length; i++)
            centerMask[i] = false;
        for (int pass = 0; pass < num; pass++) {
            double min = 0;
            int bestIndex = -1;
            for (int i = 0; i < centerMask.length; i++)
                if (!centerMask[i] && ((bestIndex < 0) || (sampleCorrRadiuses[i] < min))) {
                    bestIndex = i;
                    min = sampleCorrRadiuses[i];
                }
            centerMask[bestIndex] = true;
        }
        return centerMask;
    }

    private int getNumEnabledSamples(boolean[] enable) {
        int num_en = 0;
        for (int index = 0; index < dataVector.length; index++)
            if ((index >= enable.length) || enable[index])
                num_en++;
        return num_en;
    }

    private boolean[] filterConcave(boolean[] scanMask, // do not filter if false
            double sigma, boolean removeInsufficient, int minSeries, double concaveScale, boolean[] enable_in) {
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        if (scanMask == null) {
            scanMask = new boolean[dataVector.length];
            for (int i = 0; i < scanMask.length; i++)
                scanMask[i] = true;
        }
        boolean[] enable_masked = enable_in.clone();
        for (int i = 0; i < enable_masked.length; i++)
            if ((i < scanMask.length) && !scanMask[i])
                enable_masked[i] = false;
        boolean[] enable_out = enable_masked.clone();

        int debugThreshold = 1;
        double maxGap = sigma; // this point has this gap towards minimal
        double kexp = -0.5 / (sigma * sigma);
        //   boolean [] enable_out=enable_in.clone();
        double keepNearMin = sigma; // when removing non-concave points around min, skip very close ones

        double[][] flatSampleCoordinates = fieldFitting.getSampleCoordinates();
        int numFilteredInsufficient = 0;
        int numFiltered = 0;
        int[][] numPoints = new int[getNumChannels()][getNumSamples()];
        double[][][] z0EstData = new double[getNumChannels()][getNumSamples()][2];
        for (int chn = 0; chn < numPoints.length; chn++)
            for (int sample = 0; sample < numPoints[chn].length; sample++) {
                numPoints[chn][sample] = 0;
                z0EstData[chn][sample][0] = 0.0;
                z0EstData[chn][sample][1] = 0.0;
            }
        for (int index = 0; index < dataVector.length; index++)
            if ((index >= enable_masked.length) || enable_masked[index]) {
                numPoints[dataVector[index].channel][dataVector[index].sampleIndex]++;
            }
        int[][][] indices = new int[numPoints.length][numPoints[0].length][];
        for (int chn = 0; chn < numPoints.length; chn++)
            for (int sample = 0; sample < numPoints[chn].length; sample++) {
                indices[chn][sample] = new int[numPoints[chn][sample]]; // may be 0 length
                numPoints[chn][sample] = 0; // will be used as a counter
            }
        for (int index = 0; index < dataVector.length; index++)
            if ((index >= enable_masked.length) || enable_masked[index]) {
                int chn = dataVector[index].channel;
                int sample = dataVector[index].sampleIndex;
                //      numPoints[dataVector[index].channel][dataVector[index].sampleIndex]++;
                indices[chn][sample][numPoints[chn][sample]++] = index;
            }
        for (int chn = 0; chn < numPoints.length; chn++)
            for (int sample = 0; sample < numPoints[chn].length; sample++) {
                if (indices[chn][sample].length < minSeries) {
                    if (indices[chn][sample].length > 0) {
                        if (debugLevel > 0)
                            System.out.println("filterConcave(): Channel " + chn + " sample " + sample
                                    + " has too few points - " + indices[chn][sample].length + " < " + minSeries);
                        if (removeInsufficient) {
                            for (int i = 0; i < indices[chn][sample].length; i++) {
                                enable_out[indices[chn][sample][i]] = false;
                                numFilteredInsufficient++;
                            }
                        }
                    }
                } else {
                    int[] thisIndices = indices[chn][sample];
                    double[] point_z = new double[thisIndices.length];
                    double[] point_v = new double[thisIndices.length];
                    double[] point_filt = new double[thisIndices.length];
                    double[] point_vdz = new double[thisIndices.length];
                    double[] point_slope = new double[thisIndices.length];
                    boolean[] nonConcave = new boolean[thisIndices.length];
                    for (int i = 0; i < thisIndices.length; i++) {
                        //if ((chn==0) && (sample==7) && (i>=16)){
                        //   System.out.println("DEBUG00");            
                        //}
                        point_z[i] = fieldFitting.getMotorsZ(dataVector[thisIndices[i]].motors, // 3 motor coordinates
                                flatSampleCoordinates[sample][0], // pixel x
                                flatSampleCoordinates[sample][1]); // pixel y
                        point_v[i] = dataVector[thisIndices[i]].value;
                        nonConcave[i] = false;
                    }
                    for (int i = 0; i < thisIndices.length; i++) {
                        point_filt[i] = 0.0;
                        double weight = 0.0;
                        for (int j = 0; j < thisIndices.length; j++) {
                            double r = point_z[i] - point_z[j];
                            double w = Math.exp(kexp * r * r);
                            weight += w;
                            point_filt[i] += w * point_v[j];
                        }
                        point_filt[i] /= weight;
                    }
                    for (int i = 0; i < thisIndices.length; i++) {
                        point_vdz[i] = 0.0;
                        double S0 = 0.0, SX = 0.0, SY = 0.0, SX2 = 0.0, SXY = 0.0;
                        for (int j = 0; j < thisIndices.length; j++) {
                            double x = point_z[j] - point_z[i];
                            double v = point_filt[j];
                            double w = Math.exp(kexp * x * x);
                            S0 += w;
                            SX += w * x;
                            SY += w * v;
                            SX2 += w * x * x;
                            SXY += w * x * v;
                        }
                        point_vdz[i] = (SXY * S0 - SX * SY) / (SX2 * S0 - SX * SX);
                    }
                    // find min on filtered
                    int minIndex = 0;
                    for (int i = 1; i < thisIndices.length; i++)
                        if (point_filt[i] < point_filt[minIndex])
                            minIndex = i;
                    for (int i = 0; i < thisIndices.length; i++) {
                        if (i == minIndex)
                            point_slope[i] = 0.0;
                        else
                            point_slope[i] = (point_filt[i] - point_filt[minIndex])
                                    / (point_z[i] - point_z[minIndex]);
                    }
                    //concaveScale
                    for (int i = 0; i < thisIndices.length; i++) {
                        if ((((point_z[i] - point_z[minIndex]) > keepNearMin)
                                && (concaveScale * point_slope[i] > point_vdz[i]))
                                || (((point_z[minIndex] - point_z[i]) > keepNearMin)
                                        && (concaveScale * point_slope[i] < point_vdz[i]))) {
                            nonConcave[i] = true;
                        }
                    }
                    // find gaps    double maxGap=sigma; // this point has this gap towards minimal
                    for (int i = 0; i < thisIndices.length; i++)
                        if (!nonConcave[i]) {
                            if ((point_z[i] - point_z[minIndex]) > keepNearMin) {
                                boolean goodPoint = false;
                                for (int j = 0; j < thisIndices.length; j++)
                                    if ((point_z[j] > point_z[minIndex]) && (point_z[j] < point_z[i])
                                            && ((point_z[i] - point_z[j]) < maxGap)) {
                                        goodPoint = true;
                                        break;
                                    }
                                if (!goodPoint)
                                    nonConcave[i] = true;
                            } else if ((point_z[minIndex] - point_z[i]) > keepNearMin) {
                                boolean goodPoint = false;
                                for (int j = 0; j < thisIndices.length; j++)
                                    if ((point_z[j] < point_z[minIndex]) && (point_z[j] > point_z[i])
                                            && ((point_z[j] - point_z[i]) < maxGap)) {
                                        goodPoint = true;
                                        break;
                                    }
                                if (!goodPoint)
                                    nonConcave[i] = true;
                            }
                        }

                    // propagate
                    for (int i = 0; i < thisIndices.length; i++)
                        if (!nonConcave[i]) {
                            if ((point_z[i] - point_z[minIndex]) > keepNearMin) {
                                for (int j = 0; j < thisIndices.length; j++)
                                    if (nonConcave[j] && (point_z[j] > point_z[minIndex])
                                            && (point_z[j] < point_z[i])) {
                                        nonConcave[i] = true;
                                        break;
                                    }
                            } else if ((point_z[minIndex] - point_z[i]) > keepNearMin) {
                                for (int j = 0; j < thisIndices.length; j++)
                                    if (nonConcave[j] && (point_z[j] < point_z[minIndex])
                                            && (point_z[j] > point_z[i])) {
                                        nonConcave[i] = true;
                                        break;
                                    }
                            }
                        }
                    for (int i = 0; i < thisIndices.length; i++)
                        if (nonConcave[i]) {
                            enable_out[thisIndices[i]] = false;
                            numFiltered++;
                        }

                    // See if too few are left - remove them
                    int numPointsLeft = 0;
                    for (int i = 0; i < thisIndices.length; i++)
                        if (enable_out[thisIndices[i]]) {
                            numPointsLeft++;
                        }
                    if (numPointsLeft < minSeries) {
                        if (debugLevel > 0)
                            System.out.println("filterConcave(): Channel " + chn + " sample " + sample
                                    + " has too few points left after filter - " + numPointsLeft + " < "
                                    + minSeries);
                        if (removeInsufficient) {
                            for (int i = 0; i < indices[chn][sample].length; i++) {
                                if (enable_out[thisIndices[i]]) {
                                    enable_out[indices[chn][sample][i]] = false;
                                    numFilteredInsufficient++;
                                }
                            }
                        }
                    }

                    if (debugLevel > debugThreshold) {
                        System.out.println("filterConcave(), chn=" + chn + ", sample=" + sample);

                        for (int i = 0; i < thisIndices.length; i++) {
                            System.out.println(i + ": z=" + IJ.d2s(point_z[i], 3) + ", v=" + IJ.d2s(point_v[i], 3)
                                    + ", filt=" + IJ.d2s(point_filt[i], 3) + ", vdz="
                                    + IJ.d2s(100 * point_vdz[i], 3) + ", slope=" + IJ.d2s(100 * point_slope[i], 3)
                                    + ", concave=" + (nonConcave[i] ? 0.0 : 1.0));
                        }
                    }
                    // contribute to z0 calculation
                    for (int i = 0; i < thisIndices.length; i++)
                        if (!nonConcave[i]) {
                            z0EstData[chn][sample][1] += dataWeights[thisIndices[i]];
                        }
                    z0EstData[chn][sample][0] = point_z[minIndex];
                }
            }
        if (debugLevel > 0)
            System.out
                    .println("filterConcave(): removed for too few points " + numFilteredInsufficient + " samples");
        if (debugLevel > 0)
            System.out.println("filterConcave(): removed for non-concave " + numFiltered + " samples");

        //   for (int chn=0;chn<numPoints.length;chn++) for (int sample=0;sample<numPoints[chn].length;sample++){
        z0_estimates = new double[getNumChannels()];
        for (int chn = 0; chn < z0_estimates.length; chn++) {
            double z = 0;
            double w = 0;
            for (int sample = 0; sample < numPoints[chn].length; sample++) {
                z += z0EstData[chn][sample][0] * z0EstData[chn][sample][1];
                w += z0EstData[chn][sample][1];
            }
            z0_estimates[chn] = (w > 0.0) ? z / w : Double.NaN;
        }
        // restore masked out data
        for (int i = 0; i < enable_out.length; i++)
            if ((i < scanMask.length) && !scanMask[i] && enable_in[i])
                enable_out[i] = true;
        return enable_out;
    }

    private boolean[] filterTooFar(boolean[] scanMask, // do not filter if false
            double ratio, boolean[] enable_in) {
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        if (scanMask == null) {
            scanMask = new boolean[dataVector.length];
            for (int i = 0; i < scanMask.length; i++)
                scanMask[i] = true;
        }
        boolean[] enable_masked = enable_in.clone();
        for (int i = 0; i < enable_masked.length; i++)
            if ((i < scanMask.length) && !scanMask[i])
                enable_masked[i] = false;
        boolean[] enable_out = enable_masked.clone();
        int numFiltered = 0;
        double[][][] data = new double[getNumChannels()][getNumSamples()][3];
        double[] z_sample = new double[dataVector.length];
        for (int chn = 0; chn < data.length; chn++)
            for (int sample = 0; sample < data[chn].length; sample++)
                for (int i = 0; i < data[chn][sample].length; i++)
                    data[chn][sample][i] = 0;
        for (int index = 0; index < dataVector.length; index++)
            if (((index >= enable_masked.length) || enable_masked[index])) {
                int chn = dataVector[index].channel;
                int sample = dataVector[index].sampleIndex;
                double z = fieldFitting.getMotorsZ(dataVector[index].motors, //int [] motors, // 3 motor coordinates
                        dataVector[index].px, //        double px, // pixel x
                        dataVector[index].py); //        double py) / was px!
                double w = weightReference[chn] - dataVector[index].value; // maybe use square of w?
                data[chn][sample][0] += w;
                data[chn][sample][1] += w * z;
                data[chn][sample][2] += w * z * z;
                z_sample[index] = z;
            }
        for (int chn = 0; chn < data.length; chn++)
            for (int sample = 0; sample < data[chn].length; sample++) {
                if (data[chn][sample][0] > 0.0) {
                    double z_av = data[chn][sample][1] / data[chn][sample][0];
                    double z2 = (data[chn][sample][2] * data[chn][sample][0]
                            - data[chn][sample][1] * data[chn][sample][1])
                            / (data[chn][sample][0] * data[chn][sample][0]);
                    data[chn][sample][2] = z2 * ratio * ratio; // squared radius
                    data[chn][sample][1] = z_av; // center z
                    if (debugLevel > 1)
                        System.out.println("filterTooFar(): chn=" + chn + ", sample=" + sample + ", r_av="
                                + IJ.d2s(Math.sqrt(z2), 3) + " z_av=" + IJ.d2s(z_av, 3));
                }
            }
        for (int index = 0; index < dataVector.length; index++)
            if ((index >= enable_masked.length) || enable_masked[index]) {
                int chn = dataVector[index].channel;
                int sample = dataVector[index].sampleIndex;
                if (data[chn][sample][0] > 0.0) {
                    double diff_z = z_sample[index] - data[chn][sample][1];
                    if ((diff_z * diff_z > data[chn][sample][2]) && enable_out[index]) {
                        enable_out[index] = false;
                        numFiltered++;
                    }
                }
            }

        if (debugLevel > 0)
            System.out.println("filterTooFar(): removed " + numFiltered + " samples");
        // restore masked out data
        for (int i = 0; i < enable_out.length; i++)
            if ((i < scanMask.length) && !scanMask[i] && enable_in[i])
                enable_out[i] = true;
        return enable_out;
    }

    private boolean[] filterCrazyInput(boolean[] scanMask, // do not filter if false
            boolean[] enable_in, // [meas][cjn][sample] (or null) // can be shorter or longer than dataVector
            double maxMotDiff, double diff, boolean removeFirstLast // very first, very last in all samples (or after big move) - OK
    ) {
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        if (scanMask == null) {
            scanMask = new boolean[dataVector.length];
            for (int i = 0; i < scanMask.length; i++)
                scanMask[i] = true;
        }
        boolean[] enable_out = enable_in.clone();
        //   int lastIndex=-1;
        int numFiltered = 0;
        int[][] lastIndex = new int[getNumChannels()][getNumSamples()];
        int[][] lastTimestampIndex = new int[getNumChannels()][getNumSamples()];
        for (int i = 0; i < lastIndex.length; i++)
            for (int j = 0; j < lastIndex[i].length; j++) {
                lastIndex[i][j] = -1;
                lastTimestampIndex[i][j] = -1;
            }
        String lastTimestamp = null;
        int thisTimestampIndex = 0;
        int lastIndexAny = -1;
        boolean smallMove = false;
        //   for (int index=0;index<dataVector.length;index++) if ((index>=enable_in.length) ||enable_in[index]){
        for (int index = 0; index < dataVector.length; index++)
            if (scanMask[index]) { // crazy neighbor still kills even if is ignored itself - needed
                int chn = dataVector[index].channel;
                int sample = dataVector[index].sampleIndex;
                if (lastTimestamp == null)
                    lastTimestamp = dataVector[index].timestamp;
                if (!dataVector[index].timestamp.equals(lastTimestamp)) {
                    if (smallMove) { // see if any of the samples/channel did not move last time
                        for (int i = 0; i < lastIndex.length; i++)
                            for (int j = 0; j < lastIndex[i].length; j++) {
                                if ((lastIndex[i][j] >= 0) && (lastTimestampIndex[i][j] < thisTimestampIndex)) {
                                    if (removeFirstLast) {
                                        if (enable_out[lastIndex[i][j]])
                                            numFiltered++;
                                        enable_out[lastIndex[i][j]] = false;
                                    }
                                    lastIndex[i][j] = -1;
                                }
                            }
                    }
                    smallMove = ((Math
                            .abs(dataVector[index].motors[0] - dataVector[lastIndexAny].motors[0])) <= maxMotDiff)
                            && ((Math.abs(dataVector[index].motors[1]
                                    - dataVector[lastIndexAny].motors[1])) <= maxMotDiff)
                            && ((Math.abs(dataVector[index].motors[2]
                                    - dataVector[lastIndexAny].motors[2])) <= maxMotDiff);
                    thisTimestampIndex++;
                }
                // is it a first enabled sample after small move?
                if (smallMove) {
                    if ((lastIndex[chn][sample] < 0)
                            || (lastTimestampIndex[chn][sample] < (thisTimestampIndex - 1))) {
                        if (removeFirstLast) {
                            if (enable_out[index])
                                numFiltered++;
                            enable_out[index] = false;
                        }
                    } else { // large difference?
                        if ((Math
                                .abs(dataVector[index].value - dataVector[lastIndex[chn][sample]].value)) >= diff) {
                            // yes, remove both this and previous
                            if (enable_out[lastIndex[chn][sample]])
                                numFiltered++;
                            enable_out[lastIndex[chn][sample]] = false;
                            if (enable_out[index])
                                numFiltered++;
                            enable_out[index] = false;
                        }
                    }
                }
                lastIndex[chn][sample] = index;
                lastTimestampIndex[chn][sample] = thisTimestampIndex;
                lastTimestamp = dataVector[index].timestamp;
                lastIndexAny = index;
            }
        if (debugLevel > 0)
            System.out.println("filterCrazyInput(): removed " + numFiltered + " samples");
        return enable_out;
    }

    private boolean[] filterSets(boolean[] enable_in, double scaleRMS, boolean[] scanMask // if not null, will not touch samples where true
    ) {
        double[] sv = fieldFitting.createParameterVector(sagittalMaster); // FIXME:
        double[] fX = createFXandJacobian(sv, false);
        double maxRMS = scaleRMS * calcErrorDiffY(fX, true);
        //    int [] indices=getSetIndices();
        Integer[] indices = getSetIndices().toArray(new Integer[0]); // uses dataVector; - new measurements
        double[] setRMA = calcErrorsPerSet(fX);

        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        boolean[] enable_masked = enable_in.clone();
        if (scanMask != null) {
            for (int i = 0; i < enable_masked.length; i++)
                if ((i < scanMask.length) && scanMask[i])
                    enable_masked[i] = false;
        }
        boolean[] enable_out = enable_masked.clone();
        int numFiltered = 0;
        for (int numSet = 0; numSet < setRMA.length; numSet++)
            if (setRMA[numSet] > maxRMS) {
                int nextIndex = (numSet == (indices.length - 1) ? dataVector.length : indices[numSet + 1]);
                for (int i = indices[numSet]; i < nextIndex; i++)
                    if (enable_out[i]) {
                        numFiltered++;
                        enable_out[i] = false;
                    }
            }
        // restore masked out data
        if (scanMask != null) {
            for (int i = 0; i < enable_out.length; i++)
                if ((i < scanMask.length) && scanMask[i] && enable_in[i])
                    enable_out[i] = true;
        }
        if (debugLevel > 0) {
            int numLeft = 0;
            for (int i = 0; i < enable_out.length; i++)
                if (enable_out[i])
                    numLeft++;
            System.out.println("filterSets(): Filtered " + numFiltered + " samples, left " + numLeft + " samples");
        }
        return enable_out;
    }

    private boolean[] filterLowNeighbors(boolean[] enable_in, // [meas][cjn][sample] (or null) // can be shorter or longer than dataVector
            int minNeib, boolean calibMode) {
        if (enable_in == null) {
            enable_in = new boolean[dataVector.length];
            for (int i = 0; i < enable_in.length; i++)
                enable_in[i] = true;
        }
        boolean[] enable_out = enable_in.clone();
        boolean[][] usedSamples = new boolean[getNumChannels()][getNumSamples()];
        int height = sampleCoord.length;
        int width = sampleCoord[0].length;
        int numFiltered = 0;
        int lastIndex;
        int firstIndex;
        int nextIndex = 0;
        String lastTimestamp = "";
        int[][] dirs = { { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 } };
        while (nextIndex < dataVector.length) {
            // find first enabled sample
            for (firstIndex = nextIndex; (firstIndex < dataVector.length)
                    && ((firstIndex < enable_in.length) && !enable_in[firstIndex]); firstIndex++)
                ;
            lastTimestamp = dataVector[firstIndex].timestamp;
            lastIndex = firstIndex;
            for (nextIndex = firstIndex; nextIndex < dataVector.length; nextIndex++)
                if ((nextIndex >= enable_in.length) || enable_in[nextIndex]) {
                    if (dataVector[nextIndex].timestamp.equals(lastTimestamp))
                        lastIndex = nextIndex;
                    else
                        break;
                }
            for (int chn = 0; chn < usedSamples.length; chn++)
                for (int sample = 0; sample < usedSamples[chn].length; sample++)
                    usedSamples[chn][sample] = false;
            for (int index = firstIndex; index <= lastIndex; index++)
                if ((index >= enable_in.length) || enable_in[index]) {
                    usedSamples[dataVector[index].channel][dataVector[index].sampleIndex] = true;
                }
            for (int chn = 0; chn < usedSamples.length; chn++) {
                boolean removed;
                do {
                    removed = false;
                    boolean[] left = usedSamples[chn].clone();
                    for (int y = 0; y < height; y++)
                        for (int x = 0; x < width; x++)
                            if (usedSamples[chn][y * width + x]) {
                                int n = 0;
                                for (int d = 0; d < dirs.length; d++) {
                                    int[] dXY = dirs[d].clone();
                                    if (((x == 0) && (dXY[0] < 0)) || ((x == (width - 1)) && (dXY[0] > 0)))
                                        dXY[0] = -dXY[0];
                                    if (((y == 0) && (dXY[1] < 0)) || ((y == (height - 1)) && (dXY[1] > 0)))
                                        dXY[1] = -dXY[1];
                                    if (usedSamples[chn][(y + dXY[1]) * width + (x + dXY[0])])
                                        n++;
                                }
                                if (n < minNeib) {
                                    removed = true;
                                    left[y * width + x] = false;
                                }
                            }
                    usedSamples[chn] = left.clone();
                } while (removed);
            }
            for (int index = firstIndex; index <= lastIndex; index++)
                if ((index >= enable_in.length) || enable_in[index]) {
                    if (!usedSamples[dataVector[index].channel][dataVector[index].sampleIndex]) {
                        numFiltered++;
                        enable_out[index] = false;
                    }
                    ;
                }
        }
        if (debugLevel + (calibMode ? 1 : 0) > 1) {
            int numLeft = 0;
            for (int i = 0; i < enable_out.length; i++)
                if (enable_out[i])
                    numLeft++;
            System.out.println("filterLowNeighbors(" + minNeib + "): Filtered " + numFiltered + " samples, left "
                    + numLeft + " samples");
        }
        return enable_out;
    }

    private int[] getParallelDiff(MeasuredSample[] vector) {
        HashMap<Point, AtomicInteger> map = new HashMap<Point, AtomicInteger>();
        for (MeasuredSample ms : vector) {
            Point diff = new Point(ms.motors[1] - ms.motors[0], ms.motors[2] - ms.motors[0]);
            if (map.containsKey(diff))
                map.get(diff).incrementAndGet();
            else
                map.put(diff, new AtomicInteger(1));
        }
        Point parallelDiff = new Point(0, 0);
        int maxRun = 0;
        for (Point diff : map.keySet()) {
            if (map.get(diff).get() > maxRun) {
                maxRun = map.get(diff).get();
                parallelDiff = diff;
            }
        }
        if (debugLevel > 1)
            System.out.println("getParallelDiff(): maximal number of parallel measurements is " + maxRun
                    + ", for M2-M1=" + parallelDiff.x + ", M3-M2=" + parallelDiff.y);
        int[] result = { parallelDiff.x, parallelDiff.y };
        return result;
    }

    private boolean[] createScanMask(MeasuredSample[] vector) {
        int[] diffs = getParallelDiff(vector);
        int longestStart = 0;
        int longestRun = 0;
        int thisStart = 0;
        int thisRun = 0;
        //   boolean [] chanSel=fieldFitting.getSelectedChannels();
        //   int numSamples=0;
        for (int i = 0; i < vector.length + 1; i++) { // if (chanSel[vector[i].channel]) {
            boolean diffMatch = (i >= vector.length) ? false
                    : (((vector[i].motors[1] - vector[i].motors[0]) == diffs[0])
                            && ((vector[i].motors[2] - vector[i].motors[0]) == diffs[1]));
            if (diffMatch) {
                if (thisRun > 0) {
                    thisRun++;
                } else {
                    thisStart = i;
                    thisRun = 1;
                }
            } else {
                if (thisRun > 0) {
                    if (thisRun > longestRun) {
                        longestRun = thisRun;
                        longestStart = thisStart;
                    }
                    thisRun = 0;
                }
            }
            //      numSamples++;
        }
        //   numSamples--;
        boolean[] scanMask = new boolean[vector.length]; // numSamples];
        int index = 0;
        for (int i = 0; i < vector.length; i++) { //if (chanSel[vector[i].channel]) {
            scanMask[index] = (index >= longestStart) && ((index < (longestStart + longestRun)));
            index++;
        }
        return scanMask;
    }

    // includes deselected channels
    public void setDataVector(boolean calibrateMode, MeasuredSample[] vector) { // remove unused channels if any. vector is already corrected from input data, FWHM psf
        if (debugLevel > 1)
            System.out.println("+++++ (Re)calculating sample weights +++++");
        boolean[] chanSel = fieldFitting.getSelectedChannels();
        boolean[] fullScanMask = createScanMask(vector);
        int numSamples = 0;
        for (int i = 0; i < vector.length; i++)
            if (chanSel[vector[i].channel]) {

                if (calibrateMode && parallelOnly && !fullScanMask[i])
                    continue; // skip non-scan
                numSamples++;
            }
        dataVector = new MeasuredSample[numSamples];
        boolean[] scanMask = new boolean[numSamples];
        int n = 0;
        for (int i = 0; i < vector.length; i++)
            if (chanSel[vector[i].channel]) {
                if (calibrateMode && parallelOnly && !fullScanMask[i])
                    continue;
                scanMask[n] = fullScanMask[i];
                vector[i].scan = fullScanMask[i];
                dataVector[n++] = vector[i];
            }
        int corrLength = fieldFitting.getNumberOfCorrParameters();
        dataValues = new double[dataVector.length + corrLength];
        dataWeights = new double[dataVector.length + corrLength];
        double kw = (weightRadius > 0.0) ? (-0.5 * getPixelMM() * getPixelMM() / (weightRadius * weightRadius)) : 0;
        for (int i = 0; i < dataVector.length; i++) {
            MeasuredSample ms = dataVector[i];
            dataValues[i] = ms.value;
            dataWeights[i] = 1.0 / Math.pow(ms.value, weightMode);
            if (weightRadius > 0.0) {
                double r2 = (ms.px - currentPX0) * (ms.px - currentPX0)
                        + (ms.py - currentPY0) * (ms.py - currentPY0);
                dataWeights[i] *= Math.exp(kw * r2);
            }
        }
        for (int i = 0; i < corrLength; i++) {
            dataValues[i + dataVector.length] = 0.0; // correction target is always 0
            dataWeights[i + dataVector.length] = 1.0; // improve?
        }
        if (calibrateMode && filterInput) {
            boolean[] en = dataWeightsToBoolean();
            en = filterCrazyInput(scanMask, en, // [meas][cjn][sample] (or null) // can be shorter or longer than dataVector
                    filterInputMotorDiff, filterInputDiff, filterInputFirstLast);
            maskDataWeights(en);
        }
        if (calibrateMode && filterInputTooFar) {
            boolean[] en = dataWeightsToBoolean();
            en = filterTooFar(scanMask, filterInputFarRatio, en);
            maskDataWeights(en);
        }

        if (calibrateMode && filterInputConcave) {
            boolean[] en = dataWeightsToBoolean();
            en = filterConcave(scanMask, filterInputConcaveSigma, filterInputConcaveRemoveFew,
                    filterInputConcaveMinSeries, filterInputConcaveScale, en);
            maskDataWeights(en);
        }

        if (calibrateMode && filterTiltedZ) {
            boolean[] en = dataWeightsToBoolean();
            en = filterByZRanges(zRanges, en, scanMask);
            maskDataWeights(en);
        }

        if (calibrateMode && filterTiltedByScanValue) {
            boolean[] en = dataWeightsToBoolean();
            en = filterByScanValues(zRanges, en, scanMask);
            maskDataWeights(en);
        }

        if (calibrateMode && !Double.isNaN(filterTiltedByValueScale) && (filterTiltedByValueScale > 0.0)) {
            boolean[] en = dataWeightsToBoolean();
            en = filterByValue(filterByValueScale, en, scanMask);
            maskDataWeights(en);
        }

        if (calibrateMode && (filterCalibByNeib > 0)) {
            boolean[] en = dataWeightsToBoolean();
            en = filterLowNeighbors(en, filterCalibByNeib, true); // calibrate mode - for debug print
            maskDataWeights(en);
        }

        if (calibrateMode && (filterSetsByRMS > 0)) {
            fieldFitting.initSampleCorrVector(flattenSampleCoord(), //double [][] sampleCoordinates,
                    getSeriesWeights()); //double [][] sampleSeriesWeights);
            boolean[] en = dataWeightsToBoolean();
            en = filterSets(en, filterSetsByRMS, filterSetsByRMSTiltOnly ? scanMask : null);
            maskDataWeights(en);
        }

        // TODO: add filtering for tilt motor calibration

        fieldFitting.initSampleCorrVector(flattenSampleCoord(), //double [][] sampleCoordinates,
                getSeriesWeights()); //double [][] sampleSeriesWeights);
    }

    // for compatibility with Distortions class\

    public void commitParameterVector(double[] vector) {
        int[] zTxTyMode = fieldFitting.mechanicalFocusingModel.getZTxTyMode();
        //   fieldFitting.setCurrentVectorLength(vector.length);
        if (zTxTyMode != null) {
            fieldFitting.commitParameterVectorZTxTy(vector);
            return;
        }
        fieldFitting.commitParameterVector(vector, sagittalMaster);
        // recalculate measured S,T (depend on center) if center is among fitted parameters
        boolean[] centerSelect = fieldFitting.getCenterSelect();
        if (centerSelect[0] || centerSelect[1]) { // do not do that if XC, YC are not modified
            // recalculate data vector
            double[] pXY = fieldFitting.getCenterXY();
            if (debugLevel > 0)
                System.out.println("Updated currentPX0=" + pXY[0] + "(" + currentPX0 + ")" + ", currentPY0="
                        + pXY[1] + "(" + currentPY0 + ")");
            currentPX0 = pXY[0];
            currentPY0 = pXY[1];
            if (correct_measurement_ST && updateWeightWhileFitting) {
                setDataVector(true, createDataVector(false, // boolean updateSelection,
                        pXY[0], //double centerPX,
                        pXY[1])); //double centerPY
            }
        }
    }

    public double[] createFXandJacobian(double[] vector, boolean createJacobian) {
        commitParameterVector(vector);
        return createFXandJacobian(createJacobian);
    }

    //multiJacobian
    public double[] createFXandJacobian(boolean createJacobian) {
        if (multiJacobian && (threadsMax > 0))
            return createFXandJacobianMulti(createJacobian);
        else
            return createFXandJacobianSingle(createJacobian);
    }

    public class PartialFXJac {
        public int index; // measurement number
        public double f;
        public double[] jac = null;

        public PartialFXJac(int index, double f, double[] jac) { //, int num){
            this.index = index;
            this.f = f;
            this.jac = jac;
            //      if (num>=0) jac=new double [num];
            //      else jac=null;
            //      jac=null;
        }
    }

    public double[] createFXandJacobianMulti(final boolean createJacobian) {
        long startTime = System.nanoTime();
        //   final int [] zTxTyMode=fieldFitting.mechanicalFocusingModel.getZTxTyMode();
        final boolean useZTxTy = fieldFitting.mechanicalFocusingModel.getZTxTyMode() != null;
        int numCorrPar = fieldFitting.getNumberOfCorrParameters();
        boolean[] selChannels = fieldFitting.getSelectedChannels();
        final int[] selChanIndices = new int[selChannels.length];
        selChanIndices[0] = 0;
        for (int i = 1; i < selChanIndices.length; i++) {
            selChanIndices[i] = selChanIndices[i - 1] + (selChannels[i - 1] ? 1 : 0);
        }
        //   if (zTxTyMode!=null) {
        //      fieldFitting.commitParameterVectorZTxTy(vector);
        //      return;
        //   }

        final int numPars = fieldFitting.getNumberOfParameters(sagittalMaster);
        int numRegPars = fieldFitting.getNumberOfRegularParameters(sagittalMaster);

        final int numSelChn = fieldFitting.getNumberOfChannels();
        final Thread[] threads = newThreadArray(threadsMax);
        final ArrayList<ArrayList<PartialFXJac>> fxList = new ArrayList<ArrayList<PartialFXJac>>();
        for (int ithread = 0; ithread < threads.length; ithread++) {
            fxList.add(new ArrayList<PartialFXJac>());
        }
        // create list of indices of measurements corresponding to new timestamp/sample (up to 6 increment)
        final ArrayList<Integer> measIndicesList = getSetSampleIndices(); // uses dataVector; 
        /*   String prevTimeStamp="";
           double prevPx=-1,prevPy=-1;
            
           final ArrayList<Integer> measIndicesList = new ArrayList<Integer>(dataVector.length/getNumChannels());
           for (int n=0;n<dataVector.length;n++){
              MeasuredSample ms=dataVector[n];
              if (!ms.timestamp.equals(prevTimeStamp) || (ms.px!=prevPx) || (ms.py!=prevPy)){
                 measIndicesList.add(new Integer(n));
              }
           }
        */
        final AtomicInteger measIndex = new AtomicInteger(0);
        final AtomicInteger threadIndexAtomic = new AtomicInteger(0);
        final boolean[] centerSelect = correct_measurement_ST ? fieldFitting.getCenterSelect() : null; //falseFalse;
        for (int ithread = 0; ithread < threads.length; ithread++) {

            threads[ithread] = new Thread() {
                public void run() {
                    int threadIndex = threadIndexAtomic.getAndIncrement();
                    fxList.get(threadIndex).clear(); // not needed
                    double[][] derivs;
                    double[] blankPars = null;
                    if (useZTxTy) {
                        if (createJacobian) {
                            blankPars = new double[numPars];
                            for (int i = 0; i < numPars; i++)
                                blankPars[i] = 0.0;
                        }
                    }
                    for (int startMeasIndex = measIndex.getAndIncrement(); startMeasIndex < measIndicesList
                            .size(); startMeasIndex = measIndex.getAndIncrement()) {
                        int startMeas = measIndicesList.get(startMeasIndex);
                        int endMeas = (startMeasIndex == (measIndicesList.size() - 1)) ? dataVector.length
                                : measIndicesList.get(startMeasIndex + 1);
                        MeasuredSample ms = dataVector[startMeas];
                        derivs = createJacobian ? (new double[numSelChn][]) : null;
                        double[] subData;
                        if (useZTxTy) {
                            subData = fieldFitting.getValsDerivativesZTxTy(ms.measurementIndex, //-1, // <0 - use mechanicalFocusingModel z, tx, ty
                                    ms.sampleIndex, ms.motors, // 3 motor coordinates
                                    ms.px, // pixel x
                                    ms.py, // pixel y
                                    derivs);
                            for (int n = startMeas; n < endMeas; n++) {
                                ms = dataVector[n];
                                int chn = selChanIndices[ms.channel];
                                double[] zTxTyDerivs = null;
                                if (createJacobian && (derivs[chn] != null)) {
                                    zTxTyDerivs = blankPars.clone();
                                    for (int i = 0; i < derivs[chn].length; i++) {
                                        int parIndex = fieldFitting.getZTMap(ms.measurementIndex, i); //n);
                                        if (parIndex >= 0)
                                            zTxTyDerivs[parIndex] = derivs[chn][i];
                                    }
                                }
                                PartialFXJac partialFXJac = new PartialFXJac(n, subData[chn], zTxTyDerivs); // createJacobian?derivs[chn]:null);
                                fxList.get(threadIndex).add(partialFXJac);
                            }
                        } else {
                            subData = fieldFitting.getValsDerivatives(
                                    //                     ms.measurementIndex, //-1, // <0 - use mechanicalFocusingModel z, tx, ty
                                    ms.sampleIndex, sagittalMaster, ms.motors, // 3 motor coordinates
                                    ms.px, // pixel x
                                    ms.py, // pixel y
                                    derivs);
                            for (int n = startMeas; n < endMeas; n++) {
                                ms = dataVector[n];
                                int chn = selChanIndices[ms.channel];
                                if (createJacobian && (centerSelect != null)) {
                                    int np = 0;
                                    for (int i = 0; i < 2; i++)
                                        if (centerSelect[i]) {
                                            derivs[chn][np++] -= ms.dPxyc[i]; // subtract, as effect is opposite to fX
                                        }
                                }
                                PartialFXJac partialFXJac = new PartialFXJac(n, subData[chn],
                                        createJacobian ? derivs[chn] : null);
                                fxList.get(threadIndex).add(partialFXJac);
                            }
                        }

                    }

                }
            };
        }
        startAndJoin(threads);
        if (debugLevel > 1)
            System.out.println("#1 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 5));
        //   Combibe results
        double[] fx = new double[dataVector.length + numCorrPar];
        if (createJacobian) {
            jacobian = new double[numPars][dataVector.length + numCorrPar];
            for (double[] row : jacobian)
                Arrays.fill(row, 0.0);
        }

        for (ArrayList<PartialFXJac> partilaList : fxList) {
            for (PartialFXJac partialFXJac : partilaList) {
                int n = partialFXJac.index;
                fx[n] = partialFXJac.f;
                if (createJacobian) {
                    for (int i = 0; i < numPars; i++) {
                        jacobian[i][n] = partialFXJac.jac[i];
                    }
                }
            }
        }
        if (debugLevel > 1)
            System.out.println("#2 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 5));
        if (createJacobian && !useZTxTy && (fieldFitting.sampleCorrChnParIndex != null)) {
            // add mutual dependence of correction parameters. first - values (fx)
            int index = dataVector.length; // add to the end of vector
            int numSamples = getNumSamples();
            for (int chn = 0; chn < fieldFitting.sampleCorrChnParIndex.length; chn++)
                if (fieldFitting.sampleCorrChnParIndex[chn] != null) {
                    for (int np = 0; np < fieldFitting.sampleCorrChnParIndex[chn].length; np++) {
                        int pindex = fieldFitting.sampleCorrChnParIndex[chn][np];
                        if (pindex >= 0) {
                            for (int i = 0; i < numSamples; i++) {
                                double f = 0.0;
                                for (int j = 0; j < numSamples; j++) {
                                    f += fieldFitting.sampleCorrVector[pindex + j]
                                            * fieldFitting.sampleCorrCrossWeights[chn][np][i][j];
                                }
                                fx[index] = f;
                                //                                 f+=fieldFitting.sampleCorrVector[pindex+i]
                                if (createJacobian) {
                                    for (int j = 0; j < numSamples; j++) {
                                        jacobian[numRegPars + pindex
                                                + j][index] = fieldFitting.sampleCorrCrossWeights[chn][np][i][j];
                                    }
                                }
                                index++;
                            }
                        }
                    }
                }
        }
        if (debugLevel > 1)
            System.out.println("#3 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 5));
        if (createJacobian && (debugLevel > 1)) {
            if (debugPoint >= 0)
                debugJacobianPoint(debugPoint);
            if (debugParameter >= 0)
                debugJacobianParameter(debugParameter);
        }
        if (debugLevel > 1)
            System.out.println("#4 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 5));
        return fx;

    }

    /* 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 double[] createFXandJacobianSingle(boolean createJacobian) {
        final boolean useZTxTy = fieldFitting.mechanicalFocusingModel.getZTxTyMode() != null;

        int numCorrPar = fieldFitting.getNumberOfCorrParameters();
        double[] fx = new double[dataVector.length + numCorrPar];
        double[][] derivs = null;
        double[] subData = null;
        boolean[] selChannels = fieldFitting.getSelectedChannels();
        int[] selChanIndices = new int[selChannels.length];
        selChanIndices[0] = 0;
        for (int i = 1; i < selChanIndices.length; i++) {
            selChanIndices[i] = selChanIndices[i - 1] + (selChannels[i - 1] ? 1 : 0);
        }
        int numPars = fieldFitting.getNumberOfParameters(sagittalMaster);
        int numRegPars = fieldFitting.getNumberOfRegularParameters(sagittalMaster);
        if (createJacobian) {
            jacobian = new double[numPars][dataVector.length + numCorrPar];
            for (double[] row : jacobian)
                Arrays.fill(row, 0.0);
            derivs = new double[fieldFitting.getNumberOfChannels()][];
        }
        String prevTimeStamp = "";
        double prevPx = -1, prevPy = -1;
        for (int n = 0; n < dataVector.length; n++) {
            MeasuredSample ms = dataVector[n];
            if (!ms.timestamp.equals(prevTimeStamp) || (ms.px != prevPx) || (ms.py != prevPy)) {
                if (useZTxTy) {
                    subData = fieldFitting.getValsDerivativesZTxTy( // fixed length derivative- 3 elements
                            ms.measurementIndex, //-1, // <0 - use mechanicalFocusingModel z, tx, ty
                            ms.sampleIndex, ms.motors, // 3 motor coordinates
                            ms.px, // pixel x
                            ms.py, // pixel y
                            derivs);
                } else {
                    subData = fieldFitting.getValsDerivatives(
                            //               null, // double [] zTxTy, // null - use mechanicalFocusingModel data
                            //FIXME: - ***************** here needs actual index **************************                
                            //                     ms.measurementIndex, //-1, // <0 - use mechanicalFocusingModel z, tx, ty
                            ms.sampleIndex, sagittalMaster, ms.motors, // 3 motor coordinates
                            ms.px, // pixel x
                            ms.py, // pixel y
                            derivs);
                    prevTimeStamp = ms.timestamp;
                    prevPx = ms.px;
                    prevPy = ms.py;
                }
            }
            fx[n] = subData[selChanIndices[ms.channel]];

            if (createJacobian) {
                double[] thisDerivs = derivs[selChanIndices[ms.channel]];
                if (useZTxTy) {
                    for (int i = 0; i < numPars; i++)
                        jacobian[i][n] = 0.0;
                    for (int i = 0; i < thisDerivs.length; i++) {
                        int parIndex = fieldFitting.getZTMap(ms.measurementIndex, i); //n);
                        if (parIndex >= 0)
                            jacobian[parIndex][n] = thisDerivs[i];
                    }
                } else {
                    //         for (int i=0;i<numRegPars;i++){
                    if ((debugLevel > 1) && (debugParameter >= 0)) {
                        if ((debugParameter < thisDerivs.length) && (thisDerivs[debugParameter] != 0.0)) {
                            System.out.println("createFXandJacobian(): n=" + n + " channel=" + ms.channel
                                    + " chn. index=" + selChanIndices[ms.channel] + ", sample=" + ms.sampleIndex
                                    + " timestamp=" + ms.timestamp + " derivative=" + thisDerivs[debugParameter]);
                        }
                    }

                    // contains derivatives for normal and correction parameters
                    for (int i = 0; i < numPars; i++) {
                        //             jacobian[i][n]=derivs[selChanIndices[ms.channel]][i];
                        jacobian[i][n] = thisDerivs[i];
                    }
                    //TODO: correct /dpx, /dpy to compensate for measured S,T calcualtion
                    boolean[] centerSelect = fieldFitting.getCenterSelect();
                    if (correct_measurement_ST && (centerSelect[0] || centerSelect[1])) { // do not do that if both X and Y are disabled
                        int np = 0;
                        for (int i = 0; i < 2; i++)
                            if (centerSelect[i]) {
                                jacobian[np++][n] -= ms.dPxyc[i]; // subtract, as effect is opposite to fX
                            }
                    }
                }
            }
        }
        if (createJacobian && !useZTxTy) {
            // add mutual dependence of correction parameters. first - values (fx)
            //      System.out.println("Using sampleCorrVector 1");
            int index = dataVector.length; // add to the end of vector
            if (fieldFitting.sampleCorrChnParIndex != null) {
                int numSamples = getNumSamples();
                for (int chn = 0; chn < fieldFitting.sampleCorrChnParIndex.length; chn++)
                    if (fieldFitting.sampleCorrChnParIndex[chn] != null) {
                        for (int np = 0; np < fieldFitting.sampleCorrChnParIndex[chn].length; np++) {
                            int pindex = fieldFitting.sampleCorrChnParIndex[chn][np];
                            if (pindex >= 0) {
                                for (int i = 0; i < numSamples; i++) {
                                    double f = 0.0;
                                    for (int j = 0; j < numSamples; j++) {
                                        f += fieldFitting.sampleCorrVector[pindex + j]
                                                * fieldFitting.sampleCorrCrossWeights[chn][np][i][j];
                                    }
                                    fx[index] = f;
                                    //                                 f+=fieldFitting.sampleCorrVector[pindex+i]
                                    if (createJacobian) {
                                        for (int j = 0; j < numSamples; j++) {
                                            jacobian[numRegPars + pindex
                                                    + j][index] = fieldFitting.sampleCorrCrossWeights[chn][np][i][j];
                                        }
                                    }
                                    index++;
                                }
                            }
                        }
                    }
            }
        }
        if (createJacobian && (debugLevel > 1)) {
            if (debugPoint >= 0)
                debugJacobianPoint(debugPoint);
            if (debugParameter >= 0)
                debugJacobianParameter(debugParameter);
        }
        return fx;
    }

    public void debugJacobianPoint(int nPoint) {
        System.out.println("==== Non-zero parameters on which point #" + nPoint + " depends:");
        if (nPoint >= jacobian[0].length) {
            System.out.println("Jacobian is defined for " + jacobian[0].length + " points only");
            return;
        }
        for (int i = 0; i < jacobian.length; i++) {
            if (jacobian[i][nPoint] != 0.0) {
                String name = (debugParameterNames == null) ? "" : debugParameterNames[i];
                System.out.println(i + ": " + name + " = " + jacobian[i][nPoint]);
            }
        }
    }

    public void debugJacobianParameter(int nPar) {
        String name = (debugParameterNames == null) ? "" : debugParameterNames[nPar];
        System.out.println("==== points that depend on parameter #" + nPar + ": " + name + " :");
        if (nPar >= jacobian.length) {
            System.out.println("Jacobian is defined for " + jacobian.length + " parameters only");
            return;
        }
        String[] corrNames = fieldFitting.getCorrNames();
        for (int i = 0; i < jacobian[nPar].length; i++) {
            if (jacobian[nPar][i] != 0.0) {
                int nMeasPoints = dataVector.length;
                String pointName = "";
                if (i < nMeasPoints) {
                    pointName = "chn" + dataVector[i].channel + ":" + dataVector[i].sampleIndex + "__"
                            + dataVector[i].timestamp;
                } else {
                    int overData = i - nMeasPoints;
                    if ((corrNames != null) && (overData < corrNames.length)) {
                        pointName = "correction_parameter-" + corrNames[overData];
                    } else {
                        pointName = "unknown-point_+" + overData;
                    }
                }
                System.out.println(i + ": " + jacobian[nPar][i] + "    " + pointName);
            }
        }
    }

    public double getRMS(double[] fx, boolean pure) {
        int len = pure ? dataVector.length : fx.length;
        double sum = 0.0;
        double sum_w = 0.0;
        if (dataWeights != null) {
            for (int i = 0; i < len; i++) {
                double d = fx[i] - dataValues[i];
                sum += dataWeights[i] * d * d;
                sum_w += dataWeights[i];
            }
        } else {
            for (int i = 0; i < len; i++) {
                double d = fx[i] - dataValues[i];
                sum += d * d;
                sum_w += 1.0;
            }
        }
        if (sum_w > 0) {
            sum /= sum_w;
        }
        return Math.sqrt(sum);
    }

    public ArrayList<FocusingFieldMeasurement> singleMeasurement(FocusingFieldMeasurement measurement) {
        ArrayList<FocusingFieldMeasurement> sm = new ArrayList<FocusingFieldMeasurement>();
        sm.add(measurement);
        return sm;

    }

    public MeasuredSample[] createDataVector(FocusingFieldMeasurement measurement) {
        return createDataVector(singleMeasurement(measurement));
    }

    public MeasuredSample[] createDataVector(ArrayList<FocusingFieldMeasurement> measurements) {
        return createDataVector(measurements, false, // calibrate
                true, // update selection
                currentPX0, // ignored
                currentPY0, // ignored
                (this.useMinMeas ? this.minMeas : null), // pixels
                (this.useMaxMeas ? this.maxMeas : null), // pixels
                (this.useThresholdMax ? this.thresholdMax : null)); // pixels
    }

    public MeasuredSample[] createDataVector() {
        return createDataVector(true, // boolean updateSelection,
                currentPX0, // double centerPX,
                currentPY0); //double centerPY
    }

    public MeasuredSample[] createDataVector(boolean updateSelection, double centerPX, double centerPY) { // use this data
        return createDataVector(measurements, true, // calibrate
                updateSelection, centerPX, centerPY, (this.useMinMeas ? this.minMeas : null), // pixels
                (this.useMaxMeas ? this.maxMeas : null), // pixels
                (this.useThresholdMax ? this.thresholdMax : null)); // pixels
    }

    /**
    * Generate of usable measurement samples
    * @param minMeas minimal measurement PSF radius (in pixels) - correction that increases result
    * resolution in "sharp" areas (to compensate for measurement error). Individual for each of
    * the 6 color/direction components.
    * @param updateSelection when true - updates selection of "good" samples, when false - reuses existent one
    * @param maxMeas maximal measurement PSF radius (in pixels) - correction that decreases result
    * resolution out-of-focus areas to compensate for the limited size of the PSF window.
    * Individual for each of the 6 color/direction components.
    * @param thresholdMax maximal PSF radius to consider data usable
    * @return array of the MeasuredSample instances, including motors, PSF radius, channel and value
    */
    /*
    Need to find partial derivatives of each of the 3 coefficients: c2, s2 and cs for both px0 and py0
    r2 = (x-x0)^2+ (y-y0)^2
        
    c2=(x-x0)^2/r2
    s2=(y-y0)^2/r2
    cs=(x-x0)*(y-y0)/r2
    (p/q)'=(p'*q-q'*p)/q^2
        
    d_r2/d_x0=-2*(x-x0)
    d_r2/d_y0=-2*(y-y0)
        
    d_c2/d_x0=(2*(x-x0)*(-1)*r2 - ((d_r2/d_x0)*(x-x0)^2))/r2^2=
    (-2*(x-x0)*r2 + (2*(x-x0)*(x-x0)^2))/r2^2=
    2*delta_x*(delta_x^2 - r2)/r2^2
        
    d_c2/d_y0= (0-((d_r2/d_y0)*(x-x0)^2))/r2^2=
    ((2*(y-y0))*(x-x0)^2)/r2^2=
    2*delta_y*delta_x^2/r2^2
        
    d_cs/dx0=(-(y-y0)*r2- ((d_r2/d_x0)*(x-x0)*(y-y0)))/r2^2=
    (-(y-y0)*r2 + 2*(x-x0)*(x-x0)*(y-y0))/r2^2=
    (-delta_y*r2 + 2*delta_x^2*delta_y)/r2^2=
    delta_y*(2*delta_x^2-r2)/r2^2
        
        
    d_c2/d_x0= 2*delta_x*(delta_x^2 - r2)/r2^2
    d_c2/d_y0= 2*delta_y*delta_x^2/r2^2
        
    d_s2/d_y0= 2*delta_y*(delta_y^2 - r2)/r2^2
    d_s2/d_x0= 2*delta_x*delta_y^2/r2^2
        
    d_cs/dx0= delta_y*(2*delta_x^2-r2)/r2^2
    d_cs/dy0= delta_x*(2*delta_y^2-r2)/r2^2
        
    */
    public MeasuredSample[] createDataVector(ArrayList<FocusingFieldMeasurement> measurements, boolean calibrate, // false - adjust, should have updateSelection==true and a single-element measurements list
            boolean updateSelection, double centerPX, double centerPY, double[] minMeas, // pixels
            double[] maxMeas, // pixels
            double[] thresholdMax) { // pixels
        debugDerivatives = debugLevel == 3;
        if (calibrate) {
            currentPX0 = centerPX;
            currentPY0 = centerPY;
        }
        final int numColors = 3;
        final int numDirs = 2;
        if (sampleMask == null)
            updateSelection = true;
        if (updateSelection) {
            if (debugLevel > 1)
                System.out.println("createDataVector(true," + centerPX + "," + centerPY + "...)");
            sampleMask = new boolean[measurements
                    .size()][sampleCoord.length][sampleCoord[0].length][numColors][numDirs];
            for (int n = 0; n < sampleMask.length; n++)
                for (int i = 0; i < sampleMask[n].length; i++)
                    for (int j = 0; j < sampleMask[n][i].length; j++)
                        for (int c = 0; c < numColors; c++)
                            for (int d = 0; d < numDirs; d++)
                                sampleMask[n][i][j][c][d] = false;

        }
        /*
        d_c2/d_x0= 2*delta_x*(delta_x^2 - r2)/r2^2
        d_c2/d_y0= 2*delta_y*delta_x^2/r2^2
            
        d_s2/d_y0= 2*delta_y*(delta_y^2 - r2)/r2^2
        d_s2/d_x0= 2*delta_x*delta_y^2/r2^2
            
        2*d_cs/dx0= 2*delta_y*(2*delta_x^2-r2)/r2^2
        2*d_cs/dy0= 2*delta_x*(2*delta_y^2-r2)/r2^2
         */
        double[][][] cosSin2Tab = new double[sampleCoord.length][sampleCoord[0].length][3];
        double[][][] debugCosSin2Tab_dx = null;
        double[][][] debugCosSin2Tab_dy = null;
        double debugDelta_x_dx = 0, debugDelta_y_dy = 0, debugR2_dx = 0, debugR2_dy = 0;
        if (debugDerivatives) {
            debugCosSin2Tab_dx = new double[sampleCoord.length][sampleCoord[0].length][3];
            debugCosSin2Tab_dy = new double[sampleCoord.length][sampleCoord[0].length][3];

        }
        double[][][] cosSin2Tab_dx0 = new double[sampleCoord.length][sampleCoord[0].length][3];
        double[][][] cosSin2Tab_dy0 = new double[sampleCoord.length][sampleCoord[0].length][3];
        for (int i = 0; i < sampleCoord.length; i++)
            for (int j = 0; j < sampleCoord[i].length; j++) {
                double delta_x = sampleCoord[i][j][0] - currentPX0;
                double delta_y = sampleCoord[i][j][1] - currentPY0;
                double r2 = delta_x * delta_x + delta_y * delta_y;
                double r4 = r2 * r2;
                if (debugDerivatives) {
                    debugDelta_x_dx = sampleCoord[i][j][0] - currentPX0 - 1;
                    debugDelta_y_dy = sampleCoord[i][j][1] - currentPY0 - 1;
                    debugR2_dx = debugDelta_x_dx * debugDelta_x_dx + delta_y * delta_y;
                    debugR2_dy = delta_x * delta_x + debugDelta_y_dy * debugDelta_y_dy;

                }
                if (r2 > 0.0) {
                    cosSin2Tab[i][j][0] = delta_x * delta_x / r2; // cos^2
                    cosSin2Tab[i][j][1] = delta_y * delta_y / r2; // sin^2
                    cosSin2Tab[i][j][2] = 2 * delta_x * delta_y / r2; // 2*cos*sin

                    cosSin2Tab_dx0[i][j][0] = 2 * delta_x * (delta_x * delta_x - r2) / r4; // d(cos^2)/d(x0)
                    cosSin2Tab_dx0[i][j][1] = 2 * delta_x * delta_y * delta_y / r4; // d(sin^2)/d(x0)
                    cosSin2Tab_dx0[i][j][2] = 2 * delta_y * (2 * delta_x * delta_x - r2) / r4; // d(2*cos*sin)/d(x0)

                    cosSin2Tab_dy0[i][j][0] = 2 * delta_y * delta_x * delta_x / r4; // d(cos^2)/d(y0)
                    cosSin2Tab_dy0[i][j][1] = 2 * delta_y * (delta_y * delta_y - r2) / r4; // d(sin^2)/d(y0)
                    cosSin2Tab_dy0[i][j][2] = 2 * delta_x * (2 * delta_y * delta_y - r2) / r4; // d(2*cos*sin)/d(y0)
                    if (debugDerivatives) {
                        debugCosSin2Tab_dx[i][j][0] = debugDelta_x_dx * debugDelta_x_dx / debugR2_dx; // cos^2
                        debugCosSin2Tab_dx[i][j][1] = delta_y * delta_y / debugR2_dx; // sin^2
                        debugCosSin2Tab_dx[i][j][2] = 2 * debugDelta_x_dx * delta_y / debugR2_dx; // 2*cos*sin

                        debugCosSin2Tab_dy[i][j][0] = delta_x * delta_x / debugR2_dy; // cos^2
                        debugCosSin2Tab_dy[i][j][1] = debugDelta_y_dy * debugDelta_y_dy / debugR2_dy; // sin^2
                        debugCosSin2Tab_dy[i][j][2] = 2 * delta_x * debugDelta_y_dy / debugR2_dy; // 2*cos*sin
                    }

                } else {
                    cosSin2Tab[i][j][0] = 1.0;
                    cosSin2Tab[i][j][1] = 0.0;
                    cosSin2Tab[i][j][2] = 0.0;

                    cosSin2Tab_dx0[i][j][0] = 0.0;
                    cosSin2Tab_dx0[i][j][1] = 0.0;
                    cosSin2Tab_dx0[i][j][2] = 0.0;

                    cosSin2Tab_dy0[i][j][0] = 0.0;
                    cosSin2Tab_dy0[i][j][1] = 0.0;
                    cosSin2Tab_dy0[i][j][2] = 0.0;
                    if (debugDerivatives) {
                        debugCosSin2Tab_dx[i][j][0] = 0.0;
                        debugCosSin2Tab_dx[i][j][1] = 0.0;
                        debugCosSin2Tab_dx[i][j][2] = 0.0;

                        debugCosSin2Tab_dy[i][j][0] = 0.0;
                        debugCosSin2Tab_dy[i][j][1] = 0.0;
                        debugCosSin2Tab_dy[i][j][2] = 0.0;
                    }
                }
            }

        ArrayList<MeasuredSample> sampleList = new ArrayList<MeasuredSample>();
        if (thresholdMax != null) {
            weightReference = thresholdMax.clone();
            for (int c = 0; c < weightReference.length; c++) {
                // correct for for minimal measurement;
                if (minMeas != null) {
                    if (weightReference[c] < minMeas[c]) {
                        weightReference[c] = 0; // do not use
                        System.out.println(
                                "Weight reference calculation failed (below minimal), all samples may only have the same weight");
                        weightReference = null;
                        break;
                    }
                    weightReference[c] = Math
                            .sqrt(weightReference[c] * weightReference[c] - minMeas[c] * minMeas[c]);
                }
                // correct for for maximal measurement;
                if (maxMeas != null) {
                    if (weightReference[c] >= maxMeas[c]) {
                        weightReference[c] = 0; // do not use
                        System.out.println(
                                "Weight reference calculation failed (above maximal), all samples may only have the same weight");
                        weightReference = null;
                        break;
                    }
                    weightReference[c] = 1.0 / Math.sqrt(
                            1.0 / (weightReference[c] * weightReference[c]) - 1.0 / (maxMeas[c] * maxMeas[c]));
                }
                // convert to microns from pixels
                weightReference[c] *= getPixelUM();
                if (debugLevel > 1)
                    System.out.println("==== weightReference[" + c + "]=" + weightReference[c]);
            }
        } else {
            thresholdMax = null;
        }
        int nMeas = 0;
        for (FocusingFieldMeasurement ffm : measurements) {
            double[][][][] samples = ffm.samples;
            if (samples != null)
                for (int i = 0; i < sampleCoord.length; i++) {
                    if ((i < samples.length) && (samples[i] != null))
                        for (int j = 0; j < sampleCoord[i].length; j++) {
                            if ((j < samples[i].length) && (samples[i][j] != null))
                                for (int c = 0; c < numColors; c++) {

                                    if ((c < samples[i][j].length) && (samples[i][j][c] != null)) {
                                        double[] sagTan = {
                                                Math.sqrt(cosSin2Tab[i][j][0] * samples[i][j][c][0]
                                                        + cosSin2Tab[i][j][1] * samples[i][j][c][1]
                                                        + cosSin2Tab[i][j][2] * samples[i][j][c][2]),
                                                Math.sqrt(cosSin2Tab[i][j][1] * samples[i][j][c][0]
                                                        + cosSin2Tab[i][j][0] * samples[i][j][c][1]
                                                        - cosSin2Tab[i][j][2] * samples[i][j][c][2]) };
                                        double[] debugSagTan_dx = { 0.0, 0.0 }, debugSagTan_dy = { 0.0, 0.0 };
                                        if (debugDerivatives) {
                                            debugSagTan_dx[0] = Math
                                                    .sqrt(debugCosSin2Tab_dx[i][j][0] * samples[i][j][c][0]
                                                            + debugCosSin2Tab_dx[i][j][1] * samples[i][j][c][1]
                                                            + debugCosSin2Tab_dx[i][j][2] * samples[i][j][c][2]);
                                            debugSagTan_dx[1] = Math
                                                    .sqrt(debugCosSin2Tab_dx[i][j][1] * samples[i][j][c][0]
                                                            + debugCosSin2Tab_dx[i][j][0] * samples[i][j][c][1]
                                                            - debugCosSin2Tab_dx[i][j][2] * samples[i][j][c][2]);
                                            debugSagTan_dy[0] = Math
                                                    .sqrt(debugCosSin2Tab_dy[i][j][0] * samples[i][j][c][0]
                                                            + debugCosSin2Tab_dy[i][j][1] * samples[i][j][c][1]
                                                            + debugCosSin2Tab_dy[i][j][2] * samples[i][j][c][2]);
                                            debugSagTan_dy[1] = Math
                                                    .sqrt(debugCosSin2Tab_dy[i][j][1] * samples[i][j][c][0]
                                                            + debugCosSin2Tab_dy[i][j][0] * samples[i][j][c][1]
                                                            - debugCosSin2Tab_dy[i][j][2] * samples[i][j][c][2]);
                                        }
                                        double[] sagTan_dx0 = {
                                                (0.5 * (cosSin2Tab_dx0[i][j][0] * samples[i][j][c][0]
                                                        + cosSin2Tab_dx0[i][j][1] * samples[i][j][c][1]
                                                        + cosSin2Tab_dx0[i][j][2] * samples[i][j][c][2])
                                                        / sagTan[0]),
                                                (0.5 * (cosSin2Tab_dx0[i][j][1] * samples[i][j][c][0]
                                                        + cosSin2Tab_dx0[i][j][0] * samples[i][j][c][1]
                                                        - cosSin2Tab_dx0[i][j][2] * samples[i][j][c][2])
                                                        / sagTan[1]) };

                                        double[] sagTan_dy0 = {
                                                (0.5 * (cosSin2Tab_dy0[i][j][0] * samples[i][j][c][0]
                                                        + cosSin2Tab_dy0[i][j][1] * samples[i][j][c][1]
                                                        + cosSin2Tab_dy0[i][j][2] * samples[i][j][c][2])
                                                        / sagTan[0]),
                                                (0.5 * (cosSin2Tab_dy0[i][j][1] * samples[i][j][c][0]
                                                        + cosSin2Tab_dy0[i][j][0] * samples[i][j][c][1]
                                                        - cosSin2Tab_dy0[i][j][2] * samples[i][j][c][2])
                                                        / sagTan[1]) };

                                        if (debugLevel > 3)
                                            System.out.print("\n" + ffm.motors[2] + " i= " + i + " j= " + j + " c= "
                                                    + c + " sagTan= " + sagTan[0] + " " + sagTan[1] + " ");
                                        for (int d = 0; d < numDirs; d++) {
                                            if (debugLevel > 3)
                                                System.out.print(" d= " + d + " ");

                                            if (!updateSelection && !sampleMask[nMeas][i][j][c][d])
                                                continue;
                                            int chn = d + numDirs * c;
                                            //                     System.out.println("i="+i+", j="+j+", c="+c+", d="+d);
                                            // saved values are PSF radius, convert to FWHM by multiplying by 2.0
                                            double value = sagTan[d] * 2.0;
                                            double value_dx0 = sagTan_dx0[d] * 2.0;
                                            double value_dy0 = sagTan_dy0[d] * 2.0;
                                            double debugValue_dx0 = 0.0, debugValue_dy0 = 0.0;
                                            if (debugDerivatives) {
                                                debugValue_dx0 = debugSagTan_dx[d] * 2.0;
                                                debugValue_dy0 = debugSagTan_dy[d] * 2.0;
                                            }

                                            boolean dbg = (debugLevel == 3) && (i == 1) && (j == 3);
                                            double dbg_delta_x = sampleCoord[i][j][0] - currentPX0;
                                            double dbg_delta_y = sampleCoord[i][j][1] - currentPY0;

                                            if (dbg)
                                                System.out.print("mot=" + ffm.motors[2] + " dx=" + dbg_delta_x
                                                        + " dy=" + dbg_delta_y);
                                            if (dbg)
                                                System.out.println(" value=" + value + " value_dx0=" + value_dx0
                                                        + " value_dy0=" + value_dy0);
                                            if (dbg)
                                                System.out.println(" value(dx)=" + debugValue_dx0 + " value(dy)="
                                                        + debugValue_dy0 + " debugDiff_dx0="
                                                        + (debugValue_dx0 - value) + " debugDiff_dy0="
                                                        + (debugValue_dy0 - value));
                                            // discard above threshold (in pixels, raw FWHM data)
                                            if (Double.isNaN(value)) {
                                                if (debugLevel > 3)
                                                    System.out.println("samples[" + i + "][" + j + "][" + c + "]["
                                                            + d + "] = Double.NaN, motors[0]=" + ffm.motors[0]);
                                                if (updateSelection)
                                                    continue; // bad measurement
                                            }
                                            if (debugLevel > 3)
                                                System.out.print(" A " + value);
                                            if (thresholdMax != null) {
                                                if (value >= thresholdMax[chn]) {
                                                    if (debugLevel > 3)
                                                        System.out.print(" > " + thresholdMax[chn]);
                                                    if (updateSelection)
                                                        continue; // bad measurement (above threshold)
                                                }
                                            }
                                            if (debugLevel > 3)
                                                System.out.print(" B " + value);
                                            // correct for for minimal measurement;
                                            if (minMeas != null) {
                                                if (value < minMeas[chn]) {
                                                    if (debugLevel > 3)
                                                        System.out.print(" < " + minMeas[chn]);
                                                    if (updateSelection)
                                                        continue; // bad measurement (smaller than correction)
                                                }
                                                double f = value;
                                                value = Math.sqrt(value * value - minMeas[chn] * minMeas[chn]);
                                                value_dx0 *= f / value;
                                                value_dy0 *= f / value;
                                                if (dbg) {
                                                    System.out.println("2. value=" + value + " value_dx0="
                                                            + value_dx0 + " value_dy0=" + value_dy0);
                                                    if (debugDerivatives) {
                                                        debugValue_dx0 = Math.sqrt(debugValue_dx0 * debugValue_dx0
                                                                - minMeas[chn] * minMeas[chn]);
                                                        debugValue_dy0 = Math.sqrt(debugValue_dy0 * debugValue_dy0
                                                                - minMeas[chn] * minMeas[chn]);
                                                        if (dbg)
                                                            System.out.println("2. value(dx)=" + debugValue_dx0
                                                                    + " value(dy)=" + debugValue_dy0
                                                                    + " debugDiff_dx0=" + (debugValue_dx0 - value)
                                                                    + " debugDiff_dy0=" + (debugValue_dy0 - value));
                                                    }
                                                }
                                            }
                                            if (debugLevel > 3)
                                                System.out.print(" C " + value);
                                            // correct for for maximal measurement;
                                            if (maxMeas != null) {
                                                if (value >= maxMeas[chn]) {
                                                    if (debugLevel > 3)
                                                        System.out.print(" > " + maxMeas[chn]);
                                                    if (updateSelection)
                                                        continue; // bad measurement (larger than correction)
                                                }
                                                double f = value;
                                                value = 1.0 / Math.sqrt(1.0 / (value * value)
                                                        - 1.0 / (maxMeas[chn] * maxMeas[chn]));
                                                //                             value_dx0*=1.0/(value*f*f*f);
                                                //                             value_dy0*=1.0/(value*f*f*f);
                                                f = value / f;
                                                f *= f * f;
                                                value_dx0 *= f;
                                                value_dy0 *= f;

                                                if (dbg) {
                                                    System.out.println("3. value=" + value + " value_dx0="
                                                            + value_dx0 + " value_dy0=" + value_dy0);
                                                    if (debugDerivatives) {
                                                        debugValue_dx0 = 1.0
                                                                / Math.sqrt(1.0 / (debugValue_dx0 * debugValue_dx0)
                                                                        - 1.0 / (maxMeas[chn] * maxMeas[chn]));
                                                        debugValue_dy0 = 1.0
                                                                / Math.sqrt(1.0 / (debugValue_dy0 * debugValue_dy0)
                                                                        - 1.0 / (maxMeas[chn] * maxMeas[chn]));
                                                        if (dbg)
                                                            System.out.println("3. value(dx)=" + debugValue_dx0
                                                                    + " value(dy)=" + debugValue_dy0
                                                                    + " debugDiff_dx0=" + (debugValue_dx0 - value)
                                                                    + " debugDiff_dy0=" + (debugValue_dy0 - value));
                                                    }
                                                }
                                            }
                                            if (debugLevel > 3)
                                                System.out.print(" D " + value);
                                            // convert to microns from pixels
                                            value *= getPixelUM();
                                            value_dx0 *= getPixelUM();
                                            value_dy0 *= getPixelUM();
                                            if (dbg) {
                                                System.out.println("4. value=" + value + " value_dx0=" + value_dx0
                                                        + " value_dy0=" + value_dy0);
                                                if (debugDerivatives) {
                                                    debugValue_dx0 *= getPixelUM();
                                                    debugValue_dy0 *= getPixelUM();
                                                    if (dbg)
                                                        System.out.println("4. value(dx)=" + debugValue_dx0
                                                                + " value(dy)=" + debugValue_dy0 + " debugDiff_dx0="
                                                                + (debugValue_dx0 - value) + " debugDiff_dy0="
                                                                + (debugValue_dy0 - value));
                                                }
                                            }

                                            sampleList.add(new MeasuredSample(ffm.motors, ffm.timestamp,
                                                    sampleCoord[i][j][0], // px,
                                                    sampleCoord[i][j][1], // py,
                                                    flattenIndex(i, j), nMeas, chn, value, value_dx0, //double dPxc; // derivative of the value by optical (aberration) center pixel X
                                                    value_dy0, //double dPyc; // derivative of the value by optical (aberration) center pixel Y
                                                    false // scan (scan mode sample)
                                            ));
                                            if (debugLevel > 3)
                                                System.out.print(" E " + value);
                                            if (updateSelection)
                                                sampleMask[nMeas][i][j][c][d] = true;
                                        }
                                    }
                                }
                        }
                }
            nMeas++;
        }
        if (debugLevel > 3)
            System.out.println();
        if (debugLevel > 1)
            System.out.println("createDataVector -> " + sampleList.size() + " elements");
        return sampleList.toArray(new MeasuredSample[0]);
    }

    /**
     * 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.dataValues.clone();
        for (int i = 0; i < result.length; i++)
            result[i] -= fX[i];
        return result;
    }

    public double calcErrorDiffY(double[] fX, boolean pure) {
        int len = pure ? dataVector.length : fX.length;
        //        if (debugLevel>0) System.out.println("debug calcErrorDiffY(): fX.length="+fX.length+", dataVector.length="+dataVector.length);
        double result = 0;
        double sumWeights = 0;
        if (this.dataWeights != null) {
            for (int i = 0; i < len; i++) { //
                double diff = this.dataValues[i] - fX[i];
                result += diff * diff * this.dataWeights[i];
                if (debugLevel > 2)
                    System.out.println("" + i + " fx=" + fX[i] + " data=" + this.dataValues[i] + " diff=" + diff
                            + " w=" + this.dataWeights[i]);
                if (debugLevel > 1) {
                    if (pure) {
                        int chn = dataVector[i].channel;
                        int sample = dataVector[i].sampleIndex;
                        if ((chn == 2) && (sample == 16)) {
                            System.out.println("" + i + " fx=" + fX[i] + " data=" + this.dataValues[i] + " diff="
                                    + diff + " w=" + this.dataWeights[i]);
                        }
                    }
                }
                sumWeights += this.dataWeights[i];
            }
            if (sumWeights > 0)
                result /= sumWeights;
        } else {
            for (int i = 0; i < len; i++) {
                double diff = this.dataValues[i] - fX[i];
                result += diff * diff;
            }
            result /= fX.length;
        }
        return Math.sqrt(result);
    }

    public void printSetRMS(double[] fX) {
        //        int [] indices=getSetIndices();
        Integer[] indices = getSetIndices().toArray(new Integer[0]); // uses dataVector;
        double[] setRMA = calcErrorsPerSet(fX);
        for (int numSet = 0; numSet < indices.length; numSet++) {
            System.out.println(numSet + " " + IJ.d2s(setRMA[numSet], 3) + " "
                    + dataVector[indices[numSet]].motors[0] + ":" + dataVector[indices[numSet]].motors[1] + ":"
                    + dataVector[indices[numSet]].motors[2] + " " + dataVector[indices[numSet]].timestamp);
        }
    }

    public double[] calcErrorsPerSet(double[] fX) {
        //        int [] indices=getSetIndices();
        Integer[] indices = getSetIndices().toArray(new Integer[0]); // uses dataVector;
        double[] setRMS = new double[indices.length];
        double[] weights = this.dataWeights;
        if (weights == null) {
            weights = new double[fX.length];
            for (int i = 0; i < weights.length; i++)
                weights[i] = 1.0;
        }
        for (int numSet = 0; numSet < indices.length; numSet++) {
            int nextIndex = (numSet == (indices.length - 1) ? dataVector.length : indices[numSet + 1]);
            double result = 0;
            double sumWeights = 0;
            for (int i = indices[numSet]; i < nextIndex; i++) {
                double diff = this.dataValues[i] - fX[i];
                result += diff * diff * weights[i];
                sumWeights += weights[i];
            }
            if (sumWeights > 0)
                result /= sumWeights;
            setRMS[numSet] = Math.sqrt(result);
        }
        return setRMS;
    }

    public int[] getSetIndices_old() {
        String lastTimestamp = "";
        int numMeas = 0;
        for (int i = 0; i < dataVector.length; i++) {
            if (!dataVector[i].timestamp.equals(lastTimestamp)) {
                lastTimestamp = dataVector[i].timestamp;
                numMeas++;
            }
        }
        int[] indices = new int[numMeas];
        numMeas = 0;
        for (int i = 0; i < dataVector.length; i++) {
            if (!dataVector[i].timestamp.equals(lastTimestamp)) {
                lastTimestamp = dataVector[i].timestamp;
                indices[numMeas++] = i;
            }
        }
        return indices;
    }

    public ArrayList<Integer> getSetSampleIndices() { // indices of data with anything new but channel (maximum - 6 increment)
        String prevTimeStamp = "";
        int prevMeasurementIndex = -1;
        double prevPx = -1, prevPy = -1;
        final ArrayList<Integer> measIndicesList = new ArrayList<Integer>(dataVector.length / getNumChannels());
        for (int n = 0; n < dataVector.length; n++) {
            MeasuredSample ms = dataVector[n];
            if ((ms.measurementIndex != prevMeasurementIndex) || !ms.timestamp.equals(prevTimeStamp)
                    || (ms.px != prevPx) || (ms.py != prevPy)) {
                measIndicesList.add(new Integer(n));
                prevMeasurementIndex = ms.measurementIndex;
                prevTimeStamp = ms.timestamp;
                prevPx = ms.px;
                prevPy = ms.py;
            }
        }
        return measIndicesList;
    }

    public ArrayList<Integer> getSetIndices() {
        String prevTimeStamp = "";
        int prevMeasurementIndex = -1;
        //       double prevPx=-1,prevPy=-1;
        final ArrayList<Integer> measIndicesList = new ArrayList<Integer>(dataVector.length / getNumChannels());
        for (int n = 0; n < dataVector.length; n++) {
            MeasuredSample ms = dataVector[n];
            //          if (!ms.timestamp.equals(prevTimeStamp) || (ms.px!=prevPx) || (ms.py!=prevPy)){
            if ((ms.measurementIndex != prevMeasurementIndex) || !ms.timestamp.equals(prevTimeStamp)) {
                measIndicesList.add(new Integer(n));
                prevMeasurementIndex = ms.measurementIndex;
                prevTimeStamp = ms.timestamp;
                //             prevPx=ms.px;
                //             prevPy=ms.py;
            }
        }
        return measIndicesList;
    }

    public boolean[] getMeasurementsMask() {
        ArrayList<Integer> indices = getSetIndices(); // uses dataVector;
        boolean[] en = dataWeightsToBoolean();
        boolean[] measMask = new boolean[indices.size()];
        for (int i = 0; i < measMask.length; i++) {
            int startIndex = indices.get(i);
            int endIndex = (i < (measMask.length - 1)) ? indices.get(i + 1) : en.length;
            measMask[i] = false;
            for (int index = startIndex; index < endIndex; index++)
                measMask[i] |= en[index];
        }
        return measMask;
    }

    public LMAArrays calculateJacobianArrays(double[] fX) {
        if (multiJacobian && (threadsMax > 0))
            return calculateJacobianArraysMulti(fX);
        else
            return calculateJacobianArraysSingle(fX);
    }

    public LMAArrays calculateJacobianArraysSingle(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.dataWeights != null)
                    for (int k = 0; k < length; k++)
                        JtByJmod[i][j] += this.jacobian[i][k] * this.jacobian[j][k] * this.dataWeights[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
            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.dataWeights != null)
                for (int k = 0; k < length; k++)
                    JtByDiff[i] += this.jacobian[i][k] * diff[k] * this.dataWeights[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;
    }

    public LMAArrays calculateJacobianArraysMulti(double[] fX) {
        // calculate JtJ
        final double[] diff = calcYminusFx(fX);
        final int numPars = this.jacobian.length; // number of parameters to be adjusted
        //       int length=diff.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];

        final double[] fWeights = this.dataWeights;
        //       final double [][] fJacobian=this.jacobian;
        final AtomicInteger lineAtomic = 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 line = lineAtomic.getAndIncrement(); line < numPars; line = lineAtomic
                            .getAndIncrement()) {
                        double[] sLine = jacobian[line];
                        if (fWeights != null) {
                            sLine = jacobian[line].clone();
                            for (int i = 0; i < sLine.length; i++)
                                sLine[i] *= fWeights[i];
                        }
                        for (int line2 = line; line2 < numPars; line2++) {
                            double d = 0;
                            for (int i = 0; i < sLine.length; i++)
                                if (sLine[i] != 0.0) {
                                    d += sLine[i] * jacobian[line2][i];
                                }
                            JtByJmod[line][line2] = d;
                        }
                        double d = 0;
                        for (int i = 0; i < sLine.length; i++)
                            if (sLine[i] != 0.0) {
                                d += sLine[i] * diff[i];
                            }
                        JtByDiff[line] = d;
                    }
                } // public void run() {
            };
        }
        startAndJoin(threads);
        /*         
               for (int i=0;i<numPars;i++) for (int j=i;j<numPars;j++){
                  JtByJmod[i][j]=0.0;
                  if (this.dataWeights!=null)
         for (int k=0;k<length;k++) JtByJmod[i][j]+=this.jacobian[i][k]*this.jacobian[j][k]*this.dataWeights[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
            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.dataWeights!=null)
         for (int k=0;k<length;k++) JtByDiff[i]+=this.jacobian[i][k]*diff[k]*this.dataWeights[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;
    }

    public double[] solveLMA(LMAArrays lMAArrays, double lambda, int debugLevel) {
        this.debugLevel = debugLevel;
        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);
        if (debugLevel > 2) {
            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();
    }

    public void compareDrDerivatives(double[] vector) {
        double delta = 0.00010; // make configurable
        boolean[] centerSelect = fieldFitting.getCenterSelect();
        if (centerSelect[0] && centerSelect[1])
            delta = 1.0;
        if (debugParameter >= 0) {
            String parName = "";
            if ((debugParameterNames != null) && (debugParameterNames.length > debugParameter))
                parName = debugParameterNames[debugParameter];
            System.out.println("Debugging derivatives for parameter #" + debugParameter + " (" + parName + ")");
            //debugParameterNames
            double[] vector_dp = vector.clone();
            vector_dp[debugParameter] += delta;
            double[] fx_dp = createFXandJacobian(vector_dp, false);
            double[] fx = createFXandJacobian(vector, true);
            for (int i = 0; i < fx.length; i++) {
                if ((debugPoint >= 0) && (debugPoint != i))
                    continue; // debug only single point
                int nMeasPoints = dataVector.length;
                String pointName = "";
                if (i < nMeasPoints) {
                    pointName = "chn" + dataVector[i].channel + ":" + dataVector[i].sampleIndex + "__"
                            + dataVector[i].timestamp;
                    //             } else {
                    //                int overData=i-nMeasPoints;
                    //                if ((corrNames!=null) && (overData< corrNames.length)){
                    //                   pointName="correction_parameter-"+corrNames[overData];
                    //                } else {
                    //                   pointName="unknown-point_+"+overData;
                    //                }
                }
                System.out.println(i + ": " + pointName + " fx= " + fx[i] + " delta_fx= "
                        + ((fx_dp[i] - fx[i]) / delta) + " df/dp= " + jacobian[debugParameter][i]);
            }
        }
        /*      
                boolean [] centerSelect=fieldFitting.getCenterSelect();
                if (!centerSelect[0] || !centerSelect[1]){
        System.out.println("compareDrDerivatives(): Both px0 and px1 parameters should be enabled, aborting");
        return;
                }
                double [] vector_dx=vector.clone();
                double [] vector_dy=vector.clone();
                vector_dx[0]+=1.0;
                vector_dy[1]+=1.0;
             double [] fx_dx=createFXandJacobian(vector_dx,false);
             double [] fx_dy=createFXandJacobian(vector_dy,false);
             double [] fx= createFXandJacobian(vector,true);
             for (int i=0;i<fx.length;i++){
                 System.out.println(i+" fx= "+fx[i]+" delta_fx= "+(fx_dx[i]-fx[i])+" delta_fy= "+(fx_dy[i]-fx[i]));
                 System.out.println(" df/dx= "+jacobian[0][i]+" df/dy= "+jacobian[1][i]);
                     
             }
        */
    }

    /**
     * Calculates next parameters vector, holds some arrays
     * @param numSeries
     * @return array of two booleans: { improved, finished}
     */
    public boolean[] stepLevenbergMarquardtFirst(int debugLevel) {
        double[] deltas = null;
        if (this.currentVector == null) {
            this.currentVector = this.savedVector.clone();
            this.currentRMS = -1;
            this.currentfX = null; // invalidate
            this.jacobian = null; // invalidate
            this.lMAArrays = null;
            lastImprovements[0] = -1.0;
            lastImprovements[1] = -1.0;
        }
        this.debugLevel = debugLevel;
        // calculate this.currentfX, this.jacobian if needed
        if (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)) {
            String msg = "initial Jacobian matrix calculation. Points:" + this.dataValues.length + " Parameters:"
                    + this.currentVector.length;
            if (debugLevel > 0)
                System.out.println(msg);
            if (this.updateStatus)
                IJ.showStatus(msg);
            if (debugLevel > 1)
                System.out.println("*** 1 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
            this.currentfX = createFXandJacobian(this.currentVector, true); // is it always true here (this.jacobian==null)
            if (debugLevel > 1)
                System.out.println("*** 2 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
            this.lMAArrays = calculateJacobianArrays(this.currentfX);
            if (debugLevel > 1)
                System.out.println("*** 3 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
            this.currentRMS = calcErrorDiffY(this.currentfX, false);
            if (debugLevel > 1)
                System.out.println("*** 4 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
            this.currentRMSPure = calcErrorDiffY(this.currentfX, true);
            if (debugLevel > 1)
                System.out.println("*** 5 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
            msg = this.currentStrategyStep + ": initial RMS=" + IJ.d2s(this.currentRMS, 8) + " (pure RMS="
                    + IJ.d2s(this.currentRMSPure, 8) + ")" + ". Calculating next Jacobian. Points:"
                    + this.dataValues.length + " Parameters:" + this.currentVector.length;
            if (debugLevel > 1)
                System.out.println(msg);
            if (this.updateStatus)
                IJ.showStatus(msg);
        } else {
            // continuing, but need to re-build Y vector to match this.currentVector
            commitParameterVector(this.currentVector); // may be extra job?
            this.currentRMS = calcErrorDiffY(this.currentfX, false); // Verify - currentfX is correct, but Y vector is modified! Yes, it is so
            this.currentRMSPure = calcErrorDiffY(this.currentfX, true);
        }
        if (this.firstRMS < 0) {
            this.firstRMS = this.currentRMS;
            this.firstRMSPure = this.currentRMSPure;
        }

        deltas = solveLMA(this.lMAArrays, this.lambda, debugLevel);

        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 (debugLevel > 1) {
            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 (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
        if (debugLevel > 1)
            System.out.println("*** 6 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
        this.nextfX = createFXandJacobian(this.nextVector, true);
        if (debugLevel > 1)
            System.out.println("*** 7 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
        this.lMAArrays = calculateJacobianArrays(this.nextfX);
        if (debugLevel > 1)
            System.out.println("*** 8 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
        this.nextRMS = calcErrorDiffY(this.nextfX, false);
        if (debugLevel > 1)
            System.out.println("*** 9 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));
        this.nextRMSPure = calcErrorDiffY(this.nextfX, true);
        if (debugLevel > 1)
            System.out.println("*** 10 @ " + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 5));

        this.lastImprovements[1] = this.lastImprovements[0];
        this.lastImprovements[0] = this.currentRMS - this.nextRMS;
        String msg = "currentRMS=" + this.currentRMS + ", currentRMSPure=" + this.currentRMSPure + ", nextRMS="
                + this.nextRMS + ", nextRMSPure=" + this.nextRMSPure + ", delta="
                + (this.currentRMS - this.nextRMS);
        if (debugLevel > 2)
            System.out.println("stepLMA " + msg);
        if (this.updateStatus)
            IJ.showStatus(msg);
        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;
                status[0] = true;
                status[1] = true;
                this.lastImprovements[0] = 0.0;
                if (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));
                }

            }
        }
        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 (debugLevel > 2) {
            System.out.println("stepLevenbergMarquardtFirst(" + debugLevel + ")=>" + status[0] + "," + status[1]);
        }
        return status;
    }

    public void stepLevenbergMarquardtAction(int debugLevel) {//
        this.iterationStepNumber++;
        // apply/revert,modify lambda 
        String msg = "currentRMS=" + this.currentRMS + ", currentRMSPure=" + this.currentRMSPure + ", nextRMS="
                + this.nextRMS + ", nextRMSPure=" + this.nextRMSPure + ", delta=" + (this.currentRMS - this.nextRMS)
                + ", lambda=" + this.lambda;
        if (debugLevel > 1)
            System.out.println("stepLevenbergMarquardtAction() " + msg);
        //   if (this.updateStatus) IJ.showStatus(msg);
        if (this.nextRMS < this.currentRMS) { //improved
            this.lambda *= this.lambdaStepDown;
            this.currentRMS = this.nextRMS;
            this.currentfX = this.nextfX;
            this.currentVector = this.nextVector;
        } else {
            this.lambda *= this.lambdaStepUp;
            this.lMAArrays = this.savedLMAArrays; // restore Jt*J and Jt*diff
        }
    }

    /**
    * Dialog to select Levenberg-Marquardt algorithm and related parameters
    * @param autoSel - disable default stop, suggest strategy 0
    * @return true if OK, false if canceled
    * 
    */
    public boolean selectLMAParameters(boolean autoSel) {
        //     int numSeries=fittingStrategy.getNumSeries();
        //    boolean resetCorrections=false;
        GenericDialog gd = new GenericDialog(
                "Levenberg-Marquardt algorithm parameters lens aberrations approxiamtion");

        //TODO: change to selection using series comments
        //        gd.addNumericField("Fitting series number", this.currentStrategyStep, 0, 3," (-1 - current)");
        int suggestStep = this.currentStrategyStep;
        boolean suggestStopEachStep = this.stopEachStep;
        if (autoSel) {
            suggestStep = 0;
            suggestStopEachStep = false;
        }
        FieldStrategies fs = fieldFitting.fieldStrategies;
        String[] indices = new String[fs.getNumStrategies() + 1];
        indices[0] = "current strategy";
        for (int i = 0; i < fs.getNumStrategies(); i++) {
            indices[i + 1] = i + ": " + fs.getComment(i) + " (" + (fs.isStopAfterThis(i) ? "STOP" : "CONTINUE")
                    + ")";
        }
        if (suggestStep >= (indices.length - 1))
            suggestStep = indices.length - 2; // last one
        gd.addChoice("Fitting series", indices, indices[suggestStep + 1]);

        gd.addCheckbox("Debug df/dX0, df/dY0", false);
        gd.addNumericField("Debug Jacobian for point number", this.debugPoint, 0, 5, "(-1 - none)");
        gd.addNumericField("Debug Jacobian for parameter number", this.debugParameter, 0, 5, "(-1 - none)");

        //        gd.addCheckbox("Keep current correction parameters (do not reset)", this.keepCorrectionParameters);
        gd.addNumericField("Initial LMA Lambda ", 0.0, 5, 8, "0 - keep, last was " + this.lambda);
        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", suggestStopEachStep); //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 disabled parameters", this.showDisabledParams);
        gd.addCheckbox("Show per-sample correction parameters", this.showCorrectionParams);
        gd.addNumericField("Maximal number of threads (0 - old code)", this.threadsMax, 0);
        //threadsMax

        //        gd.addCheckbox("Reset all per-sample corrections to zero", resetCorrections);

        //        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.currentStrategyStep = gd.getNextChoiceIndex() - 1; //(int) gd.getNextNumber();
        if (this.currentStrategyStep >= 0) {
            getStrategy(this.currentStrategyStep);
        }
        this.debugDerivativesFxDxDy = gd.getNextBoolean();

        debugPoint = (int) gd.getNextNumber();
        debugParameter = (int) gd.getNextNumber();

        //        this.keepCorrectionParameters = gd.getNextBoolean();
        double preLambda = gd.getNextNumber();
        if (preLambda > 0.0)
            this.lambda = preLambda;
        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.showDisabledParams = gd.getNextBoolean();
        this.showCorrectionParams = gd.getNextBoolean();
        this.threadsMax = (int) gd.getNextNumber();
        //    if (!keepCorrectionParameters) fieldFitting.resetSampleCorr();
        return true;
    }

    public void listParameters(String title, String path) {
        String header = "State\tDescription\tValue\tUnits";
        StringBuffer sb = new StringBuffer();
        for (String s : this.fieldFitting.getParameterValueStrings(true, true)) { //this.showDisabledParams)){ //
            sb.append(s + "\n");
        }
        if (path != null) {
            CalibrationFileManagement.saveStringToFile(path, header + "\n" + sb.toString());
        } else {
            new TextWindow(title, header, sb.toString(), 800, 1000);
        }
    }

    public void listData(String title, String path) {
        if ((showSamples == null) || (showSamples.length != sampleCoord.length * sampleCoord[0].length)) {
            showSamples = new boolean[sampleCoord.length * sampleCoord[0].length];
            for (int i = 0; i < showSamples.length; i++)
                showSamples[i] = false;
        }
        GenericDialog gd = new GenericDialog(title);
        gd.addCheckbox("Show motor positions", this.showMotors);
        gd.addCheckbox("Show measured PSF FWHM", this.showMeasCalc[0]);
        gd.addCheckbox("Show calculated PSF FWHM", this.showMeasCalc[1]);
        gd.addCheckbox("Show mechanical distance", this.showMeasCalc[2]);
        gd.addCheckbox("Show RED data", this.showColors[0]);
        gd.addCheckbox("Show GREEN data", this.showColors[1]);
        gd.addCheckbox("Show BLUE data", this.showColors[2]);
        gd.addCheckbox("Show SAGITAL data", this.showDirs[0]);
        gd.addCheckbox("Show TANGENTIAL data", this.showDirs[1]);
        gd.addMessage("Select field samples");
        for (int i = 0; i < sampleCoord.length; i++)
            for (int j = 0; j < sampleCoord[0].length; j++) {
                gd.addCheckbox("Sample X" + j + "Y" + i + " (x=" + sampleCoord[i][j][0] + " y="
                        + sampleCoord[i][j][1] + ")", showSamples[j + i * sampleCoord[0].length]);
            }

        gd.addCheckbox("Show all samples", this.showAllSamples);
        gd.addCheckbox("Show ignored data", this.showIgnoredData);

        gd.addCheckbox("Show sample distance fom the optical center", this.showRad);
        WindowTools.addScrollBars(gd);
        gd.showDialog();
        if (gd.wasCanceled())
            return;
        this.showMotors = gd.getNextBoolean();
        this.showMeasCalc[0] = gd.getNextBoolean();
        this.showMeasCalc[1] = gd.getNextBoolean();
        this.showMeasCalc[2] = gd.getNextBoolean();
        this.showColors[0] = gd.getNextBoolean();
        this.showColors[1] = gd.getNextBoolean();
        this.showColors[2] = gd.getNextBoolean();
        this.showDirs[0] = gd.getNextBoolean();
        this.showDirs[1] = gd.getNextBoolean();
        for (int i = 0; i < sampleCoord.length; i++)
            for (int j = 0; j < sampleCoord[0].length; j++) {
                showSamples[j + i * sampleCoord[0].length] = gd.getNextBoolean();
            }
        this.showAllSamples = gd.getNextBoolean();
        this.showIgnoredData = gd.getNextBoolean();
        this.showRad = gd.getNextBoolean();
        boolean[] localShowSamples = showSamples.clone();
        if (showAllSamples) {
            for (int i = 0; i < localShowSamples.length; i++)
                localShowSamples[i] = true;
        }
        listData(title, path, showMotors, showMeasCalc, showColors, showDirs, localShowSamples, showIgnoredData,
                showRad);
    }

    public void listData(String title, String path, boolean showMotors, boolean[] showMeasCalc,
            boolean[] showColors, boolean[] showDirs, boolean[] showSamples, boolean showIgnoredData,
            boolean showRad) {
        String header = "";
        String[] sMeasCalc = { "M", "C", "Z" };
        String[] sDirs = { "S", "T" };
        String[] sColors = { "R", "G", "B" };
        int numColors = 3;
        int numDirs = 2;
        boolean[] selChannels = fieldFitting.getSelectedChannels();
        int[] selChanIndices = new int[selChannels.length];
        selChanIndices[0] = 0;
        for (int i = 1; i < selChanIndices.length; i++) {
            selChanIndices[i] = selChanIndices[i - 1] + (selChannels[i - 1] ? 1 : 0);
        }
        double[][][] cosSin2Tab = new double[sampleCoord.length][sampleCoord[0].length][3];
        for (int i = 0; i < sampleCoord.length; i++)
            for (int j = 0; j < sampleCoord[i].length; j++) {
                double delta_x = sampleCoord[i][j][0] - currentPX0;
                double delta_y = sampleCoord[i][j][1] - currentPY0;
                double r2 = delta_x * delta_x + delta_y * delta_y;
                if (r2 > 0.0) {
                    cosSin2Tab[i][j][0] = delta_x * delta_x / r2; // cos^2
                    cosSin2Tab[i][j][1] = delta_y * delta_y / r2; // sin^2
                    cosSin2Tab[i][j][2] = 2 * delta_x * delta_y / r2; // 2*cos*sin
                } else {
                    cosSin2Tab[i][j][0] = 1.0;
                    cosSin2Tab[i][j][1] = 0.0;
                    cosSin2Tab[i][j][2] = 0.0;
                }
            }

        StringBuffer sb = new StringBuffer();
        if (showMotors)
            header += "M1\tM2\tM3\t";
        boolean first = true;
        for (int i = 0; i < sampleCoord.length; i++)
            for (int j = 0; j < sampleCoord[i].length; j++)
                if (showSamples[j + i * sampleCoord[i].length]) {
                    if (showMeasCalc[2]) {
                        if (!first)
                            header += "\t";
                        first = false;
                        header += "Y" + i + "X" + j + sMeasCalc[2];
                    }
                    for (int c = 0; c < numColors; c++)
                        if (showColors[c]) {
                            for (int d = 0; d < numDirs; d++)
                                if (showDirs[d]) {
                                    for (int m = 0; m < 2; m++)
                                        if (showMeasCalc[m]) {
                                            if (!first)
                                                header += "\t";
                                            first = false;
                                            header += "Y" + i + "X" + j + sColors[c] + sDirs[d] + sMeasCalc[m];
                                        }
                                }
                        }
                }
        for (FocusingFieldMeasurement ffm : measurements) {
            //        double [][][][] samples=ffm.samples;
            double[][][][] samplesFull = new double[sampleCoord.length][sampleCoord[0].length][numColors][numDirs];
            double[][][][] calcSamples = new double[sampleCoord.length][sampleCoord[0].length][numColors][numDirs];
            double[][] calcZ = new double[sampleCoord.length][sampleCoord[0].length];

            for (int i = 0; i < sampleCoord.length; i++)
                for (int j = 0; j < sampleCoord[0].length; j++) {
                    calcZ[i][j] = Double.NaN;
                    for (int c = 0; c < numColors; c++)
                        for (int d = 0; d < numDirs; d++) {
                            samplesFull[i][j][c][d] = Double.NaN;
                            calcSamples[i][j][c][d] = Double.NaN;
                        }
                }
            //showIgnoredData MeasuredSample [] dataVector
            boolean[] enable = dataWeightsToBoolean();
            for (int index = 0; index < dataVector.length; index++)
                if (ffm.timestamp.equals(dataVector[index].timestamp)
                        && (showIgnoredData || (index >= enable.length) || enable[index])) {
                    int chn = dataVector[index].channel;
                    int sample = dataVector[index].sampleIndex;
                    int sampleRow = sample / sampleCoord[0].length;
                    int sampleCol = sample % sampleCoord[0].length;
                    int color = chn / 2;
                    int dir = chn % 2;
                    samplesFull[sampleRow][sampleCol][color][dir] = dataVector[index].value;
                }
            // still needed if     showIgnoredData!    
            /*        
                    if (showMeasCalc[0] && (samples!=null)) for (int i=0;i<sampleCoord.length;i++){
                       if ((i<samples.length) && (samples[i]!=null)) for (int j=0;j<sampleCoord[i].length;j++){
                          if ((j<samples[i].length) && (samples[i][j]!=null)) for (int c=0;c<numColors;c++){
             if ((c<samples[i][j].length) && (samples[i][j][c]!=null)) {
                double sagTan[] = {
                      Math.sqrt(
                            cosSin2Tab[i][j][0]*samples[i][j][c][0]+
                            cosSin2Tab[i][j][1]*samples[i][j][c][1]+
                            cosSin2Tab[i][j][2]*samples[i][j][c][2]),
                            Math.sqrt(
                                  cosSin2Tab[i][j][1]*samples[i][j][c][0]+
                                  cosSin2Tab[i][j][0]*samples[i][j][c][1]-
                                  cosSin2Tab[i][j][2]*samples[i][j][c][2])};
                if (debugLevel>3) System.out.print("\n"+ffm.motors[2]+" i= "+i+" j= "+j+" c= "+c+" sagTan= "+sagTan[0]+" "+sagTan[1]+" ");
                for (int d=0;d<numDirs;d++){
                   if (debugLevel>3) System.out.print(" d= "+d+" ");
                   int chn=d+numDirs*c;
                   //                     System.out.println("i="+i+", j="+j+", c="+c+", d="+d);
                   // saved values are PSF radius, convert to FWHM by multiplying by 2.0
                   double value=sagTan[d]*2.0;
                   // discard above threshold (in pixels, raw FWHM data)
                   if (Double.isNaN(value)) continue; // bad measurement
                   if (debugLevel>3) System.out.print(" A "+value);
                   if (!showIgnoredData && (thresholdMax != null)){
                      if (value >= thresholdMax[chn]){
                         if (debugLevel>3) System.out.print(" > "+thresholdMax[chn]);
                         continue; // bad measurement (above threshold)
                      }
                   }
                   if (debugLevel>3) System.out.print(" B "+value);
                   // correct for for minimal measurement;
                   if (useMinMeas && (minMeas != null)){
                      if (value<minMeas[chn]) {
                         if (debugLevel>3) System.out.print(" < "+minMeas[chn]);
                         continue; // bad measurement (smaller than correction)
                      }
                      value=Math.sqrt(value*value-minMeas[chn]*minMeas[chn]);
                   }
                   if (debugLevel>3) System.out.print(" C "+value);
                   // correct for for maximal measurement;
                   if (useMaxMeas && (maxMeas != null)) {
                      if (value >= maxMeas[chn]) {
                         if (debugLevel>3) System.out.print(" > "+maxMeas[chn]);
                         continue; // bad measurement (larger than correction)
                      }
                      value = 1.0/Math.sqrt(1.0/(value*value)-1.0/(maxMeas[chn]*maxMeas[chn]));
                   }
                   if (debugLevel>3) System.out.print(" D "+value);
                   // convert to microns from pixels
                   value*=getPixelUM();
                   samplesFull[i][j][c][d]=value;
                   if (debugLevel>3) System.out.print(" E "+value);
                }
             }
                          }
                       }
                    }
            */

            // Now calculate values for the same samples
            if (showMeasCalc[1]) {
                for (int i = 0; i < sampleCoord.length; i++)
                    for (int j = 0; j < sampleCoord[0].length; j++) {

                        if ((i == 0) && (j == 3) && (ffm.motors[0] == 2209)) {
                            System.out.println("listData(), i=" + i + ", j=" + j);
                        }
                        double[] subData = fieldFitting.getValsDerivatives(
                                //                       -1, // <0 - use mechanicalFocusingModel z, tx, ty
                                flattenIndex(i, j), sagittalMaster, // dependent channel does not have center parameters, but that is only used for derivs.
                                ffm.motors, // 3 motor coordinates
                                sampleCoord[i][j][0], // pixel x
                                sampleCoord[i][j][1], // pixel y
                                null);
                        for (int c = 0; c < numColors; c++)
                            for (int d = 0; d < numDirs; d++) {
                                int index = d + c * numDirs;
                                if (selChannels[index])
                                    calcSamples[i][j][c][d] = subData[selChanIndices[index]];
                            }
                    }
            }
            // calculate Z from the mechanical
            if (showMeasCalc[2]) {
                for (int i = 0; i < sampleCoord.length; i++)
                    for (int j = 0; j < sampleCoord[0].length; j++) {
                        calcZ[i][j] = fieldFitting.getMotorsZ(ffm.motors, // 3 motor coordinates
                                sampleCoord[i][j][0], // pixel x
                                sampleCoord[i][j][1]); // pixel y
                    }
            }
            // combine line
            if (showMotors)
                sb.append("" + ffm.motors[0] + "\t" + ffm.motors[1] + "\t" + ffm.motors[2] + "\t");
            first = true;
            for (int i = 0; i < sampleCoord.length; i++)
                for (int j = 0; j < sampleCoord[i].length; j++)
                    if (showSamples[j + i * sampleCoord[i].length]) {
                        if (showMeasCalc[2]) {
                            if (!first)
                                sb.append("\t");
                            first = false;
                            sb.append(calcZ[i][j]);
                        }

                        for (int c = 0; c < showColors.length; c++)
                            if (showColors[c]) {
                                for (int d = 0; d < showDirs.length; d++)
                                    if (showDirs[d]) {
                                        for (int m = 0; m < 2; m++)
                                            if (showMeasCalc[m]) {
                                                if (!first)
                                                    sb.append("\t");
                                                first = false;
                                                if (m == 0)
                                                    sb.append(samplesFull[i][j][c][d]);
                                                else
                                                    sb.append(calcSamples[i][j][c][d]);
                                            }
                                    }
                            }
                    }
            sb.append("\n");
        }

        if (debugLevel > 3)
            System.out.println();

        if (showRad) {
            sb.append(header + "\n");
            if (showMotors)
                sb.append("Sample\tradius\t(mm)\t");
            first = true;
            for (int i = 0; i < sampleCoord.length; i++)
                for (int j = 0; j < sampleCoord[i].length; j++)
                    if (showSamples[j + i * sampleCoord[i].length]) {
                        double rad = fieldFitting.getRadiusMM(sampleCoord[i][j][0], // pixel x
                                sampleCoord[i][j][1]); // pixel y
                        if (showMeasCalc[2]) {
                            if (!first)
                                sb.append("\t");
                            first = false;
                            sb.append(rad);
                        }
                        for (int c = 0; c < numColors; c++)
                            if (showColors[c]) {
                                for (int d = 0; d < numDirs; d++)
                                    if (showDirs[d]) {
                                        for (int m = 0; m < 2; m++)
                                            if (showMeasCalc[m]) {
                                                if (!first)
                                                    sb.append("\t");
                                                first = false;
                                                sb.append(rad);
                                            }
                                    }
                            }
                    }
            sb.append("\n");
        }
        if (path != null) {
            CalibrationFileManagement.saveStringToFile(path, header + "\n" + sb.toString());
        } else {
            new TextWindow(title, header, sb.toString(), 800, 1000);
        }

    }

    public void showCurvCorr() {
        fieldFitting.showCurvCorr("curv_corr");
    }

    public void listCombinedResults() {
        // private boolean rslt_show_z_axial=true;
        // private boolean rslt_show_z_individual=true;
        // private boolean rslt_show_f_axial=true;
        // private boolean rslt_show_f_individual=true;
        // private double rslt_scan_below=-10.0;
        // private double rslt_scan_above= 10.0;
        // private double rslt_scan_step= 5.0;
        // private boolean rslt_mtf50_mode= true;
        // public double fwhm_to_mtf50=500.0; // put actual number

        double[] center_z = fieldFitting.getZCenters(false); // do not solve, use z0 coefficient
        double[] centerFWHM = { fieldFitting.getCalcValuesForZ(center_z[0], 0.0, null)[1],
                fieldFitting.getCalcValuesForZ(center_z[1], 0.0, null)[3],
                fieldFitting.getCalcValuesForZ(center_z[2], 0.0, null)[5] };
        double[] best_qb_axial = fieldFitting.getBestQualB(k_red, k_blue, false);
        double[] best_qb_corr = fieldFitting.getBestQualB(k_red, k_blue, true);
        GenericDialog gd = new GenericDialog("Setup results table FWHM=" + IJ.d2s(best_qb_corr[1], 3) + "um, MTF50="
                + IJ.d2s(fwhm_to_mtf50 / best_qb_corr[1], 2) + " lp/mm");
        gd.addMessage("Best center focus for Red " + IJ.d2s(center_z[0], 3) + " um" + ", FWHM="
                + IJ.d2s(centerFWHM[0], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[0], 2) + " lp/mm");
        gd.addMessage("Best center focus for Green " + IJ.d2s(center_z[1], 3) + " um" + ", FWHM="
                + IJ.d2s(centerFWHM[1], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[1], 2) + " lp/mm");
        gd.addMessage("Best center focus for Blue " + IJ.d2s(center_z[2], 3) + " um" + ", FWHM="
                + IJ.d2s(centerFWHM[2], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[2], 2) + " lp/mm");
        gd.addMessage("Best composite distance for FWHM^4, axial model " + IJ.d2s(best_qb_axial[0], 3) + " um"
                + ", FWHM=" + IJ.d2s(best_qb_axial[1], 3) + "um, MTF50="
                + IJ.d2s(fwhm_to_mtf50 / best_qb_axial[1], 2) + " lp/mm");
        gd.addMessage("Best composite distance for FWHM^4, individual " + IJ.d2s(best_qb_corr[0], 3) + "  um"
                + ", FWHM=" + IJ.d2s(best_qb_corr[1], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / best_qb_corr[1], 2)
                + " lp/mm");
        for (int i = 0; i < rslt_show_chn.length; i++) {
            gd.addCheckbox("Show results for " + fieldFitting.getDescription(i), this.rslt_show_chn[i]);
        }
        gd.addCheckbox("Show best focus distance (axial model)", this.rslt_show_z_axial);
        gd.addCheckbox("Show ring-averaged focus distance", this.rslt_show_z_smooth);
        gd.addCheckbox("Show best focus distance (per-sample adjusted)", this.rslt_show_z_individual);
        gd.addCheckbox("Show constant-z sections (axial model)", this.rslt_show_f_axial);
        gd.addCheckbox("Show ring-averaged per-sample adjusted section data", this.rslt_show_f_smooth);
        gd.addCheckbox("Show constant-z sections (per-sample adjusted)", this.rslt_show_f_individual);
        gd.addNumericField("Ring averaging radial sigma", this.rslt_show_smooth_sigma, 3, 5, "mm");
        gd.addCheckbox("Show mtf50 (false - PSF FWHM)", this.rslt_mtf50_mode);
        gd.addCheckbox("Find z for minimum, unchecked - use parameter", this.rslt_solve);

        gd.addCheckbox("Show focal distance relative to best composite focus (false - to center green )",
                this.z_relative);

        gd.addMessage("Multiple section setup:");
        gd.addNumericField("Scan from (relative to green center)", this.rslt_scan_below, 3, 7, "um");
        gd.addNumericField("Scan to (relative to green center)", this.rslt_scan_above, 3, 7, "um");
        gd.addNumericField("Scan step", this.rslt_scan_step, 3, 7, "um");
        gd.showDialog();
        if (gd.wasCanceled()) {
            return;
        }

        for (int i = 0; i < rslt_show_chn.length; i++) {
            this.rslt_show_chn[i] = gd.getNextBoolean();
        }
        this.rslt_show_z_axial = gd.getNextBoolean();
        this.rslt_show_z_smooth = gd.getNextBoolean();
        this.rslt_show_z_individual = gd.getNextBoolean();
        this.rslt_show_f_axial = gd.getNextBoolean();
        this.rslt_show_f_smooth = gd.getNextBoolean();
        this.rslt_show_f_individual = gd.getNextBoolean();
        this.rslt_show_smooth_sigma = gd.getNextNumber();
        this.rslt_mtf50_mode = gd.getNextBoolean();
        this.rslt_solve = gd.getNextBoolean();
        this.z_relative = gd.getNextBoolean();
        this.rslt_scan_below = gd.getNextNumber();
        this.rslt_scan_above = gd.getNextNumber();
        this.rslt_scan_step = gd.getNextNumber();

        listCombinedResults("Field curvature measuremnts results", //String title,
                null, //String path,
                z_relative ? best_qb_corr[0] : center_z[1], rslt_show_chn, //boolean [] show_chn,
                rslt_show_z_axial, //boolean show_z_axial,
                rslt_show_z_smooth, // boolean rslt_show_z_smooth;
                rslt_show_z_individual, //boolean show_z_individual,
                rslt_show_f_axial, //boolean show_f_axial,
                rslt_show_f_smooth, //boolean rslt_show_f_smooth;
                rslt_show_f_individual, //boolean show_f_individual,
                rslt_show_smooth_sigma, // double rslt_show_smooth_sigma;
                (z_relative ? best_qb_corr[0] : center_z[1]) + rslt_scan_below, // double scan_below,
                (z_relative ? best_qb_corr[0] : center_z[1]) + rslt_scan_above, //double scan_above,
                rslt_scan_step, //double scan_step,
                rslt_mtf50_mode, //boolean freq_mode)
                rslt_solve); // currently if not using z^(>=2) no numeric solution is required - z0 is the minimum 
    }

    public double[][] filterListSamples(double sigma, double[] radiuses, // numsaples+1 long!
            double[] saggital, // numsamples!
            double[] tangential, double[] saggitalWeights, // numsamples long
            double[] tangentialWeights) {// numsamples long
        double[][] data = { saggital, tangential };
        double[][] weights = { saggitalWeights, tangentialWeights };

        int len = saggital.length + 1;
        double[][] result = new double[2][len];
        double kexp = -0.5 / (sigma * sigma);
        for (int dir = 0; dir < 2; dir++) {
            if (data[dir] != null) {
                result[dir] = new double[len];
                for (int i = 0; i < len; i++) {
                    double sum_w = 0.0;
                    double sum_v = 0.0;
                    for (int otherDir = 0; otherDir < 2; otherDir++) {
                        if (data[otherDir] != null)
                            for (int j = 1; j < len; j++) {
                                double r = (otherDir == dir) ? (radiuses[i] - radiuses[j])
                                        : (radiuses[i] + radiuses[j]);
                                double w = Math.exp(kexp * r * r);
                                if (weights[otherDir] != null) {
                                    w *= weights[otherDir][j - 1];
                                }
                                if (!Double.isNaN(data[otherDir][j - 1])) {
                                    sum_v += data[otherDir][j - 1] * w;
                                    sum_w += w;
                                }
                            }
                    }
                    result[dir][i] = (sum_w > 0.0) ? (sum_v / sum_w) : 0.0;
                }
            } else {
                result[dir] = null;
            }
        }
        return result;
    }

    public void listCombinedResults(String title, String path, double z0, // subtract from z
            boolean[] show_chn, boolean show_z_axial, boolean show_z_smooth, boolean show_z_individual,
            boolean show_f_axial, boolean show_f_smooth, boolean show_f_individual, double smooth_sigma,
            double scan_below, double scan_above, double scan_step, boolean freq_mode, boolean solveZ) { // currently if not using z^(>=2) no numeric solution is required - z0 is the minimum 

        String[] chnNames = { "RS", "RT", "GS", "GT", "BS", "BT" };
        // Calculate weights of each channel/sample
        double[][] sampleWeights = new double[getNumChannels()][getNumSamples()];
        for (int chn = 0; chn < sampleWeights.length; chn++)
            for (int sample = 0; sample < sampleWeights[chn].length; sample++) {
                sampleWeights[chn][sample] = 0.0;
            }
        for (int index = 0; index < dataVector.length; index++) {
            sampleWeights[dataVector[index].channel][dataVector[index].sampleIndex] += dataWeights[index];
        }

        double k = this.fwhm_to_mtf50; //TODO: correct psf fwhm to mtf50 conversion
        //     double k=2*Math.log(2.0)/Math.PI*1000;
        //     String header="Z(um)\tComposite\tRed\tGreen\tBlue";
        String header = "radius(mm)";
        if (show_z_axial) {
            for (int i = 0; i < show_chn.length; i++) {
                if (show_chn[i])
                    header += "\tZ" + chnNames[i];
            }
        }
        if (show_z_smooth) {
            for (int i = 0; i < show_chn.length; i++) {
                if (show_chn[i])
                    header += "\tZ" + chnNames[i] + "~";
            }
        }

        if (show_z_individual) {
            for (int i = 0; i < show_chn.length; i++) {
                if (show_chn[i])
                    header += "\tZ" + chnNames[i] + "*";
            }
        }
        int numSect = 0;
        if (show_f_axial || show_f_individual)
            for (double z = scan_below; z <= scan_above; z += scan_step) {
                if (show_f_axial) {
                    for (int i = 0; i < show_chn.length; i++) {
                        if (show_chn[i])
                            header += "\t" + chnNames[i] + IJ.d2s(z - z0, 1);
                    }
                }
                if (show_f_smooth) {
                    for (int i = 0; i < show_chn.length; i++) {
                        if (show_chn[i])
                            header += "\t" + chnNames[i] + IJ.d2s(z - z0, 1) + "~";
                    }
                }
                if (show_f_individual) {
                    for (int i = 0; i < show_chn.length; i++) {
                        if (show_chn[i])
                            header += "\t" + chnNames[i] + IJ.d2s(z - z0, 1) + "*";
                    }
                }
                numSect++;
            }
        double[] radiuses0 = fieldFitting.getSampleRadiuses();
        int numSamples = radiuses0.length;
        double[] radiuses = new double[numSamples + 1];
        radiuses[0] = 0.0;
        for (int i = 0; i < numSamples; i++)
            radiuses[i + 1] = radiuses0[i];
        double[][][][] f_values = new double[numSect][3][][];
        int sect = 0;
        for (double z = scan_below; z <= scan_above; z += scan_step) {
            if (show_f_axial) {
                double[][] f = fieldFitting.getCalcValuesForZ(z, false, true);
                double[] f0 = fieldFitting.getCalcValuesForZ(z, 0.0, null);
                f_values[sect][0] = new double[f.length][];
                for (int chn = 0; chn < f.length; chn++) {
                    if (f[chn] != null) {
                        f_values[sect][0][chn] = new double[f[chn].length + 1];
                        f_values[sect][0][chn][0] = f0[chn];
                        for (int i = 0; i < f[chn].length; i++) {
                            f_values[sect][0][chn][i + 1] = f[chn][i];
                        }
                    } else
                        f_values[sect][0][chn] = null;
                }
            } else {
                f_values[sect][0] = null;
            }
            if (show_f_individual) {
                double[][] f = fieldFitting.getCalcValuesForZ(z, true, true);
                f_values[sect][1] = new double[f.length][];
                for (int chn = 0; chn < f.length; chn++) {
                    if (f[chn] != null) {
                        f_values[sect][1][chn] = new double[f[chn].length + 1];
                        f_values[sect][1][chn][0] = Double.NaN;
                        for (int i = 0; i < f[chn].length; i++) {
                            f_values[sect][1][chn][i + 1] = f[chn][i];
                        }
                    } else
                        f_values[sect][1][chn] = null;
                }
            } else {
                f_values[sect][1] = null;
            }
            if (show_f_smooth) {
                double[][] f = fieldFitting.getCalcValuesForZ(z, true, true); // corrected (individual), 
                f_values[sect][2] = new double[f.length][];
                for (int chn = 0; chn < f.length; chn += 2) {
                    double[][] smooth = filterListSamples(smooth_sigma, radiuses, // all 3 arrays should be numsaples+1 long
                            f[chn], //double [] saggital, // not ordered, but [0]==0.0
                            f[chn + 1], //double []tangential,
                            sampleWeights[chn], //double [] saggitalWeights, // numsamples long
                            sampleWeights[chn + 1]); //double [] tangentialWeights){// numsamples long
                    f_values[sect][2][chn] = smooth[0];
                    f_values[sect][2][chn + 1] = smooth[1];
                }
            } else {
                f_values[sect][2] = null;
            }
            sect++;
        }
        double[][][] z_values = new double[3][][];
        if (show_z_axial) {
            double[][] zai = fieldFitting.getCalcZ(false, true, solveZ);
            double[] zai0 = fieldFitting.getCalcZ(0.0, solveZ);
            z_values[0] = new double[zai.length][];
            for (int chn = 0; chn < zai.length; chn++) {
                if (zai[chn] != null) {
                    z_values[0][chn] = new double[zai[chn].length + 1];
                    z_values[0][chn][0] = zai0[chn];
                    for (int i = 0; i < zai[chn].length; i++) {
                        z_values[0][chn][i + 1] = zai[chn][i];
                    }

                } else
                    z_values[0][chn] = null;
            }
        } else
            z_values[0] = null;
        if (show_z_individual) {
            double[][] zai = fieldFitting.getCalcZ(true, true, solveZ);
            z_values[1] = new double[zai.length][];
            for (int chn = 0; chn < zai.length; chn++) {
                if (zai[chn] != null) {
                    z_values[1][chn] = new double[zai[chn].length + 1];
                    z_values[1][chn][0] = Double.NaN;
                    for (int i = 0; i < zai[chn].length; i++) {
                        z_values[1][chn][i + 1] = zai[chn][i];
                    }

                } else
                    z_values[1][chn] = null;
            }
        } else
            z_values[1] = null;
        if (show_f_smooth) {
            double[][] zai = fieldFitting.getCalcZ(true, true, solveZ);
            z_values[2] = new double[zai.length][];
            for (int chn = 0; chn < zai.length; chn += 2) {
                double[][] smooth = filterListSamples(smooth_sigma, radiuses, // all 3 arrays should be numsaples+1 long
                        zai[chn], //double [] saggital, // not ordered, but [0]==0.0
                        zai[chn + 1], //double []tangential,
                        sampleWeights[chn], //double [] saggitalWeights, // numsamples long
                        sampleWeights[chn + 1]); //double [] tangentialWeights){// numsamples long
                z_values[2][chn] = smooth[0];
                z_values[2][chn + 1] = smooth[1];
            }
        } else {
            z_values[2] = null;
        }

        StringBuffer sb = new StringBuffer();
        for (int n = 0; n < radiuses.length; n++) {
            sb.append(IJ.d2s(radiuses[n], 3));
            if (show_z_axial) {
                for (int i = 0; i < show_chn.length; i++) {
                    if (show_chn[i])
                        sb.append("\t" + IJ.d2s(z_values[0][i][n] - z0, 3));
                }
            }
            if (show_z_smooth) {
                for (int i = 0; i < show_chn.length; i++) {
                    if (show_chn[i])
                        sb.append("\t" + IJ.d2s(z_values[2][i][n] - z0, 3));
                }
            }
            if (show_z_individual) {
                for (int i = 0; i < show_chn.length; i++) {
                    if (show_chn[i])
                        sb.append("\t" + IJ.d2s(z_values[1][i][n] - z0, 3));
                }
            }
            if (show_f_axial || show_f_individual)
                for (int s = 0; s < numSect; s++) {
                    if (show_f_axial)
                        for (int i = 0; i < show_chn.length; i++)
                            if (show_chn[i]) {
                                if (freq_mode) {
                                    sb.append("\t" + IJ.d2s(k / f_values[s][0][i][n], 3));
                                } else {
                                    sb.append("\t" + IJ.d2s(f_values[s][0][i][n], 3));
                                }
                            }
                    if (show_f_smooth)
                        for (int i = 0; i < show_chn.length; i++)
                            if (show_chn[i]) {
                                if (freq_mode) {
                                    sb.append("\t" + IJ.d2s(k / f_values[s][2][i][n], 3));
                                } else {
                                    sb.append("\t" + IJ.d2s(f_values[s][2][i][n], 3));
                                }
                            }
                    if (show_f_individual)
                        for (int i = 0; i < show_chn.length; i++)
                            if (show_chn[i]) {
                                if (freq_mode) {
                                    sb.append("\t" + IJ.d2s(k / f_values[s][1][i][n], 3));
                                } else {
                                    sb.append("\t" + IJ.d2s(f_values[s][1][i][n], 3));
                                }
                            }
                }
            sb.append("\n");
        }
        if (path != null) {
            CalibrationFileManagement.saveStringToFile(path, header + "\n" + sb.toString());
        } else {
            new TextWindow(title, header, sb.toString(), 800, 1000);
        }
    }

    public void listScanQB() {

        double[] center_z = fieldFitting.getZCenters(false); // do not solve, use z0 coefficient
        double[] centerFWHM = { fieldFitting.getCalcValuesForZ(center_z[0], 0.0, null)[1],
                fieldFitting.getCalcValuesForZ(center_z[1], 0.0, null)[3],
                fieldFitting.getCalcValuesForZ(center_z[2], 0.0, null)[5] };
        double[] best_qb_axial = fieldFitting.getBestQualB(k_red, k_blue, false);
        double[] best_qb_corr = fieldFitting.getBestQualB( //best_qb_corr[0] - distance (motorZ)
                k_red, k_blue, true);
        GenericDialog gd = new GenericDialog("Setup quality-B table FWHM=" + IJ.d2s(best_qb_corr[1], 3)
                + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / best_qb_corr[1], 2) + " lp/mm");
        gd.addMessage("Best center focus for Red " + IJ.d2s(center_z[0], 3) + " um" + ", FWHM="
                + IJ.d2s(centerFWHM[0], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[0], 2) + " lp/mm");
        gd.addMessage("Best center focus for Green " + IJ.d2s(center_z[1], 3) + " um" + ", FWHM="
                + IJ.d2s(centerFWHM[1], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[1], 2) + " lp/mm");
        gd.addMessage("Best center focus for Blue " + IJ.d2s(center_z[2], 3) + " um" + ", FWHM="
                + IJ.d2s(centerFWHM[2], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[2], 2) + " lp/mm");
        gd.addMessage("Best composite distance for FWHM^4, axial model " + IJ.d2s(best_qb_axial[0], 3) + " um"
                + ", FWHM=" + IJ.d2s(best_qb_axial[1], 3) + "um, MTF50="
                + IJ.d2s(fwhm_to_mtf50 / best_qb_axial[1], 2) + " lp/mm");
        gd.addMessage("Best composite distance for FWHM^4, individual " + IJ.d2s(best_qb_corr[0], 3) + "  um"
                + ", FWHM=" + IJ.d2s(best_qb_corr[1], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / best_qb_corr[1], 2)
                + " lp/mm");
        gd.addNumericField("Scan from (relative to green center)", this.qb_scan_below, 3, 7, "um");
        gd.addNumericField("Scan to (relative to green center)", this.qb_scan_above, 3, 7, "um");
        gd.addNumericField("Scan step", this.qb_scan_step, 3, 7, "um");
        gd.addNumericField("Relative (to green) weight of red channel", 100 * this.k_red, 3, 7, "%");
        gd.addNumericField("Relative (to green) weight of blue channel", 100 * this.k_blue, 3, 7, "%");
        gd.addCheckbox("Use per-sample location corrected data", this.qb_use_corrected);
        gd.addCheckbox("Show mtf50 (false - PSF FWHM)", this.qb_invert);
        gd.addCheckbox("Show focal distance relative to best composite focus (false - to center green )",
                this.z_relative);

        gd.showDialog();
        if (gd.wasCanceled()) {
            return;
        }
        this.qb_scan_below = gd.getNextNumber();
        this.qb_scan_above = gd.getNextNumber();
        this.qb_scan_step = gd.getNextNumber();
        this.k_red = 0.01 * gd.getNextNumber();
        this.k_blue = 0.01 * gd.getNextNumber();
        this.qb_use_corrected = gd.getNextBoolean();
        this.qb_invert = gd.getNextBoolean();
        this.z_relative = gd.getNextBoolean();

        listScanQB("Focus quality vs. distance", // String title,
                null, //String path,
                z_relative ? best_qb_corr[0] : center_z[1], center_z[1] + this.qb_scan_below, //double min_z, // absolute
                center_z[1] + this.qb_scan_above, //double max_z,
                this.qb_scan_step, //double step_z,
                this.k_red, //double kr,
                this.k_blue, //double kb,
                this.qb_use_corrected, //boolean corrected,
                this.qb_invert); //boolean freq_mode)
    }

    public void listScanQB(String title, String path, double z0, double min_z, // absolute
            double max_z, double step_z, double kr, double kb, boolean corrected, boolean freq_mode) {
        double k = this.fwhm_to_mtf50; //TODO: correct psf fwhm to mtf50 conversion
        String header = "Z(um)\tComposite\tRed\tGreen\tBlue";
        StringBuffer sb = new StringBuffer();
        for (double z = min_z; z <= max_z; z += step_z) {
            double qb_w = fieldFitting.getQualB(z, kr, kb, corrected);
            double[] qb_rgb = fieldFitting.getQualB(z, corrected);
            if (freq_mode) {
                sb.append(IJ.d2s(z - z0, 3) + "\t" + IJ.d2s(k / qb_w, 3) + "\t" + IJ.d2s(k / qb_rgb[0], 3) + "\t"
                        + IJ.d2s(k / qb_rgb[1], 3) + "\t" + IJ.d2s(k / qb_rgb[2], 3) + "\n");
            } else {
                sb.append(IJ.d2s(z - z0, 3) + "\t" + IJ.d2s(qb_w, 3) + "\t" + IJ.d2s(qb_rgb[0], 3) + "\t"
                        + IJ.d2s(qb_rgb[1], 3) + "\t" + IJ.d2s(qb_rgb[2], 3) + "\n");
            }
        }

        //        for (String s:this.fieldFitting.getParameterValueStrings(true,true)){ //this.showDisabledParams)){ //
        //         sb.append(s+"\n");
        //        }

        // Add result to the bottom of the file
        double[] center_z = fieldFitting.getZCenters(false); // z0 coefficient, do not find minimum
        double[] centerFWHM = { fieldFitting.getCalcValuesForZ(center_z[0], 0.0, null)[1],
                fieldFitting.getCalcValuesForZ(center_z[1], 0.0, null)[3],
                fieldFitting.getCalcValuesForZ(center_z[2], 0.0, null)[5] };
        //        double [] best_qb_axial= fieldFitting.getBestQualB(
        //                k_red,
        //                k_blue,
        //                false);
        double[] best_qb_corr = fieldFitting.getBestQualB(k_red, k_blue, true);
        sb.append("Results:\t---\t---\t---\t---\n");
        sb.append("Red center" + "\t" + IJ.d2s(center_z[0], 3) + "\t" + IJ.d2s(centerFWHM[0], 3) + "\t"
                + IJ.d2s(fwhm_to_mtf50 / centerFWHM[0], 2) + "\t" + "lp/mm" + "\n");
        sb.append("Green center" + "\t" + IJ.d2s(center_z[1], 3) + "\t" + IJ.d2s(centerFWHM[1], 3) + "\t"
                + IJ.d2s(fwhm_to_mtf50 / centerFWHM[1], 2) + "\t" + "lp/mm" + "\n");
        sb.append("Blue center" + "\t" + IJ.d2s(center_z[2], 3) + "\t" + IJ.d2s(centerFWHM[2], 3) + "\t"
                + IJ.d2s(fwhm_to_mtf50 / centerFWHM[2], 2) + "\t" + "lp/mm" + "\n");
        sb.append("Composite ^4" + "\t" + IJ.d2s(best_qb_corr[0], 3) + "\t" + IJ.d2s(best_qb_corr[1], 3) + "\t"
                + IJ.d2s(fwhm_to_mtf50 / best_qb_corr[1], 2) + "\t" + "lp/mm" + "\n");

        sb.append("Lens center " + "\t" + "used" + "\t" + IJ.d2s(currentPX0, 1) + "\t" + IJ.d2s(currentPY0, 1)
                + "\t" + "pix" + "\n");
        sb.append("Lens center " + "\t" + "distortions" + "\t" + IJ.d2s(pX0_distortions, 1) + "\t"
                + IJ.d2s(pY0_distortions, 1) + "\t" + "pix" + "\n");

        if (path != null) {
            CalibrationFileManagement.saveStringToFile(path, header + "\n" + sb.toString());
        } else {
            new TextWindow(title, header, sb.toString(), 800, 1000);
        }
    }

    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("Current series=" + this.currentStrategyStep);
        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) {
            gd.addMessage("==== Current parameter values ===");
            for (String s : this.fieldFitting.getParameterValueStrings(this.showDisabledParams,
                    this.showCorrectionParams)) { //
                gd.addMessage(s);
            }
            gd.addMessage("");

        }

        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 disabled parameters", this.showDisabledParams);
        gd.addCheckbox("Show per-sample correction parameters", this.showCorrectionParams);

        //        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.showDisabledParams = gd.getNextBoolean();
        this.showCorrectionParams = gd.getNextBoolean();

        //        this.showThisImages= gd.getNextBoolean();
        //        this.showNextImages= gd.getNextBoolean();
        this.saveSeries = true;
        return gd.wasOKed();
    }

    public double getAdjustRMS(
            //      FocusingFieldMeasurement measurement,
            int[] zTxTyAdjustMode, //0 - do not adjust, 1 - commonn adjust, 2 - individual adjust
            ArrayList<FocusingFieldMeasurement> measurements, boolean filterZ, boolean filterByScanValue,
            double filterByValueScale, int minNeib, double z, double tx, double ty) {
        fieldFitting.selectZTilt(false, zTxTyAdjustMode);
        fieldFitting.mechanicalFocusingModel.setZTxTy(z, tx, ty);
        //   double [] sv= fieldFitting.createParameterVector(sagittalMaster);
        setDataVector(false, // calibrate mode
                createDataVector(measurements));
        if (filterZ) {
            boolean[] en = dataWeightsToBoolean();
            en = filterByZRanges(zRanges, en, null);
            maskDataWeights(en);
            prevEnable = en;
            int numEn = getNumEnabledSamples(en);
            if (numEn < minLeftSamples)
                return Double.NaN;
        }
        if (filterByScanValue) {
            boolean[] en = dataWeightsToBoolean();
            en = filterByScanValues(zRanges, en, null);
            maskDataWeights(en);
            prevEnable = en;
            int numEn = getNumEnabledSamples(en);
            if (numEn < minLeftSamples)
                return Double.NaN;
        }
        if (filterByValueScale > 0.0) {
            boolean[] en = dataWeightsToBoolean();
            en = filterByValue(filterByValueScale, en, null);
            maskDataWeights(en);
            prevEnable = en;
            int numEn = getNumEnabledSamples(en);
            if (numEn < minLeftSamples)
                return Double.NaN;
        }
        if (minNeib > 0) {
            boolean[] en = dataWeightsToBoolean();
            en = filterLowNeighbors(en, minNeib, false); // calib mode for debug print
            maskDataWeights(en);
        }
        /*   
           if ((minCenterSamplesTotal>0) || (minCenterSamplesBest>0)){
              boolean [] centerSampesMask= getCenterSamples(centerSamples);
              boolean [] en=dataWeightsToBoolean();
              if (!checkEnoughCenter(
        centerSampesMask,
        en,
        minCenterSamplesTotal, //int minTotalSamples,
        minCenterSamplesBest )){ //int minBestChannelSamples)){
                 if (debugLevel>1) { //0
        int [] numSamples=getNumCenterSamples( // per channel
              centerSampesMask,
              en);
        System.out.print("Got:");
        for (int n:numSamples) System.out.print(" "+n);
        System.out.println(" - not enough center samples, requested "+minCenterSamplesBest+" best channel and "+minCenterSamplesTotal+" total.");
                 }
                 return Double.NaN;
              }
           }
        */
        if ((minCenterSamplesTotal > 0) || (minCenterSamplesBest > 0) || (minLeftSamples > 0)
                || (minBestLeftSamples > 0)) {
            boolean[] centerSampesMask = getCenterSamples(centerSamples);
            boolean[] en = dataWeightsToBoolean();
            en = filterNotEnoughSamples(centerSampesMask, en, minCenterSamplesTotal, // int minTotalCenterSamples,
                    minCenterSamplesBest, //int minBestChannelCenterSamples,
                    minLeftSamples, //int minTotalSamples,
                    minBestLeftSamples); //int minBestChannelSamples);
            maskDataWeights(en);
        }
        int numEn = getNumEnabledSamples(dataWeightsToBoolean());
        if ((numEn < minLeftSamples) || (numEn < 1))
            return Double.NaN;

        //   double [] sv= fieldFitting.createParameterVector(sagittalMaster);
        double[] sv = fieldFitting.createParameterVectorZTxTy(zTxTyAdjustMode); //0 - do not adjust, 1 - commonn adjust, 2 - individual adjust   
        double[] focusing_fx = createFXandJacobian(sv, false);
        double rms_pure = calcErrorDiffY(focusing_fx, true);
        //   System.out.println("rms_pure="+rms_pure);
        return rms_pure;
    }

    public double[] findAdjustZ( // Mode should already be set to adjustment!
            //      int [] zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            //      FocusingFieldMeasurement measurement,
            ArrayList<FocusingFieldMeasurement> measurements, boolean filterZ, boolean filterByScanValue,
            double filterByValueScale, int minNeib, double zMin, double zMax, double zStep, double tMin,
            double tMax, double tStep) {
        int[] zTxTyAdjustMode = fieldFitting.mechanicalFocusingModel.getZTxTyMode();
        double zBest = Double.NaN;
        double tXBest = Double.NaN;
        double tYBest = Double.NaN;
        double bestRMS = Double.NaN;
        double tMinX = tMin, tMinY = tMin, tMaxX = tMax, tMaxY = tMax, tStepX = tStep, tStepY = tStep;

        // Disable scanning tilts according to zTxTyAdjustMode (null - old way)
        if (zTxTyAdjustMode != null) {
            double[] zTxTy = fieldFitting.mechanicalFocusingModel.getZTxTy(); // current values
            if (zTxTyAdjustMode[1] == 0) {
                tMinX = zTxTy[1];
                tMaxX = zTxTy[1];
            }
            if (zTxTyAdjustMode[2] == 0) {
                tMinY = zTxTy[2];
                tMaxY = zTxTy[2];
            }
        }
        int bestEn = 0;
        for (double z = zMin; z <= zMax; z += zStep)
            for (double tx = tMinX; tx <= tMaxX; tx += tStepX)
                for (double ty = tMinY; ty <= tMaxY; ty += tStepY) {
                    double rms = getAdjustRMS(zTxTyAdjustMode,
                            //            measurement,
                            measurements, filterZ, filterByScanValue, filterByValueScale, minNeib, z, tx, ty);
                    if (((Double.isNaN(bestRMS) || (bestRMS >= rms)) && !Double.isNaN(rms) && (rms > 0.0))) {
                        zBest = z;
                        tXBest = tx;
                        tYBest = ty;
                        bestRMS = rms;
                        bestEn = numEnabled(dataWeightsToBoolean());
                    }

                    if (debugLevel > 1) {
                        int numEn = numEnabled(dataWeightsToBoolean());
                        System.out.println("findAdjustZ(): z=" + z + " tx=" + tx + " ty=" + ty + " rms=" + rms
                                + " used " + numEn + " samples");
                    }
                }
        //   if (debugLevel>0) System.out.println("findAdjustZ()-> z(absolute)="+zBest+" tx="+tXBest+" ty="+tYBest+" (best RMS = "+bestRMS+" used "+bestEn+" samples)");
        if (debugLevel > 1)
            System.out.println("findAdjustZ()-> z(absolute)=" + zBest + " tx=" + tXBest + " ty=" + tYBest
                    + " (best RMS = " + bestRMS + " used " + bestEn + " samples)");
        double[] result = { zBest, tXBest, tYBest };
        return result;
    }

    public void calculateGoodSamples() {
        this.goodCalibratedSamples = new boolean[getNumChannels()][getNumSamples()];
        for (int chn = 0; chn < this.goodCalibratedSamples.length; chn++)
            for (int sample = 0; sample < this.goodCalibratedSamples[0].length; sample++)
                this.goodCalibratedSamples[chn][sample] = false;
        for (int n = 0; n < dataVector.length; n++)
            if (dataWeights[n] > 0.0) {
                this.goodCalibratedSamples[dataVector[n].channel][dataVector[n].sampleIndex] = true;
            }
        if (debugLevel > 0) {
            System.out.println("Calculated good samples:");
            System.out.println(showSamples(this.goodCalibratedSamples));
        }
    }

    public boolean LevenbergMarquardt(
            //      FocusingFieldMeasurement measurement, // null in calibrate mode
            int[] zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            ArrayList<FocusingFieldMeasurement> measurements, boolean openDialog, boolean autoSel, int debugLevel) {
        boolean calibrate = measurements == null;
        //   FocusingFieldMeasurement measurement=measurements.get(0);   // FIXME: - process all measurements
        double savedLambda = this.lambda;
        this.debugLevel = debugLevel;
        if (openDialog && !selectLMAParameters(autoSel))
            return false;

        if (!openDialog && autoSel) {
            this.stopEachStep = false;
            this.stopEachSeries = false;
            this.currentStrategyStep = 0;
        }
        this.startTime = System.nanoTime();
        // create savedVector (it depends on parameter masks), restore from it if aborted
        //   fieldFitting.initSampleCorrVector(
        //         flattenSampleCoord(), //double [][] sampleCoordinates,
        //         getSeriesWeights()); //double [][] sampleSeriesWeights);
        //   fieldFitting.setEstimatedZ0( z0_estimates, false); // boolean force)

        //   this.savedVector=this.fieldFitting.createParameterVector(sagittalMaster);
        //   if (debugDerivativesFxDxDy){
        //      compareDrDerivatives(this.savedVector);
        //   }
        if (!calibrate) {
            this.currentStrategyStep = -1;
            //          int [] zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            fieldFitting.selectZTilt(false, zTxTyAdjustMode); // still need to create a parameter vector
            keepCorrectionParameters = true;
            resetVariableParameters = false;
            resetCenter = false;
            if (!openDialog)
                stopEachStep = false;
        }
        this.iterationStepNumber = 0;
        this.firstRMS = -1; //undefined
        while (true) { // loop for all series

            //TODO: reset firstRMS here only if enabled channels or enabled correction parameters are different
            this.firstRMS = -1; //undefined

            if (this.currentStrategyStep >= 0) {
                if (!getStrategy(this.currentStrategyStep))
                    break; //invalid strategy
            }
            if (!keepCorrectionParameters)
                fieldFitting.resetSampleCorr();
            if (resetVariableParameters)
                fieldFitting.resetSFEVariables();

            if (resetCenter) {
                if (debugLevel > 0)
                    System.out.println(
                            "Resetting center: X " + IJ.d2s(currentPX0, 2) + " -> " + IJ.d2s(pX0_distortions, 2));
                if (debugLevel > 0)
                    System.out.println(
                            "Resetting center: Y " + IJ.d2s(currentPY0, 2) + " -> " + IJ.d2s(pY0_distortions, 2));
                currentPX0 = pX0_distortions;
                currentPY0 = pY0_distortions;
                fieldFitting.setCenterXY(currentPX0, currentPY0);
            }
            //        setDataVector(createDataVector()); //new
            fieldFitting.initSampleCorrChnParIndex(flattenSampleCoord());
            if (calibrate) {
                setDataVector(true, // calibrate mode
                        createDataVector()); // Make it different for adjustment mode
                fieldFitting.initSampleCorrVector(flattenSampleCoord(), //double [][] sampleCoordinates,
                        getSeriesWeights()); //double [][] sampleSeriesWeights);
                fieldFitting.setEstimatedZ0(z0_estimates, false); // boolean force)
                this.savedVector = this.fieldFitting.createParameterVector(sagittalMaster);
            } else { // adjustment mode
                setDataVector(false, // calibrate mode
                        //                   createDataVector(measurement)); // Make it different for adjustment mode
                        createDataVector(measurements)); // Make it different for adjustment mode
                if (filterZ) {
                    boolean[] en = dataWeightsToBoolean();
                    en = filterByZRanges(zRanges, en, null);
                    maskDataWeights(en);
                    prevEnable = en;
                    int numEn = getNumEnabledSamples(en);
                    if (numEn < minLeftSamples)
                        return false;
                }
                if (filterByScanValue) {
                    boolean[] en = dataWeightsToBoolean();
                    en = filterByScanValues(zRanges, en, null);
                    maskDataWeights(en);
                    prevEnable = en;
                    int numEn = getNumEnabledSamples(en);
                    if (numEn < minLeftSamples)
                        return false;
                }
                if (filterByValueScale > 0.0) {
                    boolean[] en = dataWeightsToBoolean();
                    en = filterByValue(filterByValueScale, en, null);
                    maskDataWeights(en);
                    prevEnable = en;
                    int numEn = getNumEnabledSamples(en);
                    if (numEn < minLeftSamples)
                        return false;
                }
                if (filterByNeib > 0) {
                    boolean[] en = dataWeightsToBoolean();
                    en = filterLowNeighbors(en, filterByNeib, false); // calibrate mode - for debug print
                    maskDataWeights(en);
                }
                if ((minCenterSamplesTotal > 0) || (minCenterSamplesBest > 0) || (minLeftSamples > 0)
                        || (minBestLeftSamples > 0)) {
                    boolean[] centerSampesMask = getCenterSamples(centerSamples);
                    boolean[] en = dataWeightsToBoolean();
                    en = filterNotEnoughSamples(centerSampesMask, en, minCenterSamplesTotal, // int minTotalCenterSamples,
                            minCenterSamplesBest, //int minBestChannelCenterSamples,
                            minLeftSamples, //int minTotalSamples,
                            minBestLeftSamples); //int minBestChannelSamples);
                    maskDataWeights(en);
                }
                int numEn = getNumEnabledSamples(dataWeightsToBoolean());
                if ((numEn < minLeftSamples) || (numEn < 1))
                    return false;
                /*
                             if ((minCenterSamplesTotal>0) || (minCenterSamplesBest>0)){
                                boolean [] centerSampesMask= getCenterSamples(centerSamples);
                                boolean [] en=dataWeightsToBoolean();
                                if (!checkEnoughCenter(
                  centerSampesMask,
                  en,
                  minCenterSamplesTotal, //int minTotalSamples,
                  minCenterSamplesBest )){ //int minBestChannelSamples)){
                                   if (debugLevel>0) {
                  int [] numSamples=getNumCenterSamples( // per channel
                        centerSampesMask,
                        en);
                  System.out.print("Got (in LMA):");
                  for (int n:numSamples) System.out.print(" "+n);
                  System.out.println(" - not enough center samples, requested "+minCenterSamplesBest+" best channel and "+minCenterSamplesTotal+" total.");
                                   }
                                   return false;
                                }
                             }
                */
                fieldFitting.initSampleCorrVector(flattenSampleCoord(), //double [][] sampleCoordinates,
                        null); //getSeriesWeights()); //double [][] sampleSeriesWeights);
                //           this.savedVector=this.fieldFitting.createParameterVector(sagittalMaster);
                this.savedVector = this.fieldFitting.createParameterVectorZTxTy(zTxTyAdjustMode); //0 - do not adjust, 1 - commonn adjust, 2 - individual adjust
                this.fieldFitting.commitParameterVectorZTxTy( // to populate fieldFitting.mechanicalFocusingModel.replaceParameters array
                        this.savedVector);
            }
            //       this.savedVector=this.fieldFitting.createParameterVector(sagittalMaster);
            if (debugDerivativesFxDxDy) {
                compareDrDerivatives(this.savedVector);
            }

            //     while (this.fittingStrategy.isSeriesValid(this.seriesNumber)){ // TODO: Add "stop" tag to series
            this.currentVector = null; // invalidate for the new series
            //         boolean wasLastSeries=false;

            int saveStopRequested = this.stopRequested.get(); // preserve from caller stop requested (like temp. scan)
            this.stopRequested.set(0); // remove caller stop request

            while (true) { // loop for the same series

                boolean[] state = stepLevenbergMarquardtFirst(debugLevel);
                if (state == null) {
                    String msg = "Calculation aborted by user request, restoring saved parameter vector";
                    IJ.showMessage(msg);
                    System.out.println(msg);
                    commitParameterVector(this.savedVector);
                    this.lambda = savedLambda;
                    this.stopRequested.set(saveStopRequested); // restore caller stop request
                    return false;
                }

                if (debugLevel > 1)
                    System.out.println(this.currentStrategyStep + ":" + this.iterationStepNumber
                            + ": stepLevenbergMarquardtFirst(" + debugLevel + ")==>" + 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 (debugLevel > 1)
                        System.out.println("LMA failed to converge, but RMS improved from the initial value ("
                                + this.currentRMS + " < " + this.firstRMS + "), currentRMSPure=" + currentRMSPure
                                + ", firstRMSPure=" + firstRMSPure);
                    state[0] = true;
                }
                if ((this.stopRequested.get() > 0) || // graceful stop requested
                        (this.stopEachStep) || (this.stopEachSeries && state[1])
                        || (this.stopOnFailure && state[1] && !state[0])) {
                    if (state[1] && !state[0] && !calibrate) {
                        this.stopRequested.set(saveStopRequested); // restore caller stop request
                        return false;
                    }

                    if (debugLevel > 0) {
                        if (this.stopRequested.get() > 0)
                            System.out.println("User requested stop");
                        System.out.println("LevenbergMarquardt(): step =" + this.currentStrategyStep + ":"
                                + this.iterationStepNumber + ", RMS=" + IJ.d2s(this.currentRMS, 8) + " ("
                                + IJ.d2s(this.firstRMS, 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(debugLevel); // apply step - in any case?
                if (this.updateStatus) {
                    IJ.showStatus("Step #" + this.currentStrategyStep + ":" + this.iterationStepNumber + " RMS="
                            + IJ.d2s(this.currentRMS, 8) + " (" + IJ.d2s(this.firstRMS, 8) + ")" + " RMSPure="
                            + IJ.d2s(this.currentRMSPure, 8) + " (" + IJ.d2s(this.firstRMSPure, 8) + ")" + " ");
                }
                if (!cont) {
                    if (this.saveSeries) {
                        savedLambda = this.lambda;

                        this.savedVector = this.currentVector.clone();
                        //                        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
                    commitParameterVector(this.savedVector); // either new or original
                    this.lambda = savedLambda;
                    this.stopRequested.set(saveStopRequested); // restore caller stop request
                    return this.saveSeries; // TODO: Maybe change result?
                }
                //stepLevenbergMarquardtAction();             
                if (state[1]) {
                    if (!state[0]) {
                        commitParameterVector(this.savedVector);
                        this.lambda = savedLambda;
                        this.stopRequested.set(saveStopRequested); // restore caller stop request
                        return false; // sequence failed
                    }
                    this.savedVector = this.currentVector.clone();
                    //                 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
                }
            } // while true - same series
            this.stopRequested.set(saveStopRequested); // restore caller stop request
            //         if (wasLastSeries) break;
            //     } // while (this.fittingStrategy.isSeriesValid(this.seriesNumber)){ // TODO: Add "stop" tag to series
            if (fieldFitting.fieldStrategies.isLast(this.currentStrategyStep))
                break;
            String msg = "LMA series=" + this.currentStrategyStep + " RMS=" + this.currentRMS + " (" + this.firstRMS
                    + ") " + ", pure RMS=" + this.currentRMSPure + " (" + this.firstRMSPure + ") " + " at "
                    + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 3);
            if (debugLevel > 0)
                System.out.println("stepLevenbergMarquardtAction() " + msg);
            this.currentStrategyStep++;
            this.iterationStepNumber = 0;
            this.stopRequested.set(saveStopRequested); // restore caller stop request
        } // for all series
        String msg = "RMS=" + this.currentRMS + " (" + this.firstRMS + ") " + ", pure RMS=" + this.currentRMSPure
                + " (" + this.firstRMSPure + ") " + " at "
                + IJ.d2s(0.000000001 * (System.nanoTime() - this.startTime), 3);
        if (debugLevel > 0)
            System.out.println("stepLevenbergMarquardtAction() " + msg);
        //       if (this.updateStatus) IJ.showStatus(msg);
        if (this.updateStatus) {
            IJ.showStatus("Done: Step #" + this.currentStrategyStep + ":" + this.iterationStepNumber + " RMS="
                    + IJ.d2s(this.currentRMS, 8) + " (" + IJ.d2s(this.firstRMS, 8) + ")" + " RMSPure="
                    + IJ.d2s(this.currentRMSPure, 8) + " (" + IJ.d2s(this.firstRMSPure, 8) + ")" + " ");
        }
        this.savedVector = this.currentVector.clone();
        commitParameterVector(this.savedVector);
        if (calibrate) {
            zRanges = calcZRanges(true, // boolean scanOnly, // do not use non-scan samples
                    dataWeightsToBoolean());
            calculateGoodSamples();
        }
        return true; // all series done
    }

    public class FocusingFieldMeasurement {
        public String timestamp;
        public double temperature;
        public int[] motors;
        double[][][][] samples = null; // last - psf radius in pixels: {x2,y2, xy}

        public FocusingFieldMeasurement(String timestamp, double temperature, int[] motors,
                double[][][][] samples) {
            this.timestamp = timestamp;
            this.temperature = temperature;
            this.motors = new int[motors.length];
            for (int i = 0; i < motors.length; i++)
                this.motors[i] = motors[i];
            if (samples != null) {
                //                this.samples=new double[samples.length][samples[0].length][samples[0][0].length][2];
                try {
                    this.samples = new double[samples.length][samples[0].length][samples[0][0].length][3];
                } catch (Exception e) {
                    return;
                }
                for (int i = 0; i < this.samples.length; i++)
                    for (int j = 0; j < this.samples[i].length; j++)
                        for (int c = 0; c < this.samples[i][j].length; c++) {
                            try {
                                //                        double rt=samples[i][j][c][0]/Math.sqrt((1+samples[i][j][c][1]*samples[i][j][c][1])/2.0); // tangential;
                                //                        double rs=rt*samples[i][j][c][1]; // sagittal
                                //                        this.samples[i][j][c][0]=rs;
                                //                        this.samples[i][j][c][1]=rt;
                                this.samples[i][j][c] = samples[i][j][c].clone();
                            } catch (Exception e) {
                                for (int ii = 0; ii < this.samples[i][j][c].length; ii++)
                                    this.samples[i][j][c][ii] = Double.NaN;
                            }
                        }
            }
        }

        public FocusingFieldMeasurement(String timestamp, double temperature, int[] motors,
                //                ArrayList<String> sampleStrings
                String[] sampleStrings) {
            this.timestamp = timestamp;
            this.temperature = temperature;
            this.motors = new int[motors.length];
            for (int i = 0; i < motors.length; i++)
                this.motors[i] = motors[i];

            //            if ((sampleStrings!=null) && (sampleStrings.size()>0)) {
            if ((sampleStrings != null) && (sampleStrings.length > 0)) {
                int maxi = 0, maxj = 0;
                for (String s : sampleStrings) {
                    String[] ps = s.split(regSep);
                    int i = Integer.parseInt(ps[0]);
                    int j = Integer.parseInt(ps[1]);
                    if (i > maxi)
                        maxi = i;
                    if (j > maxj)
                        maxj = j;
                }
                int rows = maxi + 1;
                int cols = maxj + 1;
                this.samples = new double[rows][cols][][];
                for (String s : sampleStrings) {
                    String[] ps = s.split(regSep);
                    int i = Integer.parseInt(ps[0]);
                    int j = Integer.parseInt(ps[1]);
                    int colors = (ps.length - 2) / 3; // 2;
                    this.samples[i][j] = new double[colors][3]; //[2];
                    for (int c = 0; c < colors; c++) {
                        //                        this.samples[i][j][c][0]=Double.parseDouble(ps[2*c+2]);
                        //                        this.samples[i][j][c][1]=Double.parseDouble(ps[2*c+3]);
                        this.samples[i][j][c][0] = Double.parseDouble(ps[3 * c + 2]);
                        this.samples[i][j][c][1] = Double.parseDouble(ps[3 * c + 3]);
                        this.samples[i][j][c][2] = Double.parseDouble(ps[3 * c + 4]);
                    }
                }
            }
        }

        public ArrayList<String> asListString() {
            ArrayList<String> nodeList = new ArrayList<String>();
            if (this.samples != null) {
                for (int i = 0; i < this.samples.length; i++)
                    for (int j = 0; j < this.samples[i].length; j++) {
                        String sdata = i + sep + j;
                        for (int c = 0; c < samples[i][j].length; c++) {
                            //                         sdata += sep+this.samples[i][j][c][0]+ // sagittal
                            //                                 sep+this.samples[i][j][c][1]; // tangential
                            sdata += sep + this.samples[i][j][c][0] + // x2
                                    sep + this.samples[i][j][c][1] + // y2
                                    sep + this.samples[i][j][c][2]; // xy
                        }
                        nodeList.add(sdata);
                    }
            }
            return nodeList;
        }
    }

    public FocusingFieldMeasurement getFocusingFieldMeasurement(String timestamp, double temperature, int[] motors,
            double[][][][] samples) {
        return new FocusingFieldMeasurement(timestamp, temperature, motors, samples);
    }

    public FocusingField(int sensorWidth, int sensorHeight, double PIXEL_SIZE, //=0.0022; // mm
            String serialNumber, String lensSerial, // if null - do not add average
            String comment, double pX0, double pY0, double[][][] sampleCoord, //){ // x,y,r
            AtomicInteger stopRequested) {
        setDefaults();
        this.serialNumber = serialNumber;
        this.lensSerial = lensSerial;
        this.comment = comment;
        this.pX0_distortions = pX0;
        this.pY0_distortions = pY0;
        // copy distortions to current PX0/PY0
        //        this.currentPX0=pX0_distortions;
        //        this.currentPY0=pY0_distortions;
        this.sampleCoord = sampleCoord;
        this.measurements = new ArrayList<FocusingFieldMeasurement>();
        this.stopRequested = stopRequested;
        this.sensorWidth = sensorWidth;
        this.sensorHeight = sensorHeight;
        this.PIXEL_SIZE = PIXEL_SIZE; //=0.0022; // mm
    }

    public FocusingField(boolean smart, // do not open dialog if default matches
            String defaultPath, //){
            AtomicInteger stopRequested) {
        setDefaults();
        this.stopRequested = stopRequested;
        loadXML(smart, defaultPath);
    }

    public void addSample(String timestamp, double temperature, int[] motors, double[][][][] samples) {
        measurements.add(new FocusingFieldMeasurement(timestamp, temperature, motors, samples));
    }

    public boolean loadXML(boolean smart, // do not open dialog if default matches
            String defaultPath) { // x,y,r
        String[] extensions = { ".history-xml" };
        CalibrationFileManagement.MultipleExtensionsFileFilter parFilter = new CalibrationFileManagement.MultipleExtensionsFileFilter(
                "", extensions, "*.history-xml files");
        String pathname = CalibrationFileManagement.selectFile(smart, false,
                "Restore focusing field measurement data", "Restore", parFilter, defaultPath); //String defaultPath
        if ((pathname == null) || (pathname == ""))
            return false;
        XMLConfiguration hConfig = null;
        try {
            hConfig = new XMLConfiguration(pathname);
        } catch (ConfigurationException e) {
            return false;
        }
        hConfig.setThrowExceptionOnMissing(false); // default value, will return null on missing
        comment = hConfig.getString("comment", "no comments");
        //        if ((comment.length()>10) && comment.substring(0,9).equals("<![CDATA[")) comment=comment.substring(9,comment.length()-3);

        PIXEL_SIZE = Double.parseDouble(hConfig.getString("PIXEL_SIZE", PIXEL_SIZE + ""));
        sensorWidth = Integer.parseInt(hConfig.getString("sensorWidth", sensorWidth + ""));
        sensorHeight = Integer.parseInt(hConfig.getString("sensorHeight", sensorHeight + ""));

        serialNumber = hConfig.getString("serialNumber", "???");
        lensSerial = hConfig.getString("lensSerial", "???");
        pX0_distortions = Double.parseDouble(hConfig.getString("lens_center_x", "0.0"));
        pY0_distortions = Double.parseDouble(hConfig.getString("lens_center_y", "0.0"));
        // copy distortions to current PX0/PY0
        this.currentPX0 = pX0_distortions;
        this.currentPY0 = pY0_distortions;
        int rows = Integer.parseInt(hConfig.getString("samples_y", "0"));
        int cols = Integer.parseInt(hConfig.getString("samples_x", "0"));
        sampleCoord = new double[rows][cols][2];
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++) {
                String[] coords = hConfig.getString("sample_" + i + "_" + j, "0 0").split(regSep);
                sampleCoord[i][j][0] = Double.parseDouble(coords[0]);
                sampleCoord[i][j][1] = Double.parseDouble(coords[1]);
            }
        int numMeasurements = Integer.parseInt(hConfig.getString("measurements", "0"));
        measurements = new ArrayList<FocusingFieldMeasurement>();
        for (int m = 0; m < numMeasurements; m++) {
            String prefix = "measurement_" + m + ".";

            String timestamp = hConfig.getString(prefix + "timestamp", "0");
            double temperature = Double.parseDouble(hConfig.getString(prefix + "temperature", "0.0"));
            String[] sMotors = hConfig.getString(prefix + "motors", "0 0 0").split(regSep);
            int[] motors = new int[sMotors.length];
            for (int i = 0; i < sMotors.length; i++)
                motors[i] = Integer.parseInt(sMotors[i]);
            String[] sampleStrings = hConfig.getStringArray(prefix + "sample");
            measurements.add(new FocusingFieldMeasurement(timestamp, temperature, motors, sampleStrings));
        }
        if (debugLevel > 0) {
            System.out.println("Loaded measurement history " + pathname);
        }
        this.historyPath = pathname;
        return true;
    }

    public void saveXML(String path) { // x,y,r
        XMLConfiguration hConfig = new XMLConfiguration();
        hConfig.setRootElementName("focusingHistory");
        if (comment != null) {
            String comment_esc = comment.replace(",", "\\,");
            //          hConfig.addProperty("comment","<![CDATA["+comment_esc+ "]]>");
            hConfig.addProperty("comment", comment_esc);
        }
        if (serialNumber != null)
            hConfig.addProperty("serialNumber", serialNumber);
        if (lensSerial != null)
            hConfig.addProperty("lensSerial", lensSerial);
        hConfig.addProperty("lens_center_x", pX0_distortions); // distortions center, not aberrations!
        hConfig.addProperty("lens_center_y", pY0_distortions);

        hConfig.addProperty("PIXEL_SIZE", PIXEL_SIZE);
        hConfig.addProperty("sensorWidth", sensorWidth);
        hConfig.addProperty("sensorHeight", sensorHeight);

        if ((sampleCoord != null) && (sampleCoord.length > 0) && (sampleCoord[0] != null)
                && (sampleCoord[0].length > 0)) {
            hConfig.addProperty("samples_x", sampleCoord[0].length);
            hConfig.addProperty("samples_y", sampleCoord.length);
            for (int i = 0; i < sampleCoord.length; i++)
                for (int j = 0; j < sampleCoord[i].length; j++) {
                    //          double coord[] = {sampleCoord[i][j][0],sampleCoord[i][j][1]};
                    hConfig.addProperty("sample_" + i + "_" + j, sampleCoord[i][j][0] + sep + sampleCoord[i][j][1]);
                }
        }
        hConfig.addProperty("measurements", this.measurements.size());
        for (int i = 0; i < this.measurements.size(); i++) {
            FocusingFieldMeasurement meas = this.measurements.get(i);
            String prefix = "measurement_" + i + ".";
            if (meas.timestamp != null)
                hConfig.addProperty(prefix + "timestamp", meas.timestamp);
            hConfig.addProperty(prefix + "temperature", meas.temperature);
            hConfig.addProperty(prefix + "motors", meas.motors[0] + sep + meas.motors[1] + sep + meas.motors[2]);
            hConfig.addProperty(prefix + "sample", meas.asListString());
        }
        File file = new File(path);
        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.historyPath = path;
    }

    public String getHistoryPath() {
        return this.historyPath;
    }

    public void testMeasurement() {
        String[] zTxTyAdjustModeNames = { "keep", "common", "individual" };
        String[] zTxTyNames = { "z", "tx", "ty" };
        GenericDialog gd = new GenericDialog("Select measurement");
        int nMeas = measurements.size() / 2;
        //        double zMin=-40.0;
        //        double zMax= 40.0;
        //        double zStep=2.0;
        //        double targetTiltX=0.0; // for testing, normally should be 0 um/mm
        //        double targetTiltY=0.0; // for testing, normally should be 0 um/mm

        //       fieldFitting.mechanicalFocusingModel.setZTxTy(0.0,0.0,0.0); // to correctly find Z centers,

        fieldFitting.mechanicalFocusingModel.setAdjustMode(false, null); // to correctly find Z centers,

        double[] center_z = fieldFitting.getZCenters(false);
        double[] centerFWHM = { fieldFitting.getCalcValuesForZ(center_z[0], 0.0, null)[1],
                fieldFitting.getCalcValuesForZ(center_z[1], 0.0, null)[3],
                fieldFitting.getCalcValuesForZ(center_z[2], 0.0, null)[5] };
        //        String path=null;
        String title = "Test adjustment results";
        double[] best_qb_corr = fieldFitting.getBestQualB(k_red, k_blue, true);
        gd.addMessage("Best composite distance for FWHM^4 " + IJ.d2s(best_qb_corr[0], 3) + "  um" + ", FWHM="
                + IJ.d2s(best_qb_corr[1], 3) + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / best_qb_corr[1], 2)
                + " lp/mm");
        gd.addMessage("Best center focus for Red (relative to best composite) = "
                + IJ.d2s(center_z[0] - best_qb_corr[0], 3) + " um" + ", FWHM=" + IJ.d2s(centerFWHM[0], 3)
                + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[0], 2) + " lp/mm");
        gd.addMessage("Best center focus for Green (relative to best composite) = "
                + IJ.d2s(center_z[1] - best_qb_corr[0], 3) + " um" + ", FWHM=" + IJ.d2s(centerFWHM[1], 3)
                + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[1], 2) + " lp/mm");
        gd.addMessage("Best center focus for Blue (relative to best composite) = "
                + IJ.d2s(center_z[2] - best_qb_corr[0], 3) + " um" + ", FWHM=" + IJ.d2s(centerFWHM[2], 3)
                + "um, MTF50=" + IJ.d2s(fwhm_to_mtf50 / centerFWHM[2], 2) + " lp/mm");

        gd.addNumericField("Measurement number", nMeas, 0, 5, "0.." + (measurements.size() - 1));
        if (fieldFitting.channelSelect == null)
            fieldFitting.channelSelect = fieldFitting.getDefaultMask();
        for (int i = 0; i < fieldFitting.channelSelect.length; i++) {
            gd.addCheckbox(fieldFitting.getDescription(i), fieldFitting.channelSelect[i]);
        }

        //       filterZ=true;      // (adjustment mode)filter samples by Z
        //       minLeftSamples=10;  // minimal number of samples (channel/dir/location) for adjustment

        gd.addCheckbox("Filter samples/channels by Z", filterZ);
        gd.addCheckbox("Filter by value (leave lower than maximal fwhm used in focal scan mode)",
                filterByScanValue);
        gd.addNumericField("Filter by value (remove samples above scaled best FWHM for channel/location)",
                filterByValueScale, 2, 5, "x");
        gd.addNumericField("Remove samples having less neighbors (same channel) that this during ", filterByNeib, 0,
                1, "");
        gd.addNumericField("Minimal required number of channels/samples", minLeftSamples, 0, 3, "samples");
        gd.addNumericField("Minimal required number of samples in the best channel", minBestLeftSamples, 0, 3,
                "samples");
        gd.addNumericField("... of them closest to the center, best channel", minCenterSamplesBest, 0, 3,
                "samples");
        gd.addNumericField("... of them closest to the center, total in all channels", minCenterSamplesTotal, 0, 3,
                "samples");
        gd.addNumericField("Number of closest samples to consider", centerSamples, 0, 3, "samples");

        gd.addNumericField("Maximal accepted RMS", maxRMS, 3, 5, "");

        gd.addNumericField("Z min", zMin, 2, 5, "um");
        gd.addNumericField("Z max", zMax, 2, 5, "um");
        gd.addNumericField("Z step", zStep, 2, 5, "um");

        gd.addNumericField("Tilt min", tMin, 2, 5, "um/mm");
        gd.addNumericField("Tilt max", tMax, 2, 5, "um/mm");
        gd.addNumericField("Tilt step", tStep, 2, 5, "um/mm");

        gd.addNumericField("Target focus (relative to best composirte)", targetRelFocalShift, 2, 5, "um");

        gd.addNumericField("Target horizontal tilt (normally 0)", targetRelTiltX, 2, 5, "um/mm");
        gd.addNumericField("Target vertical tilt (normally 0)", targetRelTiltY, 2, 5, "um/mm");
        for (int n = 0; n < this.zTxTyAdjustMode.length; n++) {
            gd.addChoice("Adjust " + zTxTyNames[n] + " mode", zTxTyAdjustModeNames,
                    zTxTyAdjustModeNames[this.zTxTyAdjustMode[n]]);
        }

        WindowTools.addScrollBars(gd);
        gd.showDialog();
        if (gd.wasCanceled())
            return;

        nMeas = (int) gd.getNextNumber();

        for (int i = 0; i < fieldFitting.channelSelect.length; i++) {
            fieldFitting.channelSelect[i] = gd.getNextBoolean();
        }
        filterZ = gd.getNextBoolean();
        filterByScanValue = gd.getNextBoolean();
        filterByValueScale = gd.getNextNumber();
        filterByNeib = (int) gd.getNextNumber();

        minLeftSamples = (int) gd.getNextNumber();
        minBestLeftSamples = (int) gd.getNextNumber();
        minCenterSamplesBest = (int) gd.getNextNumber();
        minCenterSamplesTotal = (int) gd.getNextNumber();
        centerSamples = (int) gd.getNextNumber();
        maxRMS = gd.getNextNumber();
        zMin = gd.getNextNumber();
        zMax = gd.getNextNumber();
        zStep = gd.getNextNumber();

        tMin = gd.getNextNumber();
        tMax = gd.getNextNumber();
        tStep = gd.getNextNumber();

        targetRelFocalShift = gd.getNextNumber();
        targetRelTiltX = gd.getNextNumber(); // for testing, normally should be 0 um/mm
        targetRelTiltY = gd.getNextNumber(); // for testing, normally should be 0 um/mm
        for (int n = 0; n < this.zTxTyAdjustMode.length; n++) {
            this.zTxTyAdjustMode[n] = gd.getNextChoiceIndex();
        }

        boolean OK;
        fieldFitting.mechanicalFocusingModel.setAdjustMode(true, this.zTxTyAdjustMode); // to correctly find Z centers,
        fieldFitting.mechanicalSelect = fieldFitting.mechanicalFocusingModel.maskSetZTxTy(null); // all: z, tx, ty

        String header = "# measurement";
        for (int i = 0; i < fieldFitting.mechanicalFocusingModel.paramValues.length; i++) {
            if ((fieldFitting.mechanicalSelect == null) || fieldFitting.mechanicalSelect[i]) {
                header += "\t" + fieldFitting.mechanicalFocusingModel.getName(i) + " ("
                        + fieldFitting.mechanicalFocusingModel.getUnits(i) + ")";
            }
        }
        header += "\tRMS";
        header += "\tZc\tTiltX\tTiltY";
        for (int i = 0; i < 3; i++)
            header += "\tmz" + i;
        for (int i = 0; i < 3; i++)
            header += "\tm" + i;
        StringBuffer sb = new StringBuffer();

        boolean single = (nMeas >= 0);
        if (single) {
            if (debugLevel > 0)
                System.out.print("======== testMeasurement(" + nMeas + ") ======== ");

            OK = testMeasurement(null, // FIXME: zTxTyAdjustMode
                    singleMeasurement(measurements.get(nMeas)),
                    //                nMeas,
                    zMin, //+best_qb_corr[0],
                    zMax, //+best_qb_corr[0],
                    zStep, tMin, tMax, tStep);
            if ((debugLevel > 0) && (dataVector.length > 0)) {
                System.out.println("Motors= " + dataVector[0].motors[0] + " : " + dataVector[0].motors[1] + " : "
                        + dataVector[0].motors[2] + " timestamp= " + dataVector[0].timestamp);
            }
            if (!OK) {
                if (debugLevel > 0)
                    System.out.println("testMeasurement(" + nMeas + ") failed");
            } else {
                if (debugLevel > 0)
                    System.out.println(showSamples());
                for (int i = 0; i < fieldFitting.mechanicalFocusingModel.paramValues.length; i++) {
                    if ((fieldFitting.mechanicalSelect == null) || fieldFitting.mechanicalSelect[i]) {
                        System.out.println(fieldFitting.mechanicalFocusingModel.getDescription(i) + ": "
                                + IJ.d2s(fieldFitting.mechanicalFocusingModel.paramValues[i], 3) + " "
                                + fieldFitting.mechanicalFocusingModel.getUnits(i));
                    }
                }
                double[] zTilts = getCenterZTxTy(measurements.get(nMeas), -1); // common parameters

                double[] dmz = getAdjustedMotors(null, // double [] zM0, //  current linearized motors (or null for full adjustment) 
                        targetRelFocalShift + best_qb_corr[0], targetRelTiltX, // for testing, normally should be 0 um/mm
                        targetRelTiltY, false);
                if ((dmz != null) && (debugLevel > 0)) {
                    System.out.println("Suggested motor linearized positions: " + IJ.d2s(dmz[0], 2) + ":"
                            + IJ.d2s(dmz[1], 2) + ":" + IJ.d2s(dmz[2], 2));
                }
                double[] dm = getAdjustedMotors(null, // double [] zM0, //  current linearized motors (or null for full adjustment) 
                        targetRelFocalShift + best_qb_corr[0], targetRelTiltX, // for testing, normally should be 0 um/mm
                        targetRelTiltY, true);
                if ((dm != null) && (debugLevel > 0)) {
                    System.out.println("Suggested motor positions: " + IJ.d2s(dm[0], 0) + ":" + IJ.d2s(dm[1], 0)
                            + ":" + IJ.d2s(dm[2], 0));
                }
                sb.append(nMeas);
                for (int i = 0; i < fieldFitting.mechanicalFocusingModel.paramValues.length; i++) {
                    if ((fieldFitting.mechanicalSelect == null) || fieldFitting.mechanicalSelect[i]) {
                        sb.append("\t" + IJ.d2s(fieldFitting.mechanicalFocusingModel.paramValues[i], 3));
                    }
                }
                sb.append("\t" + IJ.d2s(currentRMSPure, 3));
                if (zTilts != null) {
                    sb.append("\t" + IJ.d2s(zTilts[0] - best_qb_corr[0], 3));
                    sb.append("\t" + IJ.d2s(zTilts[1], 3));
                    sb.append("\t" + IJ.d2s(zTilts[2], 3));
                    System.out.println("Z center=" + IJ.d2s(zTilts[0] - best_qb_corr[0], 3) + "um, TiltX="
                            + IJ.d2s(zTilts[1], 3) + "um/mm, TiltY=" + IJ.d2s(zTilts[2], 3) + "um/mm");
                } else {
                    sb.append("\t---\t---\t---");
                }
                if (dmz != null) {
                    for (int i = 0; i < dmz.length; i++)
                        sb.append("\t" + IJ.d2s(dmz[i], 1));
                } else {
                    sb.append("\t---\t---\t---");
                }
                if (dm != null) {
                    for (int i = 0; i < dm.length; i++)
                        sb.append("\t" + IJ.d2s(dm[i], 1));
                } else {
                    sb.append("\t---\t---\t---");
                }
                sb.append("\n");
            }
        } else {
            for (nMeas = 0; nMeas < measurements.size(); nMeas++) {
                if (debugLevel > 0)
                    System.out.print("======== testMeasurement(" + nMeas + ") ======== ");
                OK = testMeasurement(null, // FIXME: zTxTyAdjustMode
                        singleMeasurement(measurements.get(nMeas)), zMin, //+best_qb_corr[0],
                        zMax, // +best_qb_corr[0],
                        zStep, tMin, tMax, tStep);
                if ((debugLevel > 0) && (dataVector.length > 0)) {
                    System.out.println("Motors= " + dataVector[0].motors[0] + " : " + dataVector[0].motors[1]
                            + " : " + dataVector[0].motors[2] + " timestamp= " + dataVector[0].timestamp);
                }
                if (!OK) {
                    if (debugLevel > 0)
                        System.out.println("testMeasurement(" + nMeas + ") failed");
                } else {
                    if (debugLevel > 0)
                        System.out.println(showSamples());
                    for (int i = 0; i < fieldFitting.mechanicalFocusingModel.paramValues.length; i++) {
                        if ((fieldFitting.mechanicalSelect == null) || fieldFitting.mechanicalSelect[i]) {
                            System.out.println(fieldFitting.mechanicalFocusingModel.getDescription(i) + ": "
                                    + IJ.d2s(fieldFitting.mechanicalFocusingModel.paramValues[i], 3) + " "
                                    + fieldFitting.mechanicalFocusingModel.getUnits(i));
                        }
                    }
                    double[] zTilts = getCenterZTxTy(measurements.get(nMeas), -1); // <0 - common parameters
                    double[] dmz = getAdjustedMotors(null, // double [] zM0, //  current linearized motors (or null for full adjustment) 
                            targetRelFocalShift + best_qb_corr[0], targetRelTiltX, // for testing, normally should be 0 um/mm
                            targetRelTiltY, false);
                    if ((dmz != null) && (debugLevel > 0)) {
                        System.out.println("Suggested motor linearized positions: " + IJ.d2s(dmz[0], 2) + ":"
                                + IJ.d2s(dmz[1], 2) + ":" + IJ.d2s(dmz[2], 2));
                    }
                    double[] dm = getAdjustedMotors(null, // double [] zM0, //  current linearized motors (or null for full adjustment) 
                            targetRelFocalShift + best_qb_corr[0], targetRelTiltX, // for testing, normally should be 0 um/mm
                            targetRelTiltY, true);
                    if ((dm != null) && (debugLevel > 0)) {
                        System.out.println("Suggested motor positions: " + IJ.d2s(dm[0], 0) + ":" + IJ.d2s(dm[1], 0)
                                + ":" + IJ.d2s(dm[2], 0));
                    }

                    if (maxRMS > 0.0) {
                        if (currentRMSPure > maxRMS) {
                            if (debugLevel > 0)
                                System.out.println(
                                        "RMS too high, " + IJ.d2s(currentRMSPure, 3) + " > " + IJ.d2s(maxRMS, 3));
                            continue;
                        }
                    }
                    sb.append(nMeas);
                    for (int i = 0; i < fieldFitting.mechanicalFocusingModel.paramValues.length; i++) {
                        if ((fieldFitting.mechanicalSelect == null) || fieldFitting.mechanicalSelect[i]) {
                            sb.append("\t" + IJ.d2s(fieldFitting.mechanicalFocusingModel.paramValues[i], 3));
                        }
                    }
                    sb.append("\t" + IJ.d2s(currentRMSPure, 3));
                    if (zTilts != null) {
                        sb.append("\t" + IJ.d2s(zTilts[0] - best_qb_corr[0], 3));
                        sb.append("\t" + IJ.d2s(zTilts[1], 3));
                        sb.append("\t" + IJ.d2s(zTilts[2], 3));
                        System.out.println("Z center=" + IJ.d2s(zTilts[0] - best_qb_corr[0], 3) + "um, TiltX="
                                + IJ.d2s(zTilts[1], 3) + "um/mm, TiltY=" + IJ.d2s(zTilts[2], 3) + "um/mm");
                    } else {
                        sb.append("\t---\t---\t---");
                    }
                    if (dmz != null) {
                        for (int i = 0; i < dmz.length; i++)
                            sb.append("\t" + IJ.d2s(dmz[i], 1));
                    } else {
                        sb.append("\t---\t---\t---");
                    }
                    if (dm != null) {
                        for (int i = 0; i < dm.length; i++)
                            sb.append("\t" + IJ.d2s(dm[i], 1));
                    } else {
                        sb.append("\t---\t---\t---");
                    }
                    sb.append("\n");
                }
            }
        }
        if (!single) {
            //          if (path!=null) {
            //             CalibrationFileManagement.saveStringToFile (
            //                   path,
            //                   header+"\n"+sb.toString());
            //          } else {
            new TextWindow(title, header, sb.toString(), 800, 1000);
            //         }
        }
        //       fieldFitting.mechanicalFocusingModel.setZTxTy(0.0,0.0,0.0); // restore zeros to correctly find Z centers,
        fieldFitting.mechanicalFocusingModel.setAdjustMode(false, null); // to correctly find Z centers,
    }

    //,
    public String showSamples() {
        boolean[][] usedSamples = new boolean[getNumChannels()][getNumSamples()];
        for (int chn = 0; chn < usedSamples.length; chn++)
            for (int sample = 0; sample < usedSamples[chn].length; sample++)
                usedSamples[chn][sample] = false;
        for (int i = 0; i < dataVector.length; i++)
            if (dataWeights[i] > 0.0) {
                usedSamples[dataVector[i].channel][dataVector[i].sampleIndex] = true;
            }
        return showSamples(usedSamples);
    }

    public String showSamples(boolean[][] usedSamples) {
        int height = sampleCoord.length;
        int width = sampleCoord[0].length;
        String s = "";
        for (int i = 0; i < height; i++) {
            for (int chn = 0; chn < usedSamples.length; chn++) {
                for (int j = 0; j < width; j++) {
                    s += usedSamples[chn][i * width + j] ? "+" : ".";
                    s += " ";
                }
                if (chn < (usedSamples.length - 1))
                    s += "  ";
            }
            s += "\n";
        }
        return s;
    }

    public double[][] getAllZTT( // z, tx, ty, temperature - skips bad measurements - no, some may be null; have NaN
            boolean noTiltScan, FocusingField ff, int tiltAdjustMode) {
        if (debugLevel > 0)
            System.out.println(
                    "getAllZTM(): Calculating optimal focal/tilt, qualBOptimizeMode=" + this.qualBOptimizeMode);
        testQualB(false); // optimize qualB, store results in this.qualBOptimizationResults
        if (debugLevel > 0) {
            System.out.println("Optimal absolute Zc=" + this.qualBOptimizationResults[0]);
            System.out.println("Optimal Tx=" + this.qualBOptimizationResults[1]);
            System.out.println("Optimal Ty=" + this.qualBOptimizationResults[2]);
        }
        //      int [] zTxTyMode={1,tiltAdjustMode,tiltAdjustMode}; // all common parameters
        int[] zTxTyMode = { 2, tiltAdjustMode, tiltAdjustMode }; // z - individual, tilt - as specified
        double[][] results = matchSeriesAbsoluteLMA( // result absolute (not relative to optimal)
                zTxTyMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
                true, //boolean noTiltScan,
                ff.measurements);
        if (results == null) {
            System.out.println("getAllZTM() FAILED");
            return null;
        }
        double[][] result = new double[ff.measurements.size()][];
        for (int i = 0; i < result.length; i++) {
            result[i] = new double[results.length + 1];
            for (int j = 0; j < results.length; j++) {
                result[i][j] = results[j][(i >= results[j].length) ? 0 : i] - this.qualBOptimizationResults[j];
            }
            result[i][results.length] = ff.measurements.get(i).temperature;
            if ((result[i] != null) && Double.isNaN(result[i][0]))
                result[i] = null;
        }
        return result; // may contain bad measurements (NaN for Z)
    }

    public double[] averageZTM(// results relative to optimal
            boolean noTiltScan, FocusingField ff, int tiltAdjustMode) { // keep existent tilt
        if (debugLevel > 0)
            System.out.println("Calculating optimal focal/tilt, qualBOptimizeMode=" + this.qualBOptimizeMode);
        testQualB(false); // optimize qualB, store results in this.qualBOptimizationResults
        if (debugLevel > 0) {
            System.out.println("Optimal absolute Zc=" + this.qualBOptimizationResults[0]);
            System.out.println("Optimal Tx=" + this.qualBOptimizationResults[1]);
            System.out.println("Optimal Ty=" + this.qualBOptimizationResults[2]);
        }
        int[] zTxTyMode = { 1, tiltAdjustMode, tiltAdjustMode }; // all common parameters
        double[][] results = matchSeriesAbsoluteLMA( // result absolute (not relative to optimal)
                zTxTyMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
                true, //boolean noTiltScan,
                ff.measurements);
        // here results should not contain null - at least for z
        if (results == null) {
            System.out.println("averageZTM() FAILED");
            return null;
        }

        double[] result = new double[results.length];
        for (int n = 0; n < result.length; n++) {
            result[n] = results[n][0] - this.qualBOptimizationResults[n];
        }
        return result;
    }

    public double[][] getZ0TxTyAbsRel() { // z0, not zc!??
        double[][] zTxTyAbsRel = new double[2][];
        zTxTyAbsRel[0] = fieldFitting.mechanicalFocusingModel.getZTxTy();
        zTxTyAbsRel[1] = zTxTyAbsRel[0].clone();
        for (int i = 0; i < this.qualBOptimizationResults.length; i++)
            zTxTyAbsRel[1][i] -= this.qualBOptimizationResults[i];
        return zTxTyAbsRel;
    }

    public double[][] getZcZ0TxTy( // 0, zc, 1 - z0 abs, 2 - z0-rel
            ArrayList<FocusingFieldMeasurement> measurements) {
        double[][] zcZ0TxTy = new double[2][];
        zcZ0TxTy[0] = getCenterZTxTy(measurements.get(0), -1);
        zcZ0TxTy[1] = fieldFitting.mechanicalFocusingModel.getZTxTy();
        //       zTxTyAbsRel[1]=zTxTyAbsRel[0].clone();
        //       for (int i=0;i<this.qualBOptimizationResults.length;i++) zTxTyAbsRel[1][i]-=this.qualBOptimizationResults[i];
        return zcZ0TxTy;
    }

    public double[][] matchSeriesAbsoluteLMA( // result absolute (not relative to optimal)
            int[] zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            boolean noTiltScan, ArrayList<FocusingFieldMeasurement> measurements) {
        if (measurements.size() < 1)
            return null;
        if (!testMeasurement(zTxTyAdjustMode, measurements, zMin, //+best_qb_corr[0],
                zMax, //+best_qb_corr[0],
                zStep, (noTiltScan ? 0.0 : tMin), (noTiltScan ? 0.0 : tMax), tStep)) {
            if (debugLevel > 0)
                System.out.println("adjustLMA() failed");
            return null;
        }
        double[] zTilts = getCenterZTxTy(measurements.get(0), -1); // common values
        double[][] zTiltsIndiv = null;
        boolean indiv = false;
        if (measurements.size() > 1) {
            for (int n = 0; n < zTilts.length; n++)
                if (zTxTyAdjustMode[n] > 1)
                    indiv = true;
        }
        if (indiv) {
            zTiltsIndiv = new double[measurements.size()][];
            for (int i = 0; i < zTiltsIndiv.length; i++) {
                zTiltsIndiv[i] = getCenterZTxTy(measurements.get(0), i); // individual values // may be null
            }
        }
        // May contain NaN !!
        // TODO: change order to skip nulls (bad measurements), and also - invert indices?        

        double[][] result = new double[3][];
        for (int n = 0; n < result.length; n++) {
            if (zTxTyAdjustMode[n] <= 1) {
                result[n] = new double[1];
                result[n][0] = zTilts[n];
            } else {
                result[n] = new double[zTiltsIndiv.length];
                for (int i = 0; i < zTiltsIndiv.length; i++) {
                    if (zTiltsIndiv[i] != null)
                        result[n][i] = zTiltsIndiv[i][n];
                    else
                        result[n][i] = Double.NaN;
                }
            }
        }
        return result;
    }

    public double[] adjustLMA( // result relative to optimal
            int[] zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            boolean noTiltScan, FocusingFieldMeasurement measurement, boolean parallelMove, boolean noQualB, // do not re-claculate testQualB 
            boolean noAdjust) { // do not calculate correction
        System.out.println("adjustLMA(): mode=" + zTxTyAdjustMode[0] + "/" + zTxTyAdjustMode[1] + "/"
                + zTxTyAdjustMode[2] + ", noTiltScan=" + noTiltScan + ", parallelMove=" + parallelMove
                + ", noQualB=" + noQualB + ", noAdjust=" + noAdjust);
        if (!noQualB) {
            if (debugLevel > 0)
                System.out.println("Calculating optimal focal/tilt, qualBOptimizeMode=" + this.qualBOptimizeMode);
            testQualB(false); // optimize qualB, store results in this.qualBOptimizationResults
            if (debugLevel > 0) {
                System.out.println("Optimal absolute Zc=" + this.qualBOptimizationResults[0]);
                System.out.println("Optimal Tx=" + this.qualBOptimizationResults[1]);
                System.out.println("Optimal Ty=" + this.qualBOptimizationResults[2]);
            }
        }
        if (!testMeasurement(zTxTyAdjustMode, singleMeasurement(measurement), zMin, //+best_qb_corr[0],
                zMax, //+best_qb_corr[0],
                zStep, (noTiltScan ? 0.0 : tMin), (noTiltScan ? 0.0 : tMax), tStep)) {
            if (debugLevel > 0)
                System.out.println("adjustLMA() failed");
            return null;
        }
        double[] result = new double[noAdjust ? 3 : 6];

        double[] zTilts = getCenterZTxTy(measurement, -1);
        if (zTilts == null) {
            System.out.println("Failed to get getCenterZTxTy()");
            return null;
        }
        if (this.qualBOptimizationResults == null) {
            System.out.println("this.qualBOptimizationResults is null (yet), absolute focal distance is wrong ");
            result[0] = zTilts[0]; // not calibrated
            result[1] = zTilts[1];
            result[2] = zTilts[2];
        } else {
            result[0] = zTilts[0] - this.qualBOptimizationResults[0]; //best_qb_corr[0];
            result[1] = zTilts[1] - this.qualBOptimizationResults[1];
            result[2] = zTilts[2] - this.qualBOptimizationResults[2];
        }
        if (!noAdjust) {
            double[] zm = null;
            zm = new double[3];
            for (int i = 0; i < zm.length; i++)
                zm[i] = fieldFitting.mechanicalFocusingModel.mToZm(measurement.motors[i], i);
            if (this.debugLevel > 0) {
                System.out.println("Current linearized motor positions, center="
                        + (0.25 * zm[0] + 0.25 * zm[1] + 0.5 * zm[2]));
                for (int i = 0; i < zm.length; i++) {
                    System.out.println(i + ": " + zm[i] + " um");
                }
                double[] rzm = new double[3];
                for (int i = 0; i < zm.length; i++)
                    rzm[i] = fieldFitting.mechanicalFocusingModel.zmToM(zm[i], i);
                System.out.println(
                        "Checking back to motor positions, center=" + (0.25 * rzm[0] + 0.25 * rzm[1] + 0.5 * rzm[2])
                                + "steps (current=" + (0.25 * measurement.motors[0] + 0.25 * measurement.motors[1]
                                        + 0.5 * measurement.motors[2])
                                + " steps)");
                for (int i = 0; i < zm.length; i++) {
                    System.out.println(i + ": " + rzm[i] + " steps (was " + measurement.motors[i] + " steps)");
                }
            }
            double[] dm = getAdjustedMotors(parallelMove ? zm : null,
                    this.targetRelFocalShift + this.qualBOptimizationResults[0], //targetRelFocalShift+best_qb_corr[0],
                    this.targetRelTiltX + this.qualBOptimizationResults[1], //0.0, // targetTiltX, // for testing, normally should be 0 um/mm
                    this.targetRelTiltY + this.qualBOptimizationResults[2], //0.0, // targetTiltY,
                    true); // motor steps
            if ((dm != null) && (debugLevel > 1)) {
                System.out.println("Suggested motor positions: " + IJ.d2s(dm[0], 0) + ":" + IJ.d2s(dm[1], 0) + ":"
                        + IJ.d2s(dm[2], 0));
            }
            if (dm != null) {
                result[3] = dm[0];
                result[4] = dm[1];
                result[5] = dm[2];
            } else {
                result[3] = Double.NaN;
                result[4] = Double.NaN;
                result[5] = Double.NaN;
            }
        }
        return result;
    }

    // add tx, ty?
    public double[] getCenterZTxTy(FocusingFieldMeasurement measurement, int index) { // <0 - use mechanicalFocusingModel z, tx, ty, >=0 - use individual if available
        double[] tilts = fieldFitting.mechanicalFocusingModel.getTilts(measurement.motors, index);
        if (tilts == null)
            return null;// bad measurement
        double[] result = { fieldFitting.mechanicalFocusingModel.calc_ZdZ(index, // <0 - use mechanicalFocusingModel z, tx, ty
                measurement.motors, currentPX0, //fieldFitting.getCenterXY()[0],
                currentPY0, //fieldFitting.getCenterXY()[1],
                null), tilts[0], tilts[1] };
        //       if (Double.isNaN(result[0])) return null;
        return result; // may contain Double.NaN - no, all null
    }

    public double[] getAdjustedMotors( // Target - absolute Zc, axial tilts
            double[] zM0, //  current linearized motors (or null for full adjustment) 
            double targetRelFocalShift, double targetTiltX, // for testing, normally should be 0 um/mm
            double targetTiltY, // for testing, normally should be 0 um/mm
            boolean motorSteps) {
        double[] zM = fieldFitting.mechanicalFocusingModel.getZM(zM0, currentPX0, //fieldFitting.getCenterXY()[0],
                currentPY0, //fieldFitting.getCenterXY()[1],
                targetRelFocalShift, targetTiltX, targetTiltY);
        if (zM == null)
            return null; // not yet used
        if (!motorSteps)
            return zM;
        if (debugLevel > 0) {
            System.out.println("getAdjustedMotors(): Suggested linearized motor positions, center="
                    + (0.25 * zM[0] + 0.25 * zM[1] + 0.5 * zM[2]));
            for (int i = 0; i < zM.length; i++) {
                System.out.println(i + ": " + zM[i] + " um");
            }
        }

        //      if (debugLevel>0) System.out.println("Suggested motor linearized positions: "+IJ.d2s(zM[0],2)+":"+IJ.d2s(zM[1],2)+":"+IJ.d2s(zM[2],2));
        double[] dm = new double[zM.length];
        for (int index = 0; index < dm.length; index++) {
            dm[index] = fieldFitting.mechanicalFocusingModel.zmToM(zM[index], index);
        }
        if (debugLevel > 0) {
            System.out.println("getAdjustedMotors(): Suggested motor positions, center="
                    + (0.25 * dm[0] + 0.25 * dm[1] + 0.5 * dm[2]));
            for (int i = 0; i < dm.length; i++) {
                System.out.println(i + ": " + dm[i] + " steps");
            }
        }
        return dm;
    }

    public boolean testMeasurement(int[] zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            //          FocusingFieldMeasurement measurement, // null in calibrate mode
            ArrayList<FocusingFieldMeasurement> measurements, double zMin, double zMax, double zStep, double tMin,
            double tMax, double tStep) {
        debugDerivativesFxDxDy = false;
        int retryLimit = 20;
        fieldFitting.mechanicalFocusingModel.setAdjustMode(true, zTxTyAdjustMode);
        double[] zTxTy;
        if (zTxTyAdjustMode != null) {
            zTxTy = fieldFitting.mechanicalFocusingModel.getZTxTy(); // current values - will be ignored and overwritten
            //           if (zTxTyAdjustMode[1]==0)
            zTxTy[1] = avgTx;
            zTxTy[2] = avgTy;
            fieldFitting.mechanicalFocusingModel.setZTxTy(zTxTy); // may be used in findAdjustZ()
        }
        setDataVector(false, createDataVector(measurements)); //measurements.get(nMeas)));
        // TODO: Adjust by center samples  only?
        zTxTy = findAdjustZ(
                //             zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual (will ignore tilt Min/Max and use current if not adjusted)
                //             measurement,
                measurements, filterZ, //boolean filterZ,
                filterByScanValue, filterByValueScale, filterByNeib, zMin, zMax, zStep, tMin, tMax, tStep);
        if (Double.isNaN(zTxTy[0])) {
            if (debugLevel > 1)
                System.out.println("testMeasurement() failed: insufficient data to get z initial estimation");
            return false;
        }
        fieldFitting.mechanicalFocusingModel.setZTxTy(zTxTy[0], zTxTy[1], zTxTy[2]); //z,0.0,0.0);// z,tx,ty
        boolean[] wasPrevEnable = null;
        for (int n = 0; n < retryLimit; n++) { // TODO: Watch for the mask remain stable
            zTxTy[0] = fieldFitting.mechanicalFocusingModel.getValue(MECH_PAR.z0);
            zTxTy[1] = fieldFitting.mechanicalFocusingModel.getValue(MECH_PAR.tx);
            zTxTy[2] = fieldFitting.mechanicalFocusingModel.getValue(MECH_PAR.ty);
            if (debugLevel > 1)
                System.out.println("testMeasurement(), run " + n + " (z=" + zTxTy[0] + " tx=" + zTxTy[1] + " ty="
                        + zTxTy[2] + ")");
            boolean[] was2PrevEnable = (wasPrevEnable == null) ? null : wasPrevEnable.clone();
            wasPrevEnable = (prevEnable == null) ? null : prevEnable.clone();
            this.lambda = this.adjustmentInitialLambda;
            boolean OK = LevenbergMarquardt(
                    //                measurement, 
                    zTxTyAdjustMode, // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
                    measurements, false, // true, // open dialog
                    true, // boolean autoSel,
                    debugLevel);
            if (!OK) {
                if (debugLevel > 1)
                    System.out.println("testMeasurement() failed: LMA failed");
                return false;
            }
            /*          
                      for (int i=0;i<fieldFitting.mechanicalFocusingModel.paramValues.length;i++){
                         if ((fieldFitting.mechanicalSelect==null) || fieldFitting.mechanicalSelect[i] ) {
            System.out.println(
                  fieldFitting.mechanicalFocusingModel.getDescription(i)+": "+
                        IJ.d2s(fieldFitting.mechanicalFocusingModel.paramValues[i],3)+" "+
                        fieldFitting.mechanicalFocusingModel.getUnits(i));
                         }
                      }
            */
            if ((wasPrevEnable != null) && (prevEnable != null) && (wasPrevEnable.length == prevEnable.length)) {
                boolean changedEnable = false;
                for (int i = 0; i < prevEnable.length; i++)
                    if (prevEnable[i] != wasPrevEnable[i]) {
                        changedEnable = true;
                        break;
                    }
                if (!changedEnable) {
                    if (debugLevel > 1)
                        System.out.println(
                                "No filter cnange, finished in " + (n + 1) + " step" + ((n == 0) ? "" : "s"));
                    if (debugLevel > 1) {
                        System.out.println("=== Absolute shift/tilt from the measuremet ===");
                        for (int i = 0; i < fieldFitting.mechanicalFocusingModel.paramValues.length; i++) {
                            if ((fieldFitting.mechanicalSelect == null) || fieldFitting.mechanicalSelect[i]) {
                                System.out.println(fieldFitting.mechanicalFocusingModel.getDescription(i) + ": "
                                        + IJ.d2s(fieldFitting.mechanicalFocusingModel.paramValues[i], 3) + " "
                                        + fieldFitting.mechanicalFocusingModel.getUnits(i));
                            }
                        }
                    }
                    return true;
                } else {
                    if ((was2PrevEnable != null) && (prevEnable != null)
                            && (was2PrevEnable.length == prevEnable.length)) {
                        changedEnable = false;
                        for (int i = 0; i < prevEnable.length; i++)
                            if (prevEnable[i] != was2PrevEnable[i]) {
                                changedEnable = true;
                                break;
                            }
                        if (!changedEnable) {
                            if (debugLevel > 0)
                                System.out.println(
                                        "Filter repeats one before previous, finished in " + (n + 1) + " steps");
                            return true;
                        }
                    }
                }
            }
        }
        if (debugLevel > 0)
            System.out.println("Maximal retries exceeded in " + retryLimit + " steps");
        return true; //?
    }

    public class FieldFitting {
        //      private Properties savedProperties=null;
        private int currentVectorLength = 0;
        private double[] pXY = null;
        private boolean[] centerSelect = null;
        private boolean[] centerSelectDefault = { true, true };
        public MechanicalFocusingModel mechanicalFocusingModel;
        private CurvatureModel[] curvatureModel = new CurvatureModel[6]; // 3 colors, sagittal+tangential each
        private boolean[] channelSelect = null;
        private boolean[] mechanicalSelect = null;
        private boolean[][] curvatureSelect = new boolean[6][];

        private boolean[][] sampleCorrSelect = new boolean[6][]; // enable individual (per sample coordinates) correction of parameters
        private double[][] sampleCorrCost = new double[6][]; // equivalent cost of one unit of parameter value (in result units, um)
        private double[][] sampleCorrSigma = new double[6][]; // sigma (in mm) for neighbors influence
        private double[][] sampleCorrPullZero = new double[6][]; // 1.0 - only difference from neighbors matters, 0.0 - only difference from 0
        //       private String strategyComment="";
        //       private boolean lastInSeries=true;
        //       private double lambda=0.001; // synchronize with top?
        public FieldStrategies fieldStrategies;

        //       private double [] sampleCorrRadius=null;
        private double[][] sampleCoordinates = null;
        private double[][][][] sampleCorrCrossWeights = new double[6][][][];
        private double[] sampleCorrVector = null; // currently adjusted per-sample parameters
        private double[][][] correctionParameters = new double[6][][]; // all
        public int numberOfLocations = 0;

        private int[][] sampleCorrChnParIndex = null;
        private boolean[] dflt_sampleCorrSelect = { false, false, false, false };
        //       private double [] dflt_sampleCorrCost= {1.0,50.0,1.0,1.0};
        //       private double [] dflt_sampleCorrCost= {0.3,20.0,0.3,1.0};
        //       private double [] dflt_sampleCorrCost= {0.05,10.0,2.0,1.0};
        //       private double [] dflt_sampleCorrCost= {0.05,1.0,1.0,1.0};
        //       private double [] dflt_sampleCorrCost= {0.5,0.5,2.0,1.0};
        //       private double [] dflt_sampleCorrCost= {0.1,0.5,2.0,1.0};
        //       private double [] dflt_sampleCorrCost= {0.1,1.0,1.0,0.5};
        //       private double [] dflt_sampleCorrCost= {0.2,2.0,2.0,1.0,1.0};
        //       private double [] dflt_sampleCorrCost= {0.1,1.0,1.0,1.0,1.0};
        private double[] dflt_sampleCorrCost = { 0.01, 1.0, 1.0, 1.0, 1.0 };
        private double dflt_sampleCorrSigma = 2.0; // mm
        private double dflt_sampleCorrPullZero = 0.75; // fraction
        // outer index - parameter [0..2], inner - number of measurement
        // element may be null if this parameter is not used or [num] - common parameter
        private int[][] zTMap = null;

        public final String[] channelDescriptions = { "Red, sagittal", "Red, tangential", "Green, sagittal",
                "Green, tangential", "Blue, sagittal", "Blue, tangential" };

        public int getCurrentVectorLength() {
            return this.currentVectorLength;
        }

        public void setCurrentVectorLength(int vectorlength) {
            this.currentVectorLength = vectorlength;
        }

        public void setZTMap(int[][] map) {
            this.zTMap = map;
        }

        public int getZTMap(int meas, int parIndex) {
            try {
                if (meas >= this.zTMap[parIndex].length)
                    meas = 0; // common parameter, use spec ified for measurement 0
            } catch (Exception e) {
            }
            try {
                return this.zTMap[parIndex][meas];
            } catch (Exception e) {
                return -1;
            }
        }

        public void setProperties(String prefix, Properties properties) {
            if (mechanicalFocusingModel == null) {
                if (debugLevel > 1)
                    System.out.println("Mechanical properties not yet initialized, will save properties later");
                return;
            }
            boolean select = (properties.getProperty("selected") != null);
            boolean select_mechanicalFocusingModel = !select;
            boolean select_curvatureModel = !select;
            boolean select_fieldStrategies = !select;
            boolean select_FieldFitting = !select;
            if (select) {
                GenericDialog gd = new GenericDialog("Select FieldFitting parameters to save");
                gd.addCheckbox("MechanicalFocusingModel parameter class", select_mechanicalFocusingModel);
                gd.addCheckbox("CurvatureModel parameter classes", select_curvatureModel);
                gd.addCheckbox("FieldStrategies parameter classes", select_fieldStrategies);
                gd.addCheckbox("FieldFitting local parameters", select_FieldFitting);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                select_mechanicalFocusingModel = gd.getNextBoolean();
                select_curvatureModel = gd.getNextBoolean();
                select_fieldStrategies = gd.getNextBoolean();
                select_FieldFitting = gd.getNextBoolean();
            }

            if (select_mechanicalFocusingModel)
                mechanicalFocusingModel.setProperties(prefix + "mechanicalFocusingModel.", properties);
            if (select_curvatureModel) {
                for (int i = 0; i < curvatureModel.length; i++) {
                    if (curvatureModel[i] != null)
                        curvatureModel[i].setProperties(prefix + "curvatureModel_" + i + ".", properties);
                }
            }
            if (select_FieldFitting) {
                properties.setProperty(prefix + "numberOfLocations", numberOfLocations + "");
                properties.setProperty(prefix + "centerSelect_X", centerSelect[0] + "");
                properties.setProperty(prefix + "centerSelect_Y", centerSelect[1] + "");

                if (channelSelect != null)
                    for (int i = 0; i < channelSelect.length; i++) {
                        properties.setProperty(prefix + "channelSelect_" + i, channelSelect[i] + "");
                    }
                if (mechanicalSelect != null)
                    for (int i = 0; i < mechanicalSelect.length; i++) {
                        properties.setProperty(prefix + "mechanicalSelect_" + i, mechanicalSelect[i] + "");
                    }
                for (int chn = 0; chn < curvatureSelect.length; chn++)
                    if (curvatureSelect[chn] != null)
                        for (int i = 0; i < curvatureSelect[chn].length; i++) {
                            properties.setProperty(prefix + "curvatureSelect_" + chn + "_" + i,
                                    curvatureSelect[chn][i] + "");
                        }
                for (int chn = 0; chn < sampleCorrSelect.length; chn++)
                    if (sampleCorrSelect[chn] != null)
                        for (int i = 0; i < sampleCorrSelect[chn].length; i++) {
                            properties.setProperty(prefix + "sampleCorrSelect_" + chn + "_" + i,
                                    sampleCorrSelect[chn][i] + "");
                        }
                for (int chn = 0; chn < sampleCorrCost.length; chn++)
                    if (sampleCorrCost[chn] != null)
                        for (int i = 0; i < sampleCorrCost[chn].length; i++) {
                            properties.setProperty(prefix + "sampleCorrCost_" + chn + "_" + i,
                                    sampleCorrCost[chn][i] + "");
                        }
                for (int chn = 0; chn < sampleCorrSigma.length; chn++)
                    if (sampleCorrSigma[chn] != null)
                        for (int i = 0; i < sampleCorrSigma[chn].length; i++) {
                            properties.setProperty(prefix + "sampleCorrSigma_" + chn + "_" + i,
                                    sampleCorrSigma[chn][i] + "");
                        }
                for (int chn = 0; chn < sampleCorrPullZero.length; chn++)
                    if (sampleCorrPullZero[chn] != null)
                        for (int i = 0; i < sampleCorrPullZero[chn].length; i++) {
                            properties.setProperty(prefix + "sampleCorrPullZero_" + chn + "_" + i,
                                    sampleCorrPullZero[chn][i] + "");
                        }
                // save correction parameters values
                //           private double [][][] correctionParameters=new double[6][][]; // all
                if (correctionParameters != null) {
                    for (int chn = 0; chn < correctionParameters.length; chn++)
                        if (correctionParameters[chn] != null) {
                            for (int np = 0; np < correctionParameters[chn].length; np++)
                                if (correctionParameters[chn][np] != null) {
                                    for (int i = 0; i < correctionParameters[chn][np].length; i++) {
                                        properties.setProperty(
                                                prefix + "correctionParameters_" + chn + "_" + np + "_" + i,
                                                correctionParameters[chn][np][i] + "");
                                    }
                                }
                        }
                }
            }
            if (select_fieldStrategies)
                fieldStrategies.setProperties(prefix + "fieldStrategies.", properties);
        }

        public void getProperties(String prefix, Properties properties) {
            if (properties.getProperty(prefix + "numberOfLocations") != null)
                numberOfLocations = Integer.parseInt(properties.getProperty(prefix + "numberOfLocations"));

            if (properties.getProperty(prefix + "centerSelect_X") != null)
                centerSelect[0] = Boolean.parseBoolean(properties.getProperty(prefix + "centerSelect_X"));
            if (properties.getProperty(prefix + "centerSelect_Y") != null)
                centerSelect[1] = Boolean.parseBoolean(properties.getProperty(prefix + "centerSelect_Y"));
            if (mechanicalFocusingModel == null) {
                if (debugLevel > 1)
                    System.out.println("Mechanical properties not yet initialized, will apply properties later");
                return;
            }
            mechanicalFocusingModel.getProperties(prefix + "mechanicalFocusingModel.", properties);
            for (int i = 0; i < curvatureModel.length; i++) {
                if (curvatureModel[i] != null)
                    curvatureModel[i].getProperties(prefix + "curvatureModel_" + i + ".", properties);
            }
            if (channelSelect == null) {
                channelSelect = new boolean[6];
                for (int i = 0; i < channelSelect.length; i++)
                    channelSelect[i] = true;
            }
            for (int i = 0; i < channelSelect.length; i++)
                if (properties.getProperty(prefix + "channelSelect_" + i) != null) {
                    channelSelect[i] = Boolean.parseBoolean(properties.getProperty(prefix + "channelSelect_" + i));
                }
            if ((mechanicalSelect == null)
                    || (mechanicalSelect.length != mechanicalFocusingModel.getDefaultMask().length)) {
                mechanicalSelect = mechanicalFocusingModel.getDefaultMask();
            }
            for (int i = 0; i < mechanicalSelect.length; i++)
                if (properties.getProperty(prefix + "mechanicalSelect_" + i) != null) {
                    mechanicalSelect[i] = Boolean
                            .parseBoolean(properties.getProperty(prefix + "mechanicalSelect_" + i));
                }
            // curvature parameter selection: first index : channel, inner index - parameter number (radial fast, z - outer)
            if (curvatureSelect == null) {
                curvatureSelect = new boolean[curvatureModel.length][];
                for (int i = 0; i < curvatureSelect.length; i++)
                    curvatureSelect[i] = null;
            }
            for (int chn = 0; chn < correctionParameters.length; chn++)
                if (curvatureModel[chn] != null) {
                    if ((curvatureSelect[chn] == null)
                            || (curvatureSelect[chn].length != curvatureModel[chn].getDefaultMask().length)) {
                        curvatureSelect[chn] = curvatureModel[chn].getDefaultMask();
                    }
                    for (int i = 0; i < curvatureSelect[chn].length; i++)
                        if (properties.getProperty(prefix + "curvatureSelect_" + chn + "_" + i) != null) {
                            curvatureSelect[chn][i] = Boolean.parseBoolean(
                                    properties.getProperty(prefix + "curvatureSelect_" + chn + "_" + i));
                        }
                }

            // get correction setup parameters:
            if (sampleCorrSelect == null) {
                sampleCorrSelect = new boolean[curvatureModel.length][];
                for (int i = 0; i < sampleCorrSelect.length; i++)
                    sampleCorrSelect[i] = null;
            }
            for (int chn = 0; chn < curvatureModel.length; chn++)
                if (curvatureModel[chn] != null) {
                    if ((sampleCorrSelect[chn] == null)
                            || (sampleCorrSelect[chn].length != getDefaultSampleCorrSelect().length)) {
                        sampleCorrSelect[chn] = getDefaultSampleCorrSelect();
                    }
                    for (int i = 0; i < sampleCorrSelect[chn].length; i++)
                        if (properties.getProperty(prefix + "sampleCorrSelect_" + chn + "_" + i) != null) {
                            sampleCorrSelect[chn][i] = Boolean.parseBoolean(
                                    properties.getProperty(prefix + "sampleCorrSelect_" + chn + "_" + i));
                        }
                }

            if (sampleCorrCost == null) {
                sampleCorrCost = new double[curvatureModel.length][];
                for (int i = 0; i < sampleCorrCost.length; i++)
                    sampleCorrCost[i] = null;
            }
            for (int chn = 0; chn < curvatureModel.length; chn++)
                if (curvatureModel[chn] != null) {
                    if ((sampleCorrCost[chn] == null)
                            || (sampleCorrCost[chn].length != getDefaultSampleCorrCost().length)) {
                        sampleCorrCost[chn] = getDefaultSampleCorrCost();
                    }
                    for (int i = 0; i < sampleCorrCost[chn].length; i++)
                        if (properties.getProperty(prefix + "sampleCorrCost_" + chn + "_" + i) != null) {
                            sampleCorrCost[chn][i] = Double.parseDouble(
                                    properties.getProperty(prefix + "sampleCorrCost_" + chn + "_" + i));
                        }
                }

            if (sampleCorrSigma == null) {
                sampleCorrSigma = new double[curvatureModel.length][];
                for (int i = 0; i < sampleCorrSigma.length; i++)
                    sampleCorrSigma[i] = null;
            }
            for (int chn = 0; chn < curvatureModel.length; chn++)
                if (curvatureModel[chn] != null) {
                    if ((sampleCorrSigma[chn] == null)
                            || (sampleCorrSigma[chn].length != getDefaultSampleCorrSigma().length)) {
                        sampleCorrSigma[chn] = getDefaultSampleCorrSigma();
                    }
                    for (int i = 0; i < sampleCorrSigma[chn].length; i++)
                        if (properties.getProperty(prefix + "sampleCorrSigma_" + chn + "_" + i) != null) {
                            sampleCorrSigma[chn][i] = Double.parseDouble(
                                    properties.getProperty(prefix + "sampleCorrSigma_" + chn + "_" + i));
                        }
                }

            if (sampleCorrPullZero == null) {
                sampleCorrPullZero = new double[curvatureModel.length][];
                for (int i = 0; i < sampleCorrPullZero.length; i++)
                    sampleCorrPullZero[i] = null;
            }
            for (int chn = 0; chn < curvatureModel.length; chn++)
                if (curvatureModel[chn] != null) {
                    if ((sampleCorrPullZero[chn] == null)
                            || (sampleCorrPullZero[chn].length != getDefaultCorrPullZero().length)) {
                        sampleCorrPullZero[chn] = getDefaultCorrPullZero();
                    }
                    for (int i = 0; i < sampleCorrPullZero[chn].length; i++)
                        if (properties.getProperty(prefix + "sampleCorrPullZero_" + chn + "_" + i) != null) {
                            sampleCorrPullZero[chn][i] = Double.parseDouble(
                                    properties.getProperty(prefix + "sampleCorrPullZero_" + chn + "_" + i));
                        }
                }

            //  read/restore correction parameters values
            if (correctionParameters == null) {
                correctionParameters = new double[6][][];
                for (int i = 0; i < correctionParameters.length; i++)
                    correctionParameters[i] = null;
            }
            if (numberOfLocations > 0) {
                for (int chn = 0; chn < correctionParameters.length; chn++)
                    if (curvatureModel[chn] != null) {
                        int numPars = curvatureModel[chn].getNumPars()[0]; // number of Z-parameters
                        if ((correctionParameters[chn] == null) || (correctionParameters[chn].length != numPars)) {
                            correctionParameters[chn] = new double[numPars][numberOfLocations]; // numberOfLocations==0 ?
                            for (int np = 0; np < numPars; np++)
                                for (int i = 0; i < numberOfLocations; i++)
                                    correctionParameters[chn][np][i] = 0.0;
                        }
                        for (int np = 0; np < numPars; np++) {
                            if ((correctionParameters[chn][np] == null)
                                    || (correctionParameters[chn][np].length != numberOfLocations)) {
                                correctionParameters[chn][np] = new double[numberOfLocations];
                                for (int i = 0; i < numberOfLocations; i++)
                                    correctionParameters[chn][np][i] = 0.0;
                            }
                            for (int i = 0; i < numberOfLocations; i++)
                                if (properties.getProperty(
                                        prefix + "correctionParameters_" + chn + "_" + np + "_" + i) != null) {
                                    correctionParameters[chn][np][i] = Double.parseDouble(properties.getProperty(
                                            prefix + "correctionParameters_" + chn + "_" + np + "_" + i));
                                }
                        }
                    }
            } else {
                if (debugLevel > 1)
                    System.out.println("numberOfLocations==0, can not restore");
            }

            fieldStrategies = new FieldStrategies(); // reset old
            fieldStrategies.getProperties(prefix + "fieldStrategies.", properties);
        }

        //        public double [] getSampleRadiuses(){ // distance from the current center to each each sample
        //            return sampleCorrRadius;
        //        }

        public double[] getSampleRadiuses() {
            double[] sampleCorrRadius = new double[numberOfLocations];
            //pXY
            for (int i = 0; i < numberOfLocations; i++) {
                double dx = sampleCoordinates[i][0] - pXY[0];
                double dy = sampleCoordinates[i][1] - pXY[1];
                sampleCorrRadius[i] = getPixelMM() * Math.sqrt(dx * dx + dy * dy);
            }
            return sampleCorrRadius;
        }

        public double getSampleRadius(int sample) {
            double dx = sampleCoordinates[sample][0] - pXY[0];
            double dy = sampleCoordinates[sample][1] - pXY[1];
            return getPixelMM() * Math.sqrt(dx * dx + dy * dy);
        }

        public void showCurvCorr(String title) {
            int width = getSampleWidth();
            int numSamples = getNumSamples();
            String[] chnShortNames = { "RS", "RT", "GS", "GT", "BS", "BT" };
            int numCorrPar = 0;
            int maxNumPars = 0;
            for (int chn = 0; chn < correctionParameters.length; chn++)
                if (correctionParameters[chn] != null)
                    for (int np = 0; np < correctionParameters[chn].length; np++)
                        if (correctionParameters[chn][np] != null) {
                            numCorrPar++;
                            if (np > maxNumPars)
                                maxNumPars = np;
                        }
            maxNumPars++;
            if (numCorrPar == 0) {
                String msg = "No correction parameters are enabled";
                IJ.showMessage(msg);
                if (debugLevel > 1)
                    System.out.println(msg);
                return;
            }
            double[][] pixels = new double[numCorrPar][numSamples];
            String[] titles = new String[numCorrPar];
            int index = 0;
            for (int np = 0; np < maxNumPars; np++)
                for (int chn = 0; chn < correctionParameters.length; chn++)
                    if ((correctionParameters[chn] != null) && (correctionParameters[chn].length > np)
                            && (correctionParameters[chn][np] != null)) {
                        titles[index] = chnShortNames[chn] + "-" + curvatureModel[chn].getZName(np);
                        for (int nSample = 0; nSample < numSamples; nSample++) {
                            pixels[index][nSample] = correctionParameters[chn][np][nSample];
                        }
                        index++;
                    }
            (new showDoubleFloatArrays()).showArrays(pixels, width, numSamples / width, true, //boolean asStack,
                    title, titles);

        }

        public double[] getCalcValuesForZ(double z, double r, double[] corrPars) {
            double[] result = new double[6];
            for (int chn = 0; chn < result.length; chn++) {
                if (curvatureModel[chn] != null) {
                    result[chn] = curvatureModel[chn].getFdF(corrPars, r, // in mm,
                            Double.NaN, // py,
                            z, //mot_z,
                            null); //deriv_curv[c]);
                } else {
                    result[chn] = Double.NaN;
                }
            }
            return result;
        }

        /**
         * Calculate values (sagital and tangential PSF FWHM in um for each of color channels) for specified z
         * @param z distance from lens (from some zero point), image plane is perpendicular to the axis
         * @param corrected when false - provide averaged (axial model) for radius, if true - with individual correction applied
         * @param allChannels calculate for all (even disabled) channels, false - only for currently selected
         * @return outer dimension - number of channel, inner - number of sample (use getSampleRadiuses for radius of each)
         */
        public double[][] getCalcValuesForZ(double z, boolean corrected, boolean allChannels) {
            double[] sampleCorrRadius = getSampleRadiuses();
            int numSamples = sampleCorrRadius.length;
            double[][] result = new double[6][];
            for (int chn = 0; chn < result.length; chn++) {
                if ((curvatureModel[chn] != null) && (allChannels || channelSelect[chn])) {
                    result[chn] = new double[numSamples];
                    for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) {
                        //if ((chn==3) &&  (sampleIndex==23)){
                        //   System.out.println("getCalcValuesForZ(), chn="+chn+", sampleIndex="+sampleIndex);
                        //}

                        result[chn][sampleIndex] = curvatureModel[chn].getFdF(
                                corrected ? getCorrPar(chn, sampleIndex) : null, sampleCorrRadius[sampleIndex], //px,
                                Double.NaN, // py,
                                z, //mot_z,
                                null); //deriv_curv[c]);
                    }
                } else {
                    result[chn] = null;
                }
            }
            return result;
        }

        public double getChannelBestFWHM(int channel, int sampleIndex, // -1 for center
                boolean corrected // 
        ) {
            int r0Index = 2; // index of "r0" parameter (fwhm is twice that)
            double[] corrPars = corrected ? getCorrPar(channel, sampleIndex) : null;
            double r = (sampleIndex >= 0) ? getSampleRadius(sampleIndex) : 0.0;
            //           double fwhm=2.0 * curvatureModel[channel].getAr( r, corrPars)[r0Index];
            double fwhm = Math.exp(curvatureModel[channel].getAr(r, corrPars)[r0Index]);
            return fwhm;
        }

        public double[] getCalcZ(double r, boolean solve // currently if not using z^(>=2) no numeric solution is required - z0 is the minimum 
        ) {
            double[] result = new double[6];
            for (int chn = 0; chn < result.length; chn++) {
                if (curvatureModel[chn] != null) {
                    //                    result[chn]=curvatureModel[chn].getAr(r, null)[0];
                    result[chn] = findBestZ(chn, -1, false, solve);
                } else {
                    result[chn] = Double.NaN;
                }
            }
            return result;
        }

        public double findBestZ(int channel, int sampleIndex, // -1 for center
                boolean corrected, boolean solve // currently if not using z^(>=2) no numeric solution is required - z0 is the minimum 
        ) {
            return findBestZ(channel, sampleIndex, // -1 for center
                    corrected, //
                    solve, // currently if not using z^(>=2) no numeric solution is required - z0 is the minimum 
                    1.0, // double iniStep,
                    0.0001); //double precision)
        }

        public double findBestZ(int channel, int sampleIndex, // -1 for center
                boolean corrected, // 
                boolean solve, // currently if not using z^(>=2) no numeric solution is required - z0 is the minimum 
                double iniStep, double precision) {
            int maxSteps = 100;
            double[] corrPars = corrected ? getCorrPar(channel, sampleIndex) : null;
            double r = (sampleIndex >= 0) ? getSampleRadius(sampleIndex) : 0.0;
            double z0 = curvatureModel[channel].getAr(r, corrPars)[0];
            if (!solve)
                return z0;
            if (Double.isNaN(z0))
                return z0;
            double f0 = getCalcValuesForZ(z0, r, corrPars)[channel];
            double z1 = z0 + iniStep;
            double f1 = getCalcValuesForZ(z1, r, corrPars)[channel];
            double dir = (f1 < f0) ? 1.0 : -1.0;
            double z_prev, f_prev;
            if (dir > 0) {
                z_prev = z0;
                f_prev = f0;
                z0 = z1;
                f0 = f1;
            } else {
                z_prev = z1;
                f_prev = f1;
            }
            int step;
            for (step = 0; step < maxSteps; step++) {
                z1 = z0 + dir * iniStep;
                f1 = getCalcValuesForZ(z1, r, corrPars)[channel];
                if (f1 > f0)
                    break;
                z_prev = z0;
                f_prev = f0;
                z0 = z1;
                f0 = f1;
            }
            if (step >= maxSteps) {
                System.out.println("Failed to find minimum in " + maxSteps + " steps");
                return Double.NaN;
            }
            // now dividing z_prev - z0 - z1 range
            for (step = 0; step < maxSteps; step++) {
                if (f_prev > f1) {
                    z_prev = z0;
                    f_prev = f0;
                } else {
                    z1 = z0;
                    f1 = f0;
                }
                z0 = (z_prev + z1) / 2;
                //                f0=getCalcValuesForZ(z1,r)[channel]; // ????
                f0 = getCalcValuesForZ(z0, r, corrPars)[channel];
                if (Math.abs(z0 - z_prev) < precision)
                    break;
            }
            return z0;
        }

        /**
         * calculate distance to "best focus" for each channel (color and S/T) for each sample
         * @param corrected when false - provide averaged (axial model) for radius, if true - with individual correction applied
         * @param allChannels calculate for all (even disabled) channels, false - only for currently selected
         * @return outer dimension - number of channel, inner - number of sample (use getSampleRadiuses for radius of each)
         */
        public double[][] getCalcZ(boolean corrected, boolean allChannels, boolean solve // currently if not using z^(>=2) no numeric solution is required - z0 is the minimum 
        ) {
            double[] sampleCorrRadius = getSampleRadiuses();
            int numSamples = sampleCorrRadius.length;
            //            boolean [][] goodSamples=new boolean[getNumChannels()][getNumSamples()];
            //            for (int i=0;i<goodSamples.length;i++) for (int j=0;j<goodSamples[0].length;j++) goodSamples[i][j]=false;
            //            for (int n=0;n<dataVector.length;n++) if (dataWeights[n]>0.0){
            //               goodSamples[dataVector[n].channel][dataVector[n].sampleIndex]=true;
            //            }
            double[][] result = new double[6][];
            for (int chn = 0; chn < result.length; chn++) {
                if ((curvatureModel[chn] != null) && (allChannels || channelSelect[chn])) {
                    result[chn] = new double[numSamples];
                    for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) {
                        if ((goodCalibratedSamples == null) || ((goodCalibratedSamples[chn] != null)
                                && goodCalibratedSamples[chn][sampleIndex])) {
                            //                       if (goodSamples[chn][sampleIndex]) {
                            /*                          
                                                    result[chn][sampleIndex]=curvatureModel[chn].getAr(
                            sampleCorrRadius[sampleIndex],
                            corrected?getCorrPar(chn,sampleIndex):null
                            )[0];
                            //That was just the initial estimation, true only if z^1... are all 0                        
                            */

                            result[chn][sampleIndex] = findBestZ(chn, // int channel,
                                    sampleIndex, // int sampleIndex,
                                    corrected, //boolean corrected,
                                    solve);
                        } else {
                            result[chn][sampleIndex] = Double.NaN;
                        }
                    }
                } else {
                    result[chn] = null;
                }
            }
            return result;
        }

        /**
         * calculate FWHM of the  "best focus" for each channel (color and S/T) for each sample
         * @param corrected when false - provide averaged (axial model) for radius, if true - with individual correction applied
         * @param allChannels calculate for all (even disabled) channels, false - only for currently selected
         * @return outer dimension - number of channel, inner - number of sample (use getSampleRadiuses for radius of each)
         */
        public double[][] getFWHM(boolean corrected, boolean allChannels) {
            double[] sampleCorrRadius = getSampleRadiuses();
            int numSamples = sampleCorrRadius.length;
            /*            
                        boolean [][] goodSamples=new boolean[getNumChannels()][getNumSamples()];
                        for (int i=0;i<goodSamples.length;i++) for (int j=0;j<goodSamples[0].length;j++) goodSamples[i][j]=false;
                        for (int n=0;n<dataVector.length;n++) if (dataWeights[n]>0.0){
                           goodSamples[dataVector[n].channel][dataVector[n].sampleIndex]=true;
                        }
            */
            double[][] result = new double[6][];
            for (int chn = 0; chn < result.length; chn++) {
                if ((curvatureModel[chn] != null) && (allChannels || channelSelect[chn])) {
                    result[chn] = new double[numSamples];
                    for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) {
                        if ((goodCalibratedSamples == null) || ((goodCalibratedSamples[chn] != null)
                                && goodCalibratedSamples[chn][sampleIndex])) {
                            //                       if (goodSamples[chn][sampleIndex]) {
                            result[chn][sampleIndex] = getChannelBestFWHM(chn, // int channel,
                                    sampleIndex, // int sampleIndex,
                                    corrected); //boolean corrected,
                        } else {
                            result[chn][sampleIndex] = Double.NaN;
                        }
                    }
                } else {
                    result[chn] = null;
                }
            }
            return result;
        }

        public double getGreenZCenter() {
            int chn = 3; // Green, Tangential
            return curvatureModel[chn].getCenterVector()[0];
        }

        public double[] getZCenters(boolean solve) {
            //            int chn=3; // Green, Tangential
            double[] result = { curvatureModel[1].getCenterVector()[0], // Red, Tangential
                    curvatureModel[3].getCenterVector()[0], // Green, Tangential
                    curvatureModel[5].getCenterVector()[0] }; // Blue, Tangential
            if (solve) {
                double[] result_1 = { findBestZ(1, -1, false, true), findBestZ(3, -1, false, true),
                        findBestZ(5, -1, false, true), };
                return solve ? result_1 : result;
            }
            return result;
        }

        public double[] getQualB(double z, boolean corrected) {
            double[][] data = getCalcValuesForZ(z, corrected, true);
            double[] qualB = { 0.0, 0.0, 0.0 };
            double[] sampleCorrRadius = getSampleRadiuses();
            int numSamples = sampleCorrRadius.length;
            if (goodCalibratedSamples == null)
                calculateGoodSamples();
            /*
                        boolean [][] goodSamples=new boolean[getNumChannels()][getNumSamples()];
                        for (int i=0;i<goodSamples.length;i++) for (int j=0;j<goodSamples[0].length;j++) goodSamples[i][j]=false;
                        for (int n=0;n<dataVector.length;n++) if (dataWeights[n]>0.0){
                           goodSamples[dataVector[n].channel][dataVector[n].sampleIndex]=true;
                        }
            */
            for (int c = 0; c < 3; c++) {
                if ((data[2 * c] != null) && (data[2 * c + 1] != null)) {
                    int nSamp = 0;
                    qualB[c] = 0.0;
                    for (int i = 0; i < numSamples; i++) {
                        for (int dir = 0; dir < 2; dir++) {
                            //                           if (goodSamples[2*c+dir][i]){
                            int chn = 2 * c + dir;
                            if ((goodCalibratedSamples[chn] != null) && goodCalibratedSamples[chn][i]) {
                                qualB[c] += data[2 * c + dir][i] * data[2 * c + dir][i] * data[2 * c + dir][i]
                                        * data[2 * c + dir][i];
                                nSamp++;
                            }
                        }
                    }
                    if (nSamp > 0) {
                        qualB[c] /= nSamp;
                    }
                    qualB[c] = Math.sqrt(Math.sqrt(qualB[c]));
                } else {
                    qualB[c] = Double.NaN;
                }
            }
            //TODO: Move to a separate function
            int[] numBad = { 0, 0, 0, 0, 0, 0 };
            boolean hasBad = false;
            //            for (int i=0;i<goodSamples.length;i++) for (int j=0;j<goodSamples[0].length;j++) if (!goodSamples[i][j]){
            for (int i = 0; i < goodCalibratedSamples.length; i++) {
                if (goodCalibratedSamples[i] == null) {
                    numBad[i] += getNumSamples();
                    hasBad = true;
                } else {
                    for (int j = 0; j < goodCalibratedSamples[i].length; j++)
                        if (!goodCalibratedSamples[i][j]) {
                            numBad[i]++;
                            hasBad = true;
                        }
                }
            }
            if ((debugLevel > 1) && hasBad) { // was 0
                for (int i = 0; i < numBad.length; i++)
                    if (numBad[i] > 0) {
                        System.out.println(numBad[i] + " sample locations are missing data for "
                                + fieldFitting.getDescription(i));
                    }
            }
            return qualB;
        }

        public double getQualB(double z, double kr, double kb, boolean corrected) {
            double[] k = { kr, 1.0, kb };
            double[] qualB = getQualB(z, corrected);
            for (int i = 0; i < qualB.length; i++)
                if (Double.isNaN(qualB[i])) {
                    qualB[i] = 0.0;
                    k[i] = 0.0;
                }
            return Math.sqrt(Math.sqrt((k[0] * qualB[0] * qualB[0] * qualB[0] * qualB[0]
                    + k[1] * qualB[1] * qualB[1] * qualB[1] * qualB[1]
                    + k[2] * qualB[2] * qualB[2] * qualB[2] * qualB[2]) / (k[0] + k[1] + k[2])));
        }

        public double[] getBestQualB(double kr, double kb, boolean corrected) {
            return getBestQualB(kr, kb, corrected, 1.0, 0.001);

        }

        public double[] getBestQualB( // find minimum
                double kr, double kb, boolean corrected, double iniStep, double precision) {
            int maxSteps = 100;
            double z0 = getGreenZCenter();
            double qb0 = getQualB(z0, kr, kb, corrected);
            double z1 = z0 + iniStep;
            double qb1 = getQualB(z1, kr, kb, corrected);
            double dir = (qb1 < qb0) ? 1.0 : -1.0;
            double z_prev, qb_prev;
            double[] result = { Double.NaN, Double.NaN };
            if (dir > 0) {
                z_prev = z0;
                qb_prev = qb0;
                z0 = z1;
                qb0 = qb1;
            } else {
                z_prev = z1;
                qb_prev = qb1;
            }
            int step;
            for (step = 0; step < maxSteps; step++) {
                z1 = z0 + dir * iniStep;
                qb1 = getQualB(z1, kr, kb, corrected);
                if (qb1 > qb0)
                    break;
                z_prev = z0;
                qb_prev = qb0;
                z0 = z1;
                qb0 = qb1;
            }
            if (step >= maxSteps) {
                System.out.println("Failed to find minimum in " + maxSteps + " steps");
                return result;
            }
            // now dividing z_prev - z0 - z1 range
            for (step = 0; step < maxSteps; step++) {
                if (qb_prev > qb1) {
                    z_prev = z0;
                    qb_prev = qb0;
                } else {
                    z1 = z0;
                    qb1 = qb0;
                }
                z0 = (z_prev + z1) / 2;
                //               qb0=getQualB(z1,kr,kb,corrected); // ???????????????????
                qb0 = getQualB(z0, kr, kb, corrected);
                if (Math.abs(z0 - z_prev) < precision)
                    break;
            }
            result[0] = z0;
            result[1] = qb0;
            return result; // z0;
        }

        public String getDescription(int i) {
            return channelDescriptions[i];
        }

        public boolean[] getCenterSelect() {
            return centerSelect;
        }

        public double[] getCenterXY() {
            return pXY;
        }

        public void setCenterXY(double px, double py) {
            pXY[0] = px;
            pXY[1] = py;
        }

        public void resetSFEVariables() {
            if (mechanicalFocusingModel == null)
                return;
            if (debugLevel > 0)
                System.out.println("---Resetting lens-specific variable parameters---");
            mechanicalFocusingModel.setZTxTy(0.0, 0.0, 0.0);
            for (int chn = 0; chn < curvatureModel.length; chn++) {
                curvatureModel[chn].setDefaults();
            }
            resetSampleCorr(); // correction parameters should also be reset
        }

        public void setDefaultSampleCorr() {
            //            int numPars= getNumCurvars()[0]; // number of Z parameters ( [1] - numbnr of radial parameters).
            for (int n = 0; n < channelDescriptions.length; n++) {
                sampleCorrSelect[n] = getDefaultSampleCorrSelect(); //new boolean[numPars];
                sampleCorrCost[n] = getDefaultSampleCorrCost(); // new double [numPars];
                sampleCorrSigma[n] = getDefaultSampleCorrSigma(); //new double [numPars];
                sampleCorrPullZero[n] = getDefaultCorrPullZero(); //new double [numPars];
                //                for (int i=0;i<numPars;i++){
                //                    sampleCorrSelect[n][i]=(i<dflt_sampleCorrSelect.length)?dflt_sampleCorrSelect[i]:false;
                //                    sampleCorrCost[n][i]=(i<dflt_sampleCorrCost.length)?dflt_sampleCorrCost[i]:1.0;
                //                    sampleCorrSigma[n][i]=dflt_sampleCorrSigma;
                //                    sampleCorrPullZero[n][i]=dflt_sampleCorrPullZero;
                //                }
            }
        }

        public boolean[] getDefaultSampleCorrSelect() {
            boolean[] dflt = new boolean[getNumCurvars()[0]]; // number of Z parameters ( [1] - numbnr of radial parameters).
            for (int i = 0; i < dflt.length; i++) {
                dflt[i] = (i < dflt_sampleCorrSelect.length) ? dflt_sampleCorrSelect[i] : false;
            }
            return dflt;
        }

        public double[] getDefaultSampleCorrCost() {
            double[] dflt = new double[getNumCurvars()[0]]; // number of Z parameters ( [1] - numbnr of radial parameters).
            for (int i = 0; i < dflt.length; i++) {
                dflt[i] = (i < dflt_sampleCorrCost.length) ? dflt_sampleCorrCost[i] : 1.0;
            }
            return dflt;
        }

        public double[] getDefaultSampleCorrSigma() {
            double[] dflt = new double[getNumCurvars()[0]]; // number of Z parameters ( [1] - numbnr of radial parameters).
            for (int i = 0; i < dflt.length; i++) {
                dflt[i] = dflt_sampleCorrSigma;
            }
            return dflt;
        }

        public double[] getDefaultCorrPullZero() {
            double[] dflt = new double[getNumCurvars()[0]]; // number of Z parameters ( [1] - numbnr of radial parameters).
            for (int i = 0; i < dflt.length; i++) {
                dflt[i] = dflt_sampleCorrPullZero;
            }
            return dflt;
        }

        /**
                 * Dialog to setup per-sample (coordinate) corrections
                 * @param title Dialog title
                 * @param individualChannels configure each of the six color/dir channels separately (false - apply to all)
                 * @param disabledPars enable use of the parameters that are currently disabled from fitting
                 * @return true if OK was pressed, false - if cancel
                 */
        public boolean setupSampleCorr(String title, boolean individualChannels, boolean disabledPars) {
            int firstChn = 0;
            for (int i = 0; i < channelSelect.length; i++)
                if (channelSelect[i]) {
                    firstChn = i;
                    break;
                }
            if (!channelSelect[firstChn]) {
                String msg = "No channels selected, please select at least one";
                IJ.showMessage(msg);
                System.out.println(msg);
            }
            int fromChn = individualChannels ? 0 : firstChn;
            int toChn = individualChannels ? channelSelect.length : (firstChn + 1);
            boolean resetCorrections = false;
            GenericDialog gd = new GenericDialog(title);
            gd.addCheckbox("Reset all per-sample corrections to zero", resetCorrections);

            int numParsZR[] = getNumCurvars(); // [0] - Z, [1] - r,
            for (int nChn = fromChn; nChn < toChn; nChn++)
                if (channelSelect[nChn]) {

                    String chnName = individualChannels ? channelDescriptions[nChn] : "All selected Channels";
                    for (int i = 0; i < sampleCorrSelect[nChn].length; i++)
                        if (disabledPars || curvatureSelect[nChn][i * numParsZR[1]]) {
                            gd.addMessage(
                                    "===== " + chnName + ", " + curvatureModel[nChn].getZDescription(i) + " =====");
                            gd.addCheckbox("Enable per-sample correction for \""
                                    + curvatureModel[nChn].getZDescription(i) + "\"", sampleCorrSelect[nChn][i]);
                            gd.addNumericField("Correction cost", sampleCorrCost[nChn][i], 5, 8, "um");
                            gd.addNumericField("Correction sigma", sampleCorrSigma[nChn][i], 5, 8, "mm");
                            gd.addNumericField("Pull to zero fraction", 100 * sampleCorrPullZero[nChn][i], 4, 8,
                                    "%");
                        }
                }
            gd.enableYesNoCancel("Apply", "Keep"); // default OK (on enter) - "Apply"
            WindowTools.addScrollBars(gd);
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            if (gd.wasOKed()) { // selected non-default "Apply"
                resetCorrections = gd.getNextBoolean();
                for (int nChn = fromChn; nChn < toChn; nChn++)
                    if (channelSelect[nChn]) {
                        for (int i = 0; i < sampleCorrSelect[nChn].length; i++)
                            if (disabledPars || curvatureSelect[nChn][i * numParsZR[1]]) {
                                sampleCorrSelect[nChn][i] = gd.getNextBoolean();
                                sampleCorrCost[nChn][i] = gd.getNextNumber();
                                sampleCorrSigma[nChn][i] = gd.getNextNumber();
                                sampleCorrPullZero[nChn][i] = 0.01 * gd.getNextNumber();
                            } else {
                                sampleCorrSelect[nChn][i] = false;
                            }
                    }
                if (!individualChannels) { // copy settings to other channels
                    for (int nChn = 0; nChn < channelSelect.length; nChn++)
                        if (channelSelect[nChn] && (nChn != fromChn)) {
                            for (int i = 0; i < sampleCorrSelect[nChn].length; i++) {
                                sampleCorrSelect[nChn][i] = sampleCorrSelect[fromChn][i];
                                sampleCorrCost[nChn][i] = sampleCorrCost[fromChn][i];
                                sampleCorrSigma[nChn][i] = sampleCorrSigma[fromChn][i];
                                sampleCorrPullZero[nChn][i] = sampleCorrPullZero[fromChn][i];
                            }
                        }
                }
            }
            if (resetCorrections)
                resetSampleCorr();
            return true;
        }

        // once per data set
        public void resetSampleCorr() {
            if (debugLevel > 0)
                System.out.println("---resetSampleCorr()---");
            for (int i = 0; i < correctionParameters.length; i++)
                correctionParameters[i] = null;
        }

        public double[] getCorrVector() {
            //numberOfLocations
            int numPars = 0;
            for (int nChn = 0; nChn < sampleCorrChnParIndex.length; nChn++) {
                if (sampleCorrChnParIndex[nChn] != null) {
                    for (int nPar = 0; nPar < sampleCorrChnParIndex[nChn].length; nPar++) {
                        if (sampleCorrChnParIndex[nChn][nPar] >= 0) {
                            numPars += numberOfLocations;
                        }
                    }
                }
            }
            if (debugLevel > 1)
                System.out.println("getCorrVector 1");
            sampleCorrVector = new double[numPars];
            for (int nChn = 0; nChn < sampleCorrChnParIndex.length; nChn++) {
                if (sampleCorrChnParIndex[nChn] != null) {
                    for (int nPar = 0; nPar < sampleCorrChnParIndex[nChn].length; nPar++) {
                        if (sampleCorrChnParIndex[nChn][nPar] >= 0) {
                            for (int i = 0; i < numberOfLocations; i++) {
                                if ((correctionParameters[nChn] != null)
                                        && (correctionParameters[nChn][nPar] != null)
                                        && (correctionParameters[nChn][nPar].length > i))
                                    sampleCorrVector[sampleCorrChnParIndex[nChn][nPar]
                                            + i] = correctionParameters[nChn][nPar][i];
                                else {
                                    sampleCorrVector[sampleCorrChnParIndex[nChn][nPar] + i] = 0.0;
                                    if ((correctionParameters[nChn] != null)
                                            && (correctionParameters[nChn][nPar] != null)
                                            && (correctionParameters[nChn][nPar].length < i)) {
                                        if (debugLevel > 1)
                                            System.out.println("correctionParameters[" + nChn + "][" + nPar
                                                    + "].length < " + i);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return sampleCorrVector;
        }

        public String[] getCorrNames() {
            //numberOfLocations
            int numPars = 0;
            for (int nChn = 0; nChn < sampleCorrChnParIndex.length; nChn++) {
                if (sampleCorrChnParIndex[nChn] != null) {
                    for (int nPar = 0; nPar < sampleCorrChnParIndex[nChn].length; nPar++) {
                        if (sampleCorrChnParIndex[nChn][nPar] >= 0) {
                            numPars += numberOfLocations;
                        }
                    }
                }
            }
            if (debugLevel > 1)
                System.out.println("getCorrVector 1");
            String[] corrNames = new String[numPars];
            for (int nChn = 0; nChn < sampleCorrChnParIndex.length; nChn++) {
                if (sampleCorrChnParIndex[nChn] != null) {
                    for (int nPar = 0; nPar < sampleCorrChnParIndex[nChn].length; nPar++) {
                        if (sampleCorrChnParIndex[nChn][nPar] >= 0) {
                            for (int i = 0; i < numberOfLocations; i++) {
                                corrNames[sampleCorrChnParIndex[nChn][nPar] + i] = "chn" + nChn + ":" + nPar + "-"
                                        + i;
                            }
                        }
                    }
                }
            }
            return corrNames;
        }

        public void commitCorrVector() {
            if (debugLevel > 1)
                System.out.println("commitCorrVector()");
            commitCorrVector(sampleCorrVector);
        }

        public void commitCorrVector(double[] vector) {
            for (int nChn = 0; nChn < sampleCorrChnParIndex.length; nChn++) {
                if (sampleCorrChnParIndex[nChn] != null) {
                    for (int nPar = 0; nPar < sampleCorrChnParIndex[nChn].length; nPar++) {
                        if (sampleCorrChnParIndex[nChn][nPar] >= 0) {
                            if (correctionParameters[nChn] == null)
                                correctionParameters[nChn] = new double[sampleCorrChnParIndex[nChn].length][];
                            if ((correctionParameters[nChn][nPar] == null)
                                    || (correctionParameters[nChn][nPar].length != numberOfLocations)) {
                                if (debugLevel > 1)
                                    System.out.println("commitCorrVector(): correctionParameters[" + nChn + "]["
                                            + nPar + "].length < " + numberOfLocations);

                                correctionParameters[nChn][nPar] = new double[numberOfLocations];
                            }
                            for (int i = 0; i < numberOfLocations; i++) {
                                correctionParameters[nChn][nPar][i] = vector[sampleCorrChnParIndex[nChn][nPar] + i];
                            }
                        }
                    }
                }
            }
        }

        public void setEstimatedZ0( // needs filterConcave() to work
                double[] z0, boolean force) {
            if (curvatureModel == null)
                return;
            if (z0 == null) {
                // verify that curvature model has non-NaN, set to zero if it does not
                for (int chn = 0; chn < curvatureModel.length; chn++)
                    if (channelSelect[chn]) {
                        if (!curvatureModel[chn].z0IsValid()) {
                            curvatureModel[chn].set_z0(0.0);
                            if (debugLevel > 0)
                                System.out.println(
                                        "*** Missing initial estimations for best focal positions,  setting " + chn
                                                + " to 0.0");
                        }
                    }
                return; // no estimation available
            }
            for (int chn = 0; chn < curvatureModel.length; chn++)
                if (channelSelect[chn]) {
                    if (!Double.isNaN(z0[chn]) && (!curvatureModel[chn].z0IsValid() || force)) {
                        curvatureModel[chn].set_z0(z0[chn]);
                        if (debugLevel > 1)
                            System.out.println("Setting initial (estimated) best focal position for channel " + chn
                                    + " = " + z0[chn]);
                    } else if (!curvatureModel[chn].z0IsValid()) {
                        curvatureModel[chn].set_z0(0.0);
                        if (debugLevel > 0)
                            System.out.println("*** Missing initial estimation for best focal position for channel "
                                    + chn + ", setting to 0.0");
                    }
                }
        }

        /**
         * create matrix of weights of the other parameters influence
         * @param sampleCoordinates [sample number]{x,y} - flattened array of sample coordinates
         * Run in the beginning of fitting series (zeroes the values)
         */
        // once per fitting series (or parameter change
        public void initSampleCorrVector(double[][] sampleCoordinates, double[][] sampleSeriesWeights) {
            if (debugLevel > 1)
                System.out.println("initSampleCorrVector()");
            numberOfLocations = sampleCoordinates.length;
            this.sampleCoordinates = new double[sampleCoordinates.length][];
            for (int i = 0; i < sampleCoordinates.length; i++)
                this.sampleCoordinates[i] = sampleCoordinates[i].clone();
            //            int numSamples=sampleCoordinates.length;
            for (int nChn = 0; nChn < sampleCorrSelect.length; nChn++) {
                if (channelSelect[nChn]) {
                    int numPars = sampleCorrSelect[nChn].length;
                    sampleCorrCrossWeights[nChn] = new double[numPars][][];
                    for (int nPar = 0; nPar < numPars; nPar++) {
                        if (sampleCorrSelect[nChn][nPar]) {
                            sampleCorrCrossWeights[nChn][nPar] = new double[numberOfLocations][numberOfLocations];
                            double k = -getPixelMM() * getPixelMM()
                                    / (sampleCorrSigma[nChn][nPar] * sampleCorrSigma[nChn][nPar]);
                            for (int i = 0; i < numberOfLocations; i++) {
                                double sw = 0.0;
                                for (int j = 0; j < numberOfLocations; j++) {
                                    if (i != j) {
                                        double dx = sampleCoordinates[i][0] - sampleCoordinates[i][0];
                                        double dy = sampleCoordinates[i][1] - sampleCoordinates[i][1];
                                        double a = Math.exp(k * (dx * dx + dy * dy));
                                        if (a < 0.01)
                                            a = 0.0; // reduce numbner of non-zero matrix elements
                                        sampleCorrCrossWeights[nChn][nPar][i][j] = a;
                                        sw += a;
                                    }
                                }
                                double normalizedCost = sampleCorrCost[nChn][nPar];
                                if ((sampleSeriesWeights != null) && ((sampleSeriesWeights[nChn][i] != 0.0)))
                                    normalizedCost *= sampleSeriesWeights[nChn][i];
                                if ((sampleCorrPullZero[nChn][nPar] == 0) || (sampleCorrCost[nChn][nPar] == 0))
                                    sw = 0.0;
                                else if (sw != 0.0)
                                    sw = -normalizedCost * sampleCorrPullZero[nChn][nPar] / sw;
                                for (int j = 0; j < numberOfLocations; j++) {
                                    if (i != j) {
                                        sampleCorrCrossWeights[nChn][nPar][i][j] *= sw;
                                    } else {
                                        sampleCorrCrossWeights[nChn][nPar][i][j] = normalizedCost;
                                    }
                                }
                            }
                        } else {
                            sampleCorrCrossWeights[nChn][nPar] = null;
                        }
                    }
                } else {
                    sampleCorrCrossWeights[nChn] = null;
                }
            }
            getCorrVector();
        }

        public void initSampleCorrChnParIndex(double[][] sampleCoordinates) {
            numberOfLocations = sampleCoordinates.length;
            this.sampleCoordinates = new double[sampleCoordinates.length][];
            for (int i = 0; i < sampleCoordinates.length; i++)
                this.sampleCoordinates[i] = sampleCoordinates[i].clone();
            sampleCorrChnParIndex = new int[sampleCorrSelect.length][];
            int numPars = 0;
            for (int nChn = 0; nChn < sampleCorrSelect.length; nChn++) {
                if (channelSelect[nChn]) {
                    sampleCorrChnParIndex[nChn] = new int[sampleCorrSelect[nChn].length];
                    for (int nPar = 0; nPar < sampleCorrChnParIndex[nChn].length; nPar++) {
                        if (sampleCorrSelect[nChn][nPar]) {
                            sampleCorrChnParIndex[nChn][nPar] = numPars; // pointer to the first sample
                            numPars += numberOfLocations;
                        } else {
                            sampleCorrChnParIndex[nChn][nPar] = -1;
                        }
                    }
                } else {
                    sampleCorrChnParIndex[nChn] = null;
                }
            }
            if (debugLevel > 1)
                System.out.println("initSampleCorrChnParIndex()");
            // currently all correction parameters are initialized as zeros.
            getCorrVector();
        }

        public double[][] getSampleCoordinates() {
            return sampleCoordinates;
        }

        public double[] getCorrPar(int chn, int sampleIndex) {
            if (correctionParameters[chn] == null)
                return null;
            double[] corr = new double[correctionParameters[chn].length];
            for (int i = 0; i < corr.length; i++) {
                if ((correctionParameters[chn][i] != null) && (correctionParameters[chn][i].length <= i)) {
                    if (debugLevel > 1)
                        System.out.println("getCorrPar(): correctionParameters[" + chn + "][" + i + "].length="
                                + correctionParameters[chn][i].length);
                }
                if ((correctionParameters[chn][i] != null) && (correctionParameters[chn][i].length > i))
                    corr[i] = correctionParameters[chn][i][sampleIndex];
                else
                    corr[i] = 0.0;
            }

            return corr;
        }

        public double[][] getCorrPar(int sampleIndex) {
            if (sampleCorrChnParIndex == null)
                return null;
            double[][] result = new double[sampleCorrChnParIndex.length][];
            boolean non_null = false;
            for (int i = 0; i < sampleCorrChnParIndex.length; i++) {
                result[i] = getCorrPar(i, sampleIndex);
                non_null |= (result[i] != null);
            }
            return non_null ? result : null;
        }

        /**
         * Generate correction parameter arrays for each sample
         * @return array of [chn][parameter] arrays or nulls when the particualr sample does not have corrections
         */
        public double[][][] getCorrPar() {
            double[][][] result = new double[getNumSamples()][][];
            for (int sampleIndex = 0; sampleIndex < result.length; sampleIndex++)
                result[sampleIndex] = getCorrPar(sampleIndex);
            return result;
        }

        public boolean[] getDefaultMask() {
            boolean[] mask = { true, true, true, true, true, true };
            return mask;
        }

        public FieldFitting() {
        } // just to get descriptions

        public FieldFitting(double pX0, double pY0, int distanceParametersNumber, int radialParametersNumber) {
            fieldStrategies = new FieldStrategies();
            pXY = new double[2];
            pXY[0] = pX0;
            pXY[1] = pY0;
            channelSelect = getDefaultMask();
            mechanicalFocusingModel = new MechanicalFocusingModel();
            mechanicalSelect = mechanicalFocusingModel.getDefaultMask();
            centerSelect = centerSelectDefault.clone();
            for (int i = 0; i < curvatureModel.length; i++) {
                curvatureModel[i] = new CurvatureModel(pX0, pY0, distanceParametersNumber, radialParametersNumber);
                curvatureSelect[i] = curvatureModel[i].getDefaultMask();
            }
            setDefaultSampleCorr(); // should be after curvatureModel
        }

        public boolean[] getSelectedChannels() {
            return this.channelSelect;
        }

        public int[] getNumCurvars() {
            try {
                return curvatureModel[0].getNumPars();
            } catch (Exception e) {
                int[] dflt_numPars = { CurvatureModel.dflt_distanceParametersNumber,
                        CurvatureModel.dflt_radialParametersNumber };
                return dflt_numPars;

            }
        }

        public boolean maskSetDialog(String title//,
        ) {
            GenericDialog gd = new GenericDialog(title);
            boolean editMechMask = false;
            boolean editCurvMask = false;
            boolean commonCurvMask = true;
            boolean detailedCurvMask = false;
            boolean setupCorrectionPars = false;
            boolean commonCorrectionPars = true;
            boolean disabledCorrectionPars = false;
            gd.addCheckbox("Only use measurements acquired during parallel moves (false - use all)", parallelOnly); //parallelOnly - parent class 
            if (centerSelect == null)
                centerSelect = centerSelectDefault.clone();
            gd.addCheckbox("Adjust aberration center (pX0)", centerSelect[0]);
            gd.addCheckbox("Adjust aberration center (pY0)", centerSelect[1]);

            if (channelSelect == null)
                channelSelect = getDefaultMask();
            for (int i = 0; i < channelSelect.length; i++) {
                gd.addCheckbox(getDescription(i), channelSelect[i]);
            }
            gd.addCheckbox("Edit mechanical parameters masks", editMechMask);
            gd.addCheckbox("Edit curvature model parameters mask(s)", editCurvMask);
            gd.addCheckbox("Apply same curvature model parameters mask to all channels", commonCurvMask);
            gd.addCheckbox("Edit full matrix of the curvature model parameters masks", detailedCurvMask);
            gd.addMessage("");
            gd.addCheckbox("Setup per-sample correction", setupCorrectionPars);
            gd.addCheckbox("Apply same per-sample corrections to all channels", commonCorrectionPars);
            gd.addCheckbox("Setup correction parameters when the parameter itself is disabled",
                    disabledCorrectionPars);
            gd.addMessage("---");

            gd.addStringField("Strategy comment", strategyComment, 60);
            gd.addNumericField("Initial LMA lambda", lambda, 3, 5, "");
            gd.addCheckbox("Reset optical center to distortions center", resetCenter);
            gd.addCheckbox("Reset correction parameters before this LMA step", !keepCorrectionParameters);
            gd.addCheckbox("Reset All SFE-specific parameters before this LMA step", resetVariableParameters);
            gd.addCheckbox("Stop after this LMA step", lastInSeries);

            //         gd.enableYesNoCancel("Keep","Apply"); // default OK (on enter) - "Keep"
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            parallelOnly = gd.getNextBoolean();
            centerSelect[0] = gd.getNextBoolean();
            centerSelect[1] = gd.getNextBoolean();
            for (int i = 0; i < channelSelect.length; i++) {
                channelSelect[i] = gd.getNextBoolean();
            }
            editMechMask = gd.getNextBoolean();
            editCurvMask = gd.getNextBoolean();
            commonCurvMask = gd.getNextBoolean();
            detailedCurvMask = gd.getNextBoolean();
            setupCorrectionPars = gd.getNextBoolean();
            commonCorrectionPars = gd.getNextBoolean();
            disabledCorrectionPars = gd.getNextBoolean();

            strategyComment = gd.getNextString();
            lambda = gd.getNextNumber();
            resetCenter = gd.getNextBoolean();
            keepCorrectionParameters = !gd.getNextBoolean();
            resetVariableParameters = gd.getNextBoolean();
            lastInSeries = gd.getNextBoolean();
            //         boolean OK;
            if (editMechMask) {
                boolean[] mask = mechanicalFocusingModel.maskSetDialog("Focusing mechanical parameters mask",
                        mechanicalSelect);
                if (mask != null)
                    mechanicalSelect = mask;
                else
                    return false; // canceled
            }
            if (editCurvMask) {
                if (commonCurvMask) {
                    boolean[] mask = new boolean[curvatureSelect[0].length];
                    for (int i = 0; i < mask.length; i++) {
                        mask[i] = curvatureSelect[0][i];
                        for (int j = 1; j < curvatureSelect.length; j++) {
                            mask[i] |= curvatureSelect[j][i];
                        }
                    }
                    mask = curvatureModel[0].maskSetDialog(
                            "All channels mask for all curvature models (colors,S/T)", detailedCurvMask, mask);
                    if (mask == null)
                        return false; // canceled
                    for (int i = 0; i < curvatureSelect.length; i++) {
                        for (int j = 0; j < mask.length; j++) {
                            curvatureSelect[i][j] = mask[j];
                        }
                    }
                } else {
                    for (int i = 0; i < channelSelect.length; i++)
                        if (channelSelect[i]) {
                            boolean[] mask = curvatureSelect[i];
                            mask = curvatureModel[0].maskSetDialog(
                                    //                          "Parameter mask for curvature model, channel \""+getDescription(i)+"\"",
                                    "Channel \"" + getDescription(i) + "\" parameter mask for curvature model",
                                    detailedCurvMask, mask);
                            if (mask == null)
                                return false; // canceled
                            curvatureSelect[i] = mask;
                        }
                }
            }
            if (setupCorrectionPars) {
                if (!setupSampleCorr("Setup per-sample correction parameters", !commonCorrectionPars,
                        disabledCorrectionPars))
                    return false;
                //             initSampleCorr(flattenSampleCoord());
            }
            // will modify
            initSampleCorrChnParIndex(flattenSampleCoord()); // run always regardless of configured or not (to create zero-length array of corr)
            return true;
        }

        public void selectZTilt(boolean allChannels, int[] zTxTyAdjustMode) { // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            mechanicalSelect = mechanicalFocusingModel.maskSetZTxTy(zTxTyAdjustMode); // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            mechanicalFocusingModel.setAdjustMode(true, zTxTyAdjustMode);
            // enable all color/dir channels (add separate selection dialog?)
            for (int i = 0; i < channelSelect.length; i++) {
                if (allChannels)
                    channelSelect[i] = true;
                curvatureSelect[i] = curvatureModel[0].maskAllDisabled();
            }
            if (sampleCorrSelect != null) {
                for (int i = 0; i < sampleCorrSelect.length; i++)
                    if (sampleCorrSelect[i] != null) {
                        for (int j = 0; j < sampleCorrSelect[i].length; j++)
                            sampleCorrSelect[i][j] = false;
                    }
            }
            initSampleCorrChnParIndex(flattenSampleCoord());
        }

        ArrayList<String> getParameterValueStrings(boolean showDisabled, boolean showCorrection) {
            ArrayList<String> parList = new ArrayList<String>();
            parList.add("\t ===== Aberrations center =====\t\t");
            String[] centerDescriptions = { "Aberrations center X", "Aberrations center Y" };
            for (int i = 0; i < 2; i++) {
                if (centerSelect[i]) {
                    parList.add("\t" + centerDescriptions[i] + ":\t" + pXY[i] + "\tpix");
                } else if (showDisabled) {
                    parList.add("(disabled)\t" + centerDescriptions[i] + ":\t" + pXY[i] + "\tpix");
                }
            }
            parList.add("\t ===== Mechanical model parameters =====\t\t");
            for (int i = 0; i < mechanicalFocusingModel.paramValues.length; i++) {
                if ((mechanicalSelect == null) || mechanicalSelect[i]) {
                    parList.add("\t" + mechanicalFocusingModel.getDescription(i) + ":\t"
                            + mechanicalFocusingModel.paramValues[i] + "\t" + mechanicalFocusingModel.getUnits(i));
                } else if (showDisabled) {
                    parList.add("(disabled)\t" + mechanicalFocusingModel.getDescription(i) + ":\t"
                            + mechanicalFocusingModel.paramValues[i] + "\t" + mechanicalFocusingModel.getUnits(i));
                }
            }
            for (int n = 0; n < channelSelect.length; n++)
                if (channelSelect[n]) {
                    boolean isMasterDir = (n % 2) == (sagittalMaster ? 0 : 1);
                    parList.add("\t ===== Curvature model parameters for \"" + getDescription(n) + "\"=====\t\t");
                    int n1 = n ^ 1;
                    int index = 0;
                    for (int i = 0; i < curvatureModel[n].modelParams.length; i++)
                        for (int j = 0; j < curvatureModel[n].modelParams[0].length; j++) {
                            String name = curvatureModel[n].getZDescription(i) + ", "
                                    + curvatureModel[n].getRadialDecription(j);
                            if ((j == 0) && !isMasterDir) { // dependent, copy center coefficients from the master
                                if ((curvatureSelect[n1] == null) || curvatureSelect[n1][index]) {
                                    parList.add("(master)\t" + name + ":\t" + curvatureModel[n1].modelParams[i][j]
                                            + "\t");
                                    // debugging
                                    parList.add(
                                            "(this)\t" + name + ":\t" + curvatureModel[n].modelParams[i][j] + "\t");
                                } else if (showDisabled) {
                                    parList.add("(master_disabled)\t" + name + ":\t"
                                            + curvatureModel[n1].modelParams[i][j] + "\t");
                                    // debugging
                                    parList.add("(this_disabled)\t" + name + ":\t"
                                            + curvatureModel[n1].modelParams[i][j] + "\t");
                                }
                            } else {
                                if ((curvatureSelect[n] == null) || curvatureSelect[n][index]) {
                                    parList.add("\t" + name + ":\t" + curvatureModel[n].modelParams[i][j] + "\t");
                                } else if (showDisabled) {
                                    parList.add("(disabled)\t" + name + ":\t" + curvatureModel[n].modelParams[i][j]
                                            + "\t");
                                }
                            }
                            index++;
                        }
                }
            //            if (showCorrection && (getNumberOfCorrParameters()>0)){
            if (showCorrection) {
                parList.add("\t ===== Per-sample correction parameters =====\t\t");
                for (int n = 0; n < correctionParameters.length; n++)
                    if (correctionParameters[n] != null) {
                        for (int np = 0; np < correctionParameters[n].length; np++)
                            if ((correctionParameters[n][np] != null)
                                    && (correctionParameters[n][np].length == numberOfLocations)) {
                                //                        int numSamples=sampleCorrCrossWeights[n][np].length;
                                parList.add("\t ----- correction parameters for \"" + getDescription(n) + " "
                                        + curvatureModel[n].getZDescription(np) + "\" -----\t\t");
                                for (int i = 0; i < numberOfLocations; i++) {
                                    parList.add(i + "\t" + curvatureModel[n].getZDescription(np) + ":\t"
                                            + correctionParameters[n][np][i] + "\t");
                                }
                            } else {
                                if ((correctionParameters[n][np] != null)
                                        && (correctionParameters[n][np].length != numberOfLocations)) {
                                    if (debugLevel > 1)
                                        System.out.println("getParameterValueStrings(): correctionParameters[" + n
                                                + "][" + np + "].length=" + correctionParameters[n][np].length);
                                }
                            }
                    }
            }
            return parList;
        }

        // Show/modify all parameters in a single window.
        public boolean showModifyParameterValues(String title, boolean showDisabled) {
            GenericDialog gd = new GenericDialog(title);
            gd.addMessage("===== Aberrations center =====");
            String[] centerDescriptions = { "Aberrations center X", "Aberrations center Y" };
            for (int i = 0; i < 2; i++) {
                double distXY = (i == 0) ? pX0_distortions : pY0_distortions;
                if (centerSelect[i]) {
                    gd.addNumericField(centerDescriptions[i], pXY[i], 5, 10, "pix (" + IJ.d2s(distXY, 1) + ")");
                } else if (showDisabled) {
                    gd.addNumericField("(disabled) " + centerDescriptions[i], pXY[i], 5, 10,
                            "pix (" + IJ.d2s(distXY, 1) + ")");
                }
            }

            gd.addMessage("===== Mechanical model parameters =====");
            for (int i = 0; i < mechanicalFocusingModel.paramValues.length; i++) {
                if ((mechanicalSelect == null) || mechanicalSelect[i]) {
                    gd.addNumericField(mechanicalFocusingModel.getDescription(i),
                            mechanicalFocusingModel.paramValues[i], 5, 8, mechanicalFocusingModel.getUnits(i));
                } else if (showDisabled) {
                    gd.addNumericField("(disabled) " + mechanicalFocusingModel.getDescription(i),
                            mechanicalFocusingModel.paramValues[i], 5, 8, mechanicalFocusingModel.getUnits(i));
                }
            }
            for (int n = 0; n < channelSelect.length; n++)
                if (channelSelect[n]) {
                    boolean isMasterDir = (n % 2) == (sagittalMaster ? 0 : 1);
                    int n1 = n ^ 1;

                    gd.addMessage("===== Curvature model parameters for \"" + getDescription(n) + "\"=====");
                    int index = 0;
                    for (int i = 0; i < curvatureModel[n].modelParams.length; i++)
                        for (int j = 0; j < curvatureModel[n].modelParams[0].length; j++) {
                            String name = curvatureModel[n].getZDescription(i) + ", "
                                    + curvatureModel[n].getRadialDecription(j);
                            if ((j == 0) && !isMasterDir) { // dependent, copy center coefficients from the master
                                if ((curvatureSelect[n1] == null) || curvatureSelect[n1][index]) {
                                    gd.addNumericField("(copied from master) " + name,
                                            curvatureModel[n1].modelParams[i][j], 5, 8, "");
                                } else if (showDisabled) {
                                    gd.addNumericField("(copied from disabled master) " + name,
                                            curvatureModel[n1].modelParams[i][j], 5, 8, "");
                                }
                            } else {
                                if ((curvatureSelect[n] == null) || curvatureSelect[n][index]) {
                                    gd.addNumericField(name, curvatureModel[n].modelParams[i][j], 5, 8, "");
                                } else if (showDisabled) {
                                    gd.addNumericField("(disabled) " + name, curvatureModel[n].modelParams[i][j], 5,
                                            8, "");
                                }
                            }
                            index++;
                        }
                }

            gd.enableYesNoCancel("Apply", "Keep"); // default OK (on enter) - "Apply"
            WindowTools.addScrollBars(gd);
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            if (gd.wasOKed()) { // selected default "Apply"
                for (int i = 0; i < 2; i++) {
                    if (centerSelect[i] || showDisabled) {
                        pXY[i] = gd.getNextNumber();
                    }
                }
                for (int i = 0; i < mechanicalFocusingModel.paramValues.length; i++) {
                    if ((mechanicalSelect == null) || mechanicalSelect[i] || showDisabled) {
                        mechanicalFocusingModel.paramValues[i] = gd.getNextNumber();
                    }
                }
                for (int n = 0; n < channelSelect.length; n++)
                    if (channelSelect[n]) {
                        int index = 0;
                        for (int i = 0; i < curvatureModel[n].modelParams.length; i++)
                            for (int j = 0; j < curvatureModel[n].modelParams[0].length; j++) {
                                if ((curvatureSelect[n] == null) || curvatureSelect[n][index] || showDisabled) {
                                    curvatureModel[n].modelParams[i][j] = gd.getNextNumber();
                                }
                            }
                        index++;
                    }
            }
            return true;
        }

        public int getNumberOfCorrParameters() { // selected for fitting
            if (mechanicalFocusingModel.getZTxTyMode() != null)
                return 0;
            return ((sampleCorrVector != null) && (mechanicalFocusingModel.getZTxTyMode() == null))
                    ? sampleCorrVector.length
                    : 0;
        }

        public int getNumberOfParameters(boolean sagittalMaster) {
            if (mechanicalFocusingModel.getZTxTyMode() != null)
                return currentVectorLength;
            return getNumberOfRegularParameters(sagittalMaster) + getNumberOfCorrParameters();
        }

        /**
         * @return number of selected parameters (including center, mechanical and each selected - up to 6 - curvature)
         */
        public int getNumberOfRegularParameters(boolean sagittalMaster) {
            if (mechanicalFocusingModel.getZTxTyMode() != null)
                return currentVectorLength;
            int np = 0;
            for (int i = 0; i < 2; i++) {
                if (centerSelect[i])
                    np++;
            }
            for (int i = 0; i < mechanicalFocusingModel.paramValues.length; i++) {
                if ((mechanicalSelect == null) || mechanicalSelect[i])
                    np++;
            }
            for (int n = 0; n < channelSelect.length; n++)
                if (channelSelect[n]) {
                    boolean isMasterDir = (n % 2) == (sagittalMaster ? 0 : 1);
                    int index = 0;
                    for (int i = 0; i < curvatureModel[n].modelParams.length; i++)
                        for (int j = 0; j < curvatureModel[n].modelParams[0].length; j++) {
                            if ((isMasterDir || (j != 0))
                                    && ((curvatureSelect[n] == null) || curvatureSelect[n][index]))
                                np++;
                            index++;
                        }
                }
            return np;
        }

        /**
         * @return number of selected channels (up to 6 - colors and S/T)
         */
        public int getNumberOfChannels() {
            int nc = 0;
            for (int n = 0; n < channelSelect.length; n++)
                if (channelSelect[n])
                    nc++;
            return nc;
        }

        /**
         * @return vector of the current selected parameters values
         */
        public double[] createParameterVector(boolean sagittalMaster) {
            int debugThreshold = 0;
            int[] zTxTyMode = mechanicalFocusingModel.getZTxTyMode();

            if (zTxTyMode != null)
                return createParameterVectorZTxTy(zTxTyMode);

            double[] pars = new double[getNumberOfParameters(sagittalMaster)];
            int np = 0;
            if (debugLevel > debugThreshold)
                debugParameterNames = new String[pars.length];
            for (int i = 0; i < 2; i++) {
                if (centerSelect[i]) {
                    if (debugLevel > debugThreshold)
                        debugParameterNames[np] = "pXY" + i;
                    pars[np++] = pXY[i];
                }
            }

            for (int i = 0; i < mechanicalFocusingModel.paramValues.length; i++) {
                if ((mechanicalSelect == null) || mechanicalSelect[i]) {
                    if (debugLevel > debugThreshold)
                        debugParameterNames[np] = mechanicalFocusingModel.getName(i);
                    pars[np++] = mechanicalFocusingModel.paramValues[i];
                }
            }
            for (int n = 0; n < channelSelect.length; n++)
                if (channelSelect[n]) {
                    boolean isMasterDir = (n % 2) == (sagittalMaster ? 0 : 1);
                    int index = 0;
                    for (int i = 0; i < curvatureModel[n].modelParams.length; i++)
                        for (int j = 0; j < curvatureModel[n].modelParams[0].length; j++) {
                            if ((isMasterDir || (j != 0))
                                    && ((curvatureSelect[n] == null) || curvatureSelect[n][index])) {
                                if (debugLevel > debugThreshold)
                                    debugParameterNames[np] = "chn" + n + "-" + curvatureModel[n].getZName(i) + ":"
                                            + curvatureModel[n].getRadialName(j);
                                pars[np++] = curvatureModel[n].modelParams[i][j];
                            }
                            index++;
                        }
                }
            if (debugLevel > 1)
                System.out.println(
                        "createParameterVector(): using sampleCorrVector - do we need to create it first?");
            getCorrVector(); // do we need that?            
            int nCorrPars = getNumberOfCorrParameters();
            String[] corrNames = (debugLevel > debugThreshold) ? getCorrNames() : null;
            for (int i = 0; i < nCorrPars; i++) {
                if (debugLevel > debugThreshold)
                    debugParameterNames[np] = "corr_par-" + corrNames[i];
                pars[np++] = sampleCorrVector[i];
            }
            if (debugLevel > 1) {
                for (int i = 0; i < pars.length; i++) {
                    System.out.println(i + " " + debugParameterNames[i] + " = " + pars[i]);
                }
            }
            fieldFitting.setCurrentVectorLength(pars.length); // maybe not needed
            return pars;
        }

        private int[] getNumSubPars(int[] zTxTyMode, int numMeas) {
            //          Integer [] indices=getSetIndices().toArray(new Integer[0]); // uses dataVector;
            int[] numSubPars = new int[zTxTyMode.length];
            for (int n = 0; n < zTxTyMode.length; n++) {
                if (zTxTyMode[n] == 2)
                    numSubPars[n] = numMeas; // indices.length;
                else if (zTxTyMode[n] == 1)
                    numSubPars[n] = 1;
                else
                    numSubPars[n] = 0;
            }
            return numSubPars;
        }

        //TODO: create mask for measurements        
        public double[] createParameterVectorZTxTy(
                //              double [] zTxTy,
                int[] zTxTyMode) { //0 - do not adjust, 1 - commonn adjust, 2 - individual adjust
            //         int numMeas=0;
            boolean needMask = false; // shortcut if there are no individual parameters at all
            boolean[] measMask = null;
            for (int m : zTxTyMode)
                if (m > 1) {
                    needMask = true;
                    break;
                }
            if (needMask) {
                measMask = getMeasurementsMask();
                //              for (boolean b:measMask) if (b) numMeas++;
            }
            int[][] map = new int[zTxTyMode.length][];
            for (int n = 0; n < map.length; n++) {
                if (zTxTyMode[n] < 1) {
                    map[n] = null;
                } else {
                    map[n] = new int[(zTxTyMode[n] > 1) ? measMask.length : 1];
                    for (int i = 0; i < map[n].length; i++)
                        map[n][i] = -1;
                }
            }
            int debugThreshold = 0;
            String[] dbgParNames = { "Z", "tX", "tY" };

            //           int [] numSubPars=getNumSubPars(zTxTyMode, numMeas);
            int numPars = 0;
            for (int n = 0; n < zTxTyMode.length; n++) {
                //              numPars+=numSubPars[n];
                switch (zTxTyMode[n]) {
                case 1:
                    numPars++;
                    break;
                case 2:
                    for (int i = 0; i < measMask.length; i++) {
                        if (measMask[i])
                            numPars++;
                    }
                }
            }
            double[] pars = new double[numPars];
            if (debugLevel > debugThreshold)
                debugParameterNames = new String[pars.length];
            double[] zTxTy = fieldFitting.mechanicalFocusingModel.getZTxTy();
            int index = 0;
            for (int n = 0; n < zTxTyMode.length; n++) {
                switch (zTxTyMode[n]) {
                case 1:
                    if (debugLevel > debugThreshold) {
                        debugParameterNames[index] = dbgParNames[n];
                    }
                    map[n][0] = index;
                    pars[index++] = zTxTy[n];
                    break;
                case 2:
                    for (int i = 0; i < measMask.length; i++) {
                        if (measMask[i]) {
                            if (debugLevel > debugThreshold) {
                                debugParameterNames[index] = dbgParNames[n] + "-" + i;
                            }
                            map[n][i] = index;
                            pars[index++] = zTxTy[n];
                        } else {
                            map[n][i] = -1;
                        }
                    }
                }
                //              for (int i=0;i<numSubPars[n];i++){
                //                 if (debugLevel>debugThreshold){
                //                    debugParameterNames[index]=dbgParNames[n]+((numSubPars[n]>1)?("-"+i):"");
                //                 }
                //                 pars[index++]=zTxTy[n];
                //             }
            }
            fieldFitting.setCurrentVectorLength(pars.length);
            fieldFitting.setZTMap(map);
            // create parameter map here           
            return pars;
        }

        public void commitParameterVectorZTxTy(double[] vector) { //
            //              int [] zTxTyMode){ //0 - do not adjust, 1 - commonn adjust, 2 - individual adjust
            //           fieldFitting.commitParameterVector(vector, sagittalMaster); // Hereit does not matter ???
            int[] zTxTyMode = mechanicalFocusingModel.getZTxTyMode(); // Probably is not needed - already set
            int numMeas = 0;
            int[] numSubParsAll = null;
            boolean[] measMask = null;
            if (mechanicalFocusingModel.isNeededMask()) {
                measMask = getMeasurementsMask();
                for (boolean b : measMask)
                    if (b)
                        numMeas++;
                numSubParsAll = getNumSubPars(zTxTyMode, measMask.length);
            }
            int[] numSubPars = getNumSubPars(zTxTyMode, numMeas);
            if (numSubParsAll == null)
                numSubParsAll = numSubPars;
            double[] zTxTy = mechanicalFocusingModel.getZTxTy();
            int parIndex = 0;
            for (int n = 0; n < zTxTyMode.length; n++) {
                //              if (numSubPars[n]>1){
                if (zTxTyMode[n] > 1) {
                    double v = 0; // not used?
                    double[] pars = new double[numSubParsAll[n]];
                    for (int i = 0; i < pars.length; i++) {
                        if (measMask[i]) {
                            v += vector[parIndex]; // not used? 
                            pars[i] = vector[parIndex++];
                        } else {
                            pars[i] = Double.NaN;
                        }
                    }
                    zTxTy[n] = v / numSubPars[n]; // not used?
                    mechanicalFocusingModel.setReplaceParam(pars, n);
                } else {
                    if (numSubPars[n] == 1) {
                        zTxTy[n] = vector[parIndex++];
                    }
                    mechanicalFocusingModel.setReplaceParam(null, n); // use common parameter (z0,tx,ty) from mechanicalFocusingModel
                }
            }
            mechanicalFocusingModel.setZTxTy(zTxTy);
        }

        /**
         * Apply (modified) parameter values to selected ones
         * @param pars vector corresponding to selected parameters
         */
        public void commitParameterVector(double[] pars, boolean sagittalMaster) {
            if (mechanicalFocusingModel.getZTxTyMode() != null) {
                commitParameterVectorZTxTy(pars);
                return;
            }
            int np = 0;
            for (int i = 0; i < 2; i++) {
                if (centerSelect[i])
                    pXY[i] = pars[np++];
            }
            for (int i = 0; i < mechanicalFocusingModel.paramValues.length; i++) {
                if ((mechanicalSelect == null) || mechanicalSelect[i])
                    mechanicalFocusingModel.paramValues[i] = pars[np++];
                ;
            }
            for (int n = 0; n < channelSelect.length; n++)
                if (channelSelect[n]) {
                    boolean isMasterDir = (n % 2) == (sagittalMaster ? 0 : 1);
                    int index = 0;
                    for (int i = 0; i < curvatureModel[n].modelParams.length; i++)
                        for (int j = 0; j < curvatureModel[n].modelParams[0].length; j++) {
                            if ((isMasterDir || (j != 0))
                                    && ((curvatureSelect[n] == null) || curvatureSelect[n][index])) {
                                curvatureModel[n].modelParams[i][j] = pars[np++];
                            }
                            index++;
                        }
                }
            // copy correction parameters
            if (debugLevel > 1)
                System.out.println("commitParameterVector():  Creating and committing sampleCorrVector");
            int nCorrPars = getNumberOfCorrParameters();
            for (int i = 0; i < nCorrPars; i++)
                sampleCorrVector[i] = pars[np++];
            commitCorrVector();

            // copy center parameters to dependent
            // copy if master is selected, regardless of is dependent selected or not            
            for (int n = 0; n < channelSelect.length; n++)
                if (channelSelect[n]) {
                    boolean isMasterDir = (n % 2) == (sagittalMaster ? 0 : 1);
                    if (isMasterDir) {
                        curvatureModel[n ^ 1].setCenterVector(curvatureModel[n].getCenterVector());
                    }
                }
            // propagate pXY to each channel (even disabled)            
            for (int n = 0; n < curvatureModel.length; n++) {
                curvatureModel[n].setCenter(pXY[0], pXY[1]);
            }
        }

        public double getMotorsZ(int[] motors, // 3 motor coordinates
                double px, // pixel x
                double py) {// pixel y
            return mechanicalFocusingModel.calc_ZdZ(-1, // <0 - use mechanicalFocusingModel z, tx, ty
                    motors, px, py, null);

        }

        public double getRadiusMM(double px, // pixel x
                double py) {// pixel y
            double pd = Math.sqrt((px - currentPX0) * (px - currentPX0) + (py - currentPY0) * (py - currentPY0));
            return pd * getPixelMM();

        }

        public double getRadiusMM_distortions(double px, // pixel x
                double py) {// pixel y
            double pd = Math.sqrt((px - pX0_distortions) * (px - pX0_distortions)
                    + (py - pY0_distortions) * (py - pY0_distortions));
            return pd * getPixelMM();
        }

        /**
         * Generate vector of function values (up to 6 - 1 per selected channel), corresponding to motor positions and pixel
         * coordinates, optionally generate partial derivatives for each channel and parameter
         * //@param zTxTy  - optional z0, tx, ty instead of mechanical model parameters (used when null)  
         * @param measurementIndex  - <0 -use mechanical model regular parameters, >=0 - overwrite with per-measurement parameters  
         * @param motors array of 3 motors positions
         * @param px pixel X-coordinate of the sample center
         * @param py pixel Y-coordinate of the sample center
         * @param deriv 2d array with outer dimension correspond to number of selected channels (getNumberOfChannels())
         * or null if derivatives are not needed, just values
         * @return array of [getNumberOfChannels()] calculated function values
         */
        public double[] getValsDerivatives(
                //              int measurementIndex,  // <0 - use mechanicalFocusingModel z, tx, ty
                int sampleIndex, // double [][] corrPars, // [6][nParZ]
                boolean sagittalMaster, // false - tangential master, true - sagittal master (for center coefficients)
                int[] motors, // 3 motor coordinates
                double px, // pixel x
                double py, // pixel y
                double[][] deriv // array of (1..6][], matching getNumberOfChannels) or null if derivatives are not required
        ) {
            double[][] corrPars = getCorrPar(sampleIndex);

            double[] motorDerivs = (deriv == null) ? null : (new double[mechanicalFocusingModel.getNumPars()]);
            double[] chnValues = new double[getNumberOfChannels()];
            double mot_z = mechanicalFocusingModel.calc_ZdZ(-1, // measurementIndex,
                    motors, px, py, motorDerivs);
            int nChn = 0;
            double[][] deriv_curv = new double[channelSelect.length][];
            for (int c = 0; c < channelSelect.length; c++)
                deriv_curv[c] = null;
            for (int c = 0; c < channelSelect.length; c++)
                if (channelSelect[c]) {
                    deriv_curv[c] = (deriv == null) ? null : (new double[curvatureModel[c].getSize()]); // nr*nz+1
                    chnValues[nChn++] = curvatureModel[c].getFdF((corrPars == null) ? null : corrPars[c], // param_corr
                            px, py, mot_z, deriv_curv[c]);
                }
            if (deriv != null) {
                double dX = px - pXY[0];
                double dY = py - pXY[1];
                double r = Math.sqrt(dX * dX + dY * dY);
                double[] dr_dxy = { 1.0, 1.0 };
                if (r > 0.0) {
                    dr_dxy[0] = -dX / r;
                    dr_dxy[1] = -dY / r;
                }
                nChn = 0;
                for (int i = 0; i < 2; i++) {
                    dr_dxy[i] *= getPixelMM(); // radius in mm, dx, dy - in pixels
                }
                for (int c = 0; c < channelSelect.length; c++)
                    if (channelSelect[c]) { // c -full, nChn - disabled skipped - dependent COMPONET 
                        boolean isMasterDir = (c % 2) == (sagittalMaster ? 0 : 1);
                        int otherChannel = c ^ 1;
                        //                    deriv[nChn]=new double [getNumberOfRegularParameters(sagittalMaster)]; //???????????
                        deriv[nChn] = new double[getNumberOfParameters(sagittalMaster)]; //???????????
                        int np = 0;
                        for (int i = 0; i < 2; i++) {
                            if (centerSelect[i]) {
                                deriv[nChn][np++] = dr_dxy[i] * deriv_curv[c][deriv_curv[c].length - 1]; // needs correction from measurement conversion
                            }
                        }
                        // For dependent/master channels no difference for mechanical parameters
                        // TODO: verify they are the same?
                        // calculate derivatives for the center variations (/dXc, /dYc). Will need to be modified for Y vector too as
                        // Y-vector two values (S,T) are calculated from 3 (X2,Y2,XY) and depend on the center position.

                        for (int i = 0; i < mechanicalFocusingModel.paramValues.length; i++) {
                            if ((mechanicalSelect == null) || mechanicalSelect[i]) {
                                deriv[nChn][np++] = -motorDerivs[i] * deriv_curv[c][0]; // minus d/dz0 const part
                            }
                        }
                        // other parameters - for dependent - skip center (j==0), for master add dependent
                        for (int n = 0; n < channelSelect.length; n++)
                            if (channelSelect[n]) { // n - group of channel parameters
                                int[] ncp = curvatureModel[n].getNumPars(); // {(z),(r)}
                                boolean isDependMasterDir = (n % 2) == (sagittalMaster ? 0 : 1); // n is master
                                for (int i = 0; i < curvatureSelect[n].length; i++)
                                    if (curvatureSelect[n][i]) { // i - parameter number
                                        if (((i % ncp[1]) != 0) || isDependMasterDir) { // non center or master
                                            int dependOnChannel = (((i % ncp[1]) == 0) && !isMasterDir)
                                                    ? otherChannel
                                                    : c;
                                            deriv[nChn][np++] = (n == dependOnChannel) ? (deriv_curv[c][i]) : 0.0; // deriv[nChn][np++]=(n==dependOnChannel)?(deriv_curv[n][i]):0.0;

                                        }
                                    }
                            }
                        if (debugLevel == 10) {
                            System.out
                                    .println("getValsDerivatives(), #=" + np + " (of " + deriv[nChn].length + ")");
                            for (int ii = 0; ii < np; ii++)
                                if (deriv[nChn][ii] != 0.0) {
                                    System.out
                                            .println("getValsDerivatives(), #=" + ii + " (of " + deriv[nChn].length
                                                    + ") c=" + c + " nChn=" + nChn + " deriv=" + deriv[nChn][ii]);
                                }
                        }
                        // add correction parameters?
                        // now np points to the first correction parameter
                        // correction parameters do not depend on sagittalMaster - each mayy have own shift
                        if ((corrPars != null) && (sampleCorrChnParIndex != null)) {
                            for (int n = 0; n < channelSelect.length; n++)
                                if (sampleCorrChnParIndex[n] != null) {
                                    int[] ncp = curvatureModel[n].getNumPars(); // {(z),(r)}
                                    for (int i = 0; i < ncp[0]; i++)
                                        if (sampleCorrChnParIndex[n][i] >= 0) {
                                            // np now points to the first corr parameter
                                            deriv[nChn][np + sampleCorrChnParIndex[n][i]
                                                    + sampleIndex] = (nChn == n) ? deriv_curv[n][i * ncp[1]] : 0.0;
                                        }
                                }
                        }
                        nChn++;
                    }
            }
            return chnValues;
        }

        public double[] getValsDerivativesZTxTy( // Version for adjustment mode - returns fixed width (3)
                int measurementIndex, // <0 - use mechanicalFocusingModel z, tx, ty
                int sampleIndex, // double [][] corrPars, // [6][nParZ]
                int[] motors, // 3 motor coordinates
                double px, // pixel x
                double py, // pixel y
                double[][] deriv // array of (1..6][], matching getNumberOfChannels) or null if derivatives are not required
        ) {
            double[][] corrPars = getCorrPar(sampleIndex);

            double[] motorDerivs = (deriv == null) ? null : (new double[mechanicalFocusingModel.getNumPars()]);
            double[] chnValues = new double[getNumberOfChannels()];
            double mot_z = mechanicalFocusingModel.calc_ZdZ(measurementIndex, motors, px, py, motorDerivs);
            int nChn = 0;
            double[][] deriv_curv = new double[channelSelect.length][];
            for (int c = 0; c < channelSelect.length; c++)
                deriv_curv[c] = null;
            for (int c = 0; c < channelSelect.length; c++)
                if (channelSelect[c]) {
                    deriv_curv[c] = (deriv == null) ? null : (new double[curvatureModel[c].getSize()]); // nr*nz+1
                    chnValues[nChn++] = curvatureModel[c].getFdF((corrPars == null) ? null : corrPars[c], // param_corr
                            px, py, mot_z, deriv_curv[c]);
                }
            if (deriv != null) {
                double dX = px - pXY[0];
                double dY = py - pXY[1];
                double r = Math.sqrt(dX * dX + dY * dY);
                double[] dr_dxy = { 1.0, 1.0 };
                if (r > 0.0) {
                    dr_dxy[0] = -dX / r;
                    dr_dxy[1] = -dY / r;
                }
                nChn = 0;
                for (int i = 0; i < 2; i++) {
                    dr_dxy[i] *= getPixelMM(); // radius in mm, dx, dy - in pixels
                }
                for (int c = 0; c < channelSelect.length; c++)
                    if (channelSelect[c]) { // c -full, nChn - disabled skipped - dependent COMPONET 
                        deriv[nChn] = new double[3];
                        deriv[nChn][0] = -motorDerivs[mechanicalFocusingModel.getIndex(MECH_PAR.z0)]
                                * deriv_curv[c][0]; // minus d/dz0 const part
                        deriv[nChn][1] = -motorDerivs[mechanicalFocusingModel.getIndex(MECH_PAR.tx)]
                                * deriv_curv[c][0]; // minus d/dz0 const part
                        deriv[nChn][2] = -motorDerivs[mechanicalFocusingModel.getIndex(MECH_PAR.ty)]
                                * deriv_curv[c][0]; // minus d/dz0 const part
                        nChn++;
                    }
            }
            return chnValues;
        }

    }

    public class MechanicalFocusingModel {

        public final String[][] descriptions = { { "K0", "Average motor center travel", "um/step", "0.0124" },
                { "KD1", "M1 and M2 travel disbalance", "um/step", "0.0" },
                { "KD3", "M3 to average of M1 and M2 travel disbalance", "um/step", "0.0" },
                { "sM1", "M1: sin component amplitude, relative to tread pitch", "", "0.0" },
                { "cM1", "M1: cos component amplitude, relative to tread pitch", "", "0.0" },
                { "sM2", "M2: sin component amplitude, relative to tread pitch", "", "0.0" },
                { "cM2", "M2: cos component amplitude, relative to tread pitch", "", "0.0" },
                { "sM3", "M3: sin component amplitude, relative to tread pitch", "", "0.0" },
                { "cM3", "M3: cos component amplitude, relative to tread pitch", "", "0.0" },
                { "Lx", "Half horizontal distance between M3 and and M2 supports", "mm", "21.0" },
                { "Ly", "Half vertical distance between M1 and M2 supports", "mm", "10.0" },
                { "mpX0", "pixel X coordinate of mechanical center", "px", "1296.0" },
                { "mpY0", "pixel Y coordinate of mechanical center", "px", "968.0" },
                { "z0", "center shift, positive away from the lens", "um", "0.0" },
                { "tx", "horizontal tilt", "um/mm", "0.0" }, { "ty", "vertical tilt", "um/mm", "0.0" } };
        public double PERIOD = 3584.0; // steps/revolution
        //        public double PIXEL_SIZE=0.0022; // mm
        public double[] paramValues = new double[descriptions.length];
        public double[] paramValuesCalibrate = null; // save calibration mode parameters while adjusting
        public boolean adjustMode = false;
        private int[] zTxTyMode = null;
        public double[][] replaceZTxTy = { null, null, null };
        private boolean needMask = false;

        public void setReplaceParam(double[] pars, int index) {
            replaceZTxTy[index] = pars;
        }

        public double[] getReplaceParam(int index) {
            return replaceZTxTy[index];
        }

        public double[][] getReplaceParam() {
            return replaceZTxTy;
        }

        public void setZTxTyMode(int[] zTxTyMode) {
            if (zTxTyMode == null)
                this.zTxTyMode = null;
            else
                this.zTxTyMode = zTxTyMode.clone(); // clone() needed?
            needMask = false; // shortcut if there are no individual parameters at all
            for (int m : zTxTyMode)
                if (m > 1) {
                    needMask = true;
                    break;
                }
        }

        public boolean isNeededMask() {
            return needMask;
        }

        public int[] getZTxTyMode() {
            return adjustMode ? this.zTxTyMode : null;
        }

        public MechanicalFocusingModel() { // add arguments?
            initDefaults();
        }

        public void setAdjustMode(boolean mode, int[] zTxTyMode) { //0 - do not adjust, 1 - commonn adjust, 2 - individual adjust
            if (mode && (zTxTyMode != null))
                setZTxTyMode(zTxTyMode);
            if (mode == adjustMode)
                return;
            if (adjustMode)
                restoreCalibration();
            else
                saveCalibration();
            adjustMode = mode;
        }

        private void saveCalibration(boolean force) { // do not save if in adjust mode
            if (force)
                adjustMode = false;
            saveCalibration();
        }

        private void saveCalibration() { // do not save if in adjust mode
            if (paramValues != null) {
                if (!adjustMode)
                    paramValuesCalibrate = paramValues.clone();
                else
                    System.out.println("Possible BUG - tried to save mechanical parameters while in adjust mode");
            }
        }

        private void restoreCalibration() {
            if (paramValuesCalibrate != null)
                paramValues = paramValuesCalibrate.clone();
            adjustMode = false;
        }

        public void setProperties(String prefix, Properties properties) {
            setAdjustMode(false, null); // restore if needed
            if (paramValues != null) {
                for (int i = 0; i < paramValues.length; i++) {
                    properties.setProperty(prefix + descriptions[i][0], paramValues[i] + "");
                }
            }
        }

        public void getProperties(String prefix, Properties properties) {
            if ((paramValues == null) || (paramValues.length != descriptions.length))
                initDefaults();
            for (int i = 0; i < paramValues.length; i++) {
                if (properties.getProperty(prefix + descriptions[i][0]) != null)
                    paramValues[i] = Double.parseDouble(properties.getProperty(prefix + descriptions[i][0]));
            }
            saveCalibration(true);
        }

        public void initDefaults() {
            paramValues = new double[descriptions.length];
            for (int i = 0; i < descriptions.length; i++)
                paramValues[i] = Double.parseDouble(descriptions[i][3]);
            saveCalibration(true);
        }

        public void setVector(double[] vector) {
            paramValues = new double[descriptions.length];
            for (int i = 0; i < vector.length; i++)
                paramValues[i] = vector[i];
        }

        public void setZTxTy(double z, double tx, double ty) {
            paramValues[getIndex(MECH_PAR.z0)] = z;
            paramValues[getIndex(MECH_PAR.tx)] = tx;
            paramValues[getIndex(MECH_PAR.ty)] = ty;
        }

        public void setZTxTy(double[] zTxTy) {
            paramValues[getIndex(MECH_PAR.z0)] = zTxTy[0];
            paramValues[getIndex(MECH_PAR.tx)] = zTxTy[1];
            paramValues[getIndex(MECH_PAR.ty)] = zTxTy[2];
        }

        public double[] getZTxTy() {
            double[] vector = { paramValues[getIndex(MECH_PAR.z0)], paramValues[getIndex(MECH_PAR.tx)],
                    paramValues[getIndex(MECH_PAR.ty)] };
            return vector;
        }

        public String[] getZTxTyDescriptions() {
            String[] descriptions = { getDescription(getIndex(MECH_PAR.z0)), getDescription(getIndex(MECH_PAR.tx)),
                    getDescription(getIndex(MECH_PAR.ty)) };
            return descriptions;
        }

        public String[] getZTxTyNames() {
            String[] names = { getName(getIndex(MECH_PAR.z0)), getName(getIndex(MECH_PAR.tx)),
                    getName(getIndex(MECH_PAR.ty)) };
            return names;
        }

        public void setVector(double[] vector, boolean[] mask) {
            for (int i = 0; i < vector.length; i++)
                if (mask[i])
                    paramValues[i] = vector[i];
        }

        public double[] getVector() {
            return paramValues;
        }

        public int getNumPars() {
            return descriptions.length;
        }

        public int getIndex(String parName) {
            for (int i = 0; i < descriptions.length; i++)
                if (descriptions[i].equals(parName))
                    return i;
            return -1;
        }

        public int getIndex(MECH_PAR mech_par) {
            return mech_par.ordinal();
        }

        public String getDescription(int i) {
            return descriptions[i][1];
        }

        public String getName(int i) {
            return descriptions[i][0];
        }

        public String getDescription(MECH_PAR mech_par) {
            return descriptions[mech_par.ordinal()][1];
        }

        public String getUnits(int i) {
            return descriptions[i][2];
        }

        public String getUnits(MECH_PAR mech_par) {
            return descriptions[mech_par.ordinal()][2];
        }

        public Double getValue(int i) {
            return paramValues[i];
        }

        public Double getValue(MECH_PAR mech_par) {
            return paramValues[mech_par.ordinal()];
        }

        public double[] debugDeriv_ZdZ(double scale, int[] motors, double px, double py) {
            double[] derivSteps = { 0.001, // [0]   K0 843.6001052876973   
                    0.001, // [1]   KD1 1600.0723700390713   
                    0.001, // [2]   KD3 -2428.8998947123027   
                    0.1, // [3]   sM1 -1.1720600074585734   
                    0.1, // [4]   cM1 0.6081060292866338   
                    0.1, // [5]   sM2 -2.3717384278673035   
                    0.1, // [6]   cM2 1.2305414643438266   
                    0.1, // [7]   sM3 2.7345656468251707   
                    0.1, // [8]   cM3 1.4187890097199358   
                    1.0, // [9]   Lx -0.535718420298317   
                    1.0, // [10]  Ly    0.0   
                    10.0, // [11]  mpX0   -2.816702366108815E-5   
                    10.0, // [12]  mpY0   -8.351458550253758E-5   
                    1.0, // [13]  z0    1.0   
                    0.1, // [14]  tx -2.5168000000000004   
                    0.1 // [15]  ty -1.76   
            };
            double[] initialVector = paramValues.clone();
            double[] derivs = new double[paramValues.length];
            for (int i = 0; i < derivs.length; i++) {
                paramValues[i] = initialVector[i] - scale * derivSteps[i];
                double zm = calc_ZdZ(
                        //                   null, //double [] zTxTy, // null - old way
                        -1, // <0 - use mechanicalFocusingModel z, tx, ty
                        motors, px, py, null);
                paramValues[i] = initialVector[i] + scale * derivSteps[i];
                double zp = calc_ZdZ(
                        //                   null, //double [] zTxTy, // null - old way
                        -1, // <0 - use mechanicalFocusingModel z, tx, ty
                        motors, px, py, null);
                paramValues[i] = initialVector[i];
                derivs[i] = (zp - zm) / (2.0 * scale * derivSteps[i]);
            }
            return derivs;

        }

        /**
         * return Z for specified pixel coordinates (assuming all motors at zero) and optionally calculate its derivatives for Zcf, Tx, Ty
         * @param px pixel X
         * @param py pixel Y
         * @param calDerivs true if derivatives are needed
         * @return either a single element array {z} or a 4-element one {z, dz/dz0, dz/dtx, dz/dty}
         */
        public double[] getZdZ3(double px, double py, boolean calDerivs) {
            double[] result = new double[calDerivs ? 4 : 1];
            double[] derivs = (calDerivs) ? (new double[getNumPars()]) : null;
            //          int [] zeroMot={0,0,0};
            result[0] = calc_ZdZ(
                    //               null, //double [] zTxTy, // null - old way
                    -1, // <0 - use mechanicalFocusingModel z, tx, ty
                    null, //zeroMot,
                    px, py, derivs);
            if (calDerivs) {
                result[1] = derivs[getIndex(MECH_PAR.z0)];
                result[2] = derivs[getIndex(MECH_PAR.tx)];
                result[3] = derivs[getIndex(MECH_PAR.ty)];
            }
            return result;
        }

        /**
         * Calculate distance from the selected sensor pixel to the common "focal" plane
         * and optionally partial derivatives of the pixel distance from the focal plane by selected parameters
         * @param motors array of 3 motor positions
         * @param px horizontal sensor position
         * @param py vertical sensor pixel position
         * @param deriv returns partial derivatives if array is provided. If null - does not calculate derivatives
         * @return array of partial derivatives
         */
        public double calc_ZdZ_old(int[] motors, double px, double py, double[] deriv) {
            //             double[][] adjData){
            double debugMot = 6545;
            int debugThreshold = 2;
            boolean dbg = (debugLevel > debugThreshold);
            /*
               kM3=K0+KD3
               kM1=K0+KD1-KD3
               kM2=K0-KD1-KD3
               K0 may be fixed (overall scale), kM1, kM2 - variable
                
              dZc(m1)= 0.25* kM1 * (m1 + sM1*P/(2*pi)*sin(2pi*m1/P) + cM1*P/(2*pi)*cos(2pi*m1/P))
              dZc(m2)= 0.25* kM2 * (m2 + sM2*P/(2*pi)*sin(2pi*m2/P) + cM2*P/(2*pi)*cos(2pi*m2/P))
              dZc(m3)= 0.5 * kM3 *( m3 + sM3*P/(2*pi)*sin(2pi*m3/P) + cM3*P/(2*pi)*cos(2pi*m3/P))
                
              Assuming X - towards M3, Y - towards M1
                
              d2Z/dX/dm1=-ps/(4*Lx)* kM1 * (m1 + sM1*P/(2*pi)*sin(2pi*m1/P) + cM1*P/(2*pi)*cos(2pi*m1/P))
              d2Z/dX/dm2=-ps/(4*Lx)* kM2 *( m2 + sM2*P/(2*pi)*sin(2pi*m2/P) + cM2*P/(2*pi)*cos(2pi*m2/P))
              d2Z/dX/dm3= ps/(2*Lx)* kM3 * (m3 + sM3*P/(2*pi)*sin(2pi*m3/P) + cM3*P/(2*pi)*cos(2pi*m3/P))
                
              d2Z/dY/dm1= -ps/(2*Ly)* kM1 * (m1 + sM1*P/(2*pi)*sin(2pi*m1/P) + cM1*P/(2*pi)*cos(2pi*m1/P)) //!
              d2Z/dY/dm2= +ps/(2*Ly)* kM2 * (m2 + sM2*P/(2*pi)*sin(2pi*m2/P) + cM2*P/(2*pi)*cos(2pi*m2/P)) //!
              d2Z/dY/dm3= 0
                
             */
            //            double [] deriv=new double [paramValues.length];
            double kM1 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM2 = getValue(MECH_PAR.K0) - getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM3 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD3);
            double p2pi = PERIOD / 2 / Math.PI;
            double m1 = motors[0], m2 = motors[1], m3 = motors[2];
            double aM1 = (m1 + getValue(MECH_PAR.sM1) * p2pi * Math.sin(m1 / p2pi)
                    + getValue(MECH_PAR.cM1) * p2pi * Math.cos(m1 / p2pi));
            double aM2 = (m2 + getValue(MECH_PAR.sM2) * p2pi * Math.sin(m2 / p2pi)
                    + getValue(MECH_PAR.cM2) * p2pi * Math.cos(m2 / p2pi));
            double aM3 = (m3 + getValue(MECH_PAR.sM3) * p2pi * Math.sin(m3 / p2pi)
                    + getValue(MECH_PAR.cM3) * p2pi * Math.cos(m3 / p2pi));
            double zM1 = kM1 * aM1;
            double zM2 = kM2 * aM2;
            double zM3 = kM3 * aM3;

            double zc = 0.25 * zM1 + 0.25 * zM2 + 0.5 * zM3 + getValue(MECH_PAR.z0);
            double dx = PIXEL_SIZE * (px - getValue(MECH_PAR.mpX0));
            double dy = PIXEL_SIZE * (py - getValue(MECH_PAR.mpY0));
            double zx = dx * (getValue(MECH_PAR.tx) + (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx)));
            //            double zy=dy*(getValue(MECH_PAR.ty)-(zM1-zM2)/(2*getValue(MECH_PAR.Ly))); //!
            double zy = dy * (getValue(MECH_PAR.ty) - (zM2 - zM1) / (2 * getValue(MECH_PAR.Ly))); //!
            double z = zc + zx + zy;
            if (dbg)
                if ((Math.abs(m1) == debugMot) && (Math.abs(m2) == debugMot)) {
                    System.out.print("M: " + ((int) m1) + ":" + ((int) m2) + ":" + ((int) m3) + " dxy="
                            + IJ.d2s(dx, 3) + ":" + IJ.d2s(dy, 3) + " zcxy=" + IJ.d2s(zc, 3) + ":" + IJ.d2s(zx, 3)
                            + ":" + IJ.d2s(zy, 3) + " zxy(t)=" + IJ.d2s(dx * getValue(MECH_PAR.tx), 3) + ":"
                            + IJ.d2s(dy * getValue(MECH_PAR.ty), 3));
                }
            if (deriv == null) {
                if (dbg)
                    if ((Math.abs(m1) == debugMot) && (Math.abs(m2) == debugMot)) {
                        System.out.println();
                    }
                return z;
            }
            for (int i = 0; i < deriv.length; i++)
                deriv[i] = 0.0;
            // Above same as calc_Z
            double dx_mpX0 = -PIXEL_SIZE;
            double dy_mpY0 = -PIXEL_SIZE;
            double zM1_K0 = aM1;
            double zM1_KD1 = aM1;
            double zM1_KD3 = -aM1;
            double zM2_K0 = aM2;
            double zM2_KD1 = -aM2;
            double zM2_KD3 = -aM2;
            double zM3_K0 = aM3;
            //            double zM3_KD1=-aM3;
            //            double zM3_KD3= 0.0;
            double zM3_KD1 = 0.0;
            double zM3_KD3 = aM3;
            double zM1_sM1 = kM1 * p2pi * Math.sin(m1 / p2pi);
            double zM1_cM1 = kM1 * p2pi * Math.cos(m1 / p2pi);
            double zM2_sM2 = kM2 * p2pi * Math.sin(m2 / p2pi);
            double zM2_cM2 = kM2 * p2pi * Math.cos(m2 / p2pi);
            double zM3_sM3 = kM3 * p2pi * Math.sin(m3 / p2pi);
            double zM3_cM3 = kM3 * p2pi * Math.cos(m3 / p2pi);

            double zc_K0 = 0.25 * zM1_K0 + 0.25 * zM2_K0 + 0.5 * zM3_K0;
            double zc_KD1 = 0.25 * zM1_KD1 + 0.25 * zM2_KD1 + 0.5 * zM3_KD1;
            double zc_KD3 = 0.25 * zM1_KD3 + 0.25 * zM2_KD3 + 0.5 * zM3_KD3;
            double zc_sM1 = 0.25 * zM1_sM1;
            double zc_cM1 = 0.25 * zM1_cM1;
            double zc_sM2 = 0.25 * zM2_sM2;
            double zc_cM2 = 0.25 * zM2_cM2;
            double zc_sM3 = 0.5 * zM3_sM3;
            double zc_cM3 = 0.5 * zM3_cM3;

            //            double zx_K0=(2*zM3-zM1-zM2)* dx/(4*getValue(MECH_PAR.Lx));
            double zx_a = dx / (4 * getValue(MECH_PAR.Lx));
            double zx_K0 = (2 * zM3_K0 - zM1_K0 - zM2_K0) * zx_a;
            double zx_KD1 = (2 * zM3_KD1 - zM1_KD1 - zM2_KD1) * zx_a;
            double zx_KD3 = (2 * zM3_KD3 - zM1_KD3 - zM2_KD3) * zx_a;
            double zx_sM1 = (-zM1_sM1) * zx_a;
            double zx_cM1 = (-zM1_cM1) * zx_a;
            double zx_sM2 = (-zM2_sM2) * zx_a;
            double zx_cM2 = (-zM2_cM2) * zx_a;
            double zx_sM3 = (2 * zM3_sM3) * zx_a;
            double zx_cM3 = (2 * zM3_cM3) * zx_a;
            double zx_mpX0 = dx_mpX0
                    * (getValue(MECH_PAR.tx) + (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx))); //  double zx_mpX0=dx_mpX0/(4*getValue(MECH_PAR.Lx));
            double zx_tx = dx;
            double zx_Lx = -dx * (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx) * getValue(MECH_PAR.Lx));
            //          double zy=dy*(getValue(MECH_PAR.ty)-(zM2-zM1)/(2*getValue(MECH_PAR.Ly))); //!
            double zy_a = -dy / (2 * getValue(MECH_PAR.Ly)); //!
            double zy_K0 = (zM2_K0 - zM1_K0) * zy_a;
            double zy_KD1 = (zM2_KD1 - zM1_KD1) * zy_a;
            double zy_KD3 = (zM2_KD3 - zM1_KD3) * zy_a;
            double zy_sM1 = (-zM1_sM1) * zy_a;
            double zy_cM1 = (-zM1_cM1) * zy_a;
            double zy_sM2 = (zM2_sM2) * zy_a;
            double zy_cM2 = (zM2_cM2) * zy_a;
            double zy_sM3 = 0.0;
            double zy_cM3 = 0.0;
            double zy_mpY0 = dy_mpY0 * (getValue(MECH_PAR.ty) - (zM2 - zM1) / (2 * getValue(MECH_PAR.Ly)));//! // double zy_mpY0=-dy_mpY0/(2*getValue(MECH_PAR.Ly));//!
            double zy_ty = dy;
            //            double zy_Ly= dy*(zM1-zM2)/(2*getValue(MECH_PAR.Ly)*getValue(MECH_PAR.Ly)); //!
            //          double zy_Ly= -dy*(zM2-zM1)/(2*getValue(MECH_PAR.Ly)*getValue(MECH_PAR.Ly)); //!
            double zy_Ly = dy * (zM2 - zM1) / (2 * getValue(MECH_PAR.Ly) * getValue(MECH_PAR.Ly)); //!

            deriv[getIndex(MECH_PAR.K0)] = zc_K0 + zx_K0 + zy_K0;
            deriv[getIndex(MECH_PAR.KD1)] = zc_KD1 + zx_KD1 + zy_KD1;
            deriv[getIndex(MECH_PAR.KD3)] = zc_KD3 + zx_KD3 + zy_KD3;
            deriv[getIndex(MECH_PAR.sM1)] = zc_sM1 + zx_sM1 + zy_sM1;
            deriv[getIndex(MECH_PAR.cM1)] = zc_cM1 + zx_cM1 + zy_cM1;
            deriv[getIndex(MECH_PAR.sM2)] = zc_sM2 + zx_sM2 + zy_sM2;
            deriv[getIndex(MECH_PAR.cM2)] = zc_cM2 + zx_cM2 + zy_cM2;
            deriv[getIndex(MECH_PAR.sM3)] = zc_sM3 + zx_sM3 + zy_sM3;
            deriv[getIndex(MECH_PAR.cM3)] = zc_cM3 + zx_cM3 + zy_cM3;
            deriv[getIndex(MECH_PAR.Lx)] = zx_Lx;
            deriv[getIndex(MECH_PAR.Ly)] = zy_Ly;
            deriv[getIndex(MECH_PAR.mpX0)] = zx_mpX0;
            deriv[getIndex(MECH_PAR.mpY0)] = zy_mpY0;
            deriv[getIndex(MECH_PAR.z0)] = 1.0;
            deriv[getIndex(MECH_PAR.tx)] = zx_tx;
            deriv[getIndex(MECH_PAR.ty)] = zy_ty;
            if (dbg)
                if ((Math.abs(m1) == debugMot) && (Math.abs(m2) == debugMot)) {
                    if (m1 * m2 > 0) {
                        System.out.println("same sign");
                    } else {
                        System.out.println("opposite sign");
                    }
                    System.out.println(" zxy_txy=" + IJ.d2s(zx_tx, 3) + ":" + IJ.d2s(zy_ty, 3) + " zxy_Lxy="
                            + IJ.d2s(zx_Lx, 5) + ":" + IJ.d2s(zy_Ly, 5));
                    double[] dbg_derivs = debugDeriv_ZdZ(0.1, //scale,
                            motors, px, py);
                    for (int i = 0; i < deriv.length; i++) {
                        System.out.println(i + ": " + descriptions[i][0] + " deriv=" + deriv[i] + ", dbg_derivs="
                                + dbg_derivs[i]);
                    }
                }
            return z;
        }

        /**
         * Calculate focal shift of the specified pixel location and optionally derivatives by parameters
         * @param measIndex measurement index to replace z0,tx,ty with individual values, or -1 to use model parameters
         * @param motors 3 motor steps
         * @param px absolute position of the pixel, X
         * @param py absolute position of the pixel, Y
         * @param deriv null or the proper length array to accommodate derivatives calculated here
         * @return focal shift of the specified pixel
         */
        public double calc_ZdZ(int measIndex, // only used in adjustment mode
                int[] motors, double px, double py, double[] deriv) {
            int[] zeroMot = { 0, 0, 0 };
            if (motors == null)
                motors = zeroMot.clone();
            double[] zTxTy = getZTxTy();
            if (adjustMode && (measIndex >= 0)) {
                for (int n = 0; n < zTxTy.length; n++)
                    if (replaceZTxTy[n] != null) {
                        if (measIndex < replaceZTxTy[n].length)
                            zTxTy[n] = replaceZTxTy[n][measIndex];
                        else
                            zTxTy[n] = replaceZTxTy[n][0]; // maybe not needed - common parameter
                        if (Double.isNaN(zTxTy[n])) {
                            zTxTy[n] = getZTxTy()[0]; // use common parameter?
                            //                  return Double.NaN;// bad measurement, no derivatives calculated
                        }
                    }
            }
            double debugMot = 6545;
            int debugThreshold = 2;
            boolean dbg = (debugLevel > debugThreshold);
            /*
               kM3=K0+KD3
               kM1=K0+KD1-KD3
               kM2=K0-KD1-KD3
               K0 may be fixed (overall scale), kM1, kM2 - variable
                
              dZc(m1)= 0.25* kM1 * (m1 + sM1*P/(2*pi)*sin(2pi*m1/P) + cM1*P/(2*pi)*cos(2pi*m1/P))
              dZc(m2)= 0.25* kM2 * (m2 + sM2*P/(2*pi)*sin(2pi*m2/P) + cM2*P/(2*pi)*cos(2pi*m2/P))
              dZc(m3)= 0.5 * kM3 *( m3 + sM3*P/(2*pi)*sin(2pi*m3/P) + cM3*P/(2*pi)*cos(2pi*m3/P))
                
              Assuming X - towards M3, Y - towards M1
                
              d2Z/dX/dm1=-ps/(4*Lx)* kM1 * (m1 + sM1*P/(2*pi)*sin(2pi*m1/P) + cM1*P/(2*pi)*cos(2pi*m1/P))
              d2Z/dX/dm2=-ps/(4*Lx)* kM2 *( m2 + sM2*P/(2*pi)*sin(2pi*m2/P) + cM2*P/(2*pi)*cos(2pi*m2/P))
              d2Z/dX/dm3= ps/(2*Lx)* kM3 * (m3 + sM3*P/(2*pi)*sin(2pi*m3/P) + cM3*P/(2*pi)*cos(2pi*m3/P))
                
              d2Z/dY/dm1= -ps/(2*Ly)* kM1 * (m1 + sM1*P/(2*pi)*sin(2pi*m1/P) + cM1*P/(2*pi)*cos(2pi*m1/P)) //!
              d2Z/dY/dm2= +ps/(2*Ly)* kM2 * (m2 + sM2*P/(2*pi)*sin(2pi*m2/P) + cM2*P/(2*pi)*cos(2pi*m2/P)) //!
              d2Z/dY/dm3= 0
                
             */
            //            double [] deriv=new double [paramValues.length];
            double kM1 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM2 = getValue(MECH_PAR.K0) - getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM3 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD3);
            double p2pi = PERIOD / 2 / Math.PI;
            double m1 = motors[0], m2 = motors[1], m3 = motors[2];
            double aM1 = (m1 + getValue(MECH_PAR.sM1) * p2pi * Math.sin(m1 / p2pi)
                    + getValue(MECH_PAR.cM1) * p2pi * Math.cos(m1 / p2pi));
            double aM2 = (m2 + getValue(MECH_PAR.sM2) * p2pi * Math.sin(m2 / p2pi)
                    + getValue(MECH_PAR.cM2) * p2pi * Math.cos(m2 / p2pi));
            double aM3 = (m3 + getValue(MECH_PAR.sM3) * p2pi * Math.sin(m3 / p2pi)
                    + getValue(MECH_PAR.cM3) * p2pi * Math.cos(m3 / p2pi));
            double zM1 = kM1 * aM1;
            double zM2 = kM2 * aM2;
            double zM3 = kM3 * aM3;

            double zc = 0.25 * zM1 + 0.25 * zM2 + 0.5 * zM3 + zTxTy[0]; // getValue(MECH_PAR.z0);
            double dx = PIXEL_SIZE * (px - getValue(MECH_PAR.mpX0));
            double dy = PIXEL_SIZE * (py - getValue(MECH_PAR.mpY0));
            //zTxTy
            //          double zx=dx*(getValue(MECH_PAR.tx)+(2*zM3-zM1-zM2)/(4*getValue(MECH_PAR.Lx))) ;
            //          double zy=dy*(getValue(MECH_PAR.ty)-(zM2-zM1)/(2*getValue(MECH_PAR.Ly))); //!
            double zx = dx * (zTxTy[1] + (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx)));
            double zy = dy * (zTxTy[2] - (zM2 - zM1) / (2 * getValue(MECH_PAR.Ly))); //!
            double z = zc + zx + zy;
            if (dbg)
                if ((Math.abs(m1) == debugMot) && (Math.abs(m2) == debugMot)) {
                    System.out.print("M: " + ((int) m1) + ":" + ((int) m2) + ":" + ((int) m3) + " dxy="
                            + IJ.d2s(dx, 3) + ":" + IJ.d2s(dy, 3) + " zcxy=" + IJ.d2s(zc, 3) + ":" + IJ.d2s(zx, 3)
                            + ":" + IJ.d2s(zy, 3) + " zxy(t)=" + IJ.d2s(dx * zTxTy[1], 3) + ":"
                            + IJ.d2s(dy * zTxTy[2], 3));
                }
            if (deriv == null) {
                if (dbg)
                    if ((Math.abs(m1) == debugMot) && (Math.abs(m2) == debugMot)) {
                        System.out.println();
                    }
                return z;
            }
            for (int i = 0; i < deriv.length; i++)
                deriv[i] = 0.0;
            // Above same as calc_Z
            double dx_mpX0 = -PIXEL_SIZE;
            double dy_mpY0 = -PIXEL_SIZE;
            double zM1_K0 = aM1;
            double zM1_KD1 = aM1;
            double zM1_KD3 = -aM1;
            double zM2_K0 = aM2;
            double zM2_KD1 = -aM2;
            double zM2_KD3 = -aM2;
            double zM3_K0 = aM3;
            //            double zM3_KD1=-aM3;
            //            double zM3_KD3= 0.0;
            double zM3_KD1 = 0.0;
            double zM3_KD3 = aM3;
            double zM1_sM1 = kM1 * p2pi * Math.sin(m1 / p2pi);
            double zM1_cM1 = kM1 * p2pi * Math.cos(m1 / p2pi);
            double zM2_sM2 = kM2 * p2pi * Math.sin(m2 / p2pi);
            double zM2_cM2 = kM2 * p2pi * Math.cos(m2 / p2pi);
            double zM3_sM3 = kM3 * p2pi * Math.sin(m3 / p2pi);
            double zM3_cM3 = kM3 * p2pi * Math.cos(m3 / p2pi);

            double zc_K0 = 0.25 * zM1_K0 + 0.25 * zM2_K0 + 0.5 * zM3_K0;
            double zc_KD1 = 0.25 * zM1_KD1 + 0.25 * zM2_KD1 + 0.5 * zM3_KD1;
            double zc_KD3 = 0.25 * zM1_KD3 + 0.25 * zM2_KD3 + 0.5 * zM3_KD3;
            double zc_sM1 = 0.25 * zM1_sM1;
            double zc_cM1 = 0.25 * zM1_cM1;
            double zc_sM2 = 0.25 * zM2_sM2;
            double zc_cM2 = 0.25 * zM2_cM2;
            double zc_sM3 = 0.5 * zM3_sM3;
            double zc_cM3 = 0.5 * zM3_cM3;

            //            double zx_K0=(2*zM3-zM1-zM2)* dx/(4*getValue(MECH_PAR.Lx));
            double zx_a = dx / (4 * getValue(MECH_PAR.Lx));
            double zx_K0 = (2 * zM3_K0 - zM1_K0 - zM2_K0) * zx_a;
            double zx_KD1 = (2 * zM3_KD1 - zM1_KD1 - zM2_KD1) * zx_a;
            double zx_KD3 = (2 * zM3_KD3 - zM1_KD3 - zM2_KD3) * zx_a;
            double zx_sM1 = (-zM1_sM1) * zx_a;
            double zx_cM1 = (-zM1_cM1) * zx_a;
            double zx_sM2 = (-zM2_sM2) * zx_a;
            double zx_cM2 = (-zM2_cM2) * zx_a;
            double zx_sM3 = (2 * zM3_sM3) * zx_a;
            double zx_cM3 = (2 * zM3_cM3) * zx_a;
            //          double zx_mpX0=dx_mpX0*(getValue(MECH_PAR.tx)+(2*zM3-zM1-zM2)/(4*getValue(MECH_PAR.Lx))); //  double zx_mpX0=dx_mpX0/(4*getValue(MECH_PAR.Lx));
            double zx_mpX0 = dx_mpX0 * (zTxTy[1] + (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx))); //  double zx_mpX0=dx_mpX0/(4*getValue(MECH_PAR.Lx));
            double zx_tx = dx;
            double zx_Lx = -dx * (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx) * getValue(MECH_PAR.Lx));
            double zy_a = -dy / (2 * getValue(MECH_PAR.Ly)); //!
            double zy_K0 = (zM2_K0 - zM1_K0) * zy_a;
            double zy_KD1 = (zM2_KD1 - zM1_KD1) * zy_a;
            double zy_KD3 = (zM2_KD3 - zM1_KD3) * zy_a;
            double zy_sM1 = (-zM1_sM1) * zy_a;
            double zy_cM1 = (-zM1_cM1) * zy_a;
            double zy_sM2 = (zM2_sM2) * zy_a;
            double zy_cM2 = (zM2_cM2) * zy_a;
            double zy_sM3 = 0.0;
            double zy_cM3 = 0.0;
            //          double zy_mpY0=dy_mpY0*(getValue(MECH_PAR.ty)-(zM2-zM1)/(2*getValue(MECH_PAR.Ly)));//! // double zy_mpY0=-dy_mpY0/(2*getValue(MECH_PAR.Ly));//!
            double zy_mpY0 = dy_mpY0 * (zTxTy[2] - (zM2 - zM1) / (2 * getValue(MECH_PAR.Ly)));//! // double zy_mpY0=-dy_mpY0/(2*getValue(MECH_PAR.Ly));//!
            double zy_ty = dy;
            double zy_Ly = dy * (zM2 - zM1) / (2 * getValue(MECH_PAR.Ly) * getValue(MECH_PAR.Ly)); //!

            deriv[getIndex(MECH_PAR.K0)] = zc_K0 + zx_K0 + zy_K0;
            deriv[getIndex(MECH_PAR.KD1)] = zc_KD1 + zx_KD1 + zy_KD1;
            deriv[getIndex(MECH_PAR.KD3)] = zc_KD3 + zx_KD3 + zy_KD3;
            deriv[getIndex(MECH_PAR.sM1)] = zc_sM1 + zx_sM1 + zy_sM1;
            deriv[getIndex(MECH_PAR.cM1)] = zc_cM1 + zx_cM1 + zy_cM1;
            deriv[getIndex(MECH_PAR.sM2)] = zc_sM2 + zx_sM2 + zy_sM2;
            deriv[getIndex(MECH_PAR.cM2)] = zc_cM2 + zx_cM2 + zy_cM2;
            deriv[getIndex(MECH_PAR.sM3)] = zc_sM3 + zx_sM3 + zy_sM3;
            deriv[getIndex(MECH_PAR.cM3)] = zc_cM3 + zx_cM3 + zy_cM3;
            deriv[getIndex(MECH_PAR.Lx)] = zx_Lx;
            deriv[getIndex(MECH_PAR.Ly)] = zy_Ly;
            deriv[getIndex(MECH_PAR.mpX0)] = zx_mpX0;
            deriv[getIndex(MECH_PAR.mpY0)] = zy_mpY0;
            deriv[getIndex(MECH_PAR.z0)] = 1.0;
            deriv[getIndex(MECH_PAR.tx)] = zx_tx;
            deriv[getIndex(MECH_PAR.ty)] = zy_ty;
            if (dbg)
                if ((Math.abs(m1) == debugMot) && (Math.abs(m2) == debugMot)) {
                    if (m1 * m2 > 0) {
                        System.out.println("same sign");
                    } else {
                        System.out.println("opposite sign");
                    }
                    System.out.println(" zxy_txy=" + IJ.d2s(zx_tx, 3) + ":" + IJ.d2s(zy_ty, 3) + " zxy_Lxy="
                            + IJ.d2s(zx_Lx, 5) + ":" + IJ.d2s(zy_Ly, 5));
                    double[] dbg_derivs = debugDeriv_ZdZ(0.1, //scale,
                            motors, px, py);
                    for (int i = 0; i < deriv.length; i++) {
                        System.out.println(i + ": " + descriptions[i][0] + " deriv=" + deriv[i] + ", dbg_derivs="
                                + dbg_derivs[i]);
                    }
                }
            return z;
        }

        public double[] getTilts(int[] motors, int measIndex) {
            double[] zTxTy = getZTxTy();
            if (adjustMode && (measIndex >= 0)) {
                for (int n = 0; n < zTxTy.length; n++)
                    if (replaceZTxTy[n] != null) {
                        if (measIndex < replaceZTxTy[n].length)
                            zTxTy[n] = replaceZTxTy[n][measIndex];
                        else
                            zTxTy[n] = replaceZTxTy[n][0]; // maybe not needed - common parameter
                        if (Double.isNaN(zTxTy[n]))
                            return null;// bad measurement
                    }
            }
            double kM1 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM2 = getValue(MECH_PAR.K0) - getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM3 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD3);
            double p2pi = PERIOD / 2 / Math.PI;
            double m1 = motors[0], m2 = motors[1], m3 = motors[2];
            double aM1 = (m1 + getValue(MECH_PAR.sM1) * p2pi * Math.sin(m1 / p2pi)
                    + getValue(MECH_PAR.cM1) * p2pi * Math.cos(m1 / p2pi));
            double aM2 = (m2 + getValue(MECH_PAR.sM2) * p2pi * Math.sin(m2 / p2pi)
                    + getValue(MECH_PAR.cM2) * p2pi * Math.cos(m2 / p2pi));
            double aM3 = (m3 + getValue(MECH_PAR.sM3) * p2pi * Math.sin(m3 / p2pi)
                    + getValue(MECH_PAR.cM3) * p2pi * Math.cos(m3 / p2pi));
            double zM1 = kM1 * aM1;
            double zM2 = kM2 * aM2;
            double zM3 = kM3 * aM3;
            double[] result = {
                    //                 getValue(MECH_PAR.tx)+(2*zM3-zM1-zM2)/(4*getValue(MECH_PAR.Lx)),
                    //                 getValue(MECH_PAR.ty)-(zM2-zM1)/(2*getValue(MECH_PAR.Ly))};
                    zTxTy[1] + (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx)),
                    zTxTy[2] - (zM2 - zM1) / (2 * getValue(MECH_PAR.Ly)) };
            return result;
        }

        /**
         * Correction for z0,tx,ty to zc, tilt X (axial), tilt Y (axial) for the specified pixel (optical center)
         * and current parameters (including z0,tx,ty), motors are assumed all 0
        * @param centerPx optical center X (mechanical parameters are referenced to mechanical only)
        * @param centerPy optical center Y
         * @return 3-element array to add to z0,tx,ty to get zc, tx axial, ty axial
         */
        public double[] getZTxTyCorr(double centerPx, // optical center X
                double centerPy) {
            double kM1 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM2 = getValue(MECH_PAR.K0) - getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
            double kM3 = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD3);
            double p2pi = PERIOD / 2 / Math.PI;
            double m1 = 0.0, m2 = 0.0, m3 = 0.0;
            double aM1 = (m1 + getValue(MECH_PAR.sM1) * p2pi * Math.sin(m1 / p2pi)
                    + getValue(MECH_PAR.cM1) * p2pi * Math.cos(m1 / p2pi));
            double aM2 = (m2 + getValue(MECH_PAR.sM2) * p2pi * Math.sin(m2 / p2pi)
                    + getValue(MECH_PAR.cM2) * p2pi * Math.cos(m2 / p2pi));
            double aM3 = (m3 + getValue(MECH_PAR.sM3) * p2pi * Math.sin(m3 / p2pi)
                    + getValue(MECH_PAR.cM3) * p2pi * Math.cos(m3 / p2pi));
            double zM1 = kM1 * aM1;
            double zM2 = kM2 * aM2;
            double zM3 = kM3 * aM3;
            double diff_zc = 0.25 * zM1 + 0.25 * zM2 + 0.5 * zM3;
            double dx = PIXEL_SIZE * (centerPx - getValue(MECH_PAR.mpX0));
            double dy = PIXEL_SIZE * (centerPy - getValue(MECH_PAR.mpY0));
            double diff_tx = (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx));
            double diff_ty = -(zM2 - zM1) / (2 * getValue(MECH_PAR.Ly));
            double zx = dx * (getValue(MECH_PAR.tx) + diff_tx);
            double zy = dy * (getValue(MECH_PAR.ty) + diff_ty);
            double diff_z = diff_zc + zx + zy;
            double[] result = { diff_z, diff_tx, diff_ty };
            return result;
        }

        /**
         * Correction for z0,tx,ty to zc, tilt X (axial), tilt Y (axial) for the specified pixel (optical center)
         * and current parameters (including z0,tx,ty), motors are assumed all 0
        * @param zM - 3 linearized (in linear um, thread waves removed) motor positions
        * @param centerPx optical center X (mechanical parameters are referenced to mechanical only)
        * @param centerPy optical center Y
         * @return 3-element array to add to z0,tx,ty to get zc, tx axial, ty axial
         */
        public double[] getZTxTyCorr(double[] zM, // linearized motor coordinates
                double centerPx, // optical center X
                double centerPy) {
            double zM1 = zM[0];
            double zM2 = zM[1];
            double zM3 = zM[2];
            double diff_zc = 0.25 * zM1 + 0.25 * zM2 + 0.5 * zM3;
            double dx = PIXEL_SIZE * (centerPx - getValue(MECH_PAR.mpX0));
            double dy = PIXEL_SIZE * (centerPy - getValue(MECH_PAR.mpY0));
            double diff_tx = (2 * zM3 - zM1 - zM2) / (4 * getValue(MECH_PAR.Lx));
            double diff_ty = -(zM2 - zM1) / (2 * getValue(MECH_PAR.Ly));
            double zx = dx * (getValue(MECH_PAR.tx) + diff_tx);
            double zy = dy * (getValue(MECH_PAR.ty) + diff_ty);
            double diff_z = diff_zc + zx + zy;
            double[] result = { diff_z, diff_tx, diff_ty };
            return result;
        }

        /**
         * Correction for z0 to zc for the specified pixel (optical center)
         * and current parameters (including z0,tx,ty), for all linearized
         * (in linear um, thread waves removed) mounts positions set to 0.
         * No correction for tilts is needed, only for z.
        * @param centerPx optical center X (mechanical parameters are referenced to mechanical only)
        * @param centerPy optical center Y
         * @return 3-element array to add to z0,tx,ty to get zc, tx axial, ty axial
         */
        public double getZCorr(double centerPx, // optical center X
                double centerPy) {
            double dx = PIXEL_SIZE * (centerPx - getValue(MECH_PAR.mpX0));
            double dy = PIXEL_SIZE * (centerPy - getValue(MECH_PAR.mpY0));
            double zx = dx * getValue(MECH_PAR.tx);
            double zy = dy * getValue(MECH_PAR.ty);
            return zx + zy;
        }

        /**
         * Calculate linearized mount (motor) displacement from motor position in steps
         * @param m motor position in steps
         * @param index motor number (0..2)
         * @return mount displacement (in microns)
         */
        public double mToZm(double m, int index) {
            double p2pi = PERIOD / 2 / Math.PI;
            double kM = Double.NaN, aM = Double.NaN;
            switch (index) {
            case 0:
                kM = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
                aM = (m + getValue(MECH_PAR.sM1) * p2pi * Math.sin(m / p2pi)
                        + getValue(MECH_PAR.cM1) * p2pi * Math.cos(m / p2pi));
                break;
            case 1:
                kM = getValue(MECH_PAR.K0) - getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
                aM = (m + getValue(MECH_PAR.sM2) * p2pi * Math.sin(m / p2pi)
                        + getValue(MECH_PAR.cM2) * p2pi * Math.cos(m / p2pi));
                break;
            case 2:
                kM = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD3);
                aM = (m + getValue(MECH_PAR.sM3) * p2pi * Math.sin(m / p2pi)
                        + getValue(MECH_PAR.cM3) * p2pi * Math.cos(m / p2pi));
                break;
            }
            return kM * aM;
        }

        private double getDzmDm(double m, double kM, double s, double c) {
            double p2pi = PERIOD / 2 / Math.PI;
            return kM * (1.0 + s * Math.cos(m / p2pi) - c * Math.sin(m / p2pi));

        }

        /**
         * Convert linearized motor position to motor steps using current mechanical parameter values
         * @param zm linear motor position
         * @param index motor index (0..2)
         * @return motor position in steps
         */
        public double zmToM(double zm, int index) {
            double p2pi = PERIOD / 2 / Math.PI;
            double kM = Double.NaN, m = 0, s = 0.0, c = 1.0;
            switch (index) {
            case 0:
                kM = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
                s = getValue(MECH_PAR.sM1);
                c = getValue(MECH_PAR.cM1);
                break;
            case 1:
                kM = getValue(MECH_PAR.K0) - getValue(MECH_PAR.KD1) - getValue(MECH_PAR.KD3);
                s = getValue(MECH_PAR.sM2);
                c = getValue(MECH_PAR.cM2);
                break;
            case 2:
                kM = getValue(MECH_PAR.K0) + getValue(MECH_PAR.KD3);
                s = getValue(MECH_PAR.sM3);
                c = getValue(MECH_PAR.cM3);
                break;
            }
            m = zm / kM; // without thread sin/cos
            double eps = 0.000001;
            int maxRetries = 100;
            //           aM=(m + getValue(MECH_PAR.sM3)*p2pi*Math.sin(m/p2pi) + getValue(MECH_PAR.cM3)*p2pi*Math.cos(m/p2pi));
            //   double dzM1_dm1=kM1*(1.0+getValue(MECH_PAR.sM1)*Math.cos(m1/p2pi)-getValue(MECH_PAR.cM1)*Math.sin(m1/p2pi));
            double a = Math.sqrt(s * s + c * c);
            double phase = 0.0;
            double extrenPhase = 0.0;
            if (a > 1.0) {
                phase = Math.atan2(s, c);
                //             double rd=1.0+ s*Math.cos(m/p2pi)-c*Math.sin(m/p2pi);
                //             double rd=1.0+ a*Math.cos((m+p2pi*phase)/p2pi);
                // rd==0 => Math.cos((m+p2pi*phase)/p2pi)=-1.0/a
                extrenPhase = phase - Math.asin(1.0 / a);
                //             minPhase-=(2*Math.PI)*Math.floor(minPhase/(2*Math.PI));
                extrenPhase -= Math.PI * Math.floor(extrenPhase / Math.PI); // min/max 0<=hase<PI of the min/max zm(m)
            }
            for (int retry = 0; retry < maxRetries; retry++) {
                double zm1 = mToZm(m, index);
                if (Math.abs(zm1 - zm) < eps)
                    break;
                double dzm_dm = getDzmDm(m, kM, s, c);
                if (dzm_dm <= 0.0) {
                    if (debugLevel > 0)
                        System.out.println("Negative derivative dzm_dm");
                    double mPhase = m / p2pi;
                    double halfPeriods = Math.floor(mPhase / Math.PI);
                    double fractPhase = mPhase - Math.PI * halfPeriods;
                    double mirrExtrenFractPhase = extrenPhase;
                    if (mirrExtrenFractPhase < fractPhase)
                        mirrExtrenFractPhase += Math.PI;
                    if (zm1 > zm)
                        mirrExtrenFractPhase -= Math.PI;
                    double mirrPhase = Math.PI * halfPeriods + mirrExtrenFractPhase;
                    double mirrM = p2pi * mirrPhase;
                    m = 2 * mirrM - m; // symmetrical aqround extrenum, up if needed more, down - if needed less
                }
                double l = 1.0;
                double m2 = m;
                for (int retry2 = 0; retry2 < maxRetries; retry2++) {
                    m2 = m + l * ((zm - zm1) / dzm_dm);
                    double zm2 = mToZm(m2, index);
                    if (Math.abs(zm2 - zm) < Math.abs(zm1 - zm))
                        break;
                    l *= 0.5;
                }
                m = m2;
            }
            return m;
        }

        /**
         * Calculate manual screw adjustments for focus/tilt to reduce amount of motor travel (when it is out of limits)
         * @param zErr  current focal distance error in microns, positive - away from lens
         * @param tXErr current horizontal tilt in microns/mm , positive - 1,2 away from lens, 3 - to the lens
         * @param tYErr current vertical tilt in microns/mm , positive - 2 away from lens, 1 - to the lens
         * @return array of optimal CW rotations of each screw (1.0 == 360 deg)
         * Screw locations:
         *       5    2
         *  3    +
         *       4    1
         * + - center      
         * 1,2 M2x0.4 set screws (push)
         * 3 - M2x0.4 socket screw (push)
         * 4 - M2x0.4 socket screw (pull)
         * 5 - M2.5x0.45 screw (pull)
         */
        public double[] getManualScrews(double zErr, // positive - away from lens
                double tXErr, // positive - 1,2 away from lens, 3 - to the lens
                double tYErr) {// positive - 2 away from lens
            double[][] screws = { // right, down, thread pitch (pull) !!! Inverting Y!
                    { 20.5, -17.5, -0.4 }, { 20.5, 17.5, -0.4 }, { -20.5, 0.0, -0.4 }, { 0.0, -17.5, 0.4 },
                    { 0.0, 17.5, 0.45 } };
            double[] moveDownUm = new double[screws.length];
            double[] turnCW = new double[screws.length];
            for (int i = 0; i < screws.length; i++) {
                moveDownUm[i] = zErr + screws[i][0] * tXErr + screws[i][1] * tYErr;
                turnCW[i] = 0.001 * moveDownUm[i] / screws[i][2];
            }
            return turnCW;
        }

        /**
         * Calculate three linearized values of motor positions for current parameters, target center focal
         * shift and tilt (from the optic axis)
         * Use current values of MECH_PAR.z0, MECH_PAR.tx,MECH_PAR.ty
         * @param zM0 current linearized position (for parallel adjustment) or null for full adjustment
         * @param px lens center X (pixels)
         * @param py lens center Y (pixels)
         * @param targetZ target focal shift in the center, microns, positive - away
         * @param targetTx target horizontal tilt from the optical axis
         * @param targetTy target vertical tilt  from the optical axis 
         * @return array of 3 linearized motor positions (microns) 
         */
        public double[] getZM(double[] zMCurrent, //  current linearized motors (or null for full adjustment) 
                double px, double py, double targetZ, double targetTx, double targetTy) {
            double dx = PIXEL_SIZE * (px - getValue(MECH_PAR.mpX0));
            double dy = PIXEL_SIZE * (py - getValue(MECH_PAR.mpY0));
            if (zMCurrent != null) {
                //          0.25* zM1+ 0.25* zM2+ 0.5 * zM3 = targetZ-getValue(MECH_PAR.z0);
                //          0.25* (zM1+dzM)+ 0.25* (zM2+dzM)+ 0.5 * (zM3+dzM) = targetZ-getValue(MECH_PAR.z0);
                //          0.25* (dzM+ 0.25* dzM+ 0.5 * dzM = targetZ-getValue(MECH_PAR.z0) - (0.25* zM1+ 0.25* zM2+ 0.5 * zM3 );
                //          dzM = targetZ-getValue(MECH_PAR.z0) - (0.25* zM1+ 0.25* zM2+ 0.5 * zM3 );
                double dZM = targetZ - getValue(MECH_PAR.z0)
                        - (0.25 * zMCurrent[0] + 0.25 * zMCurrent[1] + 0.5 * zMCurrent[2]);
                double[] zM = zMCurrent.clone();
                for (int i = 0; i < zM.length; i++)
                    zM[i] += dZM;
                return zM;
            }
            //          double zc= 0.25* zM1+ 0.25* zM2+ 0.5 * zM3+getValue(MECH_PAR.z0);
            //          double zx=dx*(getValue(MECH_PAR.tx)+(2*zM3-zM1-zM2)/(4*getValue(MECH_PAR.Lx))) ;
            //          double zy=dy*(getValue(MECH_PAR.ty)-(zM2-zM1)/(2*getValue(MECH_PAR.Ly)));//!
            //          double z=zc+zx+zy          
            //         A*{zM1,zM2,zM3}={targetZ,targetTx,targetTy}
            //          A*{zM1,zM2,zM3}={targetZ-getValue(MECH_PAR.z0),targetTx-dx*getValue(MECH_PAR.tx),targetTy-dy*getValue(MECH_PAR.ty)}
            double[][] A = { { 0.25 - dx / (4 * getValue(MECH_PAR.Lx)) + dy / (2 * getValue(MECH_PAR.Ly)), //!
                    0.25 - dx / (4 * getValue(MECH_PAR.Lx)) - dy / (2 * getValue(MECH_PAR.Ly)), //!
                    0.5 + dx / (2 * getValue(MECH_PAR.Lx)) },
                    { -1.0 / (4 * getValue(MECH_PAR.Lx)), -1.0 / (4 * getValue(MECH_PAR.Lx)),
                            1.0 / (2 * getValue(MECH_PAR.Lx)) },
                    { 1.0 / (2 * getValue(MECH_PAR.Ly)), //!
                            -1.0 / (2 * getValue(MECH_PAR.Ly)), //!
                            0.0 } };
            double zCenterCorr = getZCorr(px, py);// optical center X
            System.out.println("Correcting Z_center by " + zCenterCorr
                    + " um (caused by Tx, Ty and difference between mechanical and optical centers");
            double[][] B = { { targetZ - getValue(MECH_PAR.z0) - zCenterCorr }, // calc_ZdZ()?
                    { targetTx - getValue(MECH_PAR.tx) }, { targetTy - getValue(MECH_PAR.ty) } };
            Matrix MA = new Matrix(A);
            Matrix MB = new Matrix(B);
            Matrix S = MA.solve(MB);
            return S.getColumnPackedCopy();
        }

        public boolean[] getDefaultMask() {
            boolean[] mask = new boolean[this.paramValues.length];
            for (int i = 0; i < mask.length; i++)
                mask[i] = false;
            mask[getIndex(MECH_PAR.K0)] = false;
            mask[getIndex(MECH_PAR.KD1)] = false; //true;
            mask[getIndex(MECH_PAR.KD3)] = false; //true;
            mask[getIndex(MECH_PAR.sM1)] = false;
            mask[getIndex(MECH_PAR.cM1)] = false;
            mask[getIndex(MECH_PAR.sM2)] = false;
            mask[getIndex(MECH_PAR.cM2)] = false;
            mask[getIndex(MECH_PAR.sM3)] = false;
            mask[getIndex(MECH_PAR.cM3)] = false;
            mask[getIndex(MECH_PAR.Lx)] = false;
            mask[getIndex(MECH_PAR.Ly)] = false; //true;
            mask[getIndex(MECH_PAR.mpX0)] = false; //true;
            mask[getIndex(MECH_PAR.mpY0)] = false; //true;
            mask[getIndex(MECH_PAR.tx)] = true;
            mask[getIndex(MECH_PAR.ty)] = true;
            return mask;
        }

        public boolean[] maskSetDialog(String title, boolean[] currentMask) {
            GenericDialog gd = new GenericDialog(title);
            boolean[] mask = new boolean[this.paramValues.length];
            if (currentMask == null)
                currentMask = getDefaultMask();
            for (int i = 0; i < mask.length; i++) {
                mask[i] = currentMask[i];
                gd.addCheckbox(getDescription(i), mask[i]);
            }
            gd.enableYesNoCancel("Apply", "Keep"); // default OK (on enter) - "Apply"
            gd.showDialog();
            if (gd.wasCanceled())
                return null;
            if (gd.wasOKed()) { // selected default "Apply"
                for (int i = 0; i < mask.length; i++) {
                    mask[i] = gd.getNextBoolean();
                }
            }
            return mask;
        }

        public boolean[] maskSetZTxTy(int[] zTxTyAdjustMode) { // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
            boolean[] mask = new boolean[this.paramValues.length];
            for (int i = 0; i < mask.length; i++)
                mask[i] = false;
            mask[getIndex(MECH_PAR.z0)] = true;
            mask[getIndex(MECH_PAR.tx)] = true;
            mask[getIndex(MECH_PAR.ty)] = true;
            if (zTxTyAdjustMode != null) { // z, tx, ty - 0 - fixed, 1 - common, 2 - individual
                if (zTxTyAdjustMode[0] <= 0)
                    mask[getIndex(MECH_PAR.z0)] = false;
                if (zTxTyAdjustMode[1] <= 0)
                    mask[getIndex(MECH_PAR.tx)] = false;
                if (zTxTyAdjustMode[2] <= 0)
                    mask[getIndex(MECH_PAR.ty)] = false;
            }
            return mask;
        }

        public boolean showModifyParameterValues(String title, boolean showDisabled, boolean[] mask) {
            GenericDialog gd = new GenericDialog(title);
            for (int i = 0; i < this.paramValues.length; i++) {
                if ((mask == null) || mask[i]) {
                    gd.addNumericField(getDescription(i), this.paramValues[i], 5, 8, getUnits(i));
                } else if (showDisabled) {
                    //                    gd.addMessage(getDescription(i) +": "+this.paramValues[i]+" ("+getUnits(i)+")");
                    gd.addNumericField("(disabled) " + getDescription(i), this.paramValues[i], 5, 8, getUnits(i));
                }
            }
            gd.enableYesNoCancel("Apply", "Keep"); // default OK (on enter) - "Apply"
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            if (gd.wasOKed()) { // selected default "Apply"
                for (int i = 0; i < this.paramValues.length; i++) {
                    if ((mask == null) || mask[i] || showDisabled) {
                        this.paramValues[i] = gd.getNextNumber();
                    }
                }
            }
            return true;
        }
    }

    public class CurvatureModel {
        private double dflt_na = Math.log(0.15); // um/um
        private double dflt_r0 = Math.log(4.0); //3.3; // um (1.5 pix)
        private double[][] modelParams = null;
        public static final int dflt_distanceParametersNumber = 5;
        public static final int dflt_radialParametersNumber = 4;
        private static final int min_distanceParametersNumber = 4;
        private static final int min_radialParametersNumber = 1;
        private double pX0, pY0;

        public CurvatureModel(double pX0, double pY0, int distanceParametersNumber, int radialParametersNumber) {
            this.pX0 = pX0;
            this.pY0 = pY0;
            if (distanceParametersNumber < min_distanceParametersNumber)
                distanceParametersNumber = min_distanceParametersNumber;
            if (radialParametersNumber < min_radialParametersNumber)
                radialParametersNumber = min_radialParametersNumber;
            this.modelParams = new double[distanceParametersNumber][radialParametersNumber];
            setDefaults();
        }

        public void setProperties(String prefix, Properties properties) {
            properties.setProperty(prefix + "distanceParametersNumber", modelParams.length + "");
            properties.setProperty(prefix + "radialParametersNumber", modelParams[0].length + "");
            properties.setProperty(prefix + "pX0", pX0 + "");
            properties.setProperty(prefix + "pY0", pY0 + "");
            for (int i = 0; i < modelParams.length; i++)
                for (int j = 0; j < modelParams[i].length; j++) {
                    properties.setProperty(prefix + "modelParams" + i + "_" + j, modelParams[i][j] + "");
                }
        }

        public void getProperties(String prefix, Properties properties) {
            int numZPars, numRPars;
            if (properties.getProperty(prefix + "pX0") != null)
                pX0 = Double.parseDouble(properties.getProperty(prefix + "pX0"));
            if (properties.getProperty(prefix + "pY0") != null)
                pX0 = Double.parseDouble(properties.getProperty(prefix + "pY0"));
            if (modelParams != null) {
                numZPars = modelParams.length;
                numRPars = modelParams[0].length;
            } else {
                numZPars = dflt_distanceParametersNumber;
                numRPars = dflt_radialParametersNumber;
            }
            if (properties.getProperty(prefix + "distanceParametersNumber") != null)
                numZPars = Integer.parseInt(properties.getProperty(prefix + "distanceParametersNumber"));
            if (properties.getProperty(prefix + "radialParametersNumber") != null)
                numRPars = Integer.parseInt(properties.getProperty(prefix + "radialParametersNumber"));
            if ((modelParams != null) || (modelParams.length != numZPars) || (modelParams[0].length != numRPars)) {
                modelParams = new double[numZPars][numRPars];
                setDefaults();
            }

            for (int i = 0; i < modelParams.length; i++)
                for (int j = 0; j < modelParams[i].length; j++) {
                    if (properties.getProperty(prefix + "modelParams" + i + "_" + j) != null) {
                        modelParams[i][j] = Double
                                .parseDouble(properties.getProperty(prefix + "modelParams" + i + "_" + j));
                    }
                }
        }

        public void setCenter(double pX, double pY) {
            pX0 = pX;
            pY0 = pY;
        }

        public int[] getNumPars() {
            if (modelParams == null)
                return null;
            int[] numPars = { modelParams.length, modelParams[0].length };
            return numPars;
        }

        public void setDefaults() {
            for (int i = 0; i < this.modelParams.length; i++)
                for (int j = 0; j < this.modelParams[0].length; j++) {
                    this.modelParams[i][j] = 0.0;
                }
            this.modelParams[1][0] = dflt_na;
            this.modelParams[2][0] = dflt_r0;
            this.modelParams[0][0] = Double.NaN;
        }

        public boolean z0IsValid() {
            return !Double.isNaN(this.modelParams[0][0]);
        }

        public void set_z0(double z0) {
            this.modelParams[0][0] = z0;
        }

        public double[] getCenterVector() {
            double[] vector = new double[this.modelParams.length];
            for (int i = 0; i < this.modelParams.length; i++) {
                vector[i] = this.modelParams[i][0];
            }
            return vector;
        }

        public void setCenterVector(double[] vector) {
            for (int i = 0; i < this.modelParams.length; i++) {
                this.modelParams[i][0] = vector[i];
            }
        }

        public double[] getVector() {
            double[] vector = new double[this.modelParams.length * this.modelParams[0].length];
            int index = 0;
            for (int i = 0; i < this.modelParams.length; i++)
                for (int j = 0; j < this.modelParams[0].length; j++) {
                    vector[index++] = this.modelParams[i][j];
                }
            return vector;
        }

        public void setVector(double[] vector, boolean[] mask) { // mask may be null
            int index = 0;
            for (int i = 0; i < this.modelParams.length; i++)
                for (int j = 0; j < this.modelParams[0].length; j++)
                    if ((mask == null) || mask[index]) {
                        this.modelParams[i][j] = vector[index++];
                    }
        }

        public int getSize() {
            try {
                return this.modelParams.length * this.modelParams[0].length + 1; // last element df/dr
            } catch (Exception e) {
                return 0;
            }
        }

        double[] getAr(double r, double[] param_corr) {
            double[] ar = new double[this.modelParams.length];
            for (int i = 0; i < this.modelParams.length; i++) {
                ar[i] = this.modelParams[i][0];
                if (param_corr != null)
                    ar[i] += param_corr[i];
                //                double rp=1.0;
                double rp = r;
                for (int j = 1; j < this.modelParams[i].length; j++) {
                    //                    rp*=r2;
                    rp *= r;
                    ar[i] += this.modelParams[i][j] * rp;
                }
            }
            return ar;
        }

        /**
         * Calculate function value and (optionally) partial derivatives over all parameters as 1d array
         * inner scanning by radial coefficient (for even powers of radius), outer - for f(z) parameters
         * first parameter - z0 (shift), then scale (related to numeric aperture), then r0 (related to minimal PSF radius),
         * other parameters - just polynomial coefficients for (z_in-z0)^n
         * @param param_corr - per-sample corrections, added to this.modelParams[i][0] - or null
         * @param pX - sample pixel x coordinate (currently just for radius calculation) or radius in mm (if pY is NaN)
         * @param pY - sample pixel y coordinate (currently just for radius calculation) or NaN, then pX - radius in mm
         * @param z_in - z (from mechanical) before subtracting of z0
         * @param deriv array to accommodate all partial derivatives or null (should have modelParams.length*modelParams[0].length+1
         * @return function value
         */
        public double getFdF(double[] param_corr, double pX, // if isNaN(pY), then pX is radius in mm
                double pY, double z_in, double[] deriv) {
            /*
            f=sqrt(( a*(zin-z0))^2 + r0^2)+a0+ a1*(zin-z0)+...aN*(zin-z0)^N
            each of the z0,z0,a,a[i] is polynomial of even powers of r (r^0, r^2, r^4...)
            z=z_in-z0
                
            modified parameters, r0 - PSF FWHM at z=0, k (instead of a0), so that old r0 now reff=
            r0*exp(-k), old a0= r0*(1-exp(-k)).
                
            f=sqrt((a*(zin-z0))^2 + (r0*(exp(-k))^2)+r0*(1-exp(-k))+ a1*(zin-z0)+...aN*(zin-z0)^N
                
            z0 - ar[0]
            a - ar[1]
            r0 - ar[2]
            k - ar[3]
            ar1 - ar[4]
                
            modified to avoid zero-crossing for a and r0:
            a=exp(ln_a)
            r0=exp(ln_r0)
                
            z0    - ar[0]
            ln_a  - ar[1]
            ln_r0 - ar[2]
            k     - ar[3]
            ar1   - ar[4]
            =================
            f(x)=sqrt((ax)^2+r^2)+kx-f_corr
            f(z)=sqrt((a*(z-z_corr)^2+r^2)+k*(z-z_corr)-f_corr
            f'(x)=0 -> x=sqrt(1/r^2-a^2)
            z_corr=(kx*rc/a)/*sqrt(a^2-kx^2)
            f_corr=sqrt((a*z_corr)^2+r_eff^2)-kx*(z_corr)  r_eff
            k=a*func(tilt]), func(-inf)=-1, func(0)=0, func(+inf)=1 
            k=a*2/pi*atan(tilt);
            d_k/d_tilt = 2/pi*(1/tilt^2)
                
            x=z_in-(z0+z_corr)
                
            Ideally I should match second derivative near minimum to isolate tilt from ar[3] - adjust r_eff and then shift
            Maybe not, just keep iot as it is (matching f" at minimum while tilting will cause a sharp turn nearby)
            a=exp(ln_a)
            r0=exp(ln_r0)
            z0    - ar[0]
            ln_a  - ar[1]
            ln_r0 - ar[2]
            k     - ar[3]
            a1    - ar[4]
                
            kx=a*2/pi*atan(a1);
            z_corr=(kx*rc/a)/sqrt(a^2-kx^2)
            f_corr=sqrt((a*z_corr)^2+r_eff^2)-kx*(z_corr)  r_eff
            f=sqrt((a*(zin-z0-z_corr))^2 + (r0*(exp(-k))^2)+r0*(1-exp(-k))+ kx*(zin-z0-z_corr) {+...aN*(zin-z0-z_corr)^N} - {} are not likely to be ever used 
                
            dependence:
            kx: a,a1 (ar[1], ar[4]
            z_corr: kx,r_eff,a - ar[1], ar[4], ar[2], ar[3]
            f_corr: d_fcorr/d_zcorr=0, other: a, reff, kx ->  ar[1], ar[2], ar[3],  ar[4]
            */
            double r = Double.isNaN(pY) ? pX
                    : Math.sqrt((pX - pX0) * (pX - pX0) + (pY - pY0) * (pY - pY0)) * PIXEL_SIZE; // in mm
            double[] ar = new double[this.modelParams.length];
            for (int i = 0; i < this.modelParams.length; i++) {
                ar[i] = this.modelParams[i][0];
                if (param_corr != null)
                    ar[i] += param_corr[i];
                double rp = r;
                for (int j = 1; j < this.modelParams[i].length; j++) {
                    rp *= r;
                    ar[i] += this.modelParams[i][j] * rp;
                }
            }
            double exp_a = Math.exp(ar[1]);
            double exp_r = Math.exp(ar[2]);
            double exp_mk = Math.exp(-ar[3]);
            double reff = exp_r * exp_mk;
            double reff2 = reff * reff;
            double exp_a2 = exp_a * exp_a;
            double a1 = ar[4];
            //            kx=a*2/pi*atan(a1);
            double kx = exp_a * 2.0 / Math.PI * Math.atan(a1);
            //            z_corr=(kx*rc/a)/*sqrt(a^2-kx^2)
            double z_corr = (kx * reff / exp_a) / Math.sqrt(exp_a2 - kx * kx);
            double z_corr2 = z_corr * z_corr;
            //          f_corr=sqrt((a*z_corr)^2+r_eff^2)-kx*(z_corr)  r_eff
            double f_corr = Math.sqrt(exp_a2 * z_corr2 + reff2) - kx * z_corr - reff;
            double z = z_in - ar[0] - z_corr;
            double sqrt = Math.sqrt(exp_a2 * z * z + reff2);
            //            f=sqrt((a*(zin-z0-z_corr))^2 + (r0*(exp(-k))^2)+r0*(1-exp(-k))+ kx*(zin-z0-z_corr)-f_corr {+...aN*(zin-z0-z_corr)^N} - {} are not likely to be ever used 
            double f = sqrt + exp_r * (1 - exp_mk) + kx * z - f_corr;
            double zp = z;
            for (int i = 5; i < ar.length; i++) {
                zp *= z;
                f += ar[i] * zp;
            }

            if (deriv == null)
                return f; // only value, no derivatives

            // derivatives calculation independent of z - move to a separate function that can be called once for channel/sample, stored asnd then applied to multiple z measurements        
            //  double kx=exp_a*2.0/Math.PI*Math.atan(a1);
            double dkx_dar1 = kx; // exp_a*2.0/Math.PI*Math.atan(a1);
            double dkx_dar4 = exp_a * 2.0 / Math.PI / (1.0 + a1 * a1);
            // z_corr=(kx*reff/exp_a)/Math.sqrt(exp_a*exp_a - kx*kx)  //kx,r_eff,a - ar[1], ar[4], ar[2], ar[3]
            //dzcorr_dkx=rc*a/(a^2-kx^2)/sqrt(a^2-kx^2)
            double kx2 = kx * kx;
            double exp_a2_kx2 = exp_a2 - kx2;
            double sqrt_exp_a2_kx2 = Math.sqrt(exp_a2_kx2);

            double dzcorr_dkx = reff * exp_a / (exp_a2_kx2 * sqrt_exp_a2_kx2); //(exp_a2-kx2)/Math.sqrt(exp_a2-kx2);
            double dzcorr_dar4 = dzcorr_dkx * dkx_dar4; // d_tilt
            //d_zcorr/d_tilt=d_kx/d_tilt*rc*a/(a^2-kx^2)/sqrt(a^2-kx^2)
            double dzcorr_dar1 = -z_corr; // Nice!
            // d_reff/dar2==reff; d_reff/dar3=-reff   
            // d_zcorr/dar2=z_corr d_zcorr/dar3=-z_corr verified
            //z_corr: kx,r_eff,a - ar[1], ar[4], ar[2], ar[3]
            double sqr_azr = Math.sqrt(exp_a2 * z_corr2 + reff2);

            //            double dfcorr_da=exp_a*z_corr2/sqr_azr;
            double dfcorr_dreff = reff / sqr_azr - 1;
            double dfcorr_dkx = -z_corr;
            // dfcorr_dar1==0
            double dfcorr_dar2 = dfcorr_dreff * exp_r * exp_mk;
            //                 dfcorr_dar3=dfcorr_dar2
            double dfcorr_dar4 = dfcorr_dkx * dkx_dar4;

            //            double z=z_in-ar[0]-z_corr;
            //            double sqrt=Math.sqrt(exp_a2*z*z + reff2);
            //            f=sqrt((a*(zin-z0-z_corr))^2 + (r0*(exp(-k))^2)+r0*(1-exp(-k))+ kx*(zin-z0-z_corr) -f_corr {+...aN*(zin-z0-z_corr)^N} - {} are not likely to be ever used 

            // Dependent on z:
            double z2 = z * z;
            double[] df_da = new double[this.modelParams.length]; // last element - derivative for dz
            // derivative for z0 (shift) - ar[0]
            df_da[0] = -1.0 / sqrt * exp_a2 * z;
            df_da[0] -= kx;
            zp = z;
            for (int i = 5; i < this.modelParams.length; i++) {
                df_da[0] -= ar[i] * (i - 3) * zp; // ar[i] calculated coefficients for current radius
                zp *= z;
            }
            double df_dz_corr = df_da[0];
            //            double df_df_corr=-1;//, so subtract each of dfcorr_dar* from df_dar*

            //          double z=z_in-ar[0]-z_corr;
            //          double sqrt=Math.sqrt(exp_a2*z*z + reff2);
            //          f=sqrt((a*(zin-z0-z_corr))^2 + (r0*(exp(-k))^2) +r0*(1-exp(-k)) + kx*(zin-z0-z_corr) -f_corr{+...aN*(zin-z0-z_corr)^N} - {} are not likely to be ever used 

            // derivative for a (related to numeric aperture) - ar[1]
            //            df_da[1]=1.0/sqrt*exp_a*z*z  *exp_a; // d(f)/d(exp_a) *exp_a
            // first - calculate derivatives w/O f_corr, z_corr - then apply them            
            df_da[1] = 1.0 / sqrt * exp_a2 * z2; // d(f)/d(exp_a) *exp_a
            // derivative for a (related to lowest PSF radius) - ar[2]
            df_da[2] = (1.0 / sqrt * reff * exp_mk + (1 - exp_mk)) * exp_r; // d(f)/d(exp_r) *exp_r
            // derivative for k (ar[3]
            df_da[3] = 1.0 / sqrt * reff * exp_r * exp_mk * (-1) + exp_r * exp_mk;
            // derivative for tilt (ar[4])
            df_da[4] = z * dkx_dar4;
            // derivatives for rest (polynomial) coefficients, probably not ever needed
            zp = z;
            for (int i = 5; i < this.modelParams.length; i++) {
                zp *= z;
                df_da[i] = zp;
            }
            // new extra term dependent on ar[1] f=...+ kx*(zin-z0-z_corr) +...            
            df_da[1] += dkx_dar1 * z;
            // now apply corrections for z_corr and f_corr
            df_da[1] += df_dz_corr * dzcorr_dar1; // bad 
            df_da[2] += df_dz_corr * z_corr; // good
            df_da[3] += df_dz_corr * (-z_corr); // good 
            df_da[4] += df_dz_corr * dzcorr_dar4; // good

            df_da[2] -= dfcorr_dar2; // good
            df_da[3] += dfcorr_dar2; // good
            df_da[4] -= dfcorr_dar4; // good

            // derivative for z (to be combined with mechanical is just negative of derivative for z0, no need to calcualate separately
            // calculate even powers of radius
            double[] dar = new double[this.modelParams[0].length];
            dar[0] = 1;
            for (int j = 1; j < dar.length; j++) {
                dar[j] = dar[j - 1] * r;
                if (j == 1)
                    dar[j] *= r; // 0,2,3,4,5...
            }
            int index = 0;
            for (int i = 0; i < this.modelParams.length; i++)
                for (int j = 0; j < this.modelParams[0].length; j++) {
                    deriv[index++] = df_da[i] * dar[j];
                }
            //            calculate d(f)/d(r)
            double df_dr = 0.0;
            for (int i = 0; i < this.modelParams.length; i++) {
                double da_dr = 0.0;
                double rp = 1.0;
                for (int j = 1; j < this.modelParams[0].length; j++) {
                    rp *= r;
                    da_dr += this.modelParams[i][j] * (j + 1) * rp;
                }
                df_dr += df_da[i] * da_dr;
            }
            deriv[this.modelParams.length * this.modelParams[0].length] = df_dr; // last element
            return f; // function value
        }

        public String getRadialName(int i) {
            return "ar_" + (i + 1); //TODO: chnage ar_1-> ar_0 (or ar_c),but that will break configuration files
        }

        public String getRadialDecription(int i) {
            if (i == 0)
                return "Radial constant coefficient";
            return "Radial polynomial coefficient for r^" + (i + 1);
        }

        public String getZName(int i) {
            if (i == 0)
                return "z0";
            if (i == 1)
                return "ln(na)";
            if (i == 2)
                return "ln(r0)";
            if (i == 3)
                return "ln(k)";
            if (i == 4)
                return "tilt";
            else
                return "az_" + (i - 3);
        }

        public String getZDescription(int i) {
            if (i == 0)
                return "Focal shift";
            if (i == 1)
                return "Defocus/focus shift (~NA), ln()";
            if (i == 2)
                return "Best PSF radius, ln()";
            if (i == 3)
                return "Cross shift, ln()";
            if (i == 4)
                return "Tilt (asymmetry)";
            else
                return "Polynomial coefficient for z^" + (i - 3);
        }

        public boolean[] getDefaultMask() {
            boolean[] mask = new boolean[this.modelParams.length * this.modelParams[0].length];
            int index = 0;
            for (int i = 0; i < this.modelParams.length; i++)
                for (int j = 0; j < this.modelParams[0].length; j++) {
                    mask[index++] = (i < 4) && (j < 2);
                }
            return mask;
        }

        public boolean[] maskAllDisabled() {
            boolean[] mask = new boolean[this.modelParams.length * this.modelParams[0].length];
            for (int i = 0; i < mask.length; i++)
                mask[i] = false;
            return mask;
        }

        public boolean[] maskSetDialog(String title, boolean detailed, boolean[] currentMask) {
            GenericDialog gd = new GenericDialog(title);
            boolean[] mask = new boolean[this.modelParams.length * this.modelParams[0].length];
            if (currentMask == null)
                currentMask = getDefaultMask();
            for (int i = 0; i < mask.length; i++) {
                mask[i] = currentMask[i];
            }
            boolean[] zMask = new boolean[this.modelParams.length];
            boolean[] rMask = new boolean[this.modelParams[0].length];
            for (int i = 0; i < zMask.length; i++)
                zMask[i] = false;
            for (int i = 0; i < rMask.length; i++)
                rMask[i] = false;

            if (detailed) {
                int index = 0;
                for (int i = 0; i < this.modelParams.length; i++)
                    for (int j = 0; j < this.modelParams[0].length; j++) {
                        gd.addCheckbox(getZDescription(i) + ", " + getRadialDecription(j), mask[index++]);
                    }
            } else {
                int index = 0;
                for (int i = 0; i < this.modelParams.length; i++)
                    for (int j = 0; j < this.modelParams[0].length; j++) {
                        zMask[i] |= mask[index];
                        rMask[j] |= mask[index];
                        index++;
                    }

                gd.addMessage("===== Focal parameters =====");
                for (int i = 0; i < this.modelParams.length; i++) {
                    gd.addCheckbox(getZDescription(i), zMask[i]);
                }
                gd.addMessage("===== Radial dependence parameters =====");
                for (int j = 0; j < this.modelParams[0].length; j++) {
                    gd.addCheckbox(getRadialDecription(j), rMask[j]);
                }

            }
            gd.enableYesNoCancel("Apply", "Keep"); // default OK (on enter) - "Apply"
            WindowTools.addScrollBars(gd);
            gd.showDialog();
            if (gd.wasCanceled())
                return null;
            if (gd.wasOKed()) { // selected non-default "Apply"
                if (detailed) {
                    int index = 0;
                    for (int i = 0; i < this.modelParams.length; i++)
                        for (int j = 0; j < this.modelParams[0].length; j++) {
                            mask[index++] = gd.getNextBoolean();
                        }
                } else {
                    for (int i = 0; i < zMask.length; i++) {
                        zMask[i] = gd.getNextBoolean();
                    }
                    for (int i = 0; i < rMask.length; i++) {
                        rMask[i] = gd.getNextBoolean();
                    }
                    int index = 0;
                    for (int i = 0; i < this.modelParams.length; i++)
                        for (int j = 0; j < this.modelParams[0].length; j++) {
                            mask[index++] = zMask[i] && rMask[j];
                        }
                }
            }
            return mask;
        }

        public boolean showModifyParameterValues(String title, boolean showDisabled, boolean[] mask,
                boolean isMaster) {
            GenericDialog gd = new GenericDialog(title);
            int index = 0;
            for (int i = 0; i < this.modelParams.length; i++)
                for (int j = 0; j < this.modelParams[0].length; j++) {
                    String name = getZDescription(i) + ", " + getRadialDecription(j);
                    boolean dependent = !isMaster && (j == 0);
                    if (((mask == null) || mask[index]) && !dependent) {
                        gd.addNumericField(name, this.modelParams[i][j], 5, 8, "");
                    } else if (showDisabled) {
                        if (dependent) {
                            gd.addNumericField("(from master) " + name, this.modelParams[i][j], 5, 8, "");
                        } else {
                            gd.addNumericField("(disabled) " + name, this.modelParams[i][j], 5, 8, "");
                        }
                        //                    gd.addMessage(name +": "+this.modelParams[i][j]);
                    }
                    index++;
                }
            gd.enableYesNoCancel("Apply", "Keep"); // default OK (on enter) - "Apply"
            WindowTools.addScrollBars(gd);
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            if (gd.wasOKed()) { // selected default "Apply"
                index = 0;
                for (int i = 0; i < this.modelParams.length; i++)
                    for (int j = 0; j < this.modelParams[0].length; j++) {
                        if ((mask == null) || mask[index] || showDisabled) {
                            this.modelParams[i][j] = gd.getNextNumber();
                        }
                    }
                index++;
            }
            return true;
        }
    }

    public boolean getStrategy(int strategyIndex) {
        FieldStrategies fs = fieldFitting.fieldStrategies;
        if ((strategyIndex >= 0) && (strategyIndex < fs.getNumStrategies())) {
            fs.getFromStrategy(strategyIndex, fieldFitting);
            this.strategyComment = fs.getComment(strategyIndex);
            this.lambda = fs.getInitialLambda(strategyIndex);
            this.lastInSeries = fs.isStopAfterThis(strategyIndex);
            this.keepCorrectionParameters = !fs.isResetCorrection(strategyIndex);
            this.resetVariableParameters = fs.isResetVariables(strategyIndex);
            this.resetCenter = fs.isResetCenter(strategyIndex);
            this.parallelOnly = fs.isParallelOnly(strategyIndex);
            return true;
        } else
            return false;
    }

    public int organizeStrategies(String title) {
        String[] actions = { "<select action>", // 0
                "Restore strategy", // 1
                "Save (replace) strategy", // 2
                "Save (insert before/append) strategy", // 3
                "Remove strategy", // 4
                "Edit strategy (restore-edit-save)" }; // 5
        FieldStrategies fs = fieldFitting.fieldStrategies;
        int selectedActionIndex = 0;
        int selectedStrategyIndex = fs.getNumStrategies();
        boolean editStrategy = false;
        GenericDialog gd = new GenericDialog(title);
        gd.addMessage("Current strategies:");
        String[] indices = new String[fs.getNumStrategies() + 1];
        for (int i = 0; i < fs.getNumStrategies(); i++) {
            indices[i] = i + ": " + fs.getComment(i) + " (" + (fs.isStopAfterThis(i) ? "STOP" : "CONTINUE")
                    + (fs.isResetCenter(i) ? ", RESET CENTER" : "")
                    + (fs.isResetCorrection(i) ? ", RESET CORRECTIONS" : "") + ")";
        }
        indices[fs.getNumStrategies()] = "very end";
        for (int i = 0; i < fs.getNumStrategies(); i++) {
            gd.addMessage(i + ": " + fs.getComment(i) + " (" + (fs.isStopAfterThis(i) ? "STOP" : "CONTINUE")
                    + (fs.isResetCenter(i) ? ", RESET CENTER" : "")
                    + (fs.isResetCorrection(i) ? ", RESET CORRECTIONS" : "") + ")");
        }
        gd.addMessage("=======================");
        gd.addChoice("Action", actions, actions[selectedActionIndex]);
        gd.addChoice("Index", indices, indices[selectedStrategyIndex]);
        gd.addMessage("=======================");
        gd.addCheckbox("Edit strategy", editStrategy);

        gd.enableYesNoCancel("OK", "Done");
        WindowTools.addScrollBars(gd);
        gd.showDialog();
        if (gd.wasCanceled())
            return -1;
        selectedActionIndex = gd.getNextChoiceIndex();
        selectedStrategyIndex = gd.getNextChoiceIndex();
        editStrategy = gd.getNextBoolean();
        if ((selectedActionIndex != 3) && (selectedStrategyIndex >= fs.getNumStrategies())) {
            selectedStrategyIndex = fs.getNumStrategies() - 1; // last
        }
        if (selectedStrategyIndex >= 0) {
            switch (selectedActionIndex) {
            case 1:
                getStrategy(selectedStrategyIndex);
                if (editStrategy) {
                    if (!fieldFitting.maskSetDialog("Setup restored strategy " + selectedStrategyIndex))
                        break;
                }
                break;
            case 3:
                if (editStrategy) {
                    if (!fieldFitting.maskSetDialog("Setup strategy " + selectedStrategyIndex))
                        break;
                }
                if (selectedStrategyIndex >= fs.getNumStrategies())
                    fs.addStrategy();
                else
                    fs.insertStrategy(selectedStrategyIndex);
                // fall through to the next case
            case 2:
                if ((selectedActionIndex != 3) && editStrategy) {
                    if (!fieldFitting.maskSetDialog("Setup strategy " + selectedStrategyIndex))
                        break;
                }
                fs.setStrategy(selectedStrategyIndex, fieldFitting);
                fs.setComment(selectedStrategyIndex, this.strategyComment);
                fs.setInitialLambda(selectedStrategyIndex, this.lambda);
                fs.setStopAfterThis(selectedStrategyIndex, this.lastInSeries);
                fs.setResetCorrection(selectedStrategyIndex, !this.keepCorrectionParameters);
                fs.setResetVariables(selectedStrategyIndex, this.resetVariableParameters);
                fs.setResetCenter(selectedStrategyIndex, this.resetCenter);
                fs.setParallelOnly(selectedStrategyIndex, this.parallelOnly);
                break;
            case 4:
                fs.removeStrategy(selectedStrategyIndex);
                break;
            case 0:
                if (editStrategy) {
                    if (!fieldFitting.maskSetDialog("Setup current strategy"))
                        break;
                }
                break;
            case 5:
                getStrategy(selectedStrategyIndex);
                if (!fieldFitting.maskSetDialog("Edit strategy " + selectedStrategyIndex))
                    break;
                fs.setStrategy(selectedStrategyIndex, fieldFitting);
                fs.setComment(selectedStrategyIndex, this.strategyComment);
                fs.setInitialLambda(selectedStrategyIndex, this.lambda);
                fs.setStopAfterThis(selectedStrategyIndex, this.lastInSeries);
                fs.setResetCorrection(selectedStrategyIndex, !this.keepCorrectionParameters);
                fs.setResetVariables(selectedStrategyIndex, this.resetVariableParameters);
                fs.setResetCenter(selectedStrategyIndex, this.resetCenter);
                fs.setParallelOnly(selectedStrategyIndex, this.parallelOnly);
                break;
            }

        }
        if (gd.wasOKed())
            return 0;
        return 1; // "Done" selected
    }

    public class FieldStrategies {
        ArrayList<FieldSrategy> strategies = new ArrayList<FieldSrategy>();

        public void setProperties(String prefix, Properties properties) {
            properties.setProperty(prefix + "strategies_length", strategies.size() + "");
            for (int i = 0; i < strategies.size(); i++) {
                FieldSrategy strategy = strategies.get(i);
                if (strategy != null)
                    strategy.setProperties(prefix + "strategy_" + i + "_", properties);
            }
        }

        public void getProperties(String prefix, Properties properties) {
            strategies = new ArrayList<FieldSrategy>();
            String s = properties.getProperty(prefix + "strategies_length");
            if (s != null) {
                int len = Integer.parseInt(s);
                for (int i = 0; i < len; i++) {
                    FieldSrategy strategy = new FieldSrategy();
                    // compatibility with old version
                    if (properties.getProperty(prefix + "strategy_" + i + "_" + "centerSelect") != null) {
                        if (debugLevel > 0)
                            System.out.println("Restoring new format strategy #" + i);
                        strategy.getProperties(prefix + "strategy_" + i + "_", properties);
                    } else if (properties.getProperty(prefix + "_" + i + "_" + "centerSelect") != null) {
                        if (debugLevel > 0)
                            System.out.println("Restoring old format strategy #" + i);
                        strategy.getProperties(prefix + "_" + i + "_", properties);
                    } else {
                        if (debugLevel > 0)
                            System.out.println("No info for the field LMA strategy #" + i);
                    }
                    strategies.add(strategy);
                }
            }
        }

        public int getNumStrategies() {
            return strategies.size();
        }

        public void getFromStrategy( // any of the arguments can be null - do not set this array
                int strategyIndex, FieldFitting fieldFitting) {
            strategies.get(strategyIndex).getFromStrategy( // any of the arguments can be null - do not set this array
                    fieldFitting.centerSelect, fieldFitting.channelSelect, fieldFitting.mechanicalSelect,
                    fieldFitting.curvatureSelect, fieldFitting.sampleCorrSelect, fieldFitting.sampleCorrCost,
                    fieldFitting.sampleCorrSigma, fieldFitting.sampleCorrPullZero);
        }

        public void setStrategy( // any of the arguments can be null - do not set this array
                int strategyIndex, FieldFitting fieldFitting) {
            strategies.get(strategyIndex).setStrategy( // any of the arguments can be null - do not set this array
                    fieldFitting.centerSelect, fieldFitting.channelSelect, fieldFitting.mechanicalSelect,
                    fieldFitting.curvatureSelect, fieldFitting.sampleCorrSelect, fieldFitting.sampleCorrCost,
                    fieldFitting.sampleCorrSigma, fieldFitting.sampleCorrPullZero);
        }

        public double getInitialLambda(int strategyIndex) {
            return strategies.get(strategyIndex).getInitialLambda();
        }

        public void setInitialLambda(int strategyIndex, double initialLambda) {
            strategies.get(strategyIndex).setInitialLambda(initialLambda);
        }

        public boolean isStopAfterThis(int strategyIndex) {
            return strategies.get(strategyIndex).isStopAfterThis();
        }

        public boolean isResetCorrection(int strategyIndex) {
            return strategies.get(strategyIndex).isResetCorrection();
        }

        public boolean isResetVariables(int strategyIndex) {
            return strategies.get(strategyIndex).isResetVariables();
        }

        public boolean isResetCenter(int strategyIndex) {
            return strategies.get(strategyIndex).isResetCenter();
        }

        public boolean isParallelOnly(int strategyIndex) {
            return strategies.get(strategyIndex).isParallelOnly();
        }

        public boolean isLast(int strategyIndex) {
            if (strategyIndex < 0)
                return true;
            if (strategyIndex >= (getNumStrategies() - 1))
                return true;// last
            return isStopAfterThis(strategyIndex);
        }

        public void setStopAfterThis(int strategyIndex, boolean stopAfterThis) {
            strategies.get(strategyIndex).setStopAfterThis(stopAfterThis);
        }

        public void setResetCorrection(int strategyIndex, boolean resetCorrection) {
            strategies.get(strategyIndex).setResetCorrection(resetCorrection);
        }

        public void setResetVariables(int strategyIndex, boolean resetVariables) {
            strategies.get(strategyIndex).setResetVariables(resetVariables);
        }

        public void setResetCenter(int strategyIndex, boolean resetCenter) {
            strategies.get(strategyIndex).setResetCenter(resetCenter);
        }

        public void setParallelOnly(int strategyIndex, boolean parallelOnly) {
            strategies.get(strategyIndex).setParallelOnly(parallelOnly);
        }

        public String getComment(int strategyIndex) {
            return strategies.get(strategyIndex).getComment();
        }

        public void setComment(int strategyIndex, String comment) {
            strategies.get(strategyIndex).setComment(comment);
        }

        public void insertStrategy(int strategyIndex) {
            strategies.add(strategyIndex, new FieldSrategy());
        }

        public void removeStrategy(int strategyIndex) {
            strategies.remove(strategyIndex);
        }

        public void addStrategy() {
            strategies.add(new FieldSrategy());
        }

        public void saveStrategies(String path, // full path w/o extension or null
                String directory) {
            String[] patterns = { ".fstg-xml", ".xml" };
            if (path == null) {
                path = CalibrationFileManagement.selectFile(true, // save  
                        "Save Field LMA Strategy selection", // title
                        "Select Field LMA Strategy file", // button
                        new CalibrationFileManagement.MultipleExtensionsFileFilter(patterns,
                                "Strategy files (*.fstg-xml)"), // filter
                        directory); // may be ""
            } else
                path += patterns[0];
            if (path == null)
                return;
            Properties properties = new Properties();
            setProperties("", properties); // no prefix
            OutputStream os;
            try {
                os = new FileOutputStream(path);
            } catch (FileNotFoundException e1) {
                IJ.showMessage("Error", "Failed to open field LMA strategy file for writing: " + path);
                return;
            }
            try {
                properties.storeToXML(os, "last updated " + new java.util.Date(), "UTF8");

            } catch (IOException e) {
                IJ.showMessage("Error", "Failed to write XML configuration file: " + path);
                return;
            }
            try {
                os.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (debugLevel > 0)
                System.out.println("Field LMA strategy parameters are saved to " + path);
        }

        public void loadStrategies(String path, // full path w/o extension or null
                String directory) {
            String[] patterns = { ".fstg-xml", ".xml" };
            if (path == null) {
                path = CalibrationFileManagement.selectFile(false, // save  
                        "Field LMA Strategy selection", // title
                        "Select Field LMA Strategy file", // button
                        new CalibrationFileManagement.MultipleExtensionsFileFilter(patterns,
                                "Strategy files (*.fstg-xml)"), // filter
                        directory); // may be ""
            } else {
                // do not add extension if it already exists
                if ((path.length() < patterns[0].length())
                        || (!path.substring(path.length() - patterns[0].length()).equals(patterns[0]))) {
                    path += patterns[0];
                }
            }
            if (path == null)
                return;
            InputStream is;
            try {
                is = new FileInputStream(path);
            } catch (FileNotFoundException e) {
                IJ.showMessage("Error", "Failed to open field LMA strategy file: " + path);
                return;
            }
            Properties properties = new Properties();
            try {
                properties.loadFromXML(is);

            } catch (IOException e) {
                IJ.showMessage("Error", "Failed to read field LMA strategy file: " + path);
                return;
            }
            try {
                is.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            getProperties("", properties); // no prefix
            if (debugLevel > 0)
                System.out.println("Field LMA strategy parameters are restored from " + path);
        }

        class FieldSrategy {
            private boolean[] centerSelect = null;
            private boolean[] channelSelect = null;
            private boolean[] mechanicalSelect = null;
            private boolean[][] curvatureSelect = new boolean[6][];
            private boolean[][] sampleCorrSelect = new boolean[6][]; // enable individual (per sample coordinates) correction of parameters
            private double[][] sampleCorrCost = new double[6][]; // equivalent cost of one unit of parameter value (in result units, um)
            private double[][] sampleCorrSigma = new double[6][]; // sigma (in mm) for neighbors influence
            private double[][] sampleCorrPullZero = new double[6][]; // 1.0 - only difference from neighbors matters, 0.0 - only difference from 0
            // TODO: add LMA-specific (initial lambda, stop after this
            private double initialLambda = 0.001;
            private boolean stopAfterThis = true;
            private boolean resetCorrection = false;
            private boolean resetVariables = false; // reset all but mechanical parameters of the fixture (resets correction too)
            private boolean resetCenter = false;
            private boolean parallelOnly = true;
            private String strategyComment = "";
            private Properties properties = null;
            private String prefix = null;

            public FieldSrategy() {
                setDefaults();
            }

            public FieldSrategy(String strategyComment, double lambda, boolean lastInSeries,
                    boolean resetCorrection, boolean resetVariables, boolean resetCenter, boolean parallelOnly) {
                this.strategyComment = strategyComment;
                initialLambda = lambda;
                stopAfterThis = lastInSeries;
                this.resetCorrection = resetCorrection;
                this.resetVariables = resetVariables;
                this.resetCenter = resetCenter;
                this.parallelOnly = parallelOnly;
                setDefaults();
            }

            private void setDefaults() {
                boolean[][] booleanNull6 = { null, null, null, null, null, null };
                double[][] doubleNull6 = { null, null, null, null, null, null };
                curvatureSelect = booleanNull6.clone();
                sampleCorrSelect = booleanNull6.clone();
                sampleCorrCost = doubleNull6.clone();
                sampleCorrSigma = doubleNull6.clone();
                sampleCorrPullZero = doubleNull6.clone();
            }

            private String boolToStr(boolean[] ba) {
                if (ba == null)
                    return "";
                String result = "";
                for (boolean b : ba)
                    result += b ? "+" : "-";
                return result;
            }

            private void setPropBool(boolean[] arr, String name) {
                if (arr != null)
                    properties.setProperty(prefix + name, boolToStr(arr));
            }

            private void setPropBool(boolean[][] arr, String name) {
                if (arr != null) {
                    properties.setProperty(prefix + name + "_length", arr.length + "");
                    for (int i = 0; i < arr.length; i++) {
                        setPropBool(arr[i], name + "_" + i);
                    }
                }
            }

            private void setPropDouble(double[] arr, String name) {
                if (arr != null)
                    properties.setProperty(prefix + name, doubleToStr(arr));
            }

            private void setPropDouble(double[][] arr, String name) {
                if (arr != null) {
                    properties.setProperty(prefix + name + "_length", arr.length + "");
                    for (int i = 0; i < arr.length; i++) {
                        setPropDouble(arr[i], name + "_" + i);
                    }
                }
            }

            private boolean[] strToBool(String s) {
                if (s == null)
                    return new boolean[0];
                boolean[] result = new boolean[s.length()];
                for (int i = 0; i < result.length; i++)
                    result[i] = (s.charAt(i) == '+');
                return result;
            }

            private String doubleToStr(double[] da) {
                if (da == null)
                    return "";
                String result = "";
                for (double d : da)
                    result += "," + d;
                if (result.length() > 0)
                    result = result.substring(1);
                return result;
            }

            private double[] strToDouble(String s) {
                if (s == null)
                    return new double[0];
                String[] sa = s.split(",");
                double[] result = new double[sa.length];
                for (int i = 0; i < result.length; i++)
                    result[i] = Double.parseDouble(sa[i]);
                return result;
            }

            private boolean[] getPropBool(boolean[] arr, String name) {
                String s = properties.getProperty(prefix + name);
                if (s != null)
                    return strToBool(s);
                else
                    return arr;
            }

            private boolean[][] getPropBool(boolean[][] arr, String name) {
                if (arr == null) {
                    String s = properties.getProperty(prefix + name + "_length");
                    if (s == null)
                        return null;
                    arr = new boolean[Integer.parseInt(s)][];
                    for (int i = 0; i < arr.length; i++)
                        arr[i] = null;
                }
                for (int i = 0; i < arr.length; i++) {
                    boolean[] a = getPropBool((boolean[]) null, name + "_" + i);
                    if (a != null)
                        arr[i] = a;
                }
                return arr;
            }

            private double[] getPropDouble(double[] arr, String name) {
                String s = properties.getProperty(prefix + name);
                if (s != null)
                    return strToDouble(s);
                else
                    return arr;
            }

            private double[][] getPropDouble(double[][] arr, String name) {
                if (arr == null) {
                    String s = properties.getProperty(prefix + name + "_length");
                    if (s == null)
                        return null;
                    arr = new double[Integer.parseInt(s)][];
                    for (int i = 0; i < arr.length; i++)
                        arr[i] = null;
                }
                for (int i = 0; i < arr.length; i++) {
                    double[] a = getPropDouble((double[]) null, name + "_" + i);
                    if (a != null)
                        arr[i] = a;
                }
                return arr;
            }

            public String getComment() {
                return strategyComment;
            }

            public void setComment(String comment) {
                strategyComment = comment;
            }

            public double getInitialLambda() {
                return initialLambda;
            }

            public void setInitialLambda(double initialLambda) {
                this.initialLambda = initialLambda;
            }

            public boolean isStopAfterThis() {
                return stopAfterThis;
            }

            public void setStopAfterThis(boolean stopAfterThis) {
                this.stopAfterThis = stopAfterThis;
            }

            public boolean isResetCorrection() {
                return resetCorrection;
            }

            public void setResetCorrection(boolean resetCorrection) {
                this.resetCorrection = resetCorrection;
            }

            public boolean isResetVariables() {
                return resetVariables;
            }

            public void setResetVariables(boolean resetVariables) {
                this.resetVariables = resetVariables;
            }

            public boolean isResetCenter() {
                return resetCenter;
            }

            public void setResetCenter(boolean resetCenter) {
                this.resetCenter = resetCenter;
            }

            public boolean isParallelOnly() {
                return parallelOnly;
            }

            public void setParallelOnly(boolean parallelOnly) {
                this.parallelOnly = parallelOnly;
            }

            public void setStrategy( // any of the arguments can be null - do not set this array
                    boolean[] centerSelect, boolean[] channelSelect, boolean[] mechanicalSelect,
                    boolean[][] curvatureSelect, boolean[][] sampleCorrSelect, double[][] sampleCorrCost,
                    double[][] sampleCorrSigma, double[][] sampleCorrPullZero) {
                if (centerSelect != null)
                    this.centerSelect = centerSelect.clone();
                if (channelSelect != null)
                    this.channelSelect = channelSelect.clone();
                if (mechanicalSelect != null)
                    this.mechanicalSelect = mechanicalSelect.clone();
                if (curvatureSelect != null) {
                    this.curvatureSelect = new boolean[curvatureSelect.length][];
                    for (int i = 0; i < curvatureSelect.length; i++)
                        this.curvatureSelect[i] = curvatureSelect[i].clone();
                }
                if (sampleCorrSelect != null) {
                    this.sampleCorrSelect = new boolean[sampleCorrSelect.length][];
                    for (int i = 0; i < sampleCorrSelect.length; i++)
                        this.sampleCorrSelect[i] = sampleCorrSelect[i].clone();
                }
                if (sampleCorrCost != null) {
                    this.sampleCorrCost = new double[sampleCorrCost.length][];
                    for (int i = 0; i < sampleCorrCost.length; i++)
                        this.sampleCorrCost[i] = sampleCorrCost[i].clone();
                }
                if (sampleCorrSigma != null) {
                    this.sampleCorrSigma = new double[sampleCorrSigma.length][];
                    for (int i = 0; i < sampleCorrSigma.length; i++)
                        this.sampleCorrSigma[i] = sampleCorrSigma[i].clone();
                }
                if (sampleCorrPullZero != null) {
                    this.sampleCorrPullZero = new double[sampleCorrPullZero.length][];
                    for (int i = 0; i < sampleCorrPullZero.length; i++)
                        this.sampleCorrPullZero[i] = sampleCorrPullZero[i].clone();
                }
            }

            public void getFromStrategy( // any of the arguments can be null - do not set this array
                    boolean[] centerSelect, boolean[] channelSelect, boolean[] mechanicalSelect,
                    boolean[][] curvatureSelect, boolean[][] sampleCorrSelect, double[][] sampleCorrCost,
                    double[][] sampleCorrSigma, double[][] sampleCorrPullZero) {
                if (centerSelect != null)
                    for (int i = 0; i < centerSelect.length; i++)
                        centerSelect[i] = this.centerSelect[i];
                if (channelSelect != null)
                    for (int i = 0; i < channelSelect.length; i++)
                        channelSelect[i] = this.channelSelect[i];
                if (mechanicalSelect != null)
                    for (int i = 0; i < mechanicalSelect.length; i++)
                        mechanicalSelect[i] = this.mechanicalSelect[i];
                if (curvatureSelect != null) {
                    for (int i = 0; i < curvatureSelect.length; i++)
                        curvatureSelect[i] = this.curvatureSelect[i].clone();
                }
                if (sampleCorrSelect != null) {
                    for (int i = 0; i < sampleCorrSelect.length; i++)
                        sampleCorrSelect[i] = this.sampleCorrSelect[i].clone();
                }
                if (sampleCorrCost != null) {
                    for (int i = 0; i < sampleCorrCost.length; i++)
                        sampleCorrCost[i] = this.sampleCorrCost[i].clone();
                }
                if (sampleCorrSigma != null) {
                    for (int i = 0; i < sampleCorrSigma.length; i++)
                        sampleCorrSigma[i] = this.sampleCorrSigma[i].clone();
                }
                if (sampleCorrPullZero != null) {
                    for (int i = 0; i < sampleCorrPullZero.length; i++)
                        sampleCorrPullZero[i] = this.sampleCorrPullZero[i].clone();
                }
            }

            public void setProperties(String prefix, Properties properties) {
                this.prefix = prefix;
                this.properties = properties;
                setPropBool(centerSelect, "centerSelect");
                setPropBool(channelSelect, "channelSelect");
                setPropBool(mechanicalSelect, "mechanicalSelect");
                setPropBool(curvatureSelect, "curvatureSelect");
                setPropBool(sampleCorrSelect, "sampleCorrSelect");
                setPropDouble(sampleCorrCost, "sampleCorrCost");
                setPropDouble(sampleCorrSigma, "sampleCorrSigma");
                setPropDouble(sampleCorrPullZero, "sampleCorrPullZero");
                properties.setProperty(prefix + "initialLambda", getInitialLambda() + "");
                properties.setProperty(prefix + "stopAfterThis", isStopAfterThis() + "");
                properties.setProperty(prefix + "resetCorrection", isResetCorrection() + "");
                properties.setProperty(prefix + "resetVariables", isResetVariables() + "");
                properties.setProperty(prefix + "resetCenter", isResetCenter() + "");
                properties.setProperty(prefix + "parallelOnly", isParallelOnly() + "");
                properties.setProperty(prefix + "strategyComment", "<![CDATA[" + strategyComment + "]]>");
            }

            public void getProperties(String prefix, Properties properties) {
                this.prefix = prefix;
                this.properties = properties;
                centerSelect = getPropBool(centerSelect, "centerSelect");
                channelSelect = getPropBool(channelSelect, "channelSelect");
                mechanicalSelect = getPropBool(mechanicalSelect, "mechanicalSelect");
                curvatureSelect = getPropBool(curvatureSelect, "curvatureSelect");
                sampleCorrSelect = getPropBool(sampleCorrSelect, "sampleCorrSelect");
                sampleCorrCost = getPropDouble(sampleCorrCost, "sampleCorrCost");
                sampleCorrSigma = getPropDouble(sampleCorrSigma, "sampleCorrSigma");
                sampleCorrPullZero = getPropDouble(sampleCorrPullZero, "sampleCorrPullZero");
                String s = properties.getProperty(prefix + "initialLambda");
                if (s != null)
                    initialLambda = Double.parseDouble(s);
                s = properties.getProperty(prefix + "stopAfterThis");
                if (s != null)
                    stopAfterThis = Boolean.parseBoolean(s);
                s = properties.getProperty(prefix + "resetVariables");
                if (s != null)
                    resetVariables = Boolean.parseBoolean(s);
                s = properties.getProperty(prefix + "resetCenter");
                if (s != null)
                    resetCenter = Boolean.parseBoolean(s);
                s = properties.getProperty(prefix + "parallelOnly");
                if (s != null)
                    parallelOnly = Boolean.parseBoolean(s);
                s = properties.getProperty(prefix + "strategyComment");
                if (s != null) {
                    strategyComment = s;
                    if ((strategyComment.length() > 10) && strategyComment.substring(0, 9).equals("<![CDATA[")) {
                        strategyComment = strategyComment.substring(9, strategyComment.length() - 3);
                    }
                }
            }
        }
    }
    //      qualBOptimizeMode; // 0 - none, +1 - optimize Zc, +2 - optimize Tx, +4 - optimize Ty

    public double[] testQualB(boolean interactive) {
        double[] targetZTxTy = { 0.0, 0.0, 0.0 };
        boolean debugScan = false;
        if (interactive) {
            GenericDialog gd = new GenericDialog("Calculate optimal focus/tilt");
            gd.addNumericField("Initial focus (relative to best composirte)", targetZTxTy[0], 2, 5, "um");
            gd.addNumericField("Initial tiltX", targetZTxTy[1], 2, 5, "um/mm");
            gd.addNumericField("Initial tiltY", targetZTxTy[2], 2, 5, "um/mm");
            //          gd.addCheckbox("Optimize focal distance",selectQualBPars[0]);
            //          gd.addCheckbox("Optimize tiltX",         selectQualBPars[1]);
            //          gd.addCheckbox("Optimize tiltY",         selectQualBPars[2]);
            gd.addCheckbox("Optimize focal distance", (this.qualBOptimizeMode & 1) != 0);
            gd.addCheckbox("Optimize tiltX", (this.qualBOptimizeMode & 2) != 0);
            gd.addCheckbox("Optimize tiltY", (this.qualBOptimizeMode & 4) != 0);

            gd.addNumericField("Relative (to green) weight of red channels", 100 * this.k_red, 1, 7, "%");
            gd.addNumericField("Relative (to green) weight of blue channels", 100 * this.k_blue, 1, 7, "%");
            gd.addNumericField("weight of sagittal channels", this.k_sag, 3, 7, "");
            gd.addNumericField("weight of tangential channels", this.k_tan, 3, 7, "");
            gd.addCheckbox("Remove channels with no data", this.qualBRemoveBadSamples);
            gd.addNumericField("Relative weight of peripheral areas", 100 * this.k_qualBFractionPeripheral, 1, 7,
                    "%");
            gd.addNumericField(
                    "Reduce weight of peripheral areas outside of this fraction, linear, large sensor dimension",
                    100 * this.k_qualBFractionHor, 1, 5, "%");
            gd.addNumericField(
                    "Reduce weight of peripheral areas outside of this fraction, linear, small sensor dimension",
                    100 * this.k_qualBFractionVert, 1, 5, "%");
            gd.addCheckbox("Debug scan", debugScan);
            WindowTools.addScrollBars(gd);
            gd.showDialog();
            if (gd.wasCanceled())
                return null;
            targetZTxTy[0] = gd.getNextNumber();
            targetZTxTy[1] = gd.getNextNumber();
            targetZTxTy[2] = gd.getNextNumber();
            //          selectQualBPars[0]= gd.getNextBoolean();
            //          selectQualBPars[1]= gd.getNextBoolean();
            //          selectQualBPars[2]= gd.getNextBoolean();
            //          this.qualBOptimizeMode=(selectQualBPars[0]?1:0)+(selectQualBPars[1]?2:0)+(selectQualBPars[2]?4:0);
            this.qualBOptimizeMode = 0;
            this.qualBOptimizeMode += gd.getNextBoolean() ? 1 : 0;
            this.qualBOptimizeMode += gd.getNextBoolean() ? 2 : 0;
            this.qualBOptimizeMode += gd.getNextBoolean() ? 4 : 0;
            this.k_red = 0.01 * gd.getNextNumber();
            this.k_blue = 0.01 * gd.getNextNumber();
            this.k_sag = gd.getNextNumber();
            this.k_tan = gd.getNextNumber();
            this.qualBRemoveBadSamples = gd.getNextBoolean();
            this.k_qualBFractionPeripheral = 0.01 * gd.getNextNumber();
            this.k_qualBFractionHor = 0.01 * gd.getNextNumber();
            this.k_qualBFractionVert = 0.01 * gd.getNextNumber();
            debugScan = gd.getNextBoolean();
        }
        boolean[] selectQualBPars = { ((this.qualBOptimizeMode & 1) != 0), ((this.qualBOptimizeMode & 2) != 0),
                ((this.qualBOptimizeMode & 4) != 0) };

        double[] best_qb_corr = fieldFitting.getBestQualB(this.k_red, this.k_blue, true);
        double[] zTxTy = { targetZTxTy[0] + best_qb_corr[0], targetZTxTy[1], targetZTxTy[2] };
        if (!selectQualBPars[0] && !selectQualBPars[1] && !selectQualBPars[2]) {
            this.qualBOptimizationResults = zTxTy;
            return zTxTy; // no LMA, return zc for optimal  qualB, zero tilts
        }

        fieldFitting.mechanicalFocusingModel.setAdjustMode(true, null);
        qualBOptimize.initCorrPars();

        double[][] sampleCoord = flattenSampleCoord();
        double[] sampleWeights = new double[sampleCoord.length];
        for (int i = 0; i < sampleCoord.length; i++) {
            double fractHor = Math
                    .abs((2 * sampleCoord[i][0] - (this.sensorWidth - 1.0)) / (this.sensorWidth - 1.0));
            double fractVert = Math
                    .abs((2 * sampleCoord[i][1] - (this.sensorHeight - 1.0)) / (this.sensorHeight - 1.0));
            if (interactive && (debugLevel > 2))
                System.out.println(i + ": " + sampleCoord[i][0] + ":" + sampleCoord[i][1] + " fractHor=" + fractHor
                        + " fractVert=" + fractVert);
            sampleWeights[i] = 1.0;
            if (fractHor > this.k_qualBFractionHor) {
                double w = (this.k_qualBFractionPeripheral * (fractHor - this.k_qualBFractionHor)
                        + 1.0 * (1.0 - fractHor)) / (1.0 - this.k_qualBFractionHor);
                if (w < sampleWeights[i])
                    sampleWeights[i] = w;
                if (interactive && (debugLevel > 2))
                    System.out.println("fractHor>this.k_qualBFractionHor, w=" + w);
            }
            if (fractVert > this.k_qualBFractionVert) {
                double w = (this.k_qualBFractionPeripheral * (fractVert - this.k_qualBFractionVert)
                        + 1.0 * (1.0 - fractVert)) / (1.0 - this.k_qualBFractionVert);
                if (w < sampleWeights[i])
                    sampleWeights[i] = w;
                if (interactive && (debugLevel > 2))
                    System.out.println("fractVert>this.k_qualBFractionVert, w=" + w);
            }
        }
        if (interactive && (debugLevel > 1)) {
            for (int i = 0; i < sampleCoord.length; i++) {
                System.out.print(" " + IJ.d2s(sampleWeights[i], 3));
                if (((i + 1) % this.sampleCoord[0].length) == 0)
                    System.out.println();
            }
        }

        boolean[][] goodSamples = null;
        if (this.qualBRemoveBadSamples) {
            goodSamples = new boolean[getNumChannels()][getNumSamples()];
            for (int i = 0; i < goodSamples.length; i++)
                for (int j = 0; j < goodSamples[0].length; j++)
                    goodSamples[i][j] = false;
            for (int n = 0; n < dataVector.length; n++)
                if (dataWeights[n] > 0.0) {
                    goodSamples[dataVector[n].channel][dataVector[n].sampleIndex] = true;
                }
        }
        qualBOptimize.initWeights(flattenSampleCoord(), //double [][] sampleCoord,
                this.k_red, this.k_blue, this.k_sag, this.k_tan,
                this.qualBRemoveBadSamples ? this.goodCalibratedSamples : null, //goodSamples,
                sampleWeights);
        qualBOptimize.initQPars(zTxTy, selectQualBPars);
        // Set all 3 parameter values, even if some are not selected       
        fieldFitting.mechanicalFocusingModel.setZTxTy(zTxTy);
        if (debugScan) {
            qualBOptimize.runDebugScan(-20.0, 20.0, 1.0);
        }
        boolean OK = qualBOptimize.qLevenbergMarquardt(interactive, // boolean openDialog,
                debugLevel + (interactive ? 1 : 0));
        if (OK) {
            zTxTy = fieldFitting.mechanicalFocusingModel.getZTxTy();
            System.out.println("qualBOptimize returned:\n" + "z0=" + IJ.d2s((zTxTy[0]), 3) + "um ("
                    + IJ.d2s((zTxTy[0] - best_qb_corr[0]), 3) + "um from best_qb_corr)\n" + "tX="
                    + IJ.d2s(zTxTy[1], 3) + "um/mm\n" + "tY=" + IJ.d2s(zTxTy[2], 3) + "um/mm");
            double[] zTxTyCorr = fieldFitting.mechanicalFocusingModel.getZTxTyCorr(currentPX0, // optical center X
                    currentPY0); // optical center Y
            for (int i = 0; i < zTxTyCorr.length; i++) {
                zTxTy[i] += zTxTyCorr[i];
            }
            System.out.println(
                    "qualBOptimize corrected to Zc, tx axial, ty axial:\n" + "zc=" + IJ.d2s((zTxTy[0]), 3) + "um ("
                            + IJ.d2s((zTxTy[0] - best_qb_corr[0]), 3) + "um from best_qb_corr)\n" + "tX axial="
                            + IJ.d2s(zTxTy[1], 3) + "um/mm\n" + "tY axial=" + IJ.d2s(zTxTy[2], 3) + "um/mm");

        } else {
            System.out.println("qualBOptimize LMA failed");
        }
        //       zTxTy[0]-=best_qb_corr[0]; - absolute, no need to calculate best_qb_corr;
        this.qualBOptimizationResults = zTxTy.clone();
        return zTxTy;
    }

    public class QualBOptimize {
        public double[] qCurrentVector = null; // vector of 1..3 elements - parameters used in fitting (of Zc, Tx, Ty)
        public int[] qIndices = null; // parameter index for each of qCurrentVector elements (0 - Zc, 1 - Tx, 2 - Ty)
        public double[] qSavedVector;
        public double[] qWeights = null;
        double[][] sampleCoord;
        double[][] qJacobian = null; // rows - parameters, columns - samples
        public double[][][] corrPars = null;
        int debugLevel = 0;
        //
        private double qLambdaStepUp = 8.0; // multiply lambda by this if result is worse
        private double qLambdaStepDown = 0.5; // multiply lambda by this if result is better
        public double qInitialLambda = 1.0; //0.001;
        public double qThresholdFinish = 0.001;
        public int qNumIterations = 100; // maximal number of iterations
        public double qMaxLambda = 100.0; // max lambda to fail

        public double qLambda;
        int iterationStepNumber = 0;
        double currentQualB = -1.0;
        double nextQualB = -1.0;
        double firstQualB = -1.0;
        double[] qCurrentfX = null;
        double[] qNextfX = null;
        public double[] qNextVector = null;
        private LMAArrays qLMAArrays = null;
        private LMAArrays savedQLMAArrays = null;
        private double[] qLastImprovements = { -1.0, -1.0 }; // {last improvement, previous improvement}. If both >0 and < thresholdFinish - done

        private boolean showQParams = true;
        private boolean showDisabledQParams = true;
        private boolean qSaveSeries = false; // just for the dialog
        private boolean qStopEachStep = false; // stop after each series
        private boolean qStopOnFailure = true; // open dialog when fitting series failed

        private boolean debugQDerivatives = false;
        private int debugQPoint = -1;
        private int debugQParameter = -1;
        public long qStartTime = 0;

        public void initCorrPars() { // double [][][] corrPars){ // use getCorrPar() to provide corrPars
            this.corrPars = fieldFitting.getCorrPar();
        }

        /**
         * Generate weighs array for samples
         * @param sampleCoord
         * @param kr weight of red components (relative to green)
         * @param kb weight of blue components (relative to green)
         * @param ks weight of sagittal components
         * @param kt weight of tangential components
         * @param goodSamples array [channel][sample] of all samples taken into account, or null to use all
         */
        public void initWeights(double[][] sampleCoord, double kr, double kb, double ks, double kt,
                boolean[][] goodSamples, double[] sampleWeights) {
            int numSamples = sampleCoord.length;
            this.sampleCoord = new double[numSamples][];
            for (int i = 0; i < numSamples; i++)
                this.sampleCoord[i] = sampleCoord[i].clone();
            int numChannels = 3 * 2;
            double[] colorWeights = { kr, 1.0, kb };
            double[] dirWeights = { ks, kt };
            qWeights = new double[numChannels * sampleCoord.length];
            double sumWeights = 0.0;
            for (int c = 0; c < colorWeights.length; c++)
                for (int d = 0; d < dirWeights.length; d++) {
                    int chn = c * dirWeights.length + d;
                    for (int sample = 0; sample < numSamples; sample++) {
                        double w = 0.0;
                        if ((goodSamples == null) || ((goodSamples[chn] != null) && goodSamples[chn][sample])) {
                            w = colorWeights[c] * dirWeights[d];
                        }
                        if (sampleWeights != null) {
                            w *= sampleWeights[sample];
                        }
                        qWeights[chn + sample * numChannels] = w;
                        sumWeights += w;
                    }
                }
            if (sumWeights > 0.0) {
                for (int i = 0; i < qWeights.length; i++)
                    qWeights[i] /= sumWeights;
            }
        }

        /**
         * Init parameter vectr (subset of Zc, Tx, Ty) using provided mask and values
         * @param vector parameter vector {zc,tx,ty} or null to use current values
         * @param selectedPars boolean array of selected parameters {select_zc, select_tx, select_ty} or null for all
         * @return vector of 1..3 elements of selected parameter values
         */
        public double[] initQPars(double[] vector, boolean[] selectedPars) {
            if (vector == null)
                vector = fieldFitting.mechanicalFocusingModel.getZTxTy();
            int numPars = 0;
            for (int i = 0; i < vector.length; i++)
                if ((selectedPars == null) || selectedPars[i])
                    numPars++;
            qIndices = new int[numPars];
            qCurrentVector = new double[numPars];
            int index = 0;
            for (int i = 0; i < vector.length; i++)
                if ((selectedPars == null) || selectedPars[i]) {
                    qIndices[index] = i;
                    qCurrentVector[index++] = vector[i];
                }
            return qCurrentVector;
        }

        public double[] initQPars(double zc, double tx, double ty) {
            double[] vector = { zc, tx, ty };
            boolean[] selectedPars = { true, true, true };
            for (int i = 0; i < vector.length; i++)
                if (Double.isNaN(vector[i]))
                    selectedPars[i] = false;
            return initQPars(vector, selectedPars);
        }

        public void commitQPars(double[] vector) { // should not modify qCurrentVector
            //          if (vector!=null) qCurrentVector=vector.clone();
            if (vector == null)
                vector = qCurrentVector.clone();
            double[] zTxTy = fieldFitting.mechanicalFocusingModel.getZTxTy(); // current values
            for (int i = 0; i < qIndices.length; i++) {
                zTxTy[qIndices[i]] = vector[i]; // overwrite selected

            }
            fieldFitting.mechanicalFocusingModel.setZTxTy(zTxTy);
        }

        public void saveQPars() { // may need to call  
            qSavedVector = qCurrentVector.clone();
        }

        public void restoreQPars() { // may need to call  
            qCurrentVector = qSavedVector.clone();
            commitQPars(null);
        }

        // fX here - FWHM^2, then instaed of rms will be weighted average qualB
        public double[] createFXandJacobian(double[] vector, boolean createJacobian) {
            commitQPars(vector);
            return createFXandJacobian(createJacobian);

        }

        public double[] createFXandJacobian(boolean createJacobian) {
            int numSamples = sampleCoord.length;
            int numChannels = qWeights.length / numSamples;
            double[] fX = new double[qWeights.length];
            if (createJacobian)
                this.qJacobian = new double[qIndices.length][qWeights.length];
            for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) {
                double[] zdZ = fieldFitting.mechanicalFocusingModel.getZdZ3(sampleCoord[sampleIndex][0], //double px,
                        sampleCoord[sampleIndex][1], //double py,
                        createJacobian); //boolean calDerivs)
                double[][] deriv_curv = createJacobian ? (new double[numChannels][]) : null;

                double[] chnValues = new double[numChannels];
                for (int chn = 0; chn < numChannels; chn++) {
                    if (createJacobian) {
                        deriv_curv[chn] = new double[fieldFitting.curvatureModel[chn].getSize()]; // nr*nz+1
                    }
                    chnValues[chn] = fieldFitting.curvatureModel[chn].getFdF(
                            ((corrPars == null) ? null
                                    : ((corrPars[sampleIndex] == null) ? null : corrPars[sampleIndex][chn])), // (corrPars==null)?null:corrPars[c], // param_corr
                            sampleCoord[sampleIndex][0], //px,
                            sampleCoord[sampleIndex][1], //py,
                            zdZ[0], // mot_z,
                            createJacobian ? deriv_curv[chn] : null);
                    fX[chn + sampleIndex * numChannels] = chnValues[chn] * chnValues[chn]; // squared FWHM value for qualB
                    if (createJacobian) {
                        for (int i = 0; i < qIndices.length; i++) {
                            // "-" because mot Z is opposite to z0,
                            // 2*chnValues[chn] - because fX= chnValues ^ 2 
                            this.qJacobian[i][chn + sampleIndex * numChannels] = -2 * chnValues[chn]
                                    * zdZ[qIndices[i] + 1] * deriv_curv[chn][0];
                        }
                    }
                }
            }
            return fX;
        }

        public double getQualB() {
            return getQualB(createFXandJacobian(false));
        }

        public double getQualB(double[] fX) {
            double q = 0;
            for (int i = 0; i < fX.length; i++) {
                q += qWeights[i] * fX[i] * fX[i];
            }
            return Math.sqrt(Math.sqrt(q));
        }

        public LMAArrays calculateJacobianArrays(double[] fX) {
            // calculate JtJ
            // here Y is zero vector, so just usxe -fX[i] instead of diff[i]
            //          double [] diff=calcYminusFx(fX);
            int numPars = this.qJacobian.length; // number of parameters to be adjusted
            int length = fX.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;
                    for (int k = 0; k < length; k++)
                        JtByJmod[i][j] += this.qJacobian[i][k] * this.qJacobian[j][k] * this.qWeights[k];
                }
            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
            }
            for (int i = 0; i < numPars; i++) {
                JtByDiff[i] = 0.0;
                //             for (int k=0;k<length;k++) JtByDiff[i]+=this.jacobian[i][k]*diff[k]*this.qWeights[k];
                for (int k = 0; k < length; k++)
                    JtByDiff[i] -= this.qJacobian[i][k] * fX[k] * this.qWeights[k];// here Y is zero vector, so just usxe -fX[i] instead of diff[i]

            }
            LMAArrays lMAArrays = new LMAArrays();
            lMAArrays.jTByJ = JtByJmod;
            lMAArrays.jTByDiff = JtByDiff;
            return lMAArrays;
        }

        public double[] solveLMA(LMAArrays lMAArrays, double lambda, int debugLevel) {
            this.debugLevel = debugLevel;
            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);
            if (debugLevel > 2) {
                System.out.println("qLMA 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("qLMA 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("qLMA Row of all zeros: " + n);
                    }
                }
                //                M.print(10, 5);
                return null;
            }
            Matrix Ma = M.solve(Mb); // singular
            return Ma.getColumnPackedCopy();
        }

        public void qStepLevenbergMarquardtAction(int debugLevel) {//
            this.iterationStepNumber++;
            // apply/revert,modify lambda 
            String msg = "currentQualB=" + this.currentQualB + ", nextQualB=" + this.nextQualB + ", delta="
                    + (this.currentQualB - this.nextQualB) + ", lambda=" + this.qLambda;
            if (debugLevel > 1)
                System.out.println("stepLevenbergMarquardtAction() " + msg);
            //           if (this.updateStatus) IJ.showStatus(msg);
            if (this.nextQualB < this.currentQualB) { //improved
                this.qLambda *= this.qLambdaStepDown;
                this.currentQualB = this.nextQualB;
                this.qCurrentfX = this.qNextfX;
                this.qCurrentVector = this.qNextVector;
            } else {
                this.qLambda *= this.qLambdaStepUp;
                this.qLMAArrays = this.savedQLMAArrays; // restore Jt*J and Jt*diff
                //              restoreQPars();
            }
        }

        /**
         * Calculates next parameters vector, holds some arrays
         * @param numSeries
         * @return array of two booleans: { improved, finished}
         */
        public boolean[] stepQLevenbergMarquardtFirst(int debugLevel) {
            double[] deltas = null;
            if (this.qCurrentVector == null) {
                this.qCurrentVector = this.qSavedVector.clone();
                this.currentQualB = -1;
                this.qCurrentfX = null; // invalidate
                this.qJacobian = null; // invalidate
                this.qLMAArrays = null;
                this.qLastImprovements[0] = -1.0;
                this.qLastImprovements[1] = -1.0;
            }
            this.debugLevel = debugLevel;
            // calculate this.currentfX, this.jacobian if needed
            if (debugLevel > 2) {
                System.out.println("this.qCurrentVector");
                for (int i = 0; i < this.qCurrentVector.length; i++) {
                    System.out.println(i + ": " + this.qCurrentVector[i]);
                }
            }
            //     if ((this.currentfX==null)|| ((this.jacobian==null) && !this.threadedLMA )) {
            if ((this.qCurrentfX == null) || (this.qLMAArrays == null)) {
                String msg = "qLMA: initial Jacobian matrix calculation. Points:" + this.qWeights.length
                        + " Parameters:" + this.qCurrentVector.length;
                if (debugLevel > 1)
                    System.out.println(msg);
                if (updateStatus)
                    IJ.showStatus(msg);
                this.qCurrentfX = createFXandJacobian(this.qCurrentVector, true); // is it always true here (this.jacobian==null)
                this.qLMAArrays = calculateJacobianArrays(this.qCurrentfX);
                this.currentQualB = getQualB(this.qCurrentfX);
                msg = "qLMA: initial qualB=" + IJ.d2s(this.currentQualB, 8) + ". Calculating next Jacobian. Points:"
                        + this.qWeights.length + " Parameters:" + this.qCurrentVector.length;
                if (debugLevel > 1)
                    System.out.println(msg);
                if (updateStatus)
                    IJ.showStatus(msg);
            }
            if (this.firstQualB < 0) {
                this.firstQualB = this.currentQualB;
            }
            deltas = solveLMA(this.qLMAArrays, this.qLambda, debugLevel);

            boolean matrixNonSingular = true;
            if (deltas == null) {
                deltas = new double[this.qCurrentVector.length];
                for (int i = 0; i < deltas.length; i++)
                    deltas[i] = 0.0;
                matrixNonSingular = false;
            }
            if (debugLevel > 1) {
                System.out.println("deltas");
                for (int i = 0; i < deltas.length; i++) {
                    System.out.println(i + ": " + deltas[i]);
                }
            }
            // apply deltas     
            this.qNextVector = this.qCurrentVector.clone();
            for (int i = 0; i < this.qNextVector.length; i++)
                this.qNextVector[i] += deltas[i];
            // another option - do not calculate J now, just fX. and late - calculate both if it was improvement     
            //         save current Jacobian

            if (debugLevel > 1) {
                System.out.println("qLMA: this.qNextVector");
                for (int i = 0; i < this.qNextVector.length; i++) {
                    System.out.println(i + ": " + this.qNextVector[i]);
                }
            }
            // this.savedJacobian=this.jacobian;
            this.savedQLMAArrays = this.qLMAArrays.clone();
            this.qJacobian = null; // not needed, just to catch bugs
            this.qNextfX = createFXandJacobian(this.qNextVector, true);
            this.qLMAArrays = calculateJacobianArrays(this.qNextfX);
            this.nextQualB = getQualB(this.qNextfX);
            this.qLastImprovements[1] = this.qLastImprovements[0];
            this.qLastImprovements[0] = this.currentQualB - this.nextQualB;
            String msg = "currentQualB=" + this.currentQualB + ", nextQualB=" + this.nextQualB + ", delta="
                    + (this.currentQualB - this.nextQualB);
            if (debugLevel > 1)
                System.out.println("qLMA: stepLMA " + msg);
            if (updateStatus)
                IJ.showStatus(msg);
            boolean[] status = { matrixNonSingular && (this.nextQualB <= this.currentQualB), !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.nextQualB < (this.currentQualB + this.currentQualB * this.qThresholdFinish * 0.01)) {
                    this.nextQualB = this.currentQualB;
                    status[0] = true;
                    status[1] = true;
                    this.qLastImprovements[0] = 0.0;
                    if (debugLevel > 1) {
                        System.out.println(
                                "qLMA: New RMS error is larger than the old one, but the difference is too small to be trusted ");
                        System.out.println("stepQLMA this.currentQualB=" + this.currentQualB + ", this.nextQualB="
                                + this.nextQualB + ", delta=" + (this.currentQualB - this.nextQualB));
                    }

                }
            }
            if (status[0] && matrixNonSingular) { //improved
                status[1] = (this.iterationStepNumber > this.qNumIterations) || ( // done
                (this.qLastImprovements[0] >= 0.0)
                        && (this.qLastImprovements[0] < this.qThresholdFinish * this.currentQualB)
                        && (this.qLastImprovements[1] >= 0.0)
                        && (this.qLastImprovements[1] < this.qThresholdFinish * this.currentQualB));
            } else if (matrixNonSingular) {
                //             this.jacobian=this.savedJacobian;// restore saved Jacobian
                this.qLMAArrays = this.savedQLMAArrays; // restore Jt*J and Jt*diff

                status[1] = (this.iterationStepNumber > this.qNumIterations) || // failed
                        ((this.qLambda * this.qLambdaStepUp) > this.qMaxLambda);
            }
            ///this.currentRMS     
            //TODO: add other failures leading to result failure?     
            if (debugLevel > 2) {
                System.out.println(
                        "qLMA: stepLevenbergMarquardtFirst(" + debugLevel + ")=>" + status[0] + "," + status[1]);
            }
            return status;
        }

        public boolean dialogQLMAStep(boolean[] state) {
            String[] states = { "Worse, increase lambda", "Better, decrease lambda", "Failed to fit",
                    "Fitting Successful" };
            String[] descriptions = fieldFitting.mechanicalFocusingModel.getZTxTyDescriptions();
            double[] zTxTy = fieldFitting.mechanicalFocusingModel.getZTxTy(); // current values
            boolean[] paramSelect = { false, false, false };
            for (int i : qIndices)
                paramSelect[i] = true;
            int iState = (state[0] ? 1 : 0) + (state[1] ? 2 : 0);
            GenericDialog gd = new GenericDialog("(qualB) Levenberg-Marquardt algorithm step");
            gd.addMessage("Current state=" + states[iState]);
            gd.addMessage("Iteration step=" + this.iterationStepNumber);
            gd.addMessage("Initial qualB=" + IJ.d2s(this.firstQualB, 6) + ", Current qualB="
                    + IJ.d2s(this.currentQualB, 6) + ", new qualB=" + IJ.d2s(this.nextQualB, 6));
            if (this.showQParams) {
                gd.addMessage("==== Current parameter values ===");
                for (int i = 0; i < zTxTy.length; i++)
                    if (this.showDisabledQParams || paramSelect[i]) {
                        gd.addMessage(
                                (paramSelect[i] ? "(+) " : "(-) ") + descriptions[i] + ": " + IJ.d2s(zTxTy[i], 6));
                    }
                gd.addMessage("");
                gd.addMessage("Lambda=" + this.qLambda);
            }
            gd.addNumericField("Lambda ", this.qLambda, 5);
            gd.addNumericField("Multiply lambda on success", this.qLambdaStepDown, 10);
            gd.addNumericField("Threshold RMS to exit LMA", this.qThresholdFinish, 7, 9, "pix");
            gd.addNumericField("Multiply lambda on failure", this.qLambdaStepUp, 10);
            gd.addNumericField("Threshold lambda to fail", this.qMaxLambda, 10);
            gd.addNumericField("Maximal number of iterations", this.qNumIterations, 0);
            gd.addCheckbox("Dialog after each iteration step", this.qStopEachStep);
            gd.addCheckbox("Dialog after each failure", this.qStopOnFailure);
            gd.addCheckbox("Show modified parameters", this.showQParams);
            gd.addCheckbox("Show disabled parameters", this.showDisabledQParams);
            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.qSaveSeries = false;
                return false;
            }
            this.qLambda = gd.getNextNumber();
            this.qLambdaStepDown = gd.getNextNumber();
            this.qThresholdFinish = gd.getNextNumber();
            this.qLambdaStepUp = gd.getNextNumber();
            this.qMaxLambda = gd.getNextNumber();
            this.qNumIterations = (int) gd.getNextNumber();
            this.qStopEachStep = gd.getNextBoolean();
            this.qStopOnFailure = gd.getNextBoolean();
            this.showQParams = gd.getNextBoolean();
            this.showDisabledQParams = gd.getNextBoolean();
            this.qSaveSeries = true;
            return gd.wasOKed();
        }

        public boolean selectQLMAParameters() {
            GenericDialog gd = new GenericDialog(
                    "Levenberg-Marquardt algorithm parameters for finding the optimal tilt/distance");
            gd.addCheckbox("Debug derivatives", false);
            gd.addNumericField("Debug Jacobian for point number", this.debugQPoint, 0, 5, "(-1 - none)");
            gd.addNumericField("Debug Jacobian for parameter number", this.debugQParameter, 0, 5, "(-1 - none)");
            gd.addNumericField("Initial LMA Lambda ", this.qInitialLambda, 5, 8, " last was " + this.qLambda);
            gd.addNumericField("Multiply lambda on success", this.qLambdaStepDown, 5);
            gd.addNumericField("Threshold RMS to exit LMA", this.qThresholdFinish, 7, 9, "pix");
            gd.addNumericField("Multiply lambda on failure", this.qLambdaStepUp, 5);
            gd.addNumericField("Threshold lambda to fail", this.qMaxLambda, 5);
            gd.addNumericField("Maximal number of iterations", this.qNumIterations, 0);

            gd.addCheckbox("Dialog after each iteration step", this.qStopEachStep);
            gd.addCheckbox("Dialog after each failure", this.qStopOnFailure);
            gd.addCheckbox("Show modified parameters", this.showQParams);
            gd.addCheckbox("Show disabled parameters", this.showDisabledQParams);
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            this.debugQDerivatives = gd.getNextBoolean();
            this.debugQPoint = (int) gd.getNextNumber();
            this.debugQParameter = (int) gd.getNextNumber();
            this.qInitialLambda = gd.getNextNumber();
            this.qLambdaStepDown = gd.getNextNumber();
            this.qThresholdFinish = gd.getNextNumber();
            this.qLambdaStepUp = gd.getNextNumber();
            this.qMaxLambda = gd.getNextNumber();
            this.qNumIterations = (int) gd.getNextNumber();
            this.qStopEachStep = gd.getNextBoolean();
            this.qStopOnFailure = gd.getNextBoolean();
            this.showQParams = gd.getNextBoolean();
            this.showDisabledQParams = gd.getNextBoolean();
            return true;
        }

        public void runDebugScan(double low, double high, double step) {
            double[] best_qb_corr = fieldFitting.getBestQualB(k_red, k_blue, true);

            double[] saveZTxTy = fieldFitting.mechanicalFocusingModel.getZTxTy();
            double[] zTxTy = saveZTxTy.clone();
            String header = "Z absolute\tZ relative\tqualB";
            StringBuffer sb = new StringBuffer();

            for (double dz = low; dz <= high; dz += step) {
                zTxTy[0] = best_qb_corr[0] + dz;
                fieldFitting.mechanicalFocusingModel.setZTxTy(zTxTy);
                double qualB = getQualB();
                sb.append(IJ.d2s(zTxTy[0], 3) + "\t" + IJ.d2s(dz, 3) + "\t" + IJ.d2s(qualB, 5) + "\n");
            }

            fieldFitting.mechanicalFocusingModel.setZTxTy(saveZTxTy);
            new TextWindow("qualB scan, Tx=" + IJ.d2s(zTxTy[1], 3) + " Ty=" + IJ.d2s(zTxTy[1], 3), header,
                    sb.toString(), 800, 1000);
        }

        public boolean qLevenbergMarquardt(boolean openDialog, int debugLevel) {
            double savedLambda = this.qLambda;
            this.debugLevel = debugLevel;
            if (openDialog && !selectQLMAParameters())
                return false;
            this.qLambda = this.qInitialLambda;
            this.qStartTime = System.nanoTime();
            // TODO: ASet ZTxTy, mask, 
            //initCorrPars(double [][][] corrPars)
            // initWeights...           
            if (!openDialog)
                stopEachStep = false;
            this.iterationStepNumber = 0;
            this.firstQualB = -1; //undefined
            saveQPars();
            if (debugQDerivatives) {
                qCompareDrDerivatives(this.qSavedVector);
            }
            this.qCurrentVector = null; // invalidate for the new series
            int saveStopRequested = stopRequested.get(); // preserve from caller stop requested (like temp. scan)
            stopRequested.set(0); // remove caller stop request
            while (true) { // loop for the same series
                boolean[] state = stepQLevenbergMarquardtFirst(debugLevel);
                if (state == null) {
                    String msg = "Calculation aborted by user request, restoring saved parameter vector";
                    IJ.showMessage(msg);
                    System.out.println(msg);
                    restoreQPars();
                    //                    commitParameterVector(this.savedVector);
                    this.qLambda = savedLambda;
                    stopRequested.set(saveStopRequested); // restore caller stop request                 
                    return false;
                }

                if (debugLevel > 1)
                    System.out.println(this.iterationStepNumber + ": stepQLevenbergMarquardtFirst(" + debugLevel
                            + ")==>" + 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.firstQualB > this.currentQualB)) {
                    if (debugLevel > 1)
                        System.out.println("qLMA failed to converge, but RMS improved from the initial value ("
                                + this.currentQualB + " < " + this.firstQualB + ")");
                    state[0] = true;
                }
                if ((stopRequested.get() > 0) || // graceful stop requested
                        (this.qStopEachStep) ||
                        //                       (this.stopEachSeries && state[1]) ||
                        (this.qStopOnFailure && state[1] && !state[0])) {
                    //                    if (state[1] && !state[0] && !calibrate){
                    //                       return false;
                    //                    }

                    if (debugLevel > 0) {
                        if (stopRequested.get() > 0)
                            System.out.println("User requested stop");
                        System.out.println("qLevenbergMarquardt(): step =" + this.iterationStepNumber + ", QualB="
                                + IJ.d2s(this.currentQualB, 8) + " (" + IJ.d2s(this.firstQualB, 8) + ") " + ") at "
                                + IJ.d2s(0.000000001 * (System.nanoTime() - this.qStartTime), 3));
                    }
                    long startDialogTime = System.nanoTime();
                    cont = dialogQLMAStep(state);
                    stopRequested.set(0); // Will not stop each run
                    this.qStartTime += (System.nanoTime() - startDialogTime); // do not count time used by the User.
                }
                qStepLevenbergMarquardtAction(debugLevel); // apply step - in any case?
                if (updateStatus) {
                    IJ.showStatus("Step #" + this.iterationStepNumber + " QualB=" + IJ.d2s(this.currentQualB, 8)
                            + " (" + IJ.d2s(this.firstQualB, 8) + ")" + " ");
                }
                if (!cont) {
                    if (this.qSaveSeries) {
                        savedLambda = this.qLambda;
                        //                       this.qSavedVector=this.qCurrentVector.clone();
                        saveQPars();
                    }
                    // if RMS was decreased. this.saveSeries==false after dialogQLMAStep(state) only if "cancel" was pressed
                    //                    commitParameterVector(this.savedVector); // either new or original
                    commitQPars(this.qSavedVector);
                    this.qLambda = savedLambda;
                    stopRequested.set(saveStopRequested); // restore caller stop request                 
                    return this.qSaveSeries; // TODO: Maybe change result?
                }
                //stepLevenbergMarquardtAction();             
                if (state[1]) {
                    if (!state[0]) {
                        //                       commitParameterVector(this.savedVector);
                        commitQPars(this.qSavedVector);
                        this.qLambda = savedLambda;
                        stopRequested.set(saveStopRequested); // restore caller stop request                    
                        return false; // sequence failed
                    }
                    //                    this.savedVector=this.currentVector.clone();
                    saveQPars();
                    break; // while (true), proceed to the next series
                }
            } // while true - same series

            String msg = "QualB=" + this.currentQualB + " (" + this.firstQualB + ") " + " at "
                    + IJ.d2s(0.000000001 * (System.nanoTime() - this.qStartTime), 3);
            if (debugLevel > 1)
                System.out.println("qStepLevenbergMarquardtAction() " + msg);
            //       if (this.updateStatus) IJ.showStatus(msg);
            if (updateStatus) {
                IJ.showStatus("Done: Step #" + this.iterationStepNumber + " QualB=" + IJ.d2s(this.currentQualB, 8)
                        + " (" + IJ.d2s(this.firstQualB, 8) + ")" + " ");
            }
            //           this.savedVector=this.currentVector.clone();
            //           commitParameterVector(this.savedVector);
            saveQPars();
            commitQPars(this.qSavedVector);
            stopRequested.set(saveStopRequested); // restore caller stop request           
            return true; // all series done
        }

        public void qCompareDrDerivatives(double[] vector) {
            double delta = 0.00010; // make configurable
            if (this.debugQParameter >= 0) {
                String parName = "";
                //              if ((debugParameterNames!=null) && (debugParameterNames.length>debugParameter)) parName=debugParameterNames[debugParameter];
                System.out.println(
                        "Debugging derivatives for parameter #" + this.debugQParameter + " (" + parName + ")");
                //debugParameterNames
                double[] vector_dp = vector.clone();
                vector_dp[this.debugQParameter] += delta;
                double[] fx_dp = createFXandJacobian(vector_dp, false);
                double[] fx = createFXandJacobian(vector, true);
                for (int i = 0; i < fx.length; i++) {
                    if ((this.debugQPoint >= 0) && (this.debugQPoint != i))
                        continue; // debug only single point
                    int sample = i / 6;
                    int chn = i % 6;
                    String pointName = "";
                    pointName = "chn" + chn + ":" + sample;
                    System.out.println(i + ": " + pointName + " fx= " + fx[i] + " delta_fx= "
                            + ((fx_dp[i] - fx[i]) / delta) + " df/dp= " + this.qJacobian[this.debugQParameter][i]);
                }
            }
        }

    }
}