/* Last edited on 2012-12-26 17:38:23 by stolfilocal */ /* Procedures for filtering multidimensional signals with {float}-valued samples. */ package minetto.utils; import java.lang.Math; import java.lang.Float; import java.util.Arrays; import minetto.utils.FloatSignal; public class FloatSignalFilter { /* === BINARY MULTISCALE DECOMPOSITION ==================================== */ /* Multiscale signal processing is based on two basic procedures, /downsampling/ and /upsampling/ (roughly, halving and doubling the number of samples along each domain dimension). In each case one must use filters to avoid aliasing of high frequencies to low frequencies (in downsampling) and introduction of spurious high frequencies (in upsampling). A filter is just a list of {nw} "tap weights". In a downsampling filter, these weights used to combine {nw} consecutive samples of the old signal into one sample of the new signal. They must add to 1. An upsampling filter is used in the same way; however, for each new sample only half of the taps will have an old sample to connect to. Therefore the even tap weights must add to 1, and the odd tap weights must add to 1. In either case, the weights must be symmetrical under reversal. The resulting samples will be centered on the filter window. As a consequence, the sample slots of the coarser (small) sequence will be aligned with every other sample slot of the finer (large) sequence -- either at their centers or at their low ends, depending on the parity of the filter's size. See the following diagram: For filters with even number of taps (say, 4): | up filter tap indices (odd) ............0.......2................ | up filter tap slots (odd) ........+-------+-------+............ | up filter tap indices (even) ................1.......3............ | up filter tap slots (even) ............+-------+-------+........ | | small signal sample indices ....0.......1.......2.......3........ | small signal sample slots +-------+-------+-------+-------+.... | large signal sample slots +---+---+---+---+---+---+---+---+.... | large signal sample indices ..0...1...2...3...4...5...6...7...... | | dn filter tap slots ............+---+---+---+---+........ | dn filter tap indices ..............0...1...2...3.......... For filters with an odd number of taps (say, 5): | up filter tap indices (odd) ................1.......3............ | up filter tap slots (odd) ............+-------+-------+........ | up filter tap indices (even) ............0.......2.......4........ | up filter tap slots (even) ........+-------+-------+-------+.... | | small signal sample indices ....0.......1.......2.......3........ | small signal sample slots +-------+-------+-------+-------+--.. | large signal sample slots ..+---+---+---+---+---+---+---+---+.. | large signal sample indices ....0...1...2...3...4...5...6...7.... | | dn filter tap slots ..........+---+---+---+---+---+...... | dn filter tap indices ............0...1...2...3...4........ Note that if the parity of the filter width does not match the parity of the large signal's length (number of samples) then the last sample of the large signal will be lost on downsampling, and cannot be decently reconstructed on upsampling. */ /* === DOWNSAMPLING ===================================================== */ /* Weights for downsampling with each filter size: */ /* They must add to exactly 1.0. */ /* !!! Not sure these are optimal !!! */ static final double dn_weights[][] = { { }, { 1.0/ 2, 1.0/ 2 }, { 1.0/ 4, 2.0/ 4, 1.0/ 4 }, { 1.0/ 8, 3.0/ 8, 3.0/ 8, 1.0/ 8 }, { 1.0/16, 4.0/16, 6.0/16, 4.0/16, 1.0/16 }, { 1.0/32, 5.0/32, 10.0/32, 10.0/32, 5.0/32, 1.0/32 }, }; /* ---------------------------------------------------------------------- Downsamples a multidimensional signal {sig_old} along a specified dimension {rdn}, using a filter with {filter_size} taps. Returns the resulting signal {sig_new}. In principle, {sig_new} it will have one sample for every two samples of {sig_old} that are consecutive in the direction {rdn}; but the result may be shaved or padded slightly on both extremes of that direction, and these adjustments may depend on the parity of {filter_size}. More precisely, the size {n_new} of {sig_new} along that dimension will be {downsampled_signal_size(n_old,filter_size)}, where where {n_old} is {sig_old.size[rdn]}. If {avg_old} and {avg_new} are null, the procedure assumes that each sample of {sig_old} is some sort of `density' averaged over some neighborhood of the sample's nominal position. (For example, the light energy falling on an element of imaging sensor, divided by the element's area) Thus, each sample of the downsampled signal {sig_new} will be a weighted average of nearby samples in {sig_old}. On the other hand,if {avg_old} and {avg_new} are non-null, the procedure assumes that {sig_old} is a /variance signal/, where each sample slot is the weighted variance of the point-to-point density in that neighborhood. In that case, it expects {avg_old} to be the original intensity signal corresponding to {sig_old}, and {avg_new} to be the downsampled version of {avg_old}, obtained by {downsample} with the same filter order; and it will add to each sample of {sig_new} the weighted variance of the samples of {avg_old} with respect to the sample of {avg_new}. The signals {avg_old} and {avg_new}, if, given, must have the same size as {sig_old} (and {sig_new}) along every domain dimension except dimension {rdn}. In that direction, the size of {avg_old} must be {n_old}, and that of {avg_new} must be {n_new}. */ public static FloatSignal downsample ( FloatSignal sig_old, int rdn, int filter_size, FloatSignal avg_old, FloatSignal avg_new ) { /* Decide if we are downsampling a variance signal instead of an intensity one: */ boolean is_var_signal = (avg_old != null); assert(is_var_signal == (avg_new != null)); /* Validate the array sizes and indexing parameters: */ assert(sig_old.is_consistent()); if (is_var_signal) { assert(avg_old.is_consistent() && avg_new.is_consistent()); } /* Get number of dimensions, validate {rdn}: */ int nd = sig_old.nd; if (is_var_signal) { assert((avg_old.nd == nd) && (avg_new.nd == nd)); } assert((rdn >= 0) && (rdn < nd)); /* Compute the size of the original and downsampled signal along the filtering axis: */ int n_old = sig_old.size[rdn]; int n_new = downsampled_signal_size(n_old, filter_size); /* Allocate the new signal: */ int[] size_new = Arrays.copyOf(sig_old.size, nd); size_new[rdn] = n_new; FloatSignal sig_new = new FloatSignal(nd, size_new); assert(sig_new.is_consistent()); /* Paranoia is good. */ /* Check if sizes match and whether the result is empty: */ boolean no_samples = false; for (int r = 0; r < nd; r++) { assert(sig_new.size[r] == (r == rdn ? n_new : sig_old.size[r])); if (sig_new.size[r] == 0) { no_samples = true; if (is_var_signal) { assert(avg_old.size[r] == sig_old.size[r]); assert(avg_new.size[r] == sig_new.size[r]); } } } if (no_samples) { /* Nothing to do: */ return sig_new; } for (int xn = 0; xn < n_new; xn++) { System.err.println(" xn = " + xn); /* Compute the slice of {sig_new} where {ix[rdn] == xn}: */ /* Select the filter to use for this particular slice (may be different near ends): */ double[] weights = select_downsampling_filter(xn, filter_size, n_old); /* Get index of slice of {sig_old} corresponding to filter tap 0 for slice {xn} of {sig_new}: */ int xo_min = downsampling_tapped_sample_index(xn, 0, weights.length); /* Get start positions and step vectors of those slices: */ int[] pos = new int[4]; /* Positions in {sig_old,sig_new,avg_old,avg_new}. */ int[][] step = new int[4][]; /* Positions increments of {sig_old,sig_new,avg_old,avg_new}. */ step[0] = sig_old.step; pos[0] = sig_old.start + sig_old.step[rdn]*xo_min; step[1] = sig_new.step; pos[1] = sig_new.start + sig_old.step[rdn]*xn; if (is_var_signal) { step[2] = avg_old.step; pos[2] = avg_old.start + avg_old.step[rdn]*xo_min; step[3] = avg_new.step; pos[3] = avg_new.start + avg_new.step[rdn]*xn; } else { step[2] =null; pos[2] = 0; step[3] =null; pos[3] = 0; } /* Get the size of the slices where index {ix[kdn]} is fixed: */ int[] slice_size = Arrays.copyOf(sig_old.size, nd); slice_size[rdn] = 1; /* Scan all valid combinations of the indices other than dimension {rdn}: */ int ix[] = new int[nd]; Arrays.fill(ix,0); boolean exhausted = false; while (! exhausted) { /* Let {ix!a} stand for the index vector {ix} with {ix[rdn]} replaced by {a}. In this iteration we compute one new sample whose indices are {ix!xn}. For that, we combine the old samples whose indices are {ix!xo} where {xo} ranges from {xo_min} to {xo_min + w.length-1}. At this point, {pos[1],pos[3]} are the positions of samples in {sig_new,avg_new} with indices {ix!xn}, and {pos[0],pos[2]} are the positions of samples in {sig_old,avg_old} with indices {ix!xo_min}. */ //System.err.println(" ix = [" + ix[0] + "," + ix[1] + "," + ix[2] + "]"); int sig_old_p = pos[0]; int avg_old_p = pos[2]; /* If we are downsampling a variance signal, get the mean intensity {a_new} in the window: */ double a_new = ( is_var_signal ? (double)avg_new.smp[pos[3]] : 0.0 ); double sumw = 0; double sumvw = 0; int xo = xo_min; for (int kw = 0; kw < weights.length; kw++) { assert((xo >= 0) && (xo = 0) && (rdn < nd)); /* Compute the size of the original and validate requested size: */ int n_old = sig_old.size[rdn]; assert(n_old == downsampled_signal_size(n_new, filter_size)); /* Allocate the new signal: */ int[] size_new = Arrays.copyOf(sig_old.size, nd); size_new[rdn] = n_new; FloatSignal sig_new = new FloatSignal(nd, size_new); assert(sig_new.is_consistent()); /* Paranoia is good. */ /* Check if sizes match and whether the result is empty: */ boolean no_samples = false; for (int r = 0; r < nd; r++) { assert(sig_new.size[r] == (r == rdn ? n_new : sig_old.size[r])); if (sig_new.size[r] == 0) { no_samples = true; } } if (no_samples) { /* Nothing to do: */ return sig_new; } for (int xn = 0; xn < n_new; xn++) { /* Compute the slice of {sig_new} where {ix[rdn] == xn}: */ /* Select the filter to use for this particular slice (may be different near ends): */ double[] weights = select_upsampling_filter(xn, filter_size, n_old); /* Find the first tap {kwmin} of the filter that is used for sample {xn} of {sig_new}: */ int xo_min_x2 = upsampling_tapped_sample_index_x2(xn, 0, weights.length); int kwmin = (xo_min_x2 % 2); /* Get index {xo_min} of slice of {sig_old} tapped by that tap: */ int xo_min = (xo_min_x2 + kwmin)/2; /* Get start positions and step vectors of those slices: */ int[] pos = new int[2]; /* Positions in {sig_old,sig_new,avg_old,avg_new}. */ int[][] step = new int[2][]; /* Positions increments of {sig_old,sig_new,avg_old,avg_new}. */ step[0] = sig_old.step; pos[0] = sig_old.start + sig_old.step[rdn]*xo_min; step[1] = sig_new.step; pos[1] = sig_new.start + sig_old.step[rdn]*xn; /* Get the size of the slices where index {ix[kdn]} is fixed: */ int[] slice_size = Arrays.copyOf(sig_old.size, nd); slice_size[rdn] = 1; /* Scan all valid combinations of the indices other than dimension {rdn}: */ int ix[] = new int[nd]; Arrays.fill(ix,0); boolean exhausted = false; while (! exhausted) { /* Let {ix!a} stand for the index vector {ix} with {ix[rdn]} replaced by {a}. In this iteration we compute one new sample whose indices are {ix!xn}. For that, we combine the old samples whose indices are {ix!xo} where {xo} ranges from {xo_min} to {xo_min + w.length-1}. At this point, {pos[1]} is the position of samples in {sig_new} with indices {ix!xn}, and {pos[0]} is the position of sample in {sig_old} with indices {ix!xo_min}. */ int sig_old_p = pos[0]; double sumw = 0; double sumvw = 0; int xo = xo_min; for (int kw = kwmin; kw < weights.length; kw += 2) { assert((xo >= 0) && (xo = 0); assert(n_small >= 0); assert(n_small == downsampled_signal_size(n_large, filter_size)); double shift = 0.5*(filter_size % 2); return 2*z_small + shift; } /* ---------------------------------------------------------------------- Maps a coordinate {z_large} (either X or Y) of a point in the domain of an original signal {old} to the same coordinate of the corresponding point of the downsampled signal {small}. Requires the sizes {n_small,n_large} (in sample slots) of the two signals, along the same coordinate axis. */ public static double map_coord_from_original_to_downsampled(double z_large, int n_large, int n_small, int filter_size) { assert(n_large >= 0); assert(n_small >= 0); assert(n_small == downsampled_signal_size(n_large, filter_size)); double shift = 0.5*(filter_size % 2); return (z_large - shift)/2; } }