GreycLab / CImg

The CImg Library is a small and open-source C++ toolkit for image processing
http://cimg.eu
Other
1.49k stars 284 forks source link

interrupting lengthy computations #58

Closed dahtah closed 7 years ago

dahtah commented 8 years ago

Is there a good way of interrupting lengthy computations in CImg? In the case I have in mind, the user is calling CImg via R, mistakenly tries to convolve a huge image with itself, realises that it may take a while, and wishes to interrupt the computation without killing their entire R session. It's usually handled by a monitoring function (checkUserInterrupt()) that gets called every once in while by the function doing the computation (which would be cimg::convolve in my example). I could implement that strategy but that'd represent a substantial modification of CImg's code, which would make things hard to maintain in the future. Is there a better way? Many thanks

dtschump commented 8 years ago

Yes, there is something that allows this. There is a macro cimg_test_abort() which is undefined by default, and which is put as an instruction in the CImg methods that may take a lot of time to compute (e.g. CImg<T>::convolve(), as you said).

So, you can redefine this macro to actually insert any kind of code you want in those places, for instance you can check if some 'abort' action has been requested by the user, and in this case, you can throw a CImgAbortException instance (which has been defined specifically for this purpose).

I use such a mechanism in my software G'MIC so that a filter can be stopped if a user want to change one of its parameter for instance. This is how I defined my macro cimg_test_abort() in the main header file of the project gmic.h :

#ifdef cimg_use_abort
static struct cimg_is_abort {
  bool value, *ptr;
  cimg_is_abort():value(false),ptr(&value) {}
} _cimg_is_abort;
#ifdef cimg_use_openmp
#define cimg_test_abort() if (*_cimg_is_abort.ptr && !omp_get_thread_num()) throw CImgAbortException();
#else
#define cimg_test_abort() if (*_cimg_is_abort.ptr) throw CImgAbortException()
#endif // #ifdef cimg_use_openmp
#endif // #ifdef cimg_use_abort

Note also that the macro cimg_test_abort() is not defined the same way if OpenMP parallelization is turned on or not, because we want only a primary thread to throw an exception.

It works quite well with this kind of definition. I hope this helps.

dahtah commented 8 years ago

Thanks a lot David, I'll take a look!

Le 22/04/2016 08:54, David Tschumperlé a écrit :

Yes, there is something that allows this. There is a macro |cimg_test_abort()| which is undefined by default, and which is put as an instruction in the CImg methods that may take a lot of time to compute (e.g. |CImg::convolve()|, as you said).

So, you can redefine this macro to actually insert any kind of code you want in those places, for instance you can check if some 'abort' action has been requested by the user, and in this case, you can throw a |CImgAbortException| instance (which has been defined specifically for this purpose).

I use such a mechanism in my software G'MIC http://gmic.eu so that a filter can be stopped if a user want to change one of its parameter for instance. This is how I defined my macro |cimg_test_abort()| in the main header file of the project |gmic.h| :

|#ifdef cimg_use_abort static struct cimg_is_abort { bool value, _ptr; cimg_is_abort():value(false),ptr(&value) {} } _cimg_is_abort; #ifdef cimg_use_openmp #define cimg_test_abort() if (__cimg_is_abort.ptr && !omp_get_thread_num()) throw CImgAbortException(); #else #define cimg_test_abort() if (*_cimg_is_abort.ptr) throw CImgAbortException()

endif // #ifdef cimg_use_openmp #endif // #ifdef cimg_use_abort |

Note also that the macro |cimg_test_abort()| is not defined the same way if OpenMP parallelization is turned on or not, because we want only a primary thread to throw an exception.

It works quite well with this kind of definition. I hope this helps.

— You are receiving this because you authored the thread. Reply to this email directly or view it on GitHub https://github.com/dtschump/CImg/issues/58#issuecomment-213291766

dahtah commented 8 years ago

OK, I got it to work, great! Do you think the checks happen often enough, though? Having a look at the convolution code, for example, it seems cimg_test_abort is only called in successive iterations over colour channels. If you try to filter a really large grayscale picture, you're in for a long wait. I know checking too often leads to a performance hit, but maybe we can set a flag for very large pictures?

dtschump commented 8 years ago

What I could do is introduce a 'level-2' macro 'cimg_test_abort2()' which is called more regularly (in the inner loop for instance), and is set to empty as default. So that we can manage the abortion more finely.

dahtah commented 8 years ago

That sounds good.

dtschump commented 8 years ago

that is committed, so maybe you can check it out.

dahtah commented 8 years ago

Looking good. I've just run a benchmark and the performance hit is negligible, at least on this platform (might be worse on OS X and Windows). Computations can be interrupted almost instantly. Maybe it'd even be enough to move the test up a level in the loop (run at every y instead of at every x).

dtschump commented 8 years ago

Nice. I gave a try to move the test up to a level, maybe you can test and tell me if that is OK (but then, if you convolve two images Mx1 where M is very large, the test won't be checked). Also, as you have maybe noticed, I haven't put a lot of test in the CImg code, so there are probably locations where some could be added. Do not hesitate to suggest some new ones if you think this can be useful.

dahtah commented 8 years ago

I'll take a look ASAP. There's a couple of plugins that could use this, I think (patchmatch, for instance).

dtschump commented 8 years ago

The Patchmatch plug-in should not be used actually. I've reimplemented the Patchmatch algorithm directly in the core file CImg.h, and my version is faster and also parallelized using OpenMP directives. That's the function CImg<T>::patchmatch(). I let the plug-in and the example for educational purposes only.