use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class DelaunayTriangulation method addNodeMeasurements.
/**
* Add Delaunay measurements to each pathObject.
*/
public void addNodeMeasurements() {
if (nodeMap == null)
return;
// If 0, no averaging is performed
// If 1, the averages of each object's measurements & those of its immediate neighbors are added
// If 2, the neighbors of the neighbors are included as well
// If 3, the neighbors or the neighbors of the neighbors are included... and so on...
int averagingSeparation = 0;
String[] measurementNames = new String[0];
double[] averagedMeasurements = new double[0];
Set<PathObject> neighborSet = new HashSet<>();
for (Entry<PathObject, DelaunayNode> entry : nodeMap.entrySet()) {
MeasurementList measurementList = entry.getKey().getMeasurementList();
DelaunayNode node = entry.getValue();
// Create a set of neighbors
if (averagingSeparation > 0) {
neighborSet.clear();
node.addNeighborsToSet(neighborSet, averagingSeparation);
// Get the smoothed measurements now, since access is likely to be much faster we start modifying it
measurementNames = measurementList.getMeasurementNames().toArray(measurementNames);
if (averagedMeasurements.length < measurementNames.length)
averagedMeasurements = new double[measurementNames.length];
for (int i = 0; i < measurementNames.length; i++) {
String name = measurementNames[i];
if (name == null || name.startsWith("Delaunay"))
continue;
double sum = 0;
int n = 0;
for (PathObject tempObject : neighborSet) {
double value = tempObject.getMeasurementList().getMeasurementValue(name);
if (Double.isNaN(value))
continue;
sum += value;
n++;
}
averagedMeasurements[i] = n > 0 ? sum / n : Double.NaN;
}
}
// TODO: PUT MEASUREMENTS IN UNITS OTHER THAN PIXELS????
measurementList.putMeasurement("Delaunay: Num neighbors", node.nNeighbors());
measurementList.putMeasurement("Delaunay: Mean distance", node.meanDistance());
measurementList.putMeasurement("Delaunay: Median distance", node.medianDistance());
measurementList.putMeasurement("Delaunay: Max distance", node.maxDistance());
measurementList.putMeasurement("Delaunay: Min distance", node.minDistance());
// measurementList.putMeasurement("Delaunay: Displacement sum mag", node.magDisplacementSum());
measurementList.putMeasurement("Delaunay: Mean triangle area", node.getMeanTriangleArea());
measurementList.putMeasurement("Delaunay: Max triangle area", node.getMaxTriangleArea());
// Put in averaged measurements using immediate neighbours
if (averagingSeparation > 0) {
for (int i = 0; i < measurementNames.length; i++) {
String name = measurementNames[i];
if (name == null || name.startsWith("Delaunay"))
continue;
measurementList.putMeasurement("Delaunay averaged (" + averagingSeparation + "): " + name, averagedMeasurements[i]);
}
}
measurementList.close();
}
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class PathObjectTools method transformObject.
/**
* Create a(n optionally) transformed version of a {@link PathObject}.
* <p>
* Note: only detections (including tiles and cells) and annotations are fully supported by this method.
* Root objects are duplicated.
* TMA core objects are transformed only if the resulting transform creates an ellipse ROI, since this is
* currently the only ROI type supported for a TMA core (this behavior may change).
* Any other object types result in an {@link UnsupportedOperationException} being thrown.
*
* @param pathObject the object to transform; this will be unchanged
* @param transform optional affine transform; if {@code null}, this effectively acts to duplicate the object
* @param copyMeasurements if true, the measurement list of the new object will be populated with the measurements of pathObject
*
* @return a duplicate of pathObject, with affine transform applied to the object's ROI(s) if required
*/
public static PathObject transformObject(PathObject pathObject, AffineTransform transform, boolean copyMeasurements) {
ROI roi = maybeTransformROI(pathObject.getROI(), transform);
PathClass pathClass = pathObject.getPathClass();
PathObject newObject;
if (pathObject instanceof PathCellObject) {
ROI roiNucleus = maybeTransformROI(((PathCellObject) pathObject).getNucleusROI(), transform);
newObject = PathObjects.createCellObject(roi, roiNucleus, pathClass, null);
} else if (pathObject instanceof PathTileObject) {
newObject = PathObjects.createTileObject(roi, pathClass, null);
} else if (pathObject instanceof PathDetectionObject) {
newObject = PathObjects.createDetectionObject(roi, pathClass, null);
} else if (pathObject instanceof PathAnnotationObject) {
newObject = PathObjects.createAnnotationObject(roi, pathClass, null);
} else if (pathObject instanceof PathRootObject) {
newObject = new PathRootObject();
} else if (pathObject instanceof TMACoreObject && roi instanceof EllipseROI) {
var core = (TMACoreObject) pathObject;
newObject = PathObjects.createTMACoreObject(roi.getBoundsX(), roi.getBoundsY(), roi.getBoundsWidth(), roi.getBoundsHeight(), core.isMissing());
} else
throw new UnsupportedOperationException("Unable to transform object " + pathObject);
if (copyMeasurements && !pathObject.getMeasurementList().isEmpty()) {
MeasurementList measurements = pathObject.getMeasurementList();
for (int i = 0; i < measurements.size(); i++) {
String name = measurements.getMeasurementName(i);
double value = measurements.getMeasurementValue(i);
newObject.getMeasurementList().addMeasurement(name, value);
}
newObject.getMeasurementList().close();
}
return newObject;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class HaralickFeaturesPlugin method processObject.
static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageServer<BufferedImage> server, final ColorDeconvolutionStains stains) throws IOException {
String stainsName = (String) params.getChoiceParameterValue("stainChoice");
double mag = params.getDoubleParameterValue("magnification");
int d = params.getIntParameterValue("haralickDistance");
int nBins = params.getIntParameterValue("haralickBins");
boolean includeStats = params.getBooleanParameterValue("includeStats");
boolean doCircular = params.getBooleanParameterValue("doCircular");
double downsample;
boolean hasMagnification = !Double.isNaN(server.getMetadata().getMagnification());
PixelCalibration cal = server.getPixelCalibration();
if (hasMagnification)
downsample = server.getMetadata().getMagnification() / mag;
else if (cal.hasPixelSizeMicrons()) {
downsample = params.getDoubleParameterValue("pixelSizeMicrons") / cal.getAveragedPixelSizeMicrons();
} else
downsample = params.getDoubleParameterValue("downsample");
// double downsample = server.getMagnification() / mag;
// Try to get ROI
ROI pathROI = null;
if (pathObject instanceof PathCellObject && Boolean.TRUE.equals(params.getBooleanParameterValue("useNucleusROIs")))
pathROI = ((PathCellObject) pathObject).getNucleusROI();
else
pathROI = pathObject.getROI();
if (pathROI == null)
return false;
// Get bounds
ImmutableDimension size = getPreferredTileSizePixels(server, params);
RegionRequest region;
boolean createMaskROI = false;
if (size.getWidth() <= 0 || size.getHeight() <= 0) {
region = RegionRequest.createInstance(server.getPath(), downsample, pathObject.getROI());
createMaskROI = true;
doCircular = false;
} else if (size.getWidth() / downsample < 1 || size.getHeight() / downsample < 1)
// Positive size, but insufficient to make measurements
return false;
else {
// RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, (int)(pathROI.getCentroidX() + .5) - size.width/2, (int)(pathROI.getCentroidY() + .5) - size.height/2, size.width, size.height, pathROI.getT(), pathROI.getZ());
// Try to align with pixel boundaries according to the downsample being used - otherwise, interpolation can cause some strange, pattern artefacts
int xStart = (int) ((int) (pathROI.getCentroidX() / downsample + .5) * downsample) - size.width / 2;
int yStart = (int) ((int) (pathROI.getCentroidY() / downsample + .5) * downsample) - size.height / 2;
int width = Math.min(server.getWidth(), xStart + size.width) - xStart;
int height = Math.min(server.getHeight(), yStart + size.height) - yStart;
region = RegionRequest.createInstance(server.getPath(), downsample, xStart, yStart, width, height, pathROI.getT(), pathROI.getZ());
}
// Check image large enough to do *anything* of value
if (region.getWidth() / downsample < 3 || region.getHeight() / downsample < 3)
return false;
// System.out.println(bounds);
// System.out.println("Size: " + size);
BufferedImage img = server.readBufferedImage(region);
if (img == null) {
logger.error("Could not read image - unable to compute Haralick features for {}", pathObject);
return false;
}
// Create mask ROI if necessary
byte[] maskBytes = null;
if (createMaskROI) {
ROI roi = pathObject.getROI();
// if (pathObject instanceof PathCellObject && ((PathCellObject)pathObject).getNucleusROI() != null)
// roi = ((PathCellObject)pathObject).getNucleusROI();
BufferedImage imgMask = BufferedImageTools.createROIMask(img.getWidth(), img.getHeight(), roi, region);
maskBytes = ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData();
}
double minValue = Double.NaN;
double maxValue = Double.NaN;
// Get a buffer containing the image pixels
int w = img.getWidth();
int h = img.getHeight();
int[] buf = img.getRGB(0, 0, w, h, null, 0, w);
// Create a color transformer to get the images we need
float[] pixels = new float[buf.length];
SimpleModifiableImage pxImg = SimpleImages.createFloatImage(pixels, w, h);
MeasurementList measurementList = pathObject.getMeasurementList();
String postfix = maskBytes == null ? " (" + getDiameterString(server, params) + ")" : "";
if (stainsName.equals("H-DAB")) {
minValue = 0;
maxValue = 2.0;
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "DAB" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("H&E")) {
minValue = 0;
maxValue = 2;
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("H-DAB (8-bit)")) {
minValue = 0;
maxValue = 255;
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "DAB 8-bit" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("H&E (8-bit)")) {
minValue = 0;
maxValue = 255;
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E_8_bit, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("Optical density")) {
minValue = 0;
maxValue = 2.5;
processTransformedImage(pxImg, buf, pixels, measurementList, "OD sum" + postfix, ColorTransformer.ColorTransformMethod.Optical_density_sum, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("RGB")) {
minValue = 0;
maxValue = 255;
processTransformedImage(pxImg, buf, pixels, measurementList, "Red" + postfix, ColorTransformer.ColorTransformMethod.Red, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Green" + postfix, ColorTransformer.ColorTransformMethod.Green, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Blue" + postfix, ColorTransformer.ColorTransformMethod.Blue, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("RGB OD")) {
minValue = 0;
// Actual possible max is around 2.4 for 8-bit input... but this gives a lot of bins for (almost) saturated pixels
maxValue = 1.5;
processTransformedImage(pxImg, buf, pixels, measurementList, "Red OD" + postfix, ColorTransformer.ColorTransformMethod.Red_OD, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Green OD" + postfix, ColorTransformer.ColorTransformMethod.Green_OD, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Blue OD" + postfix, ColorTransformer.ColorTransformMethod.Blue_OD, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("Grayscale")) {
minValue = 0;
maxValue = 255;
processTransformedImage(pxImg, buf, pixels, measurementList, "Grayscale" + postfix, ColorTransformer.ColorTransformMethod.RGB_mean, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
} else if (stainsName.equals("HSB")) {
minValue = 0;
maxValue = 1;
float[] hsb = null;
double sinX = 0;
double cosX = 0;
float[] pixelsBrightness = new float[pixels.length];
float[] pixelsSaturation = new float[pixels.length];
for (int i = 0; i < buf.length; i++) {
if (maskBytes != null && maskBytes[i] == (byte) 0)
continue;
int val = buf[i];
hsb = Color.RGBtoHSB(ColorTools.red(val), ColorTools.green(val), ColorTools.blue(val), hsb);
pixelsSaturation[i] = hsb[1];
pixelsBrightness[i] = hsb[2];
double alpha = hsb[0] * 2 * Math.PI;
sinX += Math.sin(alpha);
cosX += Math.cos(alpha);
}
measurementList.putMeasurement("Mean hue", Math.atan2(sinX, cosX) / (2 * Math.PI) + 0.5);
// measurementList.putMeasurement("Mean saturation", hsb[1]);
// measurementList.putMeasurement("Mean brightness", hsb[2]);
processTransformedImage(SimpleImages.createFloatImage(pixelsSaturation, w, h), buf, pixelsSaturation, measurementList, "Saturation" + postfix, null, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
processTransformedImage(SimpleImages.createFloatImage(pixelsBrightness, w, h), buf, pixelsBrightness, measurementList, "Brightness" + postfix, null, minValue, maxValue, d, nBins, stains, maskBytes, includeStats, doCircular);
}
measurementList.close();
return true;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class CoherenceFeaturePlugin method processObject.
static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageServer<BufferedImage> server, final ColorDeconvolutionStains stains) throws InterruptedException, IOException {
String stainsName = (String) params.getChoiceParameterValue("stainChoice");
double mag = params.getDoubleParameterValue("magnification");
boolean includeStats = params.getBooleanParameterValue("includeStats");
boolean doCircular = params.getBooleanParameterValue("doCircular");
double downsample = server.getMetadata().getMagnification() / mag;
ROI pathROI = pathObject.getROI();
if (pathROI == null)
return false;
// Get bounds
ImmutableDimension size = getPreferredTileSizePixels(server, params);
if (size.getWidth() / downsample < 1 || size.getHeight() / downsample < 1)
return false;
// RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, (int)(pathROI.getCentroidX() + .5) - size.width/2, (int)(pathROI.getCentroidY() + .5) - size.height/2, size.width, size.height, pathROI.getT(), pathROI.getZ());
// Try to align with pixel boundaries according to the downsample being used - otherwise, interpolation can cause some strange, pattern artefacts
int xStart = (int) ((int) (pathROI.getCentroidX() / downsample + .5) * downsample) - size.width / 2;
int yStart = (int) ((int) (pathROI.getCentroidY() / downsample + .5) * downsample) - size.height / 2;
int width = Math.min(server.getWidth(), xStart + size.width) - xStart;
int height = Math.min(server.getHeight(), yStart + size.height) - yStart;
RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, xStart, yStart, width, height, pathROI.getT(), pathROI.getZ());
// System.out.println(bounds);
// System.out.println("Size: " + size);
BufferedImage img = server.readBufferedImage(region);
// Get a buffer containing the image pixels
int w = img.getWidth();
int h = img.getHeight();
int[] buf = img.getRGB(0, 0, w, h, null, 0, w);
// Create a color transformer to get the images we need
float[] pixels = new float[buf.length];
SimpleModifiableImage pxImg = SimpleImages.createFloatImage(pixels, w, h);
MeasurementList measurementList = pathObject.getMeasurementList();
String postfix = " (" + getDiameterString(server, params) + ")";
if (stainsName.equals("H-DAB")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "DAB" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB, stains, includeStats, doCircular);
} else if (stainsName.equals("H&E")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E, stains, includeStats, doCircular);
} else if (stainsName.equals("H-DAB (8-bit)")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_DAB_8_bit, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "DAB 8-bit" + postfix, ColorTransformer.ColorTransformMethod.DAB_H_DAB_8_bit, stains, includeStats, doCircular);
} else if (stainsName.equals("H&E (8-bit)")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Hematoxylin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Hematoxylin_H_E_8_bit, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Eosin 8-bit" + postfix, ColorTransformer.ColorTransformMethod.Eosin_H_E_8_bit, stains, includeStats, doCircular);
} else if (stainsName.equals("Optical density")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "OD sum" + postfix, ColorTransformer.ColorTransformMethod.Optical_density_sum, stains, includeStats, doCircular);
} else if (stainsName.equals("RGB")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Red" + postfix, ColorTransformer.ColorTransformMethod.Red, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Green" + postfix, ColorTransformer.ColorTransformMethod.Green, stains, includeStats, doCircular);
processTransformedImage(pxImg, buf, pixels, measurementList, "Blue" + postfix, ColorTransformer.ColorTransformMethod.Blue, stains, includeStats, doCircular);
} else if (stainsName.equals("Grayscale")) {
processTransformedImage(pxImg, buf, pixels, measurementList, "Grayscale" + postfix, ColorTransformer.ColorTransformMethod.RGB_mean, stains, includeStats, doCircular);
}
measurementList.close();
return true;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class PathObjectTestWrapper method test_getMeasurementList.
// @Test
public void test_getMeasurementList(PathObject myPO) {
MeasurementList myPOML = myPO.getMeasurementList();
assertTrue(myPOML instanceof MeasurementList);
}
Aggregations