Java tutorial
package gdsc.core.utils; import java.util.Arrays; import org.apache.commons.math3.util.FastMath; /*----------------------------------------------------------------------------- * GDSC SMLM Software * * Copyright (C) 2013 Alex Herbert * Genome Damage and Stability Centre * University of Sussex, UK * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. *---------------------------------------------------------------------------*/ /** * Provides a rolling median window on a data array */ public class MedianWindow { private int radius, position = 0, cachePosition = -1; private double[] data, cache = null; private double median = Double.NaN; private boolean invalid = true; private boolean sortedScan = false; /** * @param data * @param radius * @throws IllegalArgumentException * if the input data is null * @throws IllegalArgumentException * if the radius is negative */ public MedianWindow(double[] data, int radius) { if (data == null) throw new IllegalArgumentException("Input data must not be null"); if (radius < 0) throw new IllegalArgumentException("Radius must not be negative"); this.data = data; this.radius = radius; } /** * Move the current position along the data array * * @return True if the position is valid, False if the position is beyond the end of the array */ public boolean increment() { invalid = true; return (++position < data.length); } /** * Move the current position along the data array the specified amount * * @param size * @return True if the position is valid, False if the position is beyond the end of the array */ public boolean increment(int size) { invalid = true; position += Math.abs(size); return (position < data.length); } /** * @return the radius */ public int getRadius() { return radius; } /** * @return the current position. This may be beyond the end of the array. * @see {@link #increment()} */ public int getPosition() { return position; } /** * Set the position. Negative positions are set to zero. * * @param position * Set the current position */ public void setPosition(int position) { position = FastMath.max(0, position); // If moving backwards then delete the cache if (position < this.position) cache = null; invalid = this.position != position || cache == null; this.position = position; } /** * @return True if the current position is valid */ public boolean isValidPosition() { return position < data.length; } /** * @return true if using the sorted scan method */ public boolean isSortedScan() { return sortedScan; } /** * Set to true to use a sorted scan method to replace the cached window data with new values. In this method the * values to replace are extracted into a sorted array. The cached window data can be scanned once in ascending * order. * <p> * The default is to search the window data for each value to replace directly resulting in N scans for the N * replacements. This avoids an additional sort method. Speed tests show the direct method is marginally faster. * * @param sortedScan * the sortedScan to set */ public void setSortedScan(boolean sortedScan) { this.sortedScan = sortedScan; } /** * @return The median (or NaN is the position is invalid) */ public double getMedian() { if (invalid) { median = updateMedian(); } return median; } private double updateMedian() { invalid = false; if (position >= data.length) return Double.NaN; // Special cases if (data.length == 1) return data[0]; if (radius == 0) return data[position]; // The position could be updated and then reset to the same position if (cachePosition == position) return median; // The position should always be above the cache position assert cachePosition < position : "Cache position is greater than the position"; // The cache contains the sorted window from the cachePosition. The cache should cover // a set of the data that requires updating: // cachePosition // | Position // | | // Old ------------------------- // New ========================= // Remove ------ // Keep +++++++++++++++++++ // Add ====== final int newStart = FastMath.max(0, position - radius); final int newEnd = FastMath.min(position + radius + 1, data.length); final int newLength = newEnd - newStart; // Speed tests have shown that if the total increment is more than half the radius it // is faster to recompute if (cache == null || position - cachePosition > radius / 2 || cache.length != newLength) { cache = new double[newLength]; for (int i = newStart, j = 0; i < newEnd; i++, j++) cache[j] = data[i]; } else { // This point is only reached when we have a set of sorted numbers in the cache // and we want to replace N of them with N new numbers. final int cacheStart = FastMath.max(0, cachePosition - radius); final int cacheEnd = FastMath.min(cachePosition + radius + 1, data.length); final int middle = cache.length / 2; final double middleValue = cache[middle]; if (sortedScan) { // Method using search of the cached array with sorted numbers to remove // Extract numbers to remove double[] dataToRemove = new double[position - cachePosition]; for (int remove = cacheStart, i = 0; remove < newStart; remove++, i++) dataToRemove[i] = data[remove]; Arrays.sort(dataToRemove); for (int remove = 0, add = cacheEnd, cachePosition = 0; remove < dataToRemove.length; remove++) { final double toRemove = dataToRemove[remove]; final int add2 = add; // Find the number in the cache for (; cachePosition < cache.length; cachePosition++) { if (cache[cachePosition] == toRemove) { // Replace with new data cache[cachePosition++] = data[add++]; break; } } if (add == add2) { // This is bad. Just recompute the entire cache System.out.printf("MedianWindow : Failed to replace data in the cache\n"); cache = new double[newLength]; for (int i = newStart, j = 0; i < newEnd; i++, j++) cache[j] = data[i]; break; } } } else { // Method using direct search of the cached array // Iterate over numbers to remove for (int remove = cacheStart, add = cacheEnd; remove < newStart; remove++) { final double toRemove = data[remove]; final int add2 = add; // Find the number in the cache if (toRemove > middleValue) { for (int i = cache.length; i-- > 0;) { if (cache[i] == toRemove) { // Replace with new data cache[i] = data[add++]; break; } } } else { for (int i = 0; i < cache.length; i++) { if (cache[i] == toRemove) { // Replace with new data cache[i] = data[add++]; break; } } } if (add == add2) { // This is bad. Just recompute the entire cache System.out.printf("MedianWindow : Failed to replace data in the cache\n"); cache = new double[newLength]; for (int i = newStart, j = 0; i < newEnd; i++, j++) cache[j] = data[i]; break; } } } } Arrays.sort(cache); cachePosition = position; return (cache[(cache.length - 1) / 2] + cache[cache.length / 2]) * 0.5; } }