@Override public void extractFeatures(List regions) throws FeatureExtractorException { for (Region r : (List<Region>) regions) { if (r.hasAttribute(feature)) { Contour super_c = (Contour) r.getAttribute(feature); Contour c; try { c = ContourUtils.getSubContour(super_c, r.getStart(), r.getEnd()); } catch (AuToBIException e) { throw new FeatureExtractorException(e.getMessage()); } CurveShape falling = smooth(-1, true, c); CurveShape rising = smooth(c.size(), true, c); CurveShape best_peak = null; double min_peak_rmse = Double.MAX_VALUE; // Sample the curve at at most 20 points to calculate peak and valley likelihoods. // On long regions calculating at every point gets *very* slow. int step = Math.max(1, c.size() / 20); for (int i = 1; i < c.size() - 1; i += step) { CurveShape peak = smooth(i, true, c); if (peak.rmse < min_peak_rmse) { best_peak = peak; min_peak_rmse = peak.rmse; } } CurveShape best_valley = null; double min_valley_rmse = Double.MAX_VALUE; for (int i = 1; i < c.size() - 1; i += step) { CurveShape valley = smooth(i, false, c); if (valley.rmse < min_valley_rmse) { best_valley = valley; min_valley_rmse = valley.rmse; } } r.setAttribute("risingCurve[" + feature + "]", rising); r.setAttribute("fallingCurve[" + feature + "]", falling); r.setAttribute("peakCurve[" + feature + "]", best_peak); r.setAttribute("valleyCurve[" + feature + "]", best_valley); } } }
private CurveShape smooth(int p, boolean isPeak, Contour c) { CurveShape curve = new CurveShape(p, isPeak); if (p > 0 && p < c.size() && c.isEmpty(p)) { curve.rmse = Double.MAX_VALUE; return curve; } // reflect values around the peak double[] v = new double[c.size()]; for (int i = 0; i < c.size(); ++i) { if (!c.isEmpty(i)) { if (i >= p) { if (p < 0) { v[i] = -c.get(i); } else { v[i] = 2 * c.get(p) - c.get(i); } } else { v[i] = c.get(i); } if (!isPeak) { v[i] = -v[i]; } } else { v[i] = Double.NaN; } } ArrayList<Block> pava_blocks = new ArrayList<Block>(); // initialize blocks for (int i = 0; i < c.size(); ++i) { if (!Double.isNaN(v[i])) { pava_blocks.add(new Block(i, i, v[i])); } } // Merge blocks using PAVA boolean done = false; boolean increasing = true; int idx = 1; int start_merge = -1, end_merge = -1; while (!done) { while (idx < pava_blocks.size()) { // monotonically increasing. if (pava_blocks.get(idx - 1).x < pava_blocks.get(idx).x) { if (!increasing) { // merge break; } } else if (pava_blocks.get(idx - 1).x > pava_blocks.get(idx).x) { if (increasing) { start_merge = idx - 1; } end_merge = idx; increasing = false; } idx++; } done = true; if (start_merge != end_merge) { // merge blocks double value = 0.0; for (int i = start_merge; i <= end_merge; ++i) { value += pava_blocks.get(i).x * (pava_blocks.get(i).high_idx - pava_blocks.get(i).low_idx + 1); } value /= (pava_blocks.get(end_merge).high_idx - pava_blocks.get(start_merge).low_idx + 1); Block new_block = new Block( pava_blocks.get(start_merge).low_idx, pava_blocks.get(end_merge).high_idx, value); // remove the old blocks while (start_merge <= end_merge) { pava_blocks.remove(start_merge); end_merge--; } // replace with the new, merged block pava_blocks.add(start_merge, new_block); done = false; // Reset idx = 1; start_merge = -1; end_merge = -1; increasing = true; } } // Generate a smooth contour from block representation double[] smoothed = new double[c.size()]; for (Block b : pava_blocks) { for (int i = b.low_idx; i <= b.high_idx; ++i) { if (!c.isEmpty(i)) { if (i >= p) { if (p < 0) { smoothed[i] = -b.x; } else { if (!isPeak) { smoothed[i] = 2 * -c.get(p) - b.x; } else { smoothed[i] = 2 * c.get(p) - b.x; } } } else { smoothed[i] = b.x; } } if (!isPeak) { smoothed[i] = -smoothed[i]; } } } // Calculate RMSE double rmse = 0.0; for (int i = 0; i < c.size(); ++i) { if (!c.isEmpty(i)) { rmse += (c.get(i) - smoothed[i]) * (c.get(i) - smoothed[i]); } } rmse /= c.contentSize(); rmse = Math.sqrt(rmse); curve.rmse = rmse; curve.smoothed_curve = smoothed; return curve; }