use of qupath.lib.objects.PathObject 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.objects.PathObject in project qupath by qupath.
the class TileClassificationsToAnnotationsPlugin method getParentObjects.
@Override
protected Collection<PathObject> getParentObjects(final PluginRunner<T> runner) {
PathObjectHierarchy hierarchy = runner.getImageData().getHierarchy();
List<PathObject> parents = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
// Deal with nested objects - the code is clumsy, but the idea is to take the highest
// object in the hierarchy in instances where tiles are nested within other objects
List<PathObject> tempList = new ArrayList<>(parents);
for (PathObject temp : tempList) {
Iterator<PathObject> iter = parents.iterator();
while (iter.hasNext()) {
if (PathObjectTools.isAncestor(iter.next(), temp))
iter.remove();
}
}
return parents;
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class RefineAnnotationsPlugin method getTasks.
@Override
protected Collection<Runnable> getTasks(final PluginRunner<T> runner) {
Collection<? extends PathObject> parentObjects = getParentObjects(runner);
if (parentObjects == null || parentObjects.isEmpty())
return Collections.emptyList();
// Add a single task, to avoid multithreading - which may complicate setting parents
List<Runnable> tasks = new ArrayList<>(1);
PathObjectHierarchy hierarchy = getHierarchy(runner);
double minFragmentSize;
double maxHoleSize, maxHoleSizeTemp;
ImageServer<T> server = getServer(runner);
PixelCalibration cal = server.getPixelCalibration();
if (cal.hasPixelSizeMicrons()) {
double pixelAreaMicrons = cal.getPixelWidthMicrons() * cal.getPixelHeightMicrons();
minFragmentSize = params.getDoubleParameterValue("minFragmentSizeMicrons") / pixelAreaMicrons;
maxHoleSizeTemp = params.getDoubleParameterValue("maxHoleSizeMicrons") / pixelAreaMicrons;
} else {
minFragmentSize = params.getDoubleParameterValue("minFragmentSizePixels");
maxHoleSizeTemp = params.getDoubleParameterValue("maxHoleSizePixels");
}
// Handle negative values
if (maxHoleSizeTemp < 0)
maxHoleSize = Double.POSITIVE_INFINITY;
else
maxHoleSize = maxHoleSizeTemp;
// Want to reset selection
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
Collection<PathObject> previousSelection = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
tasks.add(() -> {
List<PathObject> toRemove = new ArrayList<>();
Map<PathROIObject, ROI> toUpdate = new HashMap<>();
for (PathObject pathObject : parentObjects) {
ROI roiOrig = pathObject.getROI();
if (roiOrig == null || !roiOrig.isArea())
continue;
ROI roiUpdated = RoiTools.removeSmallPieces(roiOrig, minFragmentSize, maxHoleSize);
if (roiUpdated == null || roiUpdated.isEmpty())
toRemove.add(pathObject);
else if (roiOrig != roiUpdated && pathObject instanceof PathROIObject) {
toUpdate.put((PathROIObject) pathObject, roiUpdated);
}
}
if (toRemove.isEmpty() && toUpdate.isEmpty())
return;
hierarchy.getSelectionModel().clearSelection();
if (!toRemove.isEmpty())
hierarchy.removeObjects(toRemove, true);
if (!toUpdate.isEmpty()) {
hierarchy.removeObjects(toUpdate.keySet(), true);
toUpdate.forEach((p, r) -> p.setROI(r));
hierarchy.addPathObjects(toUpdate.keySet());
}
hierarchy.getSelectionModel().selectObjects(previousSelection);
hierarchy.getSelectionModel().setSelectedObject(selected, true);
});
return tasks;
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class MeasurementMapPane method updateMeasurements.
/**
* Update the measurements according to the current image
*/
public void updateMeasurements() {
QuPathViewer viewer = qupath.getViewer();
PathObjectHierarchy hierarchy = viewer.getHierarchy();
if (hierarchy == null) {
baseList.clear();
return;
}
Collection<PathObject> pathObjects = hierarchy.getDetectionObjects();
Set<String> measurements = PathClassifierTools.getAvailableFeatures(pathObjects);
for (PathObject pathObject : pathObjects) {
if (!Double.isNaN(pathObject.getClassProbability())) {
measurements.add("Class probability");
break;
}
}
// Apply any changes
baseList.setAll(measurements);
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class PathObjectHierarchyView method synchronizeSelectionModelToTree.
/**
* Ensure that the hierarchy selection model matches the selection within the TreeView.
* @param change
*/
private void synchronizeSelectionModelToTree(final ListChangeListener.Change<? extends TreeItem<PathObject>> change) {
if (synchronizingTreeToModel)
return;
PathObjectSelectionModel model = getHierarchySelectionModel();
if (model == null) {
return;
}
boolean wasSynchronizingToTree = synchronizingModelToTree;
try {
synchronizingModelToTree = true;
// Check - was anything removed?
boolean removed = false;
if (change != null) {
while (change.next()) removed = removed | change.wasRemoved();
}
MultipleSelectionModel<TreeItem<PathObject>> treeModel = treeView.getSelectionModel();
List<TreeItem<PathObject>> selectedItems = treeModel.getSelectedItems();
// If we just have no selected items, and something was removed, then clear the selection
if (selectedItems.isEmpty() && removed) {
model.clearSelection();
return;
}
// if (selectedItems.size() == 1 && removed) {
if (selectedItems.size() == 1) {
model.setSelectedObject(selectedItems.get(0).getValue(), false);
return;
}
// If we have multiple selected items, we need to ensure that everything in the tree matches with everything in the selection model
Set<PathObject> toSelect = treeView.getSelectionModel().getSelectedItems().stream().map(t -> t.getValue()).collect(Collectors.toSet());
TreeItem<PathObject> mainSelection = treeView.getSelectionModel().getSelectedItem();
PathObject primary = mainSelection == null ? null : mainSelection.getValue();
model.setSelectedObjects(toSelect, primary);
} finally {
synchronizingModelToTree = wasSynchronizingToTree;
}
}
Aggregations