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 ML) {
MeasurementList myPOML = myPO.getMeasurementList();
assertEquals(myPOML, ML);
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class ObjectMeasurements method addCellShapeMeasurements.
private static void addCellShapeMeasurements(PathCellObject cell, PixelCalibration cal, Collection<ShapeFeatures> features) {
var roiNucleus = cell.getNucleusROI();
var roiCell = cell.getROI();
try (MeasurementList ml = cell.getMeasurementList()) {
if (roiNucleus != null) {
addShapeMeasurements(ml, roiNucleus, cal, "Nucleus: ", features);
}
if (roiCell != null) {
addShapeMeasurements(ml, roiCell, cal, "Cell: ", features);
}
if (roiNucleus != null && roiCell != null && features.contains(ShapeFeatures.NUCLEUS_CELL_RATIO)) {
double pixelWidth = cal.getPixelWidth().doubleValue();
double pixelHeight = cal.getPixelHeight().doubleValue();
double nucleusCellAreaRatio = GeneralTools.clipValue(roiNucleus.getScaledArea(pixelWidth, pixelHeight) / roiCell.getScaledArea(pixelWidth, pixelHeight), 0, 1);
ml.putMeasurement("Nucleus/Cell area ratio", nucleusCellAreaRatio);
}
}
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class SmoothFeaturesPlugin method smoothMeasurements.
/**
* Using the centroids of the ROIs within PathObjects, 'smooth' measurements by summing up the corresponding measurements of
* nearby objects, weighted by centroid distance.
*
* @param pathObjects
* @param measurements
* @param fwhmPixels
* @param fwhmString
* @param withinClass
* @param useLegacyNames
*/
// public static Set<String> smoothMeasurements(List<PathObject> pathObjects, List<String> measurements, double fwhmPixels) {
public static void smoothMeasurements(List<PathObject> pathObjects, List<String> measurements, double fwhmPixels, String fwhmString, boolean withinClass, boolean useLegacyNames) {
if (measurements.isEmpty() || pathObjects.size() <= 1)
// Collections.emptySet();
return;
if (fwhmString == null)
fwhmString = String.format("%.2f px", fwhmPixels);
double fwhmPixels2 = fwhmPixels * fwhmPixels;
double sigmaPixels = fwhmPixels / Math.sqrt(8 * Math.log(2));
double sigma2 = 2 * sigmaPixels * sigmaPixels;
double maxDist = sigmaPixels * 3;
// Maximum separation
double maxDistSq = maxDist * maxDist;
int nObjects = pathObjects.size();
// int counter = 0;
// Sort by x-coordinate - this gives us a method of breaking early
Collections.sort(pathObjects, new Comparator<PathObject>() {
@Override
public int compare(PathObject o1, PathObject o2) {
double x1 = o1.getROI().getCentroidX();
double x2 = o2.getROI().getCentroidX();
// System.out.println(String.format("(%.2f, %.2f) vs (%.2f, %.2f)", o1.getROI().getCentroidX(), o1.getROI().getCentroidY(), o2.getROI().getCentroidX(), o2.getROI().getCentroidY())); }
return Double.compare(x1, x2);
// if (x1 > x2)
// return 1;
// if (x2 < x1)
// return -1;
// System.out.println(x1 + " vs. " + x2);
// System.out.println(String.format("(%.2f, %.2f) vs (%.2f, %.2f)", o1.getROI().getCentroidX(), o1.getROI().getCentroidY(), o2.getROI().getCentroidX(), o2.getROI().getCentroidY()));
// return 0;
// return (int)Math.signum(o1.getROI().getCentroidX() - o2.getROI().getCentroidX());
}
});
// Create a LUT for distances - calculating exp every time is expensive
double[] distanceWeights = new double[(int) (maxDist + .5) + 1];
for (int i = 0; i < distanceWeights.length; i++) {
distanceWeights[i] = Math.exp(-(i * i) / sigma2);
}
System.currentTimeMillis();
float[] xCentroids = new float[nObjects];
float[] yCentroids = new float[nObjects];
PathClass[] pathClasses = new PathClass[nObjects];
int[] nearbyDetectionCounts = new int[nObjects];
float[][] measurementsWeighted = new float[nObjects][measurements.size()];
float[][] measurementDenominators = new float[nObjects][measurements.size()];
float[][] measurementValues = new float[nObjects][measurements.size()];
for (int i = 0; i < nObjects; i++) {
PathObject pathObject = pathObjects.get(i);
if (withinClass)
pathClasses[i] = pathObject.getPathClass() == null ? null : pathObject.getPathClass().getBaseClass();
ROI roi = pathObject.getROI();
xCentroids[i] = (float) roi.getCentroidX();
yCentroids[i] = (float) roi.getCentroidY();
MeasurementList measurementList = pathObject.getMeasurementList();
int ind = 0;
for (String name : measurements) {
float value = (float) measurementList.getMeasurementValue(name);
// Used to cache values
measurementValues[i][ind] = value;
// Based on distances and measurements
measurementsWeighted[i][ind] = value;
// Based on distances along
measurementDenominators[i][ind] = 1;
ind++;
}
}
String prefix, postfix, denomName, countsName;
// Use previous syntax for naming smoothed measurements
if (useLegacyNames) {
prefix = "";
postfix = String.format(" - Smoothed (FWHM %s)", fwhmString);
denomName = String.format("Smoothed denominator (local density, FWHM %s)", fwhmString);
countsName = String.format("Nearby detection counts (radius %s)", fwhmString);
} else {
prefix = String.format("Smoothed: %s: ", fwhmString);
postfix = "";
// prefix + "Weighted density";
denomName = null;
countsName = prefix + "Nearby detection counts";
// denomName = prefix + "Denominator (local density)";
// countsName = prefix + "Nearby detection counts";
}
// Loop through objects, computing predominant class based on distance weighting
for (int i = 0; i < nObjects; i++) {
// Extract the current class index
PathObject pathObject = pathObjects.get(i);
PathClass pathClass = pathClasses[i];
MeasurementList measurementList = pathObject.getMeasurementList();
float[] mValues = measurementValues[i];
float[] mWeighted = measurementsWeighted[i];
float[] mDenominator = measurementDenominators[i];
// Compute centroid distances
double xi = xCentroids[i];
double yi = yCentroids[i];
for (int j = i + 1; j < nObjects; j++) {
double xj = xCentroids[j];
double yj = yCentroids[j];
// Break early if we are already too far away
if (Math.abs(xj - xi) > maxDist) {
break;
}
double distSq = (xj - xi) * (xj - xi) + (yj - yi) * (yj - yi);
// // Check if we are close enough to have an influence
if (distSq > maxDistSq || Double.isNaN(distSq))
continue;
// Check if the class is ok, if check needed
if (withinClass && pathClass != pathClasses[j])
continue;
// Update the counts, if close enough
if (distSq < fwhmPixels2) {
nearbyDetectionCounts[i]++;
nearbyDetectionCounts[j]++;
}
// Update the class weights for both objects currently being tested
// Compute weight based on centroid distances
// double weight = Math.exp(-distSq/sigma2);
// * pathObjects.get(j).getClassProbability();
double weight = distanceWeights[(int) (Math.sqrt(distSq) + .5)];
float[] temp = measurementValues[j];
float[] tempWeighted = measurementsWeighted[j];
float[] tempDenominator = measurementDenominators[j];
for (int ind = 0; ind < measurements.size(); ind++) {
float tempVal = temp[ind];
if (Float.isNaN(tempVal))
continue;
mWeighted[ind] += tempVal * weight;
mDenominator[ind] += weight;
float tempVal2 = mValues[ind];
if (Float.isNaN(tempVal2))
continue;
tempWeighted[ind] += tempVal2 * weight;
tempDenominator[ind] += weight;
}
}
// Store the measurements
int ind = 0;
float maxDenominator = Float.NEGATIVE_INFINITY;
for (String name : measurements) {
// if (name.contains(" - Smoothed (FWHM ") || name.startsWith("Smoothed denominator (local density, ") || name.startsWith("Nearby detection counts"))
// continue;
float denominator = mDenominator[ind];
if (denominator > maxDenominator)
maxDenominator = denominator;
String nameToAdd = prefix + name + postfix;
measurementList.putMeasurement(nameToAdd, mWeighted[ind] / denominator);
// measurementsAdded.add(nameToAdd);
// measurementList.putMeasurement(name + " - weighted sum", mWeighted[ind]); // TODO: Support optionally providing weighted sums
// measurementList.addMeasurement(name + " - smoothed", mWeighted[ind] / mDenominator[ind]);
ind++;
}
if (pathObject instanceof PathDetectionObject && denomName != null) {
measurementList.putMeasurement(denomName, maxDenominator);
// measurementsAdded.add(denomName);
}
if (pathObject instanceof PathDetectionObject && countsName != null) {
measurementList.putMeasurement(countsName, nearbyDetectionCounts[i]);
// measurementsAdded.add(countsName);
}
measurementList.close();
}
System.currentTimeMillis();
// return measurementsAdded;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class TestCompositeClassifier method test_classifyPathObjects.
@Test
public void test_classifyPathObjects() {
MeasurementList ml1 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
MeasurementList ml2 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
MeasurementList ml3 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
MeasurementList ml4 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
// Adding measurement to list2 (all measurements)
ml2.addMeasurement("intensityMeasurement1", 0.0);
ml2.addMeasurement("intensityMeasurement2", 0.2);
ml2.addMeasurement("intensityMeasurement3", 0.6);
ml2.addMeasurement("intensityMeasurement4", -4.6);
// Adding measurement to list3 (missing intensityMeasurement3)
ml3.addMeasurement("intensityMeasurement1", -1.0);
ml3.addMeasurement("intensityMeasurement2", 0.9999);
ml3.addMeasurement("intensityMeasurement4", 0.999);
// Adding measurement to list4 (missing intensityMeasurement4)
ml4.addMeasurement("intensityMeasurement1", 0.2);
ml4.addMeasurement("intensityMeasurement2", 0.3);
ml4.addMeasurement("intensityMeasurement3", 0.5);
// Create annotation objects
PathObject obj1 = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0, 0, 5, 5, ImagePlane.getDefaultPlane()), null, ml1);
PathObject obj2 = PathObjects.createAnnotationObject(ROIs.createRectangleROI(10, 10, 5, 5, ImagePlane.getDefaultPlane()), pathClass1, ml2);
PathObject obj3 = PathObjects.createAnnotationObject(ROIs.createEllipseROI(100, 100, 5, 5, ImagePlane.getDefaultPlane()), pathClass2, ml3);
PathObject obj4 = PathObjects.createAnnotationObject(ROIs.createLineROI(0, 0, ImagePlane.getDefaultPlane()), pathClass3, ml4);
// Classify objects and check classification manually
var objs = Arrays.asList(obj1, obj2, obj3, obj4);
// Composite classifier 1
var classifiedObjs1 = cp1.classifyPathObjects(objs);
// Last classifier classified one object only (obj4)
assertEquals(1, classifiedObjs1);
// Unchanged
assertEquals(null, obj1.getPathClass());
assertEquals(PathClassFactory.getNegative(pathClass1), obj2.getPathClass());
assertEquals(PathClassFactory.getPositive(pathClass2), obj3.getPathClass());
assertEquals(PathClassFactory.getPositive(pathClass3), obj4.getPathClass());
// Composite classifier 2
var classifiedObjs2 = cp2.classifyPathObjects(objs);
assertEquals(4, classifiedObjs2);
// Missing measurement -> unchanged
assertEquals(null, obj1.getPathClass());
assertEquals(PathClassFactory.getNegative(pathClass1), obj2.getPathClass());
assertEquals(PathClassFactory.getPositive(pathClass2), obj3.getPathClass());
// Missing measurement -> reset
assertEquals(pathClass3, obj4.getPathClass());
// Invalid composite classifier
var classifiedObjs3 = cpInvalid.classifyPathObjects(objs);
assertEquals(0, classifiedObjs3);
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class FeatureSelectionPane method makeFeatureSelectionPanel.
BorderPane makeFeatureSelectionPanel(final QuPathGUI qupath) {
tableFeatures.setTooltip(new Tooltip("Select object features to be used by the classifier"));
TableColumn<SelectableFeature, String> columnName = new TableColumn<>("Feature name");
columnName.setCellValueFactory(new PropertyValueFactory<>("featureName"));
columnName.setEditable(false);
TableColumn<SelectableFeature, Boolean> columnSelected = new TableColumn<>("Selected");
columnSelected.setCellValueFactory(new PropertyValueFactory<>("selected"));
columnSelected.setCellFactory(column -> new CheckBoxTableCell<>());
columnSelected.setEditable(true);
columnSelected.setResizable(false);
columnName.prefWidthProperty().bind(tableFeatures.widthProperty().subtract(columnSelected.widthProperty()));
tableFeatures.getColumns().add(columnName);
tableFeatures.getColumns().add(columnSelected);
tableFeatures.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tableFeatures.setEditable(true);
ContextMenu menu = new ContextMenu();
MenuItem itemSelect = new MenuItem("Select");
itemSelect.setOnAction(e -> {
for (SelectableFeature feature : tableFeatures.getSelectionModel().getSelectedItems()) feature.setSelected(true);
});
menu.getItems().add(itemSelect);
MenuItem itemDeselect = new MenuItem("Deselect");
itemDeselect.setOnAction(e -> {
for (SelectableFeature feature : tableFeatures.getSelectionModel().getSelectedItems()) feature.setSelected(false);
});
menu.getItems().add(itemDeselect);
menu.getItems().add(new SeparatorMenuItem());
MenuItem itemDelete = new MenuItem("Delete highlighted features");
itemDelete.setOnAction(e -> {
List<String> highlightedFeatures = new ArrayList<>();
for (SelectableFeature feature : tableFeatures.getSelectionModel().getSelectedItems()) highlightedFeatures.add(feature.getFeatureName());
int nFeatures = highlightedFeatures.size();
ImageData<?> imageData = qupath.getImageData();
if (nFeatures == 0 || imageData == null || imageData.getHierarchy().isEmpty())
return;
String f = nFeatures == 1 ? "1 feature" : nFeatures + " features";
if (!Dialogs.showYesNoDialog("Delete feature measurements", "Are you sure you want to permanently delete " + f + " from all objects?"))
return;
// Determine the features to remove
// Loop through objects and delete features
List<PathObject> changedObjects = new ArrayList<>();
for (PathObject pathObject : imageData.getHierarchy().getFlattenedObjectList(null)) {
// TODO: Consider if non-detection objects should be supported
if (!pathObject.isDetection())
continue;
// Remove measurements & log as changed, if necessary
MeasurementList ml = pathObject.getMeasurementList();
int sizeBefore = ml.size();
ml.removeMeasurements(highlightedFeatures.toArray(new String[0]));
ml.close();
int sizeAfter = ml.size();
if (sizeAfter != sizeBefore)
changedObjects.add(pathObject);
}
imageData.getHierarchy().fireObjectMeasurementsChangedEvent(this, changedObjects);
tableFeatures.getSelectionModel().clearSelection();
if (!hasFeatures())
ensureMeasurementsUpdated();
// classifierData.setFeaturesSelected(features, false);
// tableFeatures.repaint();
});
menu.getItems().add(itemDelete);
tableFeatures.setContextMenu(menu);
// Button to update the features
BorderPane panelButtons = new BorderPane();
// Button btnUpdateFeatures = new Button("Update feature table");
// btnUpdateFeatures.setTooltip(new Tooltip("Check all objects & available features"));
// btnUpdateFeatures.setOnAction(e -> {
// ensureMeasurementsUpdated();
// if (panelIntensities != null)
// panelIntensities.setAvailableMeasurements(getAvailableFeatures(), "mean", "dab");
// });
Button btnSelectAll = new Button("Select all");
btnSelectAll.setOnAction(e -> {
if (!hasFeatures())
ensureMeasurementsUpdated();
for (SelectableFeature feature : tableFeatures.getItems()) feature.setSelected(true);
});
Button btnSelectNone = new Button("Select none");
btnSelectNone.setOnAction(e -> {
if (!hasFeatures())
ensureMeasurementsUpdated();
for (SelectableFeature feature : tableFeatures.getItems()) feature.setSelected(false);
});
GridPane panelSelectButtons = PaneTools.createColumnGridControls(btnSelectAll, btnSelectNone);
panelButtons.setTop(panelSelectButtons);
// panelButtons.setBottom(btnUpdateFeatures);
// btnUpdateFeatures.prefWidthProperty().bind(panelButtons.widthProperty());
BorderPane panelFeatures = new BorderPane();
panelFeatures.setCenter(tableFeatures);
panelFeatures.setBottom(panelButtons);
ensureMeasurementsUpdated();
return panelFeatures;
}
Aggregations