//
// Calibrator.java
//
/*
LOCI Plugins for ImageJ: a collection of ImageJ plugins including the
Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions,
Data Browser and Stack Slicer. Copyright (C) 2005-@year@ Melissa Linkert,
Curtis Rueden and Christopher Peterson.
This program 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.plugins.in;
import ij.ImagePlus;
import ij.measure.Calibration;
import java.util.Arrays;
import loci.formats.FormatTools;
import loci.formats.meta.IMetadata;
import ome.xml.model.primitives.NonNegativeInteger;
import ome.xml.model.primitives.PositiveFloat;
import ome.xml.model.primitives.PositiveInteger;
/**
* Logic for calibrating images.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/loci-plugins/src/loci/plugins/in/Calibrator.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/loci-plugins/src/loci/plugins/in/Calibrator.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class Calibrator {
// -- Fields --
private final ImportProcess process;
// -- Constructor --
public Calibrator(ImportProcess process) {
this.process = process;
}
// -- Calibrator methods --
/** Applies spatial calibrations to an image stack. */
public void applyCalibration(ImagePlus imp) {
final IMetadata meta = process.getOMEMetadata();
final int series = (Integer) imp.getProperty(ImagePlusReader.PROP_SERIES);
double xcal = Double.NaN, ycal = Double.NaN;
double zcal = Double.NaN, tcal = Double.NaN;
PositiveFloat xd = meta.getPixelsPhysicalSizeX(series);
if (xd != null) xcal = xd.getValue();
PositiveFloat yd = meta.getPixelsPhysicalSizeY(series);
if (yd != null) ycal = yd.getValue();
PositiveFloat zd = meta.getPixelsPhysicalSizeZ(series);
if (zd != null) zcal = zd.getValue();
Double td = meta.getPixelsTimeIncrement(series);
if (td != null) tcal = td.floatValue();
boolean xcalPresent = !Double.isNaN(xcal);
boolean ycalPresent = !Double.isNaN(ycal);
boolean zcalPresent = !Double.isNaN(zcal);
boolean tcalPresent = !Double.isNaN(tcal) && tcal != 0;
// HACK: If the physical width or height are missing,
// assume that the width and height are equal.
if (xcalPresent && !ycalPresent) ycal = xcal;
else if (ycalPresent && !xcalPresent) xcal = ycal;
// HACK: If the time increment is missing,
// average any variable time interval values.
if (!tcalPresent) tcal = computeVariableTimeInterval(meta, series);
xcalPresent = !Double.isNaN(xcal);
ycalPresent = !Double.isNaN(ycal);
zcalPresent = !Double.isNaN(zcal);
tcalPresent = !Double.isNaN(tcal);
final boolean hasSpatial = xcalPresent || ycalPresent || zcalPresent;
final boolean hasCalibration = hasSpatial || ycalPresent;
if (hasCalibration) {
// set calibration only if at least one value is present
Calibration cal = new Calibration();
if (hasSpatial) cal.setUnit("micron");
if (xcalPresent) cal.pixelWidth = xcal == 0 ? 1 : xcal;
if (ycalPresent) cal.pixelHeight = ycal == 0 ? 1 : ycal;
if (zcalPresent) cal.pixelDepth = zcal == 0 ? 1 : zcal;
if (tcalPresent) cal.frameInterval = tcal == 0 ? 1 : tcal;
imp.setCalibration(cal);
}
String type = meta.getPixelsType(series).toString();
int pixelType = FormatTools.pixelTypeFromString(type);
// NB: INT32 is represented with FloatProcessor, so no need to calibrate.
boolean signed = pixelType == FormatTools.INT8 ||
pixelType == FormatTools.INT16; // || pixelType == FormatTools.INT32;
// set calibration function, so that both signed and unsigned pixel
// values are shown
if (signed) {
int bitsPerPixel = FormatTools.getBytesPerPixel(pixelType) * 8;
double min = -1 * Math.pow(2, bitsPerPixel - 1);
imp.getLocalCalibration().setFunction(Calibration.STRAIGHT_LINE,
new double[] {min, 1.0}, "gray value");
}
}
private double computeVariableTimeInterval(IMetadata meta, int series) {
// collect variable time interval values
final PositiveInteger sizeT = meta.getPixelsSizeT(series);
final int tSize = sizeT == null ? 1 : sizeT.getValue();
final int planeCount = meta.getPlaneCount(series);
final double[] deltas = new double[tSize];
Arrays.fill(deltas, Double.NaN);
for (int p=0; p<planeCount; p++) {
final NonNegativeInteger theZ = meta.getPlaneTheZ(series, p);
final NonNegativeInteger theC = meta.getPlaneTheC(series, p);
final NonNegativeInteger theT = meta.getPlaneTheT(series, p);
if (theZ == null || theC == null || theT == null) continue;
if (theZ.getValue() != 0 || theC.getValue() != 0) continue;
// store delta T value at appropriate index
final int t = theT.getValue();
if (t >= tSize) continue;
final Double deltaT = meta.getPlaneDeltaT(series, p);
if (deltaT == null) continue;
deltas[t] = deltaT;
}
// average delta T differences
double tiTotal = 0;
int tiCount = 0;
for (int t=1; t<tSize; t++) {
double delta1 = deltas[t - 1];
double delta2 = deltas[t];
if (Double.isNaN(delta1) || Double.isNaN(delta2)) continue;
tiTotal += delta2 - delta1;
tiCount++;
}
if (tiCount == 0) return Double.NaN;
// divide by 1000, as the DeltaT values are stored in milliseconds
// but the expected units are seconds
return (float) (tiTotal / tiCount) / 1000f;
}
}