use of uk.ac.sussex.gdsc.smlm.data.config.PSFProtos.PSF in project GDSC-SMLM by aherbert.
the class PsfCreator method fitPsf.
/**
* Fit the new PSF image and show a graph of the amplitude/width.
*
* @param psfStack the psf stack
* @param loess the loess
* @param cz the cz
* @param averageRange the average range
* @param fitCom the fit com
* @return The width of the PSF in the z-centre
*/
private double fitPsf(ImageStack psfStack, LoessInterpolator loess, int cz, double averageRange, final double[][] fitCom) {
IJ.showStatus("Fitting final PSF");
// is not appropriate for a normalised PSF.
if (fitConfig.getFitSolver() != FitSolver.LVM_LSE) {
ImageJUtils.log(" " + FitProtosHelper.getName(fitConfig.getFitSolver()) + " is not appropriate for final PSF fitting.");
ImageJUtils.log(" Switching to Least Square Estimation");
fitConfig.setFitSolver(FitSolver.LVM_LSE);
if (settings.getInteractiveMode()) {
// This assumes the LVM does not need the calibration
PeakFit.configureFitSolver(config, null, null, 0);
}
}
// Update the box radius since this is used in the fitSpot method.
boxRadius = psfStack.getWidth() / 2;
final int x = boxRadius;
final int y = boxRadius;
final double shift = fitConfig.getCoordinateShiftFactor();
// Scale the PSF
final PSF.Builder localPsf = fitConfig.getPsf().toBuilder();
for (int i = 0; i < localPsf.getParametersCount(); i++) {
final PSFParameter param = localPsf.getParameters(i);
if (param.getUnit() == PSFParameterUnit.DISTANCE) {
final PSFParameter.Builder b = param.toBuilder();
b.setValue(b.getValue() * settings.getMagnification());
localPsf.setParameters(i, b);
}
}
fitConfig.setPsf(localPsf.build());
// Need to be updated after the widths have been set
fitConfig.setCoordinateShiftFactor(shift);
fitConfig.setBackgroundFitting(false);
// Since the PSF will be normalised remove the camera calibration
fitConfig.setCameraType(CameraType.CAMERA_TYPE_NA);
fitConfig.setMinPhotons(0);
fitConfig.setBias(0);
fitConfig.setGain(1);
// No complex filtering so we get a fit. It should be easy to fit anyway.
fitConfig.setPrecisionThreshold(0);
fitConfig.setDirectFilter(null);
// fitConfig.setDisableSimpleFilter(true);
// Use this for debugging the fit
// fitConfig.setLog(uk.ac.sussex.gdsc.core.ij.ImageJPluginLoggerHelper.getDefaultLogger());
final MemoryPeakResults results = fitSpot(psfStack, psfStack.getWidth(), psfStack.getHeight(), x, y);
if (results.size() < 5) {
ImageJUtils.log(" Final PSF: Not enough fit results %d", results.size());
return 0;
}
// Get the results for the spot centre and width
final double[] z = new double[results.size()];
final double[] xCoord = new double[z.length];
final double[] yCoord = new double[z.length];
final double[] sd = new double[z.length];
final double[] a = new double[z.length];
// Set limits for the fit
final float maxWidth = (float) (Math.max(fitConfig.getInitialXSd(), fitConfig.getInitialYSd()) * settings.getMagnification() * 4);
// PSF is normalised to 1
final float maxSignal = 2;
final WidthResultProcedure wp = new WidthResultProcedure(results, DistanceUnit.PIXEL);
wp.getWxWy();
final HeightResultProcedure hp = new HeightResultProcedure(results, IntensityUnit.COUNT);
hp.getH();
final Counter counter = new Counter();
final Counter counterOk = new Counter();
// We have fit the results so they will be in the preferred units
results.forEach((PeakResultProcedure) peak -> {
int index = counter.getAndIncrement();
final float w = Math.max(wp.wx[index], wp.wy[index]);
if (peak.getIntensity() > maxSignal || w > maxWidth) {
return;
}
index = counterOk.getAndIncrement();
z[index] = peak.getFrame();
fitCom[0][peak.getFrame() - 1] = xCoord[index] = peak.getXPosition() - x;
fitCom[1][peak.getFrame() - 1] = yCoord[index] = peak.getYPosition() - y;
sd[index] = w;
a[index] = hp.heights[index];
});
// Truncate
final double[] z2 = Arrays.copyOf(z, counter.getCount());
final double[] xCoord2 = Arrays.copyOf(xCoord, z2.length);
final double[] yCoord2 = Arrays.copyOf(yCoord, z2.length);
final double[] sd2 = Arrays.copyOf(sd, z2.length);
final double[] a2 = Arrays.copyOf(a, z2.length);
// Extract the average smoothed range from the individual fits
final int r = (int) Math.ceil(averageRange / 2);
int start = 0;
int stop = z2.length - 1;
for (int j = 0; j < z2.length; j++) {
if (z2[j] > cz - r) {
start = j;
break;
}
}
for (int j = z2.length; j-- > 0; ) {
if (z2[j] < cz + r) {
stop = j;
break;
}
}
// Extract xy centre coords and smooth
double[] smoothX = new double[stop - start + 1];
double[] smoothY = new double[smoothX.length];
double[] smoothSd = new double[smoothX.length];
double[] smoothA = new double[smoothX.length];
final double[] newZ = new double[smoothX.length];
int smoothCzIndex = 0;
for (int j = start, k = 0; j <= stop; j++, k++) {
smoothX[k] = xCoord2[j];
smoothY[k] = yCoord2[j];
smoothSd[k] = sd2[j];
smoothA[k] = a2[j];
newZ[k] = z2[j];
if (newZ[k] == cz) {
smoothCzIndex = k;
}
}
smoothX = loess.smooth(newZ, smoothX);
smoothY = loess.smooth(newZ, smoothY);
smoothSd = loess.smooth(newZ, smoothSd);
smoothA = loess.smooth(newZ, smoothA);
// Update the widths and positions using the magnification
final double scale = 1.0 / settings.getMagnification();
for (int j = 0; j < xCoord2.length; j++) {
xCoord2[j] *= scale;
yCoord2[j] *= scale;
sd2[j] *= scale;
}
for (int j = 0; j < smoothX.length; j++) {
smoothX[j] *= scale;
smoothY[j] *= scale;
smoothSd[j] *= scale;
}
showPlots(z2, a2, newZ, smoothA, xCoord2, yCoord2, sd2, newZ, smoothX, smoothY, smoothSd, cz);
// Store the data for replotting
this.z = z2;
this.amplitude = a2;
this.smoothAz = newZ;
this.smoothA = smoothA;
this.xCoord = xCoord2;
this.yCoord = yCoord2;
this.sd = sd2;
this.newZ = newZ;
this.smoothX = smoothX;
this.smoothY = smoothY;
this.smoothSd = smoothSd;
// maximumIndex = findMinimumIndex(smoothSd, maximumIndex - start);
return smoothSd[smoothCzIndex];
}
use of uk.ac.sussex.gdsc.smlm.data.config.PSFProtos.PSF in project GDSC-SMLM by aherbert.
the class PeakFit method configureFitSolver.
/**
* Show a dialog to configure the fit solver. The updated settings are saved to the settings file.
* An error message is shown if the dialog is cancelled or the configuration is invalid.
*
* <p>The bounds are used to validate the camera model. The camera model must be large enough to
* cover the source bounds. If larger then it will be cropped. Optionally an internal region of
* the input image can be specified. This is relative to the width and height of the input image.
* If no camera model is present then the bounds can be null.
*
* @param config the configuration
* @param sourceBounds the source image bounds (used to validate the camera model dimensions)
* @param bounds the crop bounds (relative to the input image, used to validate the camera model
* dimensions)
* @param flags the flags
* @return True if the configuration succeeded
*/
public static boolean configureFitSolver(FitEngineConfiguration config, Rectangle sourceBounds, Rectangle bounds, int flags) {
final boolean extraOptions = BitFlagUtils.anySet(flags, FLAG_EXTRA_OPTIONS);
final boolean ignoreCalibration = BitFlagUtils.anySet(flags, FLAG_IGNORE_CALIBRATION);
final boolean saveSettings = BitFlagUtils.anyNotSet(flags, FLAG_NO_SAVE);
final FitConfiguration fitConfig = config.getFitConfiguration();
final CalibrationWriter calibration = fitConfig.getCalibrationWriter();
final FitSolver fitSolver = fitConfig.getFitSolver();
final boolean isLvm = fitSolver == FitSolver.LVM_LSE || fitSolver == FitSolver.LVM_WLSE || fitSolver == FitSolver.LVM_MLE;
// Support the deprecated backtracking FastMLE solver as a plain FastMLE solver
final boolean isFastMml = fitSolver == FitSolver.FAST_MLE || fitSolver == FitSolver.BACKTRACKING_FAST_MLE;
final boolean isSteppingFunctionSolver = isLvm || isFastMml;
if (fitSolver == FitSolver.MLE) {
final ExtendedGenericDialog gd = new ExtendedGenericDialog(TITLE);
if (!ignoreCalibration) {
gd.addMessage("Maximum Likelihood Estimation requires CCD-type camera parameters");
gd.addNumericField("Camera_bias", calibration.getBias(), 2, 6, "count");
gd.addCheckbox("Model_camera_noise", fitConfig.isModelCamera());
gd.addNumericField("Read_noise", calibration.getReadNoise(), 2, 6, "count");
gd.addNumericField("Quantum_efficiency", calibration.getQuantumEfficiency(), 2, 6, "electron/photon");
gd.addCheckbox("EM-CCD", calibration.isEmCcd());
} else {
gd.addMessage("Maximum Likelihood Estimation requires additional parameters");
}
final String[] searchNames = SettingsManager.getSearchMethodNames();
gd.addChoice("Search_method", searchNames, FitProtosHelper.getName(fitConfig.getSearchMethod()));
gd.addStringField("Relative_threshold", MathUtils.rounded(fitConfig.getRelativeThreshold()));
gd.addStringField("Absolute_threshold", MathUtils.rounded(fitConfig.getAbsoluteThreshold()));
gd.addNumericField("Max_iterations", fitConfig.getMaxIterations(), 0);
gd.addNumericField("Max_function_evaluations", fitConfig.getMaxFunctionEvaluations(), 0);
if (extraOptions) {
gd.addCheckbox("Gradient_line_minimisation", fitConfig.isGradientLineMinimisation());
}
gd.showDialog();
if (gd.wasCanceled()) {
return false;
}
if (!ignoreCalibration) {
calibration.setBias(Math.abs(gd.getNextNumber()));
fitConfig.setModelCamera(gd.getNextBoolean());
calibration.setReadNoise(Math.abs(gd.getNextNumber()));
calibration.setQuantumEfficiency(Math.abs(gd.getNextNumber()));
calibration.setCameraType((gd.getNextBoolean()) ? CameraType.EMCCD : CameraType.CCD);
fitConfig.setCalibration(calibration.getCalibration());
}
fitConfig.setSearchMethod(SettingsManager.getSearchMethodValues()[gd.getNextChoiceIndex()]);
fitConfig.setRelativeThreshold(getThresholdNumber(gd));
fitConfig.setAbsoluteThreshold(getThresholdNumber(gd));
fitConfig.setMaxIterations((int) gd.getNextNumber());
fitConfig.setMaxFunctionEvaluations((int) gd.getNextNumber());
if (extraOptions) {
fitConfig.setGradientLineMinimisation(gd.getNextBoolean());
} else {
// This option is for the Conjugate Gradient optimiser and makes it less stable
fitConfig.setGradientLineMinimisation(false);
}
if (saveSettings) {
saveFitEngineSettings(config);
}
try {
ParameterUtils.isAboveZero("Relative threshold", fitConfig.getRelativeThreshold());
ParameterUtils.isAboveZero("Absolute threshold", fitConfig.getAbsoluteThreshold());
ParameterUtils.isAboveZero("Max iterations", fitConfig.getMaxIterations());
ParameterUtils.isAboveZero("Max function evaluations", fitConfig.getMaxFunctionEvaluations());
fitConfig.getFunctionSolver();
} catch (final IllegalArgumentException | IllegalStateException ex) {
IJ.error(TITLE, ex.getMessage());
return false;
}
} else if (isSteppingFunctionSolver) {
final boolean requireCalibration = !ignoreCalibration && fitSolver != FitSolver.LVM_LSE;
// Collect options for LVM fitting
final ExtendedGenericDialog gd = new ExtendedGenericDialog(TITLE);
final String fitSolverName = FitProtosHelper.getName(fitSolver);
gd.addMessage(fitSolverName + " requires additional parameters");
gd.addStringField("Relative_threshold", MathUtils.rounded(fitConfig.getRelativeThreshold()));
gd.addStringField("Absolute_threshold", MathUtils.rounded(fitConfig.getAbsoluteThreshold()));
gd.addStringField("Parameter_relative_threshold", MathUtils.rounded(fitConfig.getParameterRelativeThreshold()));
gd.addStringField("Parameter_absolute_threshold", MathUtils.rounded(fitConfig.getParameterAbsoluteThreshold()));
gd.addNumericField("Max_iterations", fitConfig.getMaxIterations(), 0);
if (isLvm) {
gd.addNumericField("Lambda", fitConfig.getLambda(), 4);
}
if (isFastMml) {
gd.addCheckbox("Fixed_iterations", fitConfig.isFixedIterations());
// This works because the proto configuration enum matches the named enum
final String[] lineSearchNames = SettingsManager.getNames((Object[]) FastMleSteppingFunctionSolver.LineSearchMethod.values());
gd.addChoice("Line_search_method", lineSearchNames, lineSearchNames[fitConfig.getLineSearchMethod().getNumber()]);
}
gd.addCheckbox("Use_clamping", fitConfig.isUseClamping());
gd.addCheckbox("Dynamic_clamping", fitConfig.isUseDynamicClamping());
final PSF psf = fitConfig.getPsf();
final boolean isAstigmatism = psf.getPsfType() == PSFType.ASTIGMATIC_GAUSSIAN_2D;
final int nParams = PsfHelper.getParameterCount(psf);
if (extraOptions) {
gd.addNumericField("Clamp_background", fitConfig.getClampBackground(), 2);
gd.addNumericField("Clamp_signal", fitConfig.getClampSignal(), 2);
gd.addNumericField("Clamp_x", fitConfig.getClampX(), 2);
gd.addNumericField("Clamp_y", fitConfig.getClampY(), 2);
if (isAstigmatism) {
gd.addNumericField("Clamp_z", fitConfig.getClampZ(), 2);
} else {
if (nParams > 1 || !fitConfig.isFixedPsf()) {
gd.addNumericField("Clamp_sx", fitConfig.getClampXSd(), 2);
}
if (nParams > 1) {
gd.addNumericField("Clamp_sy", fitConfig.getClampYSd(), 2);
}
if (nParams > 2) {
gd.addNumericField("Clamp_angle", fitConfig.getClampAngle(), 2);
}
}
}
// Extra parameters are needed for calibrated fit solvers
if (requireCalibration) {
switch(calibration.getCameraType()) {
case CCD:
case EMCCD:
case SCMOS:
break;
default:
IJ.error(TITLE, fitSolverName + " requires camera calibration");
return false;
}
gd.addMessage(fitSolverName + " requires calibration for camera: " + CalibrationProtosHelper.getName(calibration.getCameraType()));
if (calibration.isScmos()) {
final String[] models = CameraModelManager.listCameraModels(true);
gd.addChoice("Camera_model_name", models, fitConfig.getCameraModelName());
} else {
gd.addNumericField("Camera_bias", calibration.getBias(), 2, 6, "Count");
gd.addNumericField("Gain", calibration.getCountPerPhoton(), 2, 6, "Count/photon");
gd.addNumericField("Read_noise", calibration.getReadNoise(), 2, 6, "Count");
}
}
gd.showDialog();
if (gd.wasCanceled()) {
return false;
}
fitConfig.setRelativeThreshold(getThresholdNumber(gd));
fitConfig.setAbsoluteThreshold(getThresholdNumber(gd));
fitConfig.setParameterRelativeThreshold(getThresholdNumber(gd));
fitConfig.setParameterAbsoluteThreshold(getThresholdNumber(gd));
fitConfig.setMaxIterations((int) gd.getNextNumber());
if (isLvm) {
fitConfig.setLambda(gd.getNextNumber());
}
if (isFastMml) {
fitConfig.setFixedIterations(gd.getNextBoolean());
fitConfig.setLineSearchMethod(gd.getNextChoiceIndex());
}
fitConfig.setUseClamping(gd.getNextBoolean());
fitConfig.setUseDynamicClamping(gd.getNextBoolean());
if (extraOptions) {
fitConfig.setClampBackground(Math.abs(gd.getNextNumber()));
fitConfig.setClampSignal(Math.abs(gd.getNextNumber()));
fitConfig.setClampX(Math.abs(gd.getNextNumber()));
fitConfig.setClampY(Math.abs(gd.getNextNumber()));
if (isAstigmatism) {
fitConfig.setClampZ(Math.abs(gd.getNextNumber()));
} else {
if (nParams > 1 || !fitConfig.isFixedPsf()) {
fitConfig.setClampXSd(Math.abs(gd.getNextNumber()));
}
if (nParams > 1) {
fitConfig.setClampYSd(Math.abs(gd.getNextNumber()));
}
if (nParams > 2) {
fitConfig.setClampAngle(Math.abs(gd.getNextNumber()));
}
}
}
if (requireCalibration) {
if (calibration.isScmos()) {
fitConfig.setCameraModelName(gd.getNextChoice());
} else {
calibration.setBias(Math.abs(gd.getNextNumber()));
calibration.setCountPerPhoton(Math.abs(gd.getNextNumber()));
calibration.setReadNoise(Math.abs(gd.getNextNumber()));
fitConfig.setCalibration(calibration.getCalibration());
}
}
// camera model is set.
if (calibration.isScmos()) {
fitConfig.setCameraModel(CameraModelManager.load(fitConfig.getCameraModelName()));
if (!checkCameraModel(fitConfig, sourceBounds, bounds, true)) {
return false;
}
}
if (saveSettings) {
saveFitEngineSettings(config);
}
try {
if (isLvm) {
ParameterUtils.isAboveZero("Lambda", fitConfig.getLambda());
}
// This call will check if the configuration is OK (including convergence criteria)
fitConfig.getFunctionSolver();
} catch (final IllegalArgumentException | IllegalStateException ex) {
IJ.error(TITLE, ex.getMessage());
return false;
}
} else {
IJ.error(TITLE, "Unknown fit solver: " + fitSolver);
return false;
}
if (config.isIncludeNeighbours() && !fitConfig.getFunctionSolver().isBounded()) {
IJ.error(TITLE, "Including neighbours requires a bounded fit solver");
return false;
}
return true;
}
use of uk.ac.sussex.gdsc.smlm.data.config.PSFProtos.PSF in project GDSC-SMLM by aherbert.
the class PeakResultsReader method simplifyTwoAxis.
/**
* Simplify two axis Gaussian 2D data.
*
* @param results the results
* @param removeTheta the remove theta flag
*/
private void simplifyTwoAxis(MemoryPeakResults results, boolean removeTheta) {
final int[] indices = PsfHelper.getGaussian2DWxWyIndices(psf);
final int isx = indices[0];
final int isy = indices[1];
// Columns to remove
int remove = (removeTheta) ? 1 : 0;
// New PSF type
PSFType psfType;
// Determine if sy is redundant
if (results.forEach((PeakResultProcedureX) peakResult -> peakResult.getParameter(isx) != peakResult.getParameter(isy))) {
if (!removeTheta) {
// Already a TwoAxis Gaussian
return;
}
// Otherwise this was a TwoAxisAndTheta with 1 column to remove
// so it should be simplified
psfType = PSFType.TWO_AXIS_GAUSSIAN_2D;
} else {
// sy is redundant so remove another column
psfType = PSFType.ONE_AXIS_GAUSSIAN_2D;
remove++;
}
// Update the PSF
final PSF.Builder builder = psf.toBuilder();
builder.setPsfType(psfType);
psf = builder.build();
results.setPsf(psf);
// Update the results.
// We can directly manipulate the params array
final int newLength = results.getf(0).getNumberOfParameters() - remove;
if (deviations) {
results.forEach((PeakResultProcedure) peakResult -> {
peakResult.resizeParameters(newLength);
peakResult.resizeParameterDeviations(newLength);
});
} else {
results.forEach((PeakResultProcedure) peakResult -> peakResult.resizeParameters(newLength));
}
}
use of uk.ac.sussex.gdsc.smlm.data.config.PSFProtos.PSF in project GDSC-SMLM by aherbert.
the class PeakResultsReader method readNStorm.
private MemoryPeakResults readNStorm() {
final MemoryPeakResults results = createResults();
results.setName(FileUtils.getName(filename));
try (FileInputStream fis = new FileInputStream(filename);
BufferedReader input = new BufferedReader(new UnicodeReader(fis, null))) {
final ProgressReporter reporter = createProgressReporter(fis);
String line;
int errors = 0;
// The single line header
final String header = input.readLine();
if (header == null) {
throw new IOException("NStorm header missing");
}
// NStorm files added more column fields for later formats.
// If the header contains 'Photons' then this can be used to determine the gain
boolean readPhotons = header.contains("\tPhotons\t");
while ((line = input.readLine()) != null) {
if (line.isEmpty()) {
continue;
}
final PeakResult result = createNStormResult(line, readPhotons);
if (result != null) {
results.add(result);
// Just read the photons from the first 100
if (readPhotons) {
readPhotons = results.size() < 100;
}
} else if (++errors >= 10) {
break;
}
reporter.showProgress();
}
} catch (final IOException ex) {
logError(ex);
}
// The following relationship holds when length == 1:
// intensity = height * 2 * pi * sd0 * sd1 / pixel_pitch^2
// => Pixel_pitch = sqrt(height * 2 * pi * sd0 * sd1 / intensity)
// Try and create a calibration
final Statistics pixelPitch = new Statistics();
results.forEach(new PeakResultProcedureX() {
static final double TWO_PI = 2 * Math.PI;
@Override
public boolean execute(PeakResult peakResult) {
if (peakResult.getFrame() == peakResult.getEndFrame()) {
final float height = peakResult.getOrigValue();
final float intensity = peakResult.getParameter(PeakResult.INTENSITY);
final float sd0 = peakResult.getParameter(INDEX_SX);
final float sd1 = peakResult.getParameter(INDEX_SY);
pixelPitch.add(Math.sqrt(height * TWO_PI * sd0 * sd1 / intensity));
// Stop when we have enough for a good guess
return (pixelPitch.getN() > 100);
}
return false;
}
});
// Determine the gain using the photons column
final Statistics gain = new Statistics();
results.forEach((PeakResultProcedureX) peakResult -> {
double photons = peakResult.getError();
if (photons != 0) {
peakResult.setError(0);
gain.add(peakResult.getIntensity() / photons);
return false;
}
return true;
});
// TODO - Support all the NSTORM formats: one-axis, two-axis, rotated, 3D.
// Is this information in the header?
// We could support setting the PSF as a Gaussian2D with one/two axis SD.
// This would mean updating all the result params if it is a one axis PSF.
// For now just record it as a 2 axis PSF.
// Create a calibration
calibration = new CalibrationWriter();
// NSTORM data is in counts when the Photons column is present.
// Q. Is it in counts when this column is not present?
calibration.setIntensityUnit(IntensityUnit.COUNT);
calibration.setDistanceUnit(DistanceUnit.NM);
if (pixelPitch.getN() > 0) {
final double nmPerPixel = pixelPitch.getMean();
calibration.setNmPerPixel(nmPerPixel);
}
if (gain.getN() > 0 && gain.getStandardError() < 1e-3) {
calibration.setCountPerPhoton(gain.getMean());
}
results.setCalibration(calibration.getCalibration());
return results;
}
use of uk.ac.sussex.gdsc.smlm.data.config.PSFProtos.PSF in project GDSC-SMLM by aherbert.
the class TraceManager method convertToCentroidPeakResults.
/**
* Convert a list of traces into peak results. The signal weighted centroid of each trace is used
* as the coordinates. The weighted localisation precision is used as the width. The amplitude is
* the average from all the peaks in the trace.
*
* <p>Uses the title and bounds from the provided peak results. The title has the word 'Traced
* Centroids' appended.
*
* @param source the source
* @param traces the traces
* @return the peak results
*/
public static MemoryPeakResults convertToCentroidPeakResults(MemoryPeakResults source, final Trace[] traces) {
final MemoryPeakResults results = toCentroidPeakResults(traces, source.getCalibration());
// Do not copy the PSF as it is invalid for centroids
final PSF psf = results.getPsf();
results.copySettings(source);
results.setPsf(psf);
// Change name
results.setName(source.getSource() + " Traced Centroids");
// TODO - Add the tracing settings
return results;
}
Aggregations