package cz.cuni.lf1.lge.ThunderSTORM.util; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule.DetectionStatus.FALSE_NEGATIVE; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule.DetectionStatus.FALSE_POSITIVE; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule.DetectionStatus.TRUE_POSITIVE; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor.LABEL_DISTANCE_TO_GROUND_TRUTH_XY; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor.LABEL_DISTANCE_TO_GROUND_TRUTH_Z; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor.LABEL_DISTANCE_TO_GROUND_TRUTH_XYZ; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor.LABEL_GROUND_TRUTH_ID; import static cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor.LABEL_ID; import static cz.cuni.lf1.lge.ThunderSTORM.util.MathProxy.sqrt; import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule; import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.MoleculeDescriptor.Units; import cz.cuni.lf1.lge.ThunderSTORM.util.javaml.kdtree.KDTree; import cz.cuni.lf1.lge.ThunderSTORM.util.javaml.kdtree.KeyDuplicateException; import cz.cuni.lf1.lge.ThunderSTORM.util.javaml.kdtree.KeySizeException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; public class MoleculeMatcher { public Units distUnits; public double dist2Thr; public boolean threeD; public MoleculeMatcher(boolean threeD, double dist2Thr, Units distUnits) { this.dist2Thr = dist2Thr; this.distUnits = distUnits; this.threeD = threeD; } /** * The method matches detected molecules to the ground-truth data. * @param det [in] List of detected molecules. * @param gt [in] List of ground-truth molecules. * @param TP [out] List of true-positive pairs <ground-truth,detection> (the container must be allocated by caller). * @param FP [out] List of false-positive detections (the container must be allocated by caller). * @param FN [out] List of false-negative items from ground-truth (the container must be allocated by caller). */ public void matchMolecules(List<Molecule> det, List<Molecule> gt, List<Pair<Molecule, Molecule>> TP, List<Molecule> FP, List<Molecule> FN) { // Check for empty variables if (gt == null) gt = new Vector<Molecule>(); if (det == null) det = new Vector<Molecule>(); // // Clean the data for (Molecule d : det) { d.clearNeighbors(); d.setStatus(Molecule.DetectionStatus.UNSPECIFIED); d.setParam(LABEL_GROUND_TRUTH_ID, Units.UNITLESS, 0); d.setParam(LABEL_DISTANCE_TO_GROUND_TRUTH_XY, distUnits, Double.POSITIVE_INFINITY); d.setParam(LABEL_DISTANCE_TO_GROUND_TRUTH_Z, distUnits, Double.POSITIVE_INFINITY); d.setParam(LABEL_DISTANCE_TO_GROUND_TRUTH_XYZ, distUnits, Double.POSITIVE_INFINITY); } for (Molecule g : gt) { g.clearNeighbors(); g.setStatus(Molecule.DetectionStatus.UNSPECIFIED); } // // Initialize KDTree<Molecule> tree = new KDTree<Molecule>(2); try { for (Molecule mol : gt) { try { tree.insert(new double[]{mol.getX(distUnits), mol.getY(distUnits)}, mol); } catch(KeyDuplicateException ex) { // this might theoretically happen if two molecules are located at the same exact spot; but it is very unlikely } } double dist = sqrt(dist2Thr); for (Molecule mol : det) { FP.add(mol); mol.addNeighbors(tree.range( new double[] { mol.getX(distUnits) - dist, mol.getY(distUnits) - dist }, new double[] { mol.getX(distUnits) + dist, mol.getY(distUnits) + dist }), threeD, dist2Thr, distUnits); } } catch(KeySizeException ex) { // this will never happen since all the input is administered here } // // Perform the matching in the neighbourhood (given by dist2Thr) of each molecule // - note1: `det` must store the neighbors (`gt`)! // - note2: returns <det,gt> KV pairs, thus needs to be swapped for further processing Map<Molecule, Molecule> pairs = new HashMap<Molecule, Molecule>(); for (Map.Entry<Molecule, Molecule> entry : StableMatching.match(det).entrySet()) { pairs.put(entry.getValue(), entry.getKey()); } // // Set the results (TP, FP, FN) for (Molecule gtMol : gt) { Molecule detMol = pairs.get(gtMol); if(detMol != null) { gtMol.setStatus(TRUE_POSITIVE); detMol.setStatus(TRUE_POSITIVE); FP.remove(detMol); TP.add(new Pair<Molecule, Molecule>(gtMol, detMol)); detMol.addParam(LABEL_GROUND_TRUTH_ID, Units.UNITLESS, gtMol.getParam(LABEL_ID)); detMol.addParam(LABEL_DISTANCE_TO_GROUND_TRUTH_XY, distUnits, gtMol.getDistLateral(detMol, distUnits)); detMol.addParam(LABEL_DISTANCE_TO_GROUND_TRUTH_Z, distUnits, gtMol.getDistAxial(detMol, distUnits)); detMol.addParam(LABEL_DISTANCE_TO_GROUND_TRUTH_XYZ, distUnits, gtMol.getDist(detMol, distUnits)); } else { FN.add(gtMol); gtMol.setStatus(FALSE_NEGATIVE); } } for(Molecule detMol : FP) { detMol.setStatus(FALSE_POSITIVE); detMol.addParam(LABEL_GROUND_TRUTH_ID, Units.UNITLESS, 0); detMol.addParam(LABEL_DISTANCE_TO_GROUND_TRUTH_XY, distUnits, Double.POSITIVE_INFINITY); detMol.addParam(LABEL_DISTANCE_TO_GROUND_TRUTH_Z, distUnits, Double.POSITIVE_INFINITY); detMol.addParam(LABEL_DISTANCE_TO_GROUND_TRUTH_XYZ, distUnits, Double.POSITIVE_INFINITY); } } }