001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.processing.algorithm;
031
032import org.openimaj.image.FImage;
033
034import edu.emory.mathcs.jtransforms.fft.FloatFFT_2D;
035
036/**
037 * Perform forward and inverse Fast Fourier Transforms on image data.
038 * 
039 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
040 *
041 */
042public class FourierTransform {
043        private FImage phase;
044        private FImage magnitude;
045        private boolean centre;
046
047        /**
048         * Construct Fourier Transform by performing a forward transform
049         * on the given image. If the centre option is set, the FFT will 
050         * be re-ordered so that the DC component is in the centre.
051         * @param image the image to transform
052         * @param centre should the FFT be reordered so the centre is DC component
053         */
054        public FourierTransform(FImage image, boolean centre) {
055                this.centre = centre;
056
057                process(image);
058        }
059        
060        /**
061         * Construct Fourier Transform object from the given magnitude and
062         * phase images in the frequency domain. The resultant object can
063         * then be used to construct the image using the {@link #inverse()}
064         * method.
065         * 
066         * @param magnitude the magnitude image
067         * @param phase the phase image
068         * @param centre is the DC component in the image centre?
069         */
070        public FourierTransform(FImage magnitude, FImage phase, boolean centre) {
071                this.centre = centre;
072                this.magnitude = magnitude;
073                this.phase = phase;
074        }
075        
076        /**
077         * Prepare data for a input to the FFT, padding if necessary.
078         * 
079         * @param input input data
080         * @param rs desired number of rows
081         * @param cs desired number of columns
082         * @param centre if true, then the data will be prepared so that the DC component is centred.
083         * @return prepared data
084         */
085        public static float[][] prepareData(FImage input, int rs, int cs, boolean centre) {
086                return prepareData(input.pixels, rs, cs, centre);
087        }
088        
089        /**
090         * Prepare data for a input to the FFT, padding if necessary. The data is
091         * prepared as a packed 1D array.
092         * 
093         * @param input input data
094         * @param rs desired number of rows
095         * @param cs desired number of columns
096         * @param centre if true, then the data will be prepared so that the DC component is centred.
097         * @return prepared data
098         */
099        public static float[] prepareData1d(FImage input, int rs, int cs, boolean centre) {
100                return prepareData1d(input.pixels, rs, cs, centre);
101        }
102        
103        /**
104         * Prepare data for a input to the FFT, padding if necessary.
105         * 
106         * @param input input data
107         * @param rs desired number of rows
108         * @param cs desired number of columns
109         * @param centre if true, then the data will be prepared so that the DC component is centered.
110         * @return prepared data
111         */
112        public static float[][] prepareData(float[][] input, int rs, int cs, boolean centre) {
113                float[][] prepared = new float[rs][cs*2];
114
115                if (centre) {
116                        for(int r = 0; r < Math.min(rs, input.length) ; r++) {
117                                for(int c = 0; c < Math.min(cs, input[0].length); c++) {
118                                        prepared[r][c*2] = input[r][c] * (1 - 2 * ((r+c)%2) );
119                                }
120                        }
121                } else {
122                        for(int r = 0; r < Math.min(rs, input.length) ; r++) {
123                                for(int c = 0; c < Math.min(cs, input[0].length); c++) {
124                                        prepared[r][c*2] = input[r][c];
125                                }
126                        }
127                }
128
129                return prepared;
130        }
131        
132        /**
133         * Prepare data for a input to the FFT, padding if necessary. The data is
134         * prepared as a packed 1D array.
135         * 
136         * @param input input data
137         * @param rs desired number of rows
138         * @param cs desired number of columns
139         * @param centre if true, then the data will be prepared so that the DC component is centered.
140         * @return prepared data
141         */
142        public static float[] prepareData1d(float[][] input, int rs, int cs, boolean centre) {
143                float[] prepared = new float[rs * cs * 2];
144                
145                if (centre) {
146                        for(int r = 0; r < Math.min(rs, input.length) ; r++) {
147                                for(int c = 0; c < Math.min(cs, input[0].length); c++) {
148                                        prepared[r * 2 * cs + 2*c] = input[r][c] * (1 - 2 * ((r+c)%2) );
149                                }
150                        }
151                } else {
152                        for(int r = 0; r < Math.min(rs, input.length) ; r++) {
153                                for(int c = 0; c < Math.min(cs, input[0].length); c++) {
154                                        prepared[r * 2 * cs + 2*c] = input[r][c];
155                                }
156                        }
157                }
158
159                return prepared;
160        }
161
162        /**
163         * Extract the actual data from prepared data. The output image
164         * must have the same number of rows as the prepared data, and
165         * half the number of columns.
166         * 
167         * @param prepared the prepared data
168         * @param output the output 
169         * @param centre if true, then the data will be prepared so that the DC component is centered.
170         */
171        public static void unprepareData(float[][] prepared, FImage output, boolean centre) {
172                unprepareData(prepared, output.pixels, centre);
173        }
174        
175        /**
176         * Extract the actual data from prepared data. The output image
177         * must have the same number of rows as the prepared data, and
178         * half the number of columns.
179         * 
180         * @param prepared the prepared data
181         * @param output the output 
182         * @param centre if true, then the data will be prepared so that the DC component is centered.
183         */
184        public static void unprepareData(float[] prepared, FImage output, boolean centre) {
185                unprepareData(prepared, output.pixels, centre);
186        }
187        
188        /**
189         * Extract the actual data from prepared data. The output array
190         * must have the same number of rows as the prepared data, and
191         * half the number of columns.
192         * 
193         * @param prepared the prepared data
194         * @param output the output 
195         * @param centre if true, then the data will be prepared so that the DC component is centered.
196         */
197        public static void unprepareData(float[][] prepared, float[][] output, boolean centre) {
198                int rs = output.length;
199                int cs = output[0].length;
200                
201                if (centre) {
202                        for(int r = 0; r < rs ; r++) {
203                                for(int c = 0; c < cs; c++) {
204                                        output[r][c] = prepared[r][c*2] * (1 - 2 * ((r+c)%2) );
205                                }
206                        }
207                } else {
208                        for(int r = 0; r < rs ; r++) {
209                                for(int c = 0; c < cs; c++) {
210                                        output[r][c] = prepared[r][c*2];
211                                }
212                        }
213                }
214        }
215        
216        /**
217         * Extract the actual data from prepared data. The output array
218         * must have the same number of rows as the prepared data, and
219         * half the number of columns.
220         * 
221         * @param prepared the prepared data
222         * @param output the output 
223         * @param centre if true, then the data will be prepared so that the DC component is centered.
224         */
225        public static void unprepareData(float[] prepared, float[][] output, boolean centre) {
226                int rs = output.length;
227                int cs = output[0].length;
228                
229                if (centre) {
230                        for(int r = 0; r < rs ; r++) {
231                                for(int c = 0; c < cs; c++) {
232                                        output[r][c] = prepared[r * 2 * cs + 2*c] * (1 - 2 * ((r+c)%2) );
233                                }
234                        }
235                } else {
236                        for(int r = 0; r < rs ; r++) {
237                                for(int c = 0; c < cs; c++) {
238                                        output[r][c] = prepared[r * 2 * cs + 2*c];
239                                }
240                        }
241                }
242        }
243        
244        private void process(FImage image) {
245                int cs = image.getCols();
246                int rs = image.getRows();
247                
248                phase = new FImage(cs, rs);
249                magnitude = new FImage(cs, rs);
250                
251                FloatFFT_2D fft = new FloatFFT_2D(rs,cs);
252                float[][] prepared = prepareData(image.pixels, rs, cs, centre);
253        
254                fft.complexForward(prepared);
255                
256                for(int y = 0; y < rs; y++){
257                        for(int x = 0; x < cs; x++){
258                                float re = prepared[y][x*2];
259                                float im = prepared[y][1 + x*2];
260                                
261                                phase.pixels[y][x] = (float) Math.atan2(im, re);
262                                magnitude.pixels[y][x] = (float) Math.sqrt(re*re + im*im);
263                        }
264                }
265        }
266        
267        /**
268         * Perform the inverse FFT using the underlying magnitude
269         * and phase images. The resultant reconstructed image
270         * may need normalisation.
271         * 
272         * @return the reconstructed image
273         */
274        public FImage inverse() {
275                int cs = magnitude.getCols();
276                int rs = magnitude.getRows();
277                
278                FloatFFT_2D fft = new FloatFFT_2D(rs,cs);
279                float[][] prepared = new float[rs][cs*2];
280                for(int y = 0; y < rs; y++) {
281                        for(int x = 0; x < cs; x++) {
282                                float p = phase.pixels[y][x];
283                                float m = magnitude.pixels[y][x];
284
285                                float re = (float) (m*Math.cos(p));
286                                float im = (float) (m*Math.sin(p));
287                                
288                                prepared[y][x*2] = re;
289                                prepared[y][1 + x*2] = im;                              
290                        }
291                }
292                
293                fft.complexInverse(prepared, true);
294                
295                FImage image = new FImage(cs, rs);
296                unprepareData(prepared, image, centre);
297                
298                return image;
299        }
300        
301        /**
302         * Get a log-normalised copy of the magnitude image suitable
303         * for displaying.
304         * @return a log-normalised copy of the magnitude image 
305         */
306        public FImage getLogNormalisedMagnitude() {
307                FImage im = magnitude.clone();
308                
309                for (int y=0; y<im.height; y++) {
310                        for (int x=0; x<im.width; x++) {
311                                im.pixels[y][x] = (float) Math.log(im.pixels[y][x] + 1);
312                        }
313                }
314                
315                return im.normalise();
316        }
317        
318        /**
319         * @return the phase image
320         */
321        public FImage getPhase() {
322                return phase;
323        }
324
325        /**
326         * @return the magnitude image
327         */
328        public FImage getMagnitude() {
329                return magnitude;
330        }
331
332        /**
333         * @return true if the DC component is in the centre; false otherwise
334         */
335        public boolean isCentre() {
336                return centre;
337        }
338}