001: /*
002: * @(#) $Header: /cvs/jai-operators/src/main/ca/forklabs/media/jai/opimage/DFT3DOpImage.java,v 1.5 2007/09/07 18:11:54 forklabs Exp $
003: *
004: * Copyright (C) 2007 Forklabs Daniel Léonard
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License
008: * as published by the Free Software Foundation; either version 2
009: * of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: */
020:
021: package ca.forklabs.media.jai.opimage;
022:
023: import java.awt.image.DataBuffer;
024: import java.awt.image.Raster;
025: import java.awt.image.RenderedImage;
026: import java.awt.image.SampleModel;
027: import java.lang.reflect.Array;
028: import java.lang.reflect.Field;
029: import java.util.Collection;
030: import javax.media.jai.CollectionImage;
031: import javax.media.jai.operator.DFTDataNature;
032: import javax.media.jai.operator.DFTScalingType;
033: import com.sun.media.jai.opimage.FFT;
034: import com.sun.media.jai.util.DataBufferUtils;
035: import ca.forklabs.baselib.util.Arrays;
036: import ca.forklabs.media.jai.RasterAdapter;
037: import ca.forklabs.media.jai.SimpleCollectionImage;
038: import ca.forklabs.media.jai.operator.DFT3DDescriptor;
039:
040: /**
041: * Class {@code DFT3DOpImage} is the {@link CollectionImage} for both operator
042: * <em>dft3d</em> and <em>idft3d</em>.
043: *
044: * @author <a href="mailto:forklabs at dev.java.net?subject=ca.forklabs.media.jai.opimage.DFT3DOpImage">Daniel Léonard</a>
045: * @version $Revision: 1.5 $
046: */
047: public class DFT3DOpImage extends SimpleCollectionImage {
048:
049: //---------------------------
050: // Inner classes
051: //---------------------------
052:
053: /**
054: * Class {@code Transform} configures {@link FFT} objects.
055: */
056: public static abstract class Transform {
057:
058: /**
059: * Constructor.
060: */
061: protected Transform() {
062: // nothing
063: }
064:
065: /**
066: * Creates a newly configured {@link FFT}.
067: * @param scaling the scaling type.
068: * @return a newly configured {@link FFT}.
069: */
070: public abstract FFT getDefaultTransform(DFTScalingType scaling);
071:
072: }
073:
074: // there are more inner classes at the end
075:
076: //---------------------------
077: // Class variables
078: //---------------------------
079:
080: /** Configures forward transforms. */
081: public static final Transform FORWARD_TRANSFORM = new Transform() {
082: @Override
083: public FFT getDefaultTransform(DFTScalingType scaling) {
084: boolean forward_transform = true;
085: Integer scaling_factor = Integer
086: .valueOf(scaling.getValue());
087: int len = 2;
088: FFT fft = new FFT(forward_transform, scaling_factor, len);
089: return fft;
090: }
091: };
092:
093: /** Configures inverse transforms. */
094: public static final Transform INVERSE_TRANSFORM = new Transform() {
095: @Override
096: public FFT getDefaultTransform(DFTScalingType scaling) {
097: boolean inverse_transform = false;
098: Integer scaling_factor = Integer
099: .valueOf(scaling.getValue());
100: int len = 2;
101: FFT fft = new FFT(inverse_transform, scaling_factor, len);
102: return fft;
103: }
104: };
105:
106: // there are more class variables at the end
107:
108: //---------------------------
109: // Constructors
110: //---------------------------
111:
112: /**
113: * Constructor.
114: * @param sources the list of sources.
115: * @param transform the {@link FFT} configurator.
116: * @param scaling the desired scaling.
117: * @param nature the data nature.
118: */
119: public DFT3DOpImage(CollectionImage sources, Transform transform,
120: DFTScalingType scaling, DFTDataNature nature) {
121: this .transform(sources, transform, scaling, nature);
122: }
123:
124: //---------------------------
125: // Instance methods
126: //---------------------------
127:
128: /**
129: * Performs the 3D Fourier transform.
130: * @param <T> the type of pixel.
131: * @param sources the source images.
132: * @param fft the transform object.
133: * @param nature the data nature.
134: * @param specialization the pixel specialization.
135: */
136: @SuppressWarnings({"hiding","unchecked"})
137: protected <T> void transform(CollectionImage sources, FFT fft,
138: DFTDataNature nature, PixelSpecialization<T> specialization) {
139: int width, height, depth, bands, data_type;
140: {
141: RenderedImage image = (RenderedImage) sources.get(0);
142:
143: width = image.getWidth();
144: height = image.getHeight();
145: depth = sources.size();
146:
147: SampleModel sample_model = image.getSampleModel();
148: bands = sample_model.getNumBands();
149:
150: data_type = sample_model.getDataType();
151: }
152: int len = width * height * depth;
153: int image_stride = width * height;
154:
155: DataBuffer[] source_buffers = new DataBuffer[depth];
156: DataBuffer[] sink_buffers = new DataBuffer[depth];
157: for (int i = 0; i < depth; i++) {
158: RenderedImage image = (RenderedImage) sources.get(i);
159: Raster raster = image.getData();
160:
161: DataBuffer source_buffer = raster.getDataBuffer();
162: source_buffers[i] = source_buffer;
163:
164: DataBuffer sink_buffer = specialization.newSinkBuffer(
165: image_stride, bands, nature);
166: sink_buffers[i] = sink_buffer;
167: }
168:
169: T real_data = specialization.newWorkingArray(len);
170: T imag_data = specialization.newWorkingArray(len);
171:
172: // if the source is complex, the complex data is stored as consecutive bands
173: int band_stride = (specialization.isComplexSource(nature) ? 2
174: : 1);
175: for (int b = 0; b < bands; b += band_stride) {
176: for (int d = 0, offset = 0; d < depth; d++, offset += image_stride) {
177: DataBuffer buffer = source_buffers[d];
178: specialization.fill(real_data, imag_data, buffer, b,
179: offset, nature);
180: }
181:
182: // the row transform
183: boolean should_do_row_transform = (1 != width);
184: if (should_do_row_transform) {
185: fft.setLength(width);
186: for (int i = 0, num_rows = height * depth; i < num_rows; i++) {
187: fft.setData(data_type, real_data, i * width, 1,
188: imag_data, i * width, 1, width);
189: fft.transform();
190: fft.getData(data_type, real_data, i * width, 1,
191: imag_data, i * width, 1);
192: }
193: }
194:
195: // the column transform
196: boolean should_do_col_transform = (1 != height);
197: if (should_do_col_transform) {
198: fft.setLength(height);
199: // the start of each colum is on the first
200: // row of each image, the striding is special
201: for (int k = 0; k < depth; k++) {
202: for (int i = 0; i < width; i++) {
203: int start = k * image_stride + i;
204: fft.setData(data_type, real_data, start, width,
205: imag_data, start, width, width);
206: fft.transform();
207: fft.getData(data_type, real_data, start, width,
208: imag_data, start, width);
209: }
210: }
211: }
212:
213: // the rod transform
214: boolean should_do_rod_transform = (1 != depth);
215: if (should_do_rod_transform) {
216: fft.setLength(depth);
217: for (int i = 0, num_rods = width * height; i < num_rods; i++) {
218: fft.setData(data_type, real_data, i,
219: width * height, imag_data, i, width
220: * height, depth);
221: fft.transform();
222: fft.getData(data_type, real_data, i,
223: width * height, imag_data, i, width
224: * height);
225: }
226: }
227:
228: // put data into the sink buffers
229: for (int d = 0, offset = 0; d < depth; d++, offset += image_stride) {
230: DataBuffer buffer = sink_buffers[d];
231: specialization.drain(real_data, imag_data, buffer, b,
232: offset, nature);
233: }
234:
235: }
236:
237: Collection sinks = this .getImages();
238: for (int i = 0; i < depth; i++) {
239: DataBuffer buffer = sink_buffers[i];
240: RenderedImage sink = RasterAdapter.buildImage(buffer,
241: width, height);
242: sinks.add(sink);
243: }
244: }
245:
246: protected void transformFloat(CollectionImage sources, FFT fft,
247: DFTDataNature nature) {
248: PixelSpecialization<float[]> specialization = DFT3DOpImage.FLOAT_SPECIALIZATION;
249: this .transform(sources, fft, nature, specialization);
250: }
251:
252: protected void transformDouble(CollectionImage sources, FFT fft,
253: DFTDataNature nature) {
254: PixelSpecialization<double[]> specialization = DFT3DOpImage.DOUBLE_SPECIALIZATION;
255: this .transform(sources, fft, nature, specialization);
256: }
257:
258: protected void transform(CollectionImage sources,
259: Transform transform, DFTScalingType scaling,
260: DFTDataNature nature) {
261: FFT fft = transform.getDefaultTransform(scaling);
262:
263: RenderedImage image = (RenderedImage) sources.get(0);
264: SampleModel sample_model = image.getSampleModel();
265: int data_type = sample_model.getDataType();
266: switch (data_type) {
267: case DataBuffer.TYPE_FLOAT:
268: this .transformFloat(sources, fft, nature);
269: break;
270: case DataBuffer.TYPE_DOUBLE:
271: this .transformDouble(sources, fft, nature);
272: break;
273: default:
274: String message = this .getBadDataType(data_type);
275: throw new IllegalStateException(message);
276: }
277: }
278:
279: @SuppressWarnings("boxing")
280: protected String getBadDataType(int type) {
281: String key = Resources.DFT3D_BAD_DATA_TYPE;
282: String message = Resources.getLocalizedString(key, type);
283: return message;
284: }
285:
286: //---------------------------
287: // Inner classes
288: //---------------------------
289:
290: /**
291: * Class {@link PixelSpecialization} specializes the work on the type of
292: * pixels.
293: * @param <T> the type of pixel.
294: */
295: protected static abstract class PixelSpecialization<T> {
296:
297: /**
298: * Fills both the real and imaginary work arrays with all the data of the
299: * desired band in row order.
300: * @param real the real work array.
301: * @param imag the imaginary work array.
302: * @param buffer the data buffer containing the data.
303: * @param band the desired band.
304: * @param offset the starting offset in both work arrays.
305: * @param nature the data nature.
306: */
307: public abstract void fill(T real, T imag, DataBuffer buffer,
308: int band, int offset, DFTDataNature nature);
309:
310: /**
311: * Drains both the real and imaginary work arrays into the buffer.
312: * @param real the real work array.
313: * @param imag the imaginary work array.
314: * @param buffer the data buffer containing the data.
315: * @param band the desired band.
316: * @param offset the starting offset in both work arrays.
317: * @param nature the data nature.
318: */
319: public abstract void drain(T real, T imag, DataBuffer buffer,
320: int band, int offset, DFTDataNature nature);
321:
322: /**
323: * Creates a new work array of the desired length.
324: * @param len the length.
325: * @return a new array of the desired length.
326: */
327: public abstract T newWorkingArray(int len);
328:
329: /**
330: * Creates a {@link DataBuffer} of the specialized type big enough to hold
331: * the data.
332: * @param size the size of each band.
333: * @param bands the number of bands.
334: * @param nature the data nature, complex images must have twice the
335: * number of desired bands.
336: * @return a new data buffer.
337: */
338: public abstract DataBuffer newSinkBuffer(int size, int bands,
339: DFTDataNature nature);
340:
341: /**
342: * Gets the raw array inside a {@link DataBuffer}.
343: * @param name the name of the field.
344: * @param buffer the object to search into.
345: * @return the value of the field, the raw array inside the data buffer.
346: */
347: protected Object getBankArray(String name, DataBuffer buffer) {
348: Object value;
349: try {
350: Class<?> clazz = buffer.getClass();
351:
352: Field field = clazz.getDeclaredField(name);
353: field.setAccessible(true);
354:
355: value = field.get(buffer);
356: } catch (Exception e) {
357: throw new IllegalStateException(e);
358: }
359: return value;
360: }
361:
362: /**
363: * Determines if the nature is of type <em>REAL_TO_COMPLEX</em>.
364: * @param nature the data nature.
365: * @return {@code true} if the nature is of type
366: * <em>REAL_TO_COMPLEX</em>, {@code false} otherwise.
367: */
368: protected boolean isRealToComplex(DFTDataNature nature) {
369: boolean is_real_to_complex = (nature == DFT3DDescriptor.REAL_TO_COMPLEX);
370: return is_real_to_complex;
371: }
372:
373: /**
374: * Determines if the nature is of type <em>COMPLEX_TO_COMPLEX</em>.
375: * @param nature the data nature.
376: * @return {@code true} if the nature is of type
377: * <em>COMPLEX_TO_COMPLEX</em>, {@code false} otherwise.
378: */
379: protected boolean isComplexToComplex(DFTDataNature nature) {
380: boolean is_complex_to_complex = (nature == DFT3DDescriptor.COMPLEX_TO_COMPLEX);
381: return is_complex_to_complex;
382: }
383:
384: /**
385: * Determines if the nature is of type <em>COMPLEX_TO_REAL</em>.
386: * @param nature the data nature.
387: * @return {@code true} if the nature is of type
388: * <em>COMPLEX_TO_REAL</em>, {@code false} otherwise.
389: */
390: protected boolean isComplexToReal(DFTDataNature nature) {
391: boolean is_complex_to_real = (nature == DFT3DDescriptor.COMPLEX_TO_REAL);
392: return is_complex_to_real;
393: }
394:
395: /**
396: * Determines from the nature if the source is of real type.
397: * @param nature the data nature.
398: * @return {@code true} of the source if or real type, {@code false}
399: * otherwise.
400: */
401: public boolean isRealSource(DFTDataNature nature) {
402: boolean is_real_to_complex = this .isRealToComplex(nature);
403: boolean is_real_source = is_real_to_complex;
404: return is_real_source;
405: }
406:
407: /**
408: * Determines from the nature if the source is of complex type.
409: * @param nature the data nature.
410: * @return {@code true} of the source if or complex type, {@code false}
411: * otherwise.
412: */
413: public boolean isComplexSource(DFTDataNature nature) {
414: boolean is_complex_to_real = this .isComplexToReal(nature);
415: boolean is_complex_to_complex = this
416: .isComplexToComplex(nature);
417: boolean is_complex_source = is_complex_to_real
418: || is_complex_to_complex;
419: return is_complex_source;
420: }
421:
422: /**
423: * Determines from the nature if the destination is of complex type.
424: * @param nature the data nature.
425: * @return {@code true} of the destination if or complex type,
426: * {@code false} otherwise.
427: */
428: public boolean isComplexDestination(DFTDataNature nature) {
429: boolean is_real_to_complex = this .isRealToComplex(nature);
430: boolean is_complex_to_complex = this
431: .isComplexToComplex(nature);
432: boolean is_complex_destination = is_real_to_complex
433: || is_complex_to_complex;
434: return is_complex_destination;
435: }
436:
437: /**
438: * Calculates the number of band of the sink buffer.
439: * @param bands the number of bands in the sources.
440: * @param nature the data nature, complex buffer have twice the
441: * desired number of bands.
442: * @return the adjusted number of bands.
443: */
444: protected int calculateSinkBanks(int bands, DFTDataNature nature) {
445: // the number of bands is from the source
446: // image, it is used to calculate the number
447: // of band of the destination image
448: boolean is_real_to_complex = this .isRealToComplex(nature);
449: boolean is_complex_to_complex = this
450: .isComplexToComplex(nature);
451: boolean is_complex_to_real = this .isComplexToReal(nature);
452:
453: int banks;
454: if (is_real_to_complex) {
455: banks = bands * 2;
456: } else if (is_complex_to_complex) {
457: banks = bands;
458: } else if (is_complex_to_real) {
459: banks = bands / 2;
460: } else {
461: String message = this .getUnknownDataNature(nature);
462: throw new IllegalStateException(message);
463: }
464: return banks;
465: }
466:
467: /**
468: * Gets the error message saying that the data nature is unknown.
469: * @param nature the data nature.
470: * @return the error message.
471: */
472: protected String getUnknownDataNature(DFTDataNature nature) {
473: String key = Resources.UNKNOWN_DATA_NATURE;
474: String message = Resources.getLocalizedString(key, nature);
475: return message;
476: }
477:
478: }
479:
480: /** Specialization for float pixels. */
481: protected static final PixelSpecialization<float[]> FLOAT_SPECIALIZATION = new PixelSpecialization<float[]>() {
482:
483: @Override
484: @SuppressWarnings("nls")
485: public void drain(float[] real, float[] imag,
486: DataBuffer buffer, int band, int offset,
487: DFTDataNature nature) {
488: // there is no method that provide bulk access to the
489: // data inside the data buffer, reflection is required
490: Object value = this .getBankArray("bankdata", buffer);
491:
492: // alway drain the real part
493: float[] sink = (float[]) Array.get(value, band);
494: int len = sink.length;
495: System.arraycopy(real, offset, sink, 0, len);
496:
497: // maybe sink the imaginary part
498: boolean is_complex_destination = this
499: .isComplexDestination(nature);
500: if (is_complex_destination) {
501: sink = (float[]) Array.get(value, band + 1);
502: len = sink.length;
503: System.arraycopy(imag, offset, sink, 0, len);
504: }
505: }
506:
507: @Override
508: public void fill(float[] real, float[] imag, DataBuffer buffer,
509: int band, int offset, DFTDataNature nature) {
510: // there is alway a real part
511: float[] data = DataBufferUtils.getDataFloat(buffer, band);
512: int len = data.length;
513: System.arraycopy(data, 0, real, offset, len);
514:
515: // maybe there is an imaginary part ...
516: boolean is_complex_source = this .isComplexSource(nature);
517: boolean is_real_source = this .isRealSource(nature);
518: if (is_complex_source) {
519: data = DataBufferUtils.getDataFloat(buffer, band + 1);
520: len = data.length;
521: System.arraycopy(data, 0, imag, offset, len);
522: }
523: // ... but if not, fill with zeros
524: else if (is_real_source) {
525: Arrays.memset(imag, 0.0f, offset, len);
526: } else {
527: String message = this .getUnknownDataNature(nature);
528: throw new IllegalStateException(message);
529: }
530: }
531:
532: @Override
533: public float[] newWorkingArray(int len) {
534: float[] array = new float[len];
535: return array;
536: }
537:
538: @Override
539: public DataBuffer newSinkBuffer(int size, int bands,
540: DFTDataNature nature) {
541: int banks = this .calculateSinkBanks(bands, nature);
542: DataBuffer buffer = DataBufferUtils.createDataBufferFloat(
543: size, banks);
544: return buffer;
545: }
546:
547: };
548:
549: /** Specialization for double pixels. */
550: protected static final PixelSpecialization<double[]> DOUBLE_SPECIALIZATION = new PixelSpecialization<double[]>() {
551:
552: @Override
553: @SuppressWarnings("nls")
554: public void drain(double[] real, double[] imag,
555: DataBuffer buffer, int band, int offset,
556: DFTDataNature nature) {
557: // there is no method that provide bulk access to the
558: // data inside the data buffer, reflection is required
559: Object value = this .getBankArray("bankdata", buffer);
560:
561: // alway drain the real part
562: double[] sink = (double[]) Array.get(value, band);
563: int len = sink.length;
564: System.arraycopy(real, offset, sink, 0, len);
565:
566: // maybe sink the imaginary part
567: boolean is_complex_destination = this
568: .isComplexDestination(nature);
569: if (is_complex_destination) {
570: sink = (double[]) Array.get(value, band + 1);
571: len = sink.length;
572: System.arraycopy(imag, offset, sink, 0, len);
573: }
574: }
575:
576: @Override
577: public void fill(double[] real, double[] imag,
578: DataBuffer buffer, int band, int offset,
579: DFTDataNature nature) {
580: // there is alway a real part
581: double[] data = DataBufferUtils.getDataDouble(buffer, band);
582: int len = data.length;
583: System.arraycopy(data, 0, real, offset, len);
584:
585: // maybe there is an imaginary part ...
586: boolean is_complex_source = this .isComplexSource(nature);
587: boolean is_real_source = this .isRealSource(nature);
588: if (is_complex_source) {
589: data = DataBufferUtils.getDataDouble(buffer, band + 1);
590: len = data.length;
591: System.arraycopy(data, 0, imag, offset, len);
592: }
593: // ... but if not, fill with zeros
594: else if (is_real_source) {
595: Arrays.memset(imag, 0.0, offset, len);
596: } else {
597: String message = this .getUnknownDataNature(nature);
598: throw new IllegalStateException(message);
599: }
600: }
601:
602: @Override
603: public double[] newWorkingArray(int len) {
604: double[] array = new double[len];
605: return array;
606: }
607:
608: @Override
609: public DataBuffer newSinkBuffer(int size, int bands,
610: DFTDataNature nature) {
611: int banks = this .calculateSinkBanks(bands, nature);
612: DataBuffer buffer = DataBufferUtils.createDataBufferDouble(
613: size, banks);
614: return buffer;
615: }
616:
617: };
618:
619: }
620:
621: /*
622: * $Log: DFT3DOpImage.java,v $
623: * Revision 1.5 2007/09/07 18:11:54 forklabs
624: * Added conditions that prevents a transform if the dimension is one.
625: *
626: * Revision 1.4 2007/08/08 19:19:49 forklabs
627: * Corrected javadoc documentation.
628: *
629: * Revision 1.3 2007/07/05 18:22:54 forklabs
630: * Now extends SimpleCollectionImage instead of CollectionImage.
631: *
632: * Revision 1.2 2007/06/13 18:57:21 forklabs
633: * Changed parent to use CollectionDescriptor.
634: *
635: * Revision 1.1 2007/06/05 02:33:45 forklabs
636: * Operators dft3d and idft3d
637: *
638: */
|