Search in sources :

Example 46 with PathClass

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;
}
Also used : MeasurementList(qupath.lib.measurements.MeasurementList) RunningStatistics(qupath.lib.analysis.stats.RunningStatistics) LinkedHashMap(java.util.LinkedHashMap) PathClass(qupath.lib.objects.classes.PathClass) PathObject(qupath.lib.objects.PathObject) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) ParameterList(qupath.lib.plugins.parameters.ParameterList) List(java.util.List) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) Map(java.util.Map)

Example 47 with PathClass

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);
}
Also used : PathClass(qupath.lib.objects.classes.PathClass)

Example 48 with PathClass

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;
}
Also used : MeasurementList(qupath.lib.measurements.MeasurementList) EllipseROI(qupath.lib.roi.EllipseROI) PointsROI(qupath.lib.roi.PointsROI) LineROI(qupath.lib.roi.LineROI) ROI(qupath.lib.roi.interfaces.ROI) PathClass(qupath.lib.objects.classes.PathClass) EllipseROI(qupath.lib.roi.EllipseROI)

Example 49 with PathClass

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;
}
Also used : PathClassTools(qupath.lib.objects.classes.PathClassTools) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) Function(java.util.function.Function) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) EllipseROI(qupath.lib.roi.EllipseROI) HashSet(java.util.HashSet) ROIs(qupath.lib.roi.ROIs) PointsROI(qupath.lib.roi.PointsROI) Point2(qupath.lib.geom.Point2) ImageRegion(qupath.lib.regions.ImageRegion) Map(java.util.Map) PreparedGeometry(org.locationtech.jts.geom.prep.PreparedGeometry) LinkedHashSet(java.util.LinkedHashSet) LineROI(qupath.lib.roi.LineROI) Line2D(java.awt.geom.Line2D) RoiTools(qupath.lib.roi.RoiTools) Logger(org.slf4j.Logger) Iterator(java.util.Iterator) Predicate(java.util.function.Predicate) Collection(java.util.Collection) PathClass(qupath.lib.objects.classes.PathClass) Set(java.util.Set) AffineTransform(java.awt.geom.AffineTransform) Collectors(java.util.stream.Collectors) Objects(java.util.Objects) ROI(qupath.lib.roi.interfaces.ROI) List(java.util.List) Entry(java.util.Map.Entry) ImagePlane(qupath.lib.regions.ImagePlane) Geometry(org.locationtech.jts.geom.Geometry) Comparator(java.util.Comparator) TMAGrid(qupath.lib.objects.hierarchy.TMAGrid) Collections(java.util.Collections) PreparedGeometryFactory(org.locationtech.jts.geom.prep.PreparedGeometryFactory) STRtree(org.locationtech.jts.index.strtree.STRtree) PathClass(qupath.lib.objects.classes.PathClass)

Example 50 with PathClass

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;
}
Also used : PathClass(qupath.lib.objects.classes.PathClass) HashMap(java.util.HashMap)

Aggregations

PathClass (qupath.lib.objects.classes.PathClass)66 ArrayList (java.util.ArrayList)42 PathObject (qupath.lib.objects.PathObject)34 List (java.util.List)29 Map (java.util.Map)25 IOException (java.io.IOException)21 Logger (org.slf4j.Logger)20 LoggerFactory (org.slf4j.LoggerFactory)20 Collections (java.util.Collections)17 Collectors (java.util.stream.Collectors)17 BufferedImage (java.awt.image.BufferedImage)16 LinkedHashMap (java.util.LinkedHashMap)16 ROI (qupath.lib.roi.interfaces.ROI)16 HashMap (java.util.HashMap)15 ImageData (qupath.lib.images.ImageData)15 PathClassFactory (qupath.lib.objects.classes.PathClassFactory)15 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)15 ParameterList (qupath.lib.plugins.parameters.ParameterList)15 Collection (java.util.Collection)14 TreeMap (java.util.TreeMap)11