use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class OpenCvClassifier method updateClassifier.
@Override
public boolean updateClassifier(final Map<PathClass, List<PathObject>> map, final List<String> measurements, Normalization normalization) {
// There is a chance we don't need to retrain... to find out, cache the most important current variables
boolean maybeSameClassifier = isValid() && this.normalization == normalization && !classifierOptionsChanged() && this.measurements.equals(measurements) && pathClasses.size() == map.size() && map.keySet().containsAll(pathClasses);
float[] arrayTrainingPrevious = arrayTraining;
int[] arrayResponsesPrevious = arrayResponses;
pathClasses = new ArrayList<>(map.keySet());
Collections.sort(pathClasses);
int n = 0;
for (Map.Entry<PathClass, List<PathObject>> entry : map.entrySet()) {
n += entry.getValue().size();
}
// Compute running statistics for normalization
HashMap<String, RunningStatistics> statsMap = new LinkedHashMap<>();
for (String m : measurements) statsMap.put(m, new RunningStatistics());
this.measurements.clear();
this.measurements.addAll(measurements);
int nMeasurements = measurements.size();
arrayTraining = new float[n * nMeasurements];
arrayResponses = new int[n];
int row = 0;
int nnan = 0;
for (PathClass pathClass : pathClasses) {
List<PathObject> list = map.get(pathClass);
int classIndex = pathClasses.indexOf(pathClass);
for (int i = 0; i < list.size(); i++) {
MeasurementList measurementList = list.get(i).getMeasurementList();
int col = 0;
for (String m : measurements) {
double value = measurementList.getMeasurementValue(m);
if (Double.isNaN(value))
nnan++;
else
statsMap.get(m).addValue(value);
arrayTraining[row * nMeasurements + col] = (float) value;
col++;
}
arrayResponses[row] = classIndex;
row++;
}
}
// Normalise, if required
if (normalization != null && normalization != Normalization.NONE) {
logger.debug("Training classifier with normalization: {}", normalization);
int numMeasurements = measurements.size();
normOffset = new double[numMeasurements];
normScale = new double[numMeasurements];
for (int i = 0; i < numMeasurements; i++) {
RunningStatistics stats = statsMap.get(measurements.get(i));
if (normalization == Normalization.MEAN_VARIANCE) {
normOffset[i] = -stats.getMean();
if (stats.getStdDev() > 0)
normScale[i] = 1.0 / stats.getStdDev();
} else if (normalization == Normalization.MIN_MAX) {
normOffset[i] = -stats.getMin();
if (stats.getRange() > 0)
normScale[i] = 1.0 / (stats.getMax() - stats.getMin());
else
normScale[i] = 1.0;
}
}
// Apply normalisation
for (int i = 0; i < arrayTraining.length; i++) {
int k = i % numMeasurements;
arrayTraining[i] = (float) ((arrayTraining[i] + normOffset[k]) * normScale[k]);
}
this.normalization = normalization;
} else {
logger.debug("Training classifier without normalization");
normScale = null;
normOffset = null;
this.normalization = Normalization.NONE;
}
// Record that we have NaNs
if (nnan > 0)
logger.debug("Number of NaNs in training set: " + nnan);
// Having got this far, check to see whether we really do need to retrain
if (maybeSameClassifier) {
if (Arrays.equals(arrayTrainingPrevious, arrayTraining) && Arrays.equals(arrayResponsesPrevious, arrayResponses)) {
logger.info("Classifier already trained with the same samples - existing classifier will be used");
return false;
}
}
createAndTrainClassifier();
timestamp = System.currentTimeMillis();
this.measurements = new ArrayList<>(measurements);
return true;
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class RTreesClassifier method setPredictedClass.
@Override
protected void setPredictedClass(final RTrees classifier, final List<PathClass> pathClasses, final Mat samples, final Mat results, final PathObject pathObject) {
if (pathClasses.size() == 2 && termCriteria != null && ((TermCriteria.EPS & termCriteria.type()) == 0) && termCriteria.maxCount() > 0) {
double prediction = classifier.predict(samples, results, RTrees.PREDICT_SUM) / termCriteria.maxCount();
// Round the prediction - NOTE: this gives a slightly different result from OpenCV's own predictions (which would round 0.5 down)
int index = (int) Math.round(prediction);
// Convert to a probability based on the number of trees
double probability = prediction;
if (index == 0)
probability = 1 - probability;
// Set the class & probability
PathClass pathClass = pathClasses.get(index);
pathObject.setPathClass(pathClass, probability);
} else
super.setPredictedClass(classifier, pathClasses, samples, results, pathObject);
}
use of qupath.lib.objects.classes.PathClass 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.objects.classes.PathClass in project qupath by qupath.
the class PathObjectTools method mergePointsForSelectedObjectClasses.
// private static void addPathObjectsRecursively(Collection<PathObject> pathObjectsInput, Collection<PathObject> pathObjects, Class<? extends PathObject> cls) {
// Collection<PathObject> buffer = null;
// for (PathObject childObject : pathObjectsInput) {
// if (cls == null || cls.isInstance(childObject)) {
// pathObjects.add(childObject);
// }
// if (childObject.hasChildren()) {
// if (buffer == null)
// buffer = new ArrayList<>();
// else
// buffer.clear();
// childObject.getChildObjects(buffer);
// addPathObjectsRecursively(buffer, pathObjects, cls);
// }
// }
// }
// /**
// * Split annotations containing multi-point ROIs into separate single-point ROIs.
// *
// * @param hierarchy the object hierarchy
// * @param selectedOnly if true, consider only annotations that are currently selected; if false, consider all point annotations in the hierarchy
// * @return true if changes are made to the hierarchy, false otherwise
// */
// public static boolean splitPoints(PathObjectHierarchy hierarchy, boolean selectedOnly) {
// if (hierarchy == null) {
// logger.debug("No hierarchy available, cannot split points!");
// return false;
// }
// return splitPoints(hierarchy, selectedOnly ? hierarchy.getSelectionModel().getSelectedObjects() : hierarchy.getAnnotationObjects());
// }
//
// /**
// * Split annotations containing multi-point ROIs into separate single-point ROIs.
// *
// * @param hierarchy the object hierarchy
// * @param pathObjects a collection of point annotations to split; non-points and non-annotations will be ignored
// * @return pathObjects if changes are made to the hierarchy, false otherwise
// */
// public static boolean splitPoints(PathObjectHierarchy hierarchy, Collection<PathObject> pathObjects) {
// var points = pathObjects.stream().filter(p -> p.isAnnotation() && p.getROI().isPoint() && p.getROI().getNumPoints() > 1).collect(Collectors.toList());
// if (points.isEmpty()) {
// logger.debug("No (multi)point ROIs available to split!");
// return false;
// }
// List<PathObject> newObjects = new ArrayList<>();
// for (PathObject pathObject : points) {
// ROI p = pathObject.getROI();
// ImagePlane plane = p.getImagePlane();
// PathClass pathClass = pathObject.getPathClass();
// for (Point2 p2 : p.getAllPoints()) {
// PathObject temp = PathObjects.createAnnotationObject(ROIs.createPointsROI(p2.getX(), p2.getY(), plane), pathClass);
// newObjects.add(temp);
// }
// }
// hierarchy.removeObjects(points, true);
// hierarchy.addPathObjects(newObjects);
// // Reset the selection
// hierarchy.getSelectionModel().clearSelection();
// return true;
// }
/**
* Merge point annotations sharing the same {@link PathClass} and {@link ImagePlane} as the selected annotations,
* creating multi-point annotations for all matching points and removing the (previously-separated) annotations.
*
* @param hierarchy object hierarchy to modify
* @return true if changes are made to the hierarchy, false otherwise
*/
public static boolean mergePointsForSelectedObjectClasses(PathObjectHierarchy hierarchy) {
var pathClasses = hierarchy.getSelectionModel().getSelectedObjects().stream().filter(p -> p.isAnnotation() && p.getROI().isPoint()).map(p -> p.getPathClass()).collect(Collectors.toSet());
boolean changes = false;
for (PathClass pathClass : pathClasses) changes = changes || mergePointsForClass(hierarchy, pathClass);
return changes;
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class PathObjectTools method standardizeClassifications.
/**
* Standardize the classifications for a collection of objects.
* This involves sorting the names of derived classes, and removing duplicates.
*
* @param pathObjects collection of objects with classifications that should be standardized
* @param comparator comparator to use when sorting
* @return true if changes were made, false otherwise
*/
public static boolean standardizeClassifications(Collection<PathObject> pathObjects, Comparator<String> comparator) {
int nChanges = 0;
Map<PathClass, PathClass> map = new HashMap<>();
for (var pathObject : pathObjects) {
var pathClass = pathObject.getPathClass();
if (pathClass == null)
continue;
PathClass pathClassNew;
if (!map.containsKey(pathClass)) {
pathClassNew = PathClassTools.sortNames(PathClassTools.uniqueNames(pathClass), comparator);
map.put(pathClass, pathClassNew);
} else
pathClassNew = map.get(pathClass);
if (!pathClass.equals(pathClassNew)) {
pathObject.setPathClass(pathClassNew);
nChanges++;
}
}
return nChanges > 0;
}
Aggregations