001package org.openimaj.image.processing;
002
003import java.io.IOException;
004
005import org.apache.commons.io.IOUtils;
006import org.openimaj.image.CLImageConversion;
007import org.openimaj.image.Image;
008
009import com.nativelibs4java.opencl.CLBuildException;
010import com.nativelibs4java.opencl.CLContext;
011import com.nativelibs4java.opencl.CLEvent;
012import com.nativelibs4java.opencl.CLImage2D;
013import com.nativelibs4java.opencl.CLKernel;
014import com.nativelibs4java.opencl.CLMem;
015import com.nativelibs4java.opencl.CLProgram;
016import com.nativelibs4java.opencl.CLQueue;
017import com.nativelibs4java.opencl.JavaCL;
018import com.nativelibs4java.opencl.CLPlatform.DeviceFeature;
019
020/**
021 * Simple image arithmetic functions implemented using OpenCL.
022 * 
023 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
024 */
025public class CLImageArithmetic {
026        private CLContext context;
027
028        private CLKernel addImage;
029        private CLKernel addConstant;
030        private CLKernel subtractImage;
031        private CLKernel subtractConstant;
032        private CLKernel multiplyImage;
033        private CLKernel multiplyConstant;
034        private CLKernel divideImage;
035        private CLKernel divideConstant;
036
037        /**
038         * Default constructor. Automatically selects the context.
039         */
040        public CLImageArithmetic() {
041                CLProgram program = null;
042                try {
043                        this.context = JavaCL.createBestContext(DeviceFeature.GPU);
044                        try {
045                                program = context.createProgram(IOUtils.toString(CLImageArithmetic.class.getResource("ImageArithmetic.cl")));
046                        } catch (IOException e) { e.printStackTrace(); }
047                        loadKernels(program);
048                } catch (CLBuildException e) {
049                        //fallback to OpenCL on the CPU
050                        this.context = JavaCL.createBestContext(DeviceFeature.CPU);
051                        try {
052                                program = context.createProgram(IOUtils.toString(CLImageArithmetic.class.getResource("ImageArithmetic.cl")));
053                        } catch (IOException e1) { e.printStackTrace(); }
054                        loadKernels(program);
055                }
056        }
057
058        /**
059         * Construct with the given context.
060         * @param context the context.
061         */
062        public CLImageArithmetic(CLContext context) {
063                try {
064                        this.context = context;
065                        CLProgram program = context.createProgram(IOUtils.toString(CLImageArithmetic.class.getResource("ImageArithmetic.cl")));
066                        loadKernels(program);
067                } catch (IOException e) { e.printStackTrace(); }
068        }
069
070        private void loadKernels(CLProgram program) {
071                addImage = program.createKernel("addImage");
072                addConstant = program.createKernel("addConstant");
073                subtractImage = program.createKernel("subtractImage");
074                subtractConstant = program.createKernel("subtractConstant");
075                multiplyImage = program.createKernel("multiplyImage");
076                multiplyConstant = program.createKernel("multiplyConstant");
077                divideImage = program.createKernel("divideImage");
078                divideConstant = program.createKernel("divideConstant");
079        }
080        
081        private synchronized CLEvent process(CLKernel kernel, CLQueue queue, CLImage2D in1, CLImage2D in2, CLImage2D out) {
082                kernel.setArgs(in1, in2, out);
083                return kernel.enqueueNDRange(queue, new int[] {(int) in1.getWidth(), (int) in1.getHeight()});
084        }
085        
086        private synchronized CLEvent process(CLKernel kernel, CLQueue queue, CLImage2D in1, float[] amt, CLImage2D out) {
087                kernel.setArgs(in1, amt, out);
088                return kernel.enqueueNDRange(queue, new int[] {(int) in1.getWidth(), (int) in1.getHeight()});
089        }
090
091        private synchronized CLImage2D process(CLKernel kernel, CLImage2D in1, CLImage2D in2) {
092                CLQueue queue = context.createDefaultQueue();
093                CLImage2D out = context.createImage2D(CLMem.Usage.Output, in1.getFormat(), in1.getWidth(), in1.getHeight());
094
095                process(kernel, queue, in1, in2, out).waitFor();
096                queue.release();
097                
098                return out;
099        }
100
101        private synchronized CLImage2D process(CLKernel kernel, CLImage2D in, float[] amt) {
102                CLQueue queue = context.createDefaultQueue();
103                CLImage2D out = context.createImage2D(CLMem.Usage.Output, in.getFormat(), in.getWidth(), in.getHeight());
104
105                process(kernel, queue, in, amt, out).waitFor();
106                queue.release();
107                
108                return out;
109        }
110
111        /**
112         * Add two images, storing the result in another image
113         * 
114         * @param queue the command queue
115         * @param in1 the first image to add
116         * @param in2 the second image to add
117         * @param out the result image
118         * @return the event
119         */
120        public CLEvent add(CLQueue queue, CLImage2D in1, CLImage2D in2, CLImage2D out) {
121                return process(addImage, queue, in1, in2, out);
122        }
123        
124        /**
125         * Add a constant to an image, storing the result in another image
126         * 
127         * @param queue the command queue
128         * @param in1 the first image to add
129         * @param amt the constant
130         * @param out the result image
131         * @return the event
132         */
133        public CLEvent add(CLQueue queue, CLImage2D in1, float[] amt, CLImage2D out) {
134                return process(addConstant, queue, in1, amt, out);
135        }
136        
137        /**
138         * Add two images, returning a new image with the result
139         * 
140         * @param in1 the first image to add
141         * @param in2 the second image to add
142         * @return the event
143         */
144        public CLImage2D add(CLImage2D in1, CLImage2D in2) {
145                return process(addImage, in1, in2);
146        }
147        
148        /**
149         * Add a constant to an image, returning a new image with the result
150         * 
151         * @param in1 the first image to add
152         * @param amt the constant
153         * @return the event
154         */
155        public CLImage2D add(CLImage2D in1, float[] amt) {
156                return process(addImage, in1, amt);
157        }
158
159        /**
160         * Add two images, returning a new image with the result
161         * 
162         * @param <I> The type of image
163         * 
164         * @param in1 the first image to add
165         * @param in2 the second image to add
166         * @return the event
167         */
168        public <I extends Image<?, I>> I add(I in1, I in2) {
169                CLQueue queue = context.createDefaultQueue();
170                
171                CLImage2D clin1 = CLImageConversion.convert(context, in1);
172                CLImage2D clin2 = CLImageConversion.convert(context, in2);
173                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
174                
175                CLEvent evt = process(addImage, queue, clin1, clin2, clout);
176                
177                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
178                
179                clin1.release();
180                clin2.release();
181                clout.release();
182                queue.release();
183                
184                return out;
185        }
186        
187        /**
188         * Add a constant to an image, returning a new image with the result
189         * 
190         * @param <I> The type of image
191         * 
192         * @param in1 the first image to add
193         * @param amt the constant
194         * @return the event
195         */
196        public <I extends Image<?, I>> I add(I in1, float[] amt) {
197                CLQueue queue = context.createDefaultQueue();
198                
199                CLImage2D clin1 = CLImageConversion.convert(context, in1);
200                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
201                
202                CLEvent evt = process(addImage, queue, clin1, amt, clout);
203                
204                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
205                
206                clin1.release();
207                clout.release();
208                queue.release();
209                
210                return out;
211        }
212        
213        /**
214         * Subtract two images, storing the result in another image
215         * 
216         * @param queue the command queue
217         * @param in1 the first image to subtract
218         * @param in2 the second image to subtract
219         * @param out the result image
220         * @return the event
221         */
222        public CLEvent subtract(CLQueue queue, CLImage2D in1, CLImage2D in2, CLImage2D out) {
223                return process(subtractImage, queue, in1, in2, out);
224        }
225
226        /**
227         * Subtract a constant from an image, storing the result in another image
228         * 
229         * @param queue the command queue
230         * @param in1 the first image to subtract
231         * @param amt the constant
232         * @param out the result image
233         * @return the event
234         */
235        public CLEvent subtract(CLQueue queue, CLImage2D in1, float[] amt, CLImage2D out) {
236                return process(subtractConstant, queue, in1, amt, out);
237        }
238
239        /**
240         * Subtract two images, returning a new image with the result
241         * 
242         * @param in1 the first image to subtract
243         * @param in2 the second image to subtract
244         * @return the event
245         */
246        public CLImage2D subtract(CLImage2D in1, CLImage2D in2) {
247                return process(subtractImage, in1, in2);
248        }
249
250        /**
251         * Subtract a constant from an image, returning a new image with the result
252         * 
253         * @param in1 the first image to subtract
254         * @param amt the constant
255         * @return the event
256         */
257        public CLImage2D subtract(CLImage2D in1, float[] amt) {
258                return process(subtractImage, in1, amt);
259        }
260
261        /**
262         * Subtract two images, returning a new image with the result
263         * 
264         * @param <I> The type of image
265         * 
266         * @param in1 the first image to subtract
267         * @param in2 the second image to subtract
268         * @return the event
269         */
270        public <I extends Image<?, I>> I subtract(I in1, I in2) {
271                CLQueue queue = context.createDefaultQueue();
272                
273                CLImage2D clin1 = CLImageConversion.convert(context, in1);
274                CLImage2D clin2 = CLImageConversion.convert(context, in2);
275                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
276                
277                CLEvent evt = process(subtractImage, queue, clin1, clin2, clout);
278                
279                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
280                
281                clin1.release();
282                clin2.release();
283                clout.release();
284                queue.release();
285                
286                return out;
287        }
288
289        /**
290         * Subtract a constant from an image, returning a new image with the result
291         * 
292         * @param <I> The type of image
293         * 
294         * @param in1 the first image to subtract
295         * @param amt the constant
296         * @return the event
297         */
298        public <I extends Image<?, I>> I subtract(I in1, float[] amt) {
299                CLQueue queue = context.createDefaultQueue();
300                
301                CLImage2D clin1 = CLImageConversion.convert(context, in1);
302                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
303                
304                CLEvent evt = process(subtractImage, queue, clin1, amt, clout);
305                
306                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
307                
308                clin1.release();
309                clout.release();
310                queue.release();
311                
312                return out;
313        }
314        
315        /**
316         * Multiply two images, storing the result in another image
317         * 
318         * @param queue the command queue
319         * @param in1 the first image to multiply
320         * @param in2 the second image to multiply
321         * @param out the result image
322         * @return the event
323         */
324        public CLEvent multiply(CLQueue queue, CLImage2D in1, CLImage2D in2, CLImage2D out) {
325                return process(multiplyImage, queue, in1, in2, out);
326        }
327
328        /**
329         * Multiply an image by a constant, storing the result in another image
330         * 
331         * @param queue the command queue
332         * @param in1 the first image to multiply
333         * @param amt the constant
334         * @param out the result image
335         * @return the event
336         */
337        public CLEvent multiply(CLQueue queue, CLImage2D in1, float[] amt, CLImage2D out) {
338                return process(multiplyConstant, queue, in1, amt, out);
339        }
340
341        /**
342         * Multiply two images, returning a new image with the result
343         * 
344         * @param in1 the first image to multiply
345         * @param in2 the second image to multiply
346         * @return the event
347         */
348        public CLImage2D multiply(CLImage2D in1, CLImage2D in2) {
349                return process(multiplyImage, in1, in2);
350        }
351
352        /**
353         * Multiply an image by a constant, returning a new image with the result
354         * 
355         * @param in1 the first image to multiply
356         * @param amt the constant
357         * @return the event
358         */
359        public CLImage2D multiply(CLImage2D in1, float[] amt) {
360                return process(multiplyImage, in1, amt);
361        }
362
363        /**
364         * Multiply two images, returning a new image with the result
365         * 
366         * @param <I> The type of image
367         * 
368         * @param in1 the first image to multiply
369         * @param in2 the second image to multiply
370         * @return the event
371         */
372        public <I extends Image<?, I>> I multiply(I in1, I in2) {
373                CLQueue queue = context.createDefaultQueue();
374                
375                CLImage2D clin1 = CLImageConversion.convert(context, in1);
376                CLImage2D clin2 = CLImageConversion.convert(context, in2);
377                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
378                
379                CLEvent evt = process(multiplyImage, queue, clin1, clin2, clout);
380                
381                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
382                
383                clin1.release();
384                clin2.release();
385                clout.release();
386                queue.release();
387                
388                return out;
389        }
390
391        /**
392         * Multiply an image by a constant, returning a new image with the result
393         * 
394         * @param <I> The type of image
395         * 
396         * @param in1 the first image to multiply
397         * @param amt the constant
398         * @return the event
399         */
400        public <I extends Image<?, I>> I multiply(I in1, float[] amt) {
401                CLQueue queue = context.createDefaultQueue();
402                
403                CLImage2D clin1 = CLImageConversion.convert(context, in1);
404                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
405                
406                CLEvent evt = process(multiplyImage, queue, clin1, amt, clout);
407                
408                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
409                
410                clin1.release();
411                clout.release();
412                queue.release();
413                
414                return out;
415        }
416        
417        /**
418         * Divide two images, storing the result in another image
419         * 
420         * @param queue the command queue
421         * @param in1 the first image to divide
422         * @param in2 the second image to divide
423         * @param out the result image
424         * @return the event
425         */
426        public CLEvent divide(CLQueue queue, CLImage2D in1, CLImage2D in2, CLImage2D out) {
427                return process(divideImage, queue, in1, in2, out);
428        }
429
430        /**
431         * Divide an image by a constant, storing the result in another image
432         * 
433         * @param queue the command queue
434         * @param in1 the first image to divide
435         * @param amt the constant
436         * @param out the result image
437         * @return the event
438         */
439        public CLEvent divide(CLQueue queue, CLImage2D in1, float[] amt, CLImage2D out) {
440                return process(divideConstant, queue, in1, amt, out);
441        }
442
443        /**
444         * Divide two images, returning a new image with the result
445         * 
446         * @param in1 the first image to divide
447         * @param in2 the second image to divide
448         * @return the event
449         */
450        public CLImage2D divide(CLImage2D in1, CLImage2D in2) {
451                return process(divideImage, in1, in2);
452        }
453
454        /**
455         * Divide an image by a constant, returning a new image with the result
456         * 
457         * @param in1 the first image to divide
458         * @param amt the constant
459         * @return the event
460         */
461        public CLImage2D divide(CLImage2D in1, float[] amt) {
462                return process(divideImage, in1, amt);
463        }
464
465        /**
466         * Divide two images, returning a new image with the result
467         * 
468         * @param <I> The type of image
469         * 
470         * @param in1 the first image to divide
471         * @param in2 the second image to divide
472         * @return the event
473         */
474        public <I extends Image<?, I>> I divide(I in1, I in2) {
475                CLQueue queue = context.createDefaultQueue();
476                
477                CLImage2D clin1 = CLImageConversion.convert(context, in1);
478                CLImage2D clin2 = CLImageConversion.convert(context, in2);
479                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
480                
481                CLEvent evt = process(divideImage, queue, clin1, clin2, clout);
482                
483                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
484                
485                clin1.release();
486                clin2.release();
487                clout.release();
488                queue.release();
489                
490                return out;
491        }
492
493        /**
494         * Divide an image by a constant, returning a new image with the result
495         * 
496         * @param <I> The type of image
497         * 
498         * @param in1 the first image to divide
499         * @param amt the constant
500         * @return the event
501         */
502        public <I extends Image<?, I>> I divide(I in1, float[] amt) {
503                CLQueue queue = context.createDefaultQueue();
504                
505                CLImage2D clin1 = CLImageConversion.convert(context, in1);
506                CLImage2D clout = context.createImage2D(CLMem.Usage.Output, clin1.getFormat(), clin1.getWidth(), clin1.getHeight());
507                
508                CLEvent evt = process(divideImage, queue, clin1, amt, clout);
509                
510                I out = CLImageConversion.convert(queue, evt, clout, in1.newInstance(in1.getWidth(), in1.getHeight()));
511                
512                clin1.release();
513                clout.release();
514                queue.release();
515                
516                return out;
517        }
518}