[HaarWavelets.rar]
As I mentioned in a recent post, I’ve recently started studying the subject of wavelets, a mathematical transform with many interesting applications than range from image compression to audio de-noising. I won’t go into detail of what wavelets are and how they work, though I intend to do so in a later blog post once I feel more comfortable with the subject, but in the meantime I decided to implement the Haar Wavelets, which are the simplest kind, in C#. The implementation itself is nothing fancy and rather straightforward, but I find the operations themselves and how they work to be very appealing.
The interface for the class is very conservative, offering methods to acquire the trend and fluctuation sub-signals from an input as well as methods to reconstruct a signal from its trend and fluctuations. I also included methods to perform transforms and inverse transforms which take the number of levels of the desired (Each level represents one application of the transform). Since I’m using doubles as the numeric type, there are a few numeric stability issues which I decided not to get into at the time. They could be reduced by simply using the decimal type instead or considering them when working with the class.
Here’s the full source code, which you can also download from the link above. The class could be made much shorter and a bit more efficient by offering only methods to perform the transform and the inverse transform, instead of breaking the functionality into pieces and allowing clients to obtain only the trend or fluctuation sub-signals. Particuarly, performing a full one-level transform would require traversing the input only once whereas in the current implementation it’s done twice since the trend and fluctuations are calculated separately. In spite of this, I prefer the current implementation because it offers a clear and explicit way to work with the transforms rather than having to use one array that holds all the required sub-signals similar to what the Transform method returns.
1: using System;
2: using System.Linq;
3:
4: namespace RaineTech.Math.Wavelets
5: {
6: ///
7: /// Represents the Haar Wavelets.
8: ///
9: public class HaarWavelets
10: {
11: ///
12: /// Calculates the trend sub-signal for the specified input.
13: ///
14: /// The input from which to calculate the trend. Its length must be divisible by 2.
15: ///An array of half the length of the input containing the trend sub-signal.
16: public double[] GetTrendSignal(double[] input)
17: {
18: return GetTrendSignal(input, 0, input.Length);
19: }
20:
21: ///
22: /// Calculates the trend sub-signal for the specified input.
23: ///
24: /// The input from which to calculate the trend.
25: /// The offset into the input array.
26: /// The number of elements to take from the input array. This must be a multiple of 2.
27: ///An array of half the length of the input containing the trend sub-signal.
28: public double[] GetTrendSignal(double[] input, int offset, int count)
29: {
30: if (count % 2 != 0)
31: {
32: throw new ArgumentException("Input must have an even number of elements.");
33: }
34: int trendLength = count / 2;
35: double[] trend = new double[trendLength];
36: for (int i = 0; i < trendLength; ++i)
37: {
38: trend[i] = (input[offset + 2 * i] + input[offset + 2 * i + 1]) / System.Math.Sqrt(2.0);
39: }
40: return trend;
41: }
42:
43: ///
44: /// Calculates the fluctuation sub-signal for the specified input.
45: ///
46: /// The input from which to calculate the fluctuations. Its length must be divisible by 2.
47: ///An array of half the length of the input containing the fluctuation sub-signal.
48: public double[] GetFluctuationSignal(double[] input)
49: {
50: return GetFluctuationSignal(input, 0, input.Length);
51: }
52:
53: ///
54: /// Calculates the fluctuation sub-signal for the specified input.
55: ///
56: /// The input from which to calculate the fluctuations.
57: /// The offset into the input array.
58: /// The number of elements to take from the input array. This number must be a multiple of 2.
59: ///An array of half the length of the input containing the fluctuation sub-signal.
60: public double[] GetFluctuationSignal(double[] input, int offset, int count)
61: {
62: if (count % 2 != 0)
63: {
64: throw new ArgumentException("Input must have an even number of elements.");
65: }
66: int fluctuationsLength = count / 2;
67: double[] fluctuations = new double[fluctuationsLength];
68: for (int i = 0; i < fluctuationsLength; ++i)
69: {
70: fluctuations[i] = (input[offset + 2 * i] - input[offset + 2 * i + 1]) / System.Math.Sqrt(2.0);
71: }
72: return fluctuations;
73: }
74:
75: ///
76: /// Reconstructs a signal from its equal-length trend and fluctuation sub-signals. This is equivalent to a
77: /// one-level inverse transform.
78: ///
79: /// The trend sub-signal.
80: /// The fluctuation sub-signal.
81: ///The reconstructed signal.
82: public double[] Reconstruct(double[] trendSignal, double[] fluctuationSignal)
83: {
84: double[] result = new double[trendSignal.Length + fluctuationSignal.Length];
85: for (int i = 0; i < trendSignal.Length; ++i)
86: {
87: result[2 * i] = (trendSignal[i] + fluctuationSignal[i]) / System.Math.Sqrt(2.0);
88: result[2 * i + 1] = (trendSignal[i] - fluctuationSignal[i]) / System.Math.Sqrt(2.0);
89: }
90: return result;
91: }
92:
93: ///
94: /// Performs an n-leveled transform on the specified input.
95: ///
96: /// The input to transform. The length of this array must be divisilbe by 2 ^ level.
97: /// The levels of the desired transform.
98: ///
99: /// An array containing the results of the transform starting by the highest leveled thrend sub-signal followed
100: /// by the fluctuation sub-signals in descending order.
101: ///
102: public double[] Transform(double[] input, int level)
103: {
104: if (input.Length % System.Math.Pow(2, level) != 0)
105: {
106: throw new ArgumentException("The number of levels specified can't be applied on the input.");
107: }
108: double[] result = new double[input.Length];
109: double[] trend, fluctuation;
110: {
111: for (int i = 0; i < level; ++i)
112: {
113: trend = GetTrendSignal(input);
114: fluctuation = GetFluctuationSignal(input);
115: input = trend;
116: for (int j = 0; j < input.Length; ++j)
117: {
118: result[j] = trend[j];
119: result[j + trend.Length] = fluctuation[j];
120: }
121: }
122: }
123: return result;
124: }
125:
126: ///
127: /// Performs an n-leveled inverse transform on the input array containing the trend and fluctuation sub-signals.
128: ///
129: ///
130: /// An array that starts with the highest leveled trend sub-signal followed by the fluctuation
131: /// sub-signals in descending order. The length of this array must be divisible by 2 ^ level.
132: ///
133: /// The desired level of the inverse transform.
134: ///An array containing the result of the inverse transform.
135: public double[] InverseTransform(double[] input, int level)
136: {
137: if (input.Length % System.Math.Pow(2, level) != 0)
138: {
139: throw new ArgumentException("The number of levels specified can't be applied on the input.");
140: }
141: double[] trend = input, fluctuation = new double[0];
142: for (int i = level; i > 0; --i)
143: {
144: int subsignalLength = input.Length / (int)System.Math.Pow(2, i);
145: trend = trend.Take(subsignalLength).ToArray();
146: fluctuation = input.Skip(subsignalLength).Take(subsignalLength).ToArray();
147: trend = Reconstruct(trend, fluctuation);
148: }
149: return trend;
150: }
151: }