From da0039221448db93de5411ddcc30dae7fb08aafc Mon Sep 17 00:00:00 2001
From: Tristan Walter <twalter@orn.mpg.de>
Date: Wed, 28 Oct 2020 12:24:51 +0100
Subject: [PATCH] * updating, adding background accumulator * using
 averaging_method_t

---
 .../src/commons/common/gui/FileChooser.cpp    |   1 +
 .../src/commons/common/video/GenericVideo.cpp |  73 ++-----
 .../src/commons/common/video/GenericVideo.h   | 118 +++++++++++
 .../src/commons/common/video/VideoSource.cpp  |  43 ++--
 .../src/commons/common/video/VideoSource.h    |   7 -
 Application/src/grabber/default_config.cpp    |   4 +-
 Application/src/grabber/default_config.h      |   3 -
 Application/src/grabber/grabber.cpp           | 196 ++++++++++--------
 Application/src/grabber/grabber.h             |  15 +-
 Application/src/grabber/gui.cpp               |   2 +-
 Application/src/tracker/VideoOpener.cpp       |  29 +--
 Application/src/tracker/VideoOpener.h         |   8 +-
 docs/parameters_tgrabs.rst                    |  12 +-
 13 files changed, 317 insertions(+), 194 deletions(-)

diff --git a/Application/src/commons/common/gui/FileChooser.cpp b/Application/src/commons/common/gui/FileChooser.cpp
index 5cf4509..8a769c1 100644
--- a/Application/src/commons/common/gui/FileChooser.cpp
+++ b/Application/src/commons/common/gui/FileChooser.cpp
@@ -14,6 +14,7 @@ FileChooser::FileChooser(const file::Path& start, const std::string& extension,
     _overall(std::make_shared<VerticalLayout>()),
     _base("Choose file", *_graph, [this](){
         using namespace gui;
+        tf::show();
         
         {
             std::lock_guard<std::mutex> guard(_execute_mutex);
diff --git a/Application/src/commons/common/video/GenericVideo.cpp b/Application/src/commons/common/video/GenericVideo.cpp
index c3078ad..9eff5e6 100644
--- a/Application/src/commons/common/video/GenericVideo.cpp
+++ b/Application/src/commons/common/video/GenericVideo.cpp
@@ -2,9 +2,19 @@
 #include <misc/Timer.h>
 #include <misc/GlobalSettings.h>
 #include <grabber/default_config.h>
+#include <misc/Image.h>
 
 using namespace cmn;
 
+namespace cmn {
+ENUM_CLASS_DOCS(averaging_method_t,
+    "Sum all samples and divide by N.",
+    "Calculate a per-pixel median of the samples.",
+    "Use a per-pixel minimum across samples.",
+    "Use a per-pixel maximum across samples."
+);
+}
+
 CropOffsets GenericVideo::crop_offsets() const {
     return SETTING(crop_offsets);
 }
@@ -98,75 +108,38 @@ void GenericVideo::processImage(const gpuMat& display, gpuMat&out, bool do_mask)
 }
 
 void GenericVideo::generate_average(cv::Mat &av, uint64_t frameIndex) {
-    gpuMat average;
-    av.copyTo(average);
+    if(length() < 10) {
+        gpuMat average;
+        av.copyTo(average);
+        this->processImage(average, average);
+        return;
+    }
+    AveragingAccumulator accumulator;
     
-    Debug("Generating average for frame %d...", frameIndex);
+    Debug("Generating average for frame %d (method='%s')...", frameIndex, accumulator.mode().name());
     
-    //const uint64_t channel = SETTING(color_channel);
     double count = 0;
     float samples = GlobalSettings::has("average_samples") ? SETTING(average_samples).value<int>() : (length() * 0.01);
     const int step = max(1, length() / samples);
     
-    gpuMat float_mat;
-    gpuMat f, ref;
-    std::vector<gpuMat> vec;
-    
-    auto averaging_method = GlobalSettings::has("averaging_method") ?  SETTING(averaging_method).value<grab::averaging_method_t::Class>() : grab::averaging_method_t::mean;
-    bool use_mean = averaging_method != grab::averaging_method_t::max && averaging_method != grab::averaging_method_t::min;
-    Debug("Use max: %d", !use_mean);
-    
-    if(average.empty() || average.cols != size().width || average.rows != size().height) {
-        average = gpuMat::zeros(size().height, size().width, use_mean ? CV_32FC1 : CV_8UC1);
-    } else
-        average = cv::Scalar(0);
-    
-    if (length() < 10) {
-        processImage(average, average);
-        return;
-    }
-    
-    if(use_mean)
-        float_mat = gpuMat::zeros(average.rows, average.cols, CV_32FC1);
-    else
-        float_mat = gpuMat::zeros(average.rows, average.cols, CV_8UC1);
-    
-    cv::Mat local;
-    //for (uint64_t i=0; i<length(); i+=step) {
+    cv::Mat f;
     uint64_t counted = 0;
     for(long_t i=length() ? length()-1 : 0; i>=0; i-=step) {
         frame(i, f);
         
         assert(f.channels() == 1);
-        ref = f;
-        
-        if(use_mean) {
-            ref.convertTo(float_mat, CV_32FC1, 1.0/255.0);
-            //Debug("%d,%d - %d,%d", float_mat.cols, float_mat.rows, average.cols, average.rows);
-            cv::add(average, float_mat, average);
-        } else {
-            ref.copyTo(local);
-            
-            if(averaging_method == grab::averaging_method_t::max)
-                av = cv::max(av, local);
-            else
-                av = cv::min(av, local);
-        }
-        
-        ++count;
+        accumulator.add(f);
         counted += step;
         
         if(counted > float(length()) * 0.1) {
             Debug("generating average: %d/%d step:%d (frame %d)", (samples - i) / step, int(samples), step, i);
             counted = 0;
         }
+        
         if(GlobalSettings::has("terminate") && SETTING(terminate))
             break;
     }
     
-    if(use_mean) {
-        cv::divide(average, cv::Scalar(count), average);
-        average.convertTo(av, CV_8UC1, 255.0);
-    } else
-        average.copyTo(av);
+    auto image = accumulator.finalize();
+    image->get().copyTo(av);
 }
diff --git a/Application/src/commons/common/video/GenericVideo.h b/Application/src/commons/common/video/GenericVideo.h
index 8fa2958..2c10ddc 100644
--- a/Application/src/commons/common/video/GenericVideo.h
+++ b/Application/src/commons/common/video/GenericVideo.h
@@ -3,8 +3,126 @@
 
 #include <types.h>
 #include <misc/CropOffsets.h>
+#include <misc/GlobalSettings.h>
 
 namespace cmn { class GenericVideo; }
+namespace cmn {
+ENUM_CLASS(averaging_method_t, mean, mode, max, min);
+ENUM_CLASS_HAS_DOCS(averaging_method_t);
+
+template<typename Mat = cv::Mat, bool threaded = false>
+class AveragingAccumulator {
+protected:
+    GETTER(averaging_method_t::Class, mode)
+    Mat _accumulator;
+    Mat _float_mat;
+    Mat _local;
+    double count = 0;
+    bool use_mean;
+    Size2 _size;
+    std::vector<std::array<uint8_t, 256>> spatial_histogram;
+    std::vector<std::unique_ptr<std::mutex>> spatial_mutex;
+    
+public:
+    AveragingAccumulator() {
+        _mode = GlobalSettings::has("averaging_method")
+            ?  SETTING(averaging_method).template value<averaging_method_t::Class>()
+            : averaging_method_t::mean;
+    }
+    AveragingAccumulator(averaging_method_t::Class mode)
+        : _mode(mode)
+    { }
+    
+    void add(const Mat &f) {
+        assert(f.channels() == 1);
+        assert(f.type() == CV_8UC1);
+        
+        // initialization code
+        if(_accumulator.empty()) {
+            _size = Size2(f.cols, f.rows);
+            _accumulator = cv::Mat::zeros(_size.height, _size.width, _mode == averaging_method_t::mean ? CV_32FC1 : CV_8UC1);
+            if(_mode == averaging_method_t::min)
+                _accumulator.setTo(255);
+            
+            if(_mode == averaging_method_t::mode) {
+                spatial_histogram.resize(f.cols * f.rows);
+                for(uint64_t i=0; i<spatial_histogram.size(); ++i) {
+                    std::fill(spatial_histogram.at(i).begin(), spatial_histogram.at(i).end(), 0);
+                    spatial_mutex.push_back(std::make_unique<std::mutex>());
+                }
+            }
+        }
+        
+        if(_mode == averaging_method_t::mean) {
+            f.convertTo(_float_mat, CV_32FC1);
+            cv::add(_accumulator, _float_mat, _accumulator);
+            ++count;
+            
+        } else if(_mode == averaging_method_t::mode) {
+            assert(f.isContinuous());
+            
+            auto ptr = f.data;
+            const auto end = f.data + f.cols * f.rows;
+            auto array_ptr = spatial_histogram.data();
+            auto mutex_ptr = spatial_mutex.begin();
+            
+            assert(spatial_histogram.size() == (uint64_t)(f.cols * f.rows));
+            if constexpr(threaded) {
+                for (; ptr != end; ++ptr, ++array_ptr, ++mutex_ptr) {
+                    (*mutex_ptr)->lock();
+                    ++(*array_ptr)[*ptr];
+                    (*mutex_ptr)->unlock();
+                }
+                
+            } else {
+                for (; ptr != end; ++ptr, ++array_ptr)
+                    ++(*array_ptr)[*ptr];
+            }
+            
+        } else if(_mode == averaging_method_t::max) {
+            cv::max(_accumulator, f, _accumulator);
+        } else if(_mode == averaging_method_t::min) {
+            cv::min(_accumulator, f, _accumulator);
+        } else
+            U_EXCEPTION("Unknown averaging_method '%s'.", _mode.name())
+    }
+    
+    std::unique_ptr<cmn::Image> finalize() {
+        auto image = std::make_unique<cmn::Image>(_accumulator.rows, _accumulator.cols, 1);
+        
+        if(_mode == averaging_method_t::mean) {
+            cv::divide(_accumulator, cv::Scalar(count), _local);
+            _local.convertTo(image->get(), CV_8UC1);
+            
+        } else if(_mode == averaging_method_t::mode) {
+            _accumulator.copyTo(image->get());
+            
+            auto ptr = image->data();
+            const auto end = image->data() + image->cols * image->rows;
+            auto array_ptr = spatial_histogram.data();
+            
+            for (; ptr != end; ++ptr, ++array_ptr) {
+                uchar max_code = 0;
+                uint8_t max_number = 0;
+                //for(auto && [code, number] : *array_ptr) {
+                for(uint64_t code=0; code<array_ptr->size(); ++code) {
+                    const auto& number = (*array_ptr)[code];
+                    if(number > max_number) {
+                        max_number = number;
+                        max_code = code;
+                    }
+                }
+                
+                *ptr = max_code;
+            }
+        } else
+            _accumulator.copyTo(image->get());
+        
+        return std::move(image);
+    }
+};
+
+}
 
 //! Interface for things that can load Videos
 class cmn::GenericVideo {
diff --git a/Application/src/commons/common/video/VideoSource.cpp b/Application/src/commons/common/video/VideoSource.cpp
index 062da96..05d6d6e 100644
--- a/Application/src/commons/common/video/VideoSource.cpp
+++ b/Application/src/commons/common/video/VideoSource.cpp
@@ -478,21 +478,14 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
     gpuMat float_mat, f, ref;
     std::vector<gpuMat> vec;
     
-    AveragingMethod::Class method(AveragingMethod::mean);
-    if(GlobalSettings::has("averaging_method")) {
-        auto name = SETTING(averaging_method).value<std::string>();
-        if(AveragingMethod::has(name))
-            method = AveragingMethod::get(name);
-        else {
-            auto str = Meta::toStr(AveragingMethod::names);
-            Warning("Invalid value for 'averaging_method': '%S'. Known methods are: %S", &name, &str);
-        }
-    }
+    averaging_method_t::Class method(averaging_method_t::mean);
+    if(GlobalSettings::has("averaging_method"))
+        method = SETTING(averaging_method).value<averaging_method_t::Class>();
     //bool use_mean = GlobalSettings::has("averaging_method") && utils::lowercase(SETTING(averaging_method).value<std::string>()) != "max";
     Debug("Use averaging method: '%s'", method.name());
     
     if(average.empty() || average.cols != size().width || average.rows != size().height) {
-        average = gpuMat::zeros(size().height, size().width, method == AveragingMethod::mean ? CV_32FC1 : CV_8UC1);
+        average = gpuMat::zeros(size().height, size().width, method == averaging_method_t::mean ? CV_32FC1 : CV_8UC1);
     } else
         average = cv::Scalar(0);
     
@@ -501,10 +494,10 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
         return;
     }
     
-    if(method == AveragingMethod::mean)
+    if(method == averaging_method_t::mean)
         float_mat = gpuMat::zeros(average.rows, average.cols, CV_32FC1);
     else {
-        if(method == AveragingMethod::min) {
+        if(method == averaging_method_t::min) {
             cv::Mat tmp = cv::Mat::ones(average.rows, average.cols, CV_8UC1);
             tmp = tmp.mul(255);
             tmp.copyTo(float_mat);
@@ -517,14 +510,14 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
     uint64_t step = max(1, _files_in_seq.size() < samples ? 1 : ceil(_files_in_seq.size() / samples));
     uint64_t frames_per_file = max(1, _files_in_seq.size() < samples ? (length() / _files_in_seq.size()) / (length() / samples) : 1);
     
-    if(samples > 255 && method == AveragingMethod::mode)
+    if(samples > 255 && method == averaging_method_t::mode)
         U_EXCEPTION("Cannot take more than 255 samples with 'averaging_method' = 'mode'. Choose fewer samples or a different averaging method.");
     std::map<File*, std::set<uint64_t>> file_indexes;
     
     //std::vector<std::map<uchar, uint8_t>> spatial_histogram;
     std::vector<std::array<uint8_t, 256>> spatial_histogram;
     std::vector<std::unique_ptr<std::mutex>> spatial_mutex;
-    if(method == AveragingMethod::mode) {
+    if(method == averaging_method_t::mode) {
         spatial_histogram.resize(average.cols * average.rows);
         for(uint64_t i=0; i<spatial_histogram.size(); ++i) {
             std::fill(spatial_histogram.at(i).begin(), spatial_histogram.at(i).end(), 0);
@@ -556,7 +549,7 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
             cv::Mat float_mat;
             cv::Mat average;
             
-            if(method == AveragingMethod::min) {
+            if(method == averaging_method_t::min) {
                 average = cv::Mat::ones(gAverage->rows, gAverage->cols, gAverage->type());
                 cv::multiply(average, cv::Scalar(255), average);
             } else
@@ -569,12 +562,12 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
                     file->frame(index, f, true);
                     assert(f.channels() == 1);
                     
-                    if(method == AveragingMethod::mean) {
+                    if(method == averaging_method_t::mean) {
                         //Debug("%d,%d - %d,%d", float_mat.cols, float_mat.rows, average.cols, average.rows);
                         f.convertTo(float_mat, CV_32FC1, 1.0/255.0);
                         cv::add(average, float_mat, average);
                         
-                    } else if(method == AveragingMethod::mode) {
+                    } else if(method == averaging_method_t::mode) {
                         assert(f.isContinuous());
                         
                         auto ptr = f.data;
@@ -594,9 +587,9 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
                                 ++(*array_ptr)[*ptr];
                         }
                         
-                    } else if(method == AveragingMethod::max) {
+                    } else if(method == averaging_method_t::max) {
                         average = cv::max(average, f);
-                    } else if(method == AveragingMethod::min) {
+                    } else if(method == averaging_method_t::min) {
                         average = cv::min(average, f);
                     } else
                         U_EXCEPTION("Unknown averaging_method '%s'.", method.name())
@@ -618,15 +611,15 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
             file->close();
             
             std::lock_guard<std::mutex> guard(mutex);
-            if(method == AveragingMethod::mean) {
+            if(method == averaging_method_t::mean) {
                 *gCount += count;
                 cv::add(average, *gAverage, *gAverage);
-            } else if(method == AveragingMethod::mode) {
+            } else if(method == averaging_method_t::mode) {
                 
             } else {
                 if(gAv->empty())
                     *gAv = average;
-                else if(method == AveragingMethod::max)
+                else if(method == averaging_method_t::max)
                     *gAv = cv::max(average, *gAv);
                 else
                     *gAv = cv::min(average, *gAv);
@@ -645,11 +638,11 @@ void VideoSource::generate_average(cv::Mat &av, uint64_t) {
     if(SETTING(terminate))
         return;
     
-    if(method == AveragingMethod::mean) {
+    if(method == averaging_method_t::mean) {
         cv::divide(average, cv::Scalar(count), average);
         average.convertTo(av, CV_8UC1, 255.0);
         
-    } else if(method == AveragingMethod::mode) {
+    } else if(method == averaging_method_t::mode) {
         Debug("Combining mode image...");
         average.copyTo(av);
         
diff --git a/Application/src/commons/common/video/VideoSource.h b/Application/src/commons/common/video/VideoSource.h
index bfbd442..e10fd1d 100644
--- a/Application/src/commons/common/video/VideoSource.h
+++ b/Application/src/commons/common/video/VideoSource.h
@@ -8,13 +8,6 @@
 #define VIDEO_SEQUENCE_INVALID_VALUE (-1)
 #define VIDEO_SEQUENCE_UNSPECIFIED_VALUE (-2)
 
-ENUM_CLASS(AveragingMethod,
-           mean,
-           max,
-           min,
-           mode
-)
-
 namespace cmn {
     class Video;
     class VideoSource;
diff --git a/Application/src/grabber/default_config.cpp b/Application/src/grabber/default_config.cpp
index a52e473..6397cd0 100644
--- a/Application/src/grabber/default_config.cpp
+++ b/Application/src/grabber/default_config.cpp
@@ -2,6 +2,7 @@
 #include <misc/SpriteMap.h>
 #include <file/Path.h>
 #include <misc/CropOffsets.h>
+#include <video/GenericVideo.h>
 
 #ifndef WIN32
 #include <unistd.h>
@@ -11,9 +12,6 @@
 #include <misc/default_settings.h>
 
 namespace grab {
-
-    ENUM_CLASS_DOCS(averaging_method_t, "Mean", "Mode", "Min", "Max");
-
 #ifndef WIN32
 struct passwd *pw = getpwuid(getuid());
 const char *homedir = pw->pw_dir;
diff --git a/Application/src/grabber/default_config.h b/Application/src/grabber/default_config.h
index 767f4b7..ac98d9a 100644
--- a/Application/src/grabber/default_config.h
+++ b/Application/src/grabber/default_config.h
@@ -4,9 +4,6 @@
 #include <misc/GlobalSettings.h>
 
 namespace grab {
-    ENUM_CLASS(averaging_method_t, mean, mode, max, min);
-    ENUM_CLASS_HAS_DOCS(averaging_method_t);
-
 namespace default_config {
     using namespace cmn;
     
diff --git a/Application/src/grabber/grabber.cpp b/Application/src/grabber/grabber.cpp
index b186cf5..39e965c 100644
--- a/Application/src/grabber/grabber.cpp
+++ b/Application/src/grabber/grabber.cpp
@@ -274,11 +274,22 @@ void FrameGrabber::prepare_average() {
     Debug("--- done preparing");
 }
 
+template<typename F>
+auto async_deferred(F&& func) -> std::future<decltype(func())>
+{
+    auto task   = std::packaged_task<decltype(func())()>(std::forward<F>(func));
+    auto future = task.get_future();
+
+    std::thread(std::move(task)).detach();
+
+    return std::move(future);
+}
+
 FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_starting)
   : //_current_image(NULL),
     _current_average_timestamp(0),
     _average_finished(false),
-    _average_samples(0),
+    _average_samples(0), _last_index(0),
     _video(NULL), _video_mask(NULL),
     _camera(NULL),
     _current_fps(0), _fps(0),
@@ -367,11 +378,64 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st
         _current_average_timestamp = 1337;
         
     } else {
-        initialize_video();
+        std::vector<file::Path> filenames;
+        auto video_source = SETTING(video_source).value<std::string>();
+        try {
+            filenames = Meta::fromStr<std::vector<file::Path>>(video_source);
+            if(filenames.size() > 1) {
+                Debug("Found an array of filenames (%d).", filenames.size());
+            } else if(filenames.size() == 1) {
+                SETTING(video_source) = filenames.front();
+                filenames.clear();
+            } else
+                U_EXCEPTION("Empty input filename '%S'. Please specify an input name.", &video_source);
+            
+        } catch(const illegal_syntax& e) {
+            // ... do nothing
+        }
+        
+        if(filenames.empty()) {
+            auto filepath = file::Path(SETTING(video_source).value<std::string>());
+            if(filepath.remove_filename().empty()) {
+                auto path = (SETTING(output_dir).value<file::Path>() / filepath);
+                filenames.push_back(path);
+            } else
+                filenames.push_back(filepath);
+        }
+        
+        for(auto &name : filenames) {
+            name = pv::DataLocation::parse("input", name);
+        }
+        
+        if(filenames.size() == 1) {
+            _video = new VideoSource(filenames.front().str());
+            
+        } else {
+            _video = new VideoSource(filenames);
+        }
+        
+        int frame_rate = _video->framerate();
+        if(frame_rate == -1) {
+            frame_rate = 25;
+        }
+        
+        if(SETTING(frame_rate).value<int>() == -1) {
+            Debug("Setting frame rate to %d (from video).", frame_rate);
+            SETTING(frame_rate) = frame_rate;
+        } else if(SETTING(frame_rate).value<int>() != frame_rate) {
+            Warning("Overwriting default frame rate of %d with %d.", frame_rate, SETTING(frame_rate).value<int>());
+        }
+        
+        if(!SETTING(mask_path).value<file::Path>().empty()) {
+            auto path = pv::DataLocation::parse("input", SETTING(mask_path).value<file::Path>());
+            if(path.exists()) {
+                _video_mask = new VideoSource(path.str());
+            }
+        }
     }
     
     // determine recording resolution and set it
-	_cam_size = determine_resolution();
+    _cam_size = determine_resolution();
     SETTING(video_size) = Size2(_cam_size) * GRAB_SETTINGS(cam_scale);
     
 #if WITH_FFMPEG
@@ -403,6 +467,39 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st
         Output::Library::Init();
     }
     
+    // determine offsets
+    CropOffsets roff = SETTING(crop_offsets);
+    _processed.set_offsets(roff);
+    
+    _crop_rect = roff.toPixels(_cam_size);
+    _cropped_size = cv::Size(_crop_rect.width * GRAB_SETTINGS(cam_scale), _crop_rect.height * GRAB_SETTINGS(cam_scale));
+    
+    {
+        std::lock_guard<std::mutex> guard(_camera_lock);
+        if(_camera) {
+            _processed.set_resolution(_cropped_size);
+            _camera->set_crop(_crop_rect);
+        }
+    }
+    
+    // create mask if necessary
+    if(SETTING(cam_circle_mask)) {
+        cv::Mat mask = cv::Mat::zeros(_cropped_size.height, _cropped_size.width, CV_8UC1);
+        cv::circle(mask, cv::Point(mask.cols/2, mask.rows/2), min(mask.cols, mask.rows)/2, cv::Scalar(1), -1);
+        _processed.set_mask(mask);
+    }
+    
+    _task._complete = false;
+    _task._future = async_deferred([this, callback = std::move(callback_before_starting)]() mutable {
+        initialize(std::move(callback));
+        _task._complete = true;
+    });
+}
+
+void FrameGrabber::initialize(std::function<void(FrameGrabber&)>&& callback_before_starting) {
+    if(_video)
+        initialize_video();
+    
     if (GRAB_SETTINGS(enable_closed_loop)) {
         track::PythonIntegration::set_settings(GlobalSettings::instance());
         track::PythonIntegration::set_display_function([](auto& name, auto& mat) { tf::imshow(name, mat); });
@@ -452,21 +549,6 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st
     _average.copyTo(_original_average);
     callback_before_starting(*this);
     
-    // determine offsets
-    CropOffsets roff = SETTING(crop_offsets);
-    _processed.set_offsets(roff);
-    
-    _crop_rect = roff.toPixels(_cam_size);
-    _cropped_size = cv::Size(_crop_rect.width * GRAB_SETTINGS(cam_scale), _crop_rect.height * GRAB_SETTINGS(cam_scale));
-    
-    {
-        std::lock_guard<std::mutex> guard(_camera_lock);
-        if(_camera) {
-            _processed.set_resolution(_cropped_size);
-            _camera->set_crop(_crop_rect);
-        }
-    }
-    
     if(_video) {
         _average.copyTo(_original_average);
         prepare_average();
@@ -474,17 +556,10 @@ FrameGrabber::FrameGrabber(std::function<void(FrameGrabber&)> callback_before_st
         _current_average_timestamp = 42;
     }
     
-    // create mask if necessary
-    if(SETTING(cam_circle_mask)) {
-        cv::Mat mask = cv::Mat::zeros(_cropped_size.height, _cropped_size.width, CV_8UC1);
-        cv::circle(mask, cv::Point(mask.cols/2, mask.rows/2), min(mask.cols, mask.rows)/2, cv::Scalar(1), -1);
-        _processed.set_mask(mask);
-    }
-    
     //auto epoch = std::chrono::time_point<std::chrono::system_clock>();
     _start_timing = _video && !_video->has_timestamps() ? 0 : UINT64_MAX;//Image::clock_::now();
     _real_timing = std::chrono::system_clock::now();
-	
+    
     _analysis = new std::decay<decltype(*_analysis)>::type(
           [&]() -> Image_t* { // create object
               return new Image_t(_cam_size.height, _cam_size.width);
@@ -671,64 +746,9 @@ file::Path FrameGrabber::average_name() const {
 }
 
 void FrameGrabber::initialize_video() {
-    std::vector<file::Path> filenames;
-    auto video_source = SETTING(video_source).value<std::string>();
-    try {
-        filenames = Meta::fromStr<std::vector<file::Path>>(video_source);
-        if(filenames.size() > 1) {
-            Debug("Found an array of filenames (%d).", filenames.size());
-        } else if(filenames.size() == 1) {
-            SETTING(video_source) = filenames.front();
-            filenames.clear();
-        } else
-            U_EXCEPTION("Empty input filename '%S'. Please specify an input name.", &video_source);
-        
-    } catch(const illegal_syntax& e) {
-        // ... do nothing
-    }
-    
-    if(filenames.empty()) {
-        auto filepath = file::Path(SETTING(video_source).value<std::string>());
-        if(filepath.remove_filename().empty()) {
-            auto path = (SETTING(output_dir).value<file::Path>() / filepath);
-            filenames.push_back(path);
-        } else
-            filenames.push_back(filepath);
-    }
-    
-    for(auto &name : filenames) {
-        name = pv::DataLocation::parse("input", name);
-    }
-    
-    if(filenames.size() == 1) {
-        _video = new VideoSource(filenames.front().str());
-        
-    } else {
-        _video = new VideoSource(filenames);
-    }
-    
-    int frame_rate = _video->framerate();
-    if(frame_rate == -1) {
-        frame_rate = 25;
-    }
-    
     auto path = average_name();
     Debug("Saving average at or loading from '%S'.", &path.str());
     
-    if(SETTING(frame_rate).value<int>() == -1) {
-        Debug("Setting frame rate to %d (from video).", frame_rate);
-        SETTING(frame_rate) = frame_rate;
-    } else if(SETTING(frame_rate).value<int>() != frame_rate) {
-        Warning("Overwriting default frame rate of %d with %d.", frame_rate, SETTING(frame_rate).value<int>());
-    }
-    
-    if(!SETTING(mask_path).value<file::Path>().empty()) {
-        auto path = pv::DataLocation::parse("input", SETTING(mask_path).value<file::Path>());
-        if(path.exists()) {
-            _video_mask = new VideoSource(path.str());
-        }
-    }
-    
     if(path.exists()) {
         if(SETTING(reset_average)) {
             Warning("Average exists, but will not be used because 'reset_average' is set to true.");
@@ -791,8 +811,9 @@ bool FrameGrabber::add_image_to_average(const Image_t& current) {
             _current_image = nullptr;
         }
         
-        static auto averaging_method = GlobalSettings::has("averaging_method") ?  utils::lowercase(SETTING(averaging_method).value<grab::averaging_method_t::Class>()) : grab::averaging_method_t::mean;
-        static bool use_mean = averaging_method != grab::averaging_method_t::max && averaging_method != grab::averaging_method_t::max;
+        static auto averaging_method = GlobalSettings::has("averaging_method") ? SETTING(averaging_method).value<averaging_method_t::Class>() : averaging_method_t::mean;
+        static bool use_mean = averaging_method != averaging_method_t::max
+            && averaging_method != averaging_method_t::max;
         static gpuMat empty_image;
         if(empty_image.empty())
             empty_image = gpuMat::zeros(_cropped_size.height, _cropped_size.width, CV_8UC1);
@@ -815,9 +836,9 @@ bool FrameGrabber::add_image_to_average(const Image_t& current) {
                 _current_average.copyTo(local_av);
                 empty_image.copyTo(local);
                 
-                if(averaging_method == grab::averaging_method_t::max)
+                if(averaging_method == averaging_method_t::max)
                     _current_average = cv::max(local, local_av);
-                else if(averaging_method == grab::averaging_method_t::min)
+                else if(averaging_method == averaging_method_t::min)
                     _current_average = cv::min(local, local_av);
             }
         }
@@ -1356,6 +1377,11 @@ Queue::Code FrameGrabber::process_image(Image_t& current) {
     static Timing timing("process_image", 10);
     TakeTiming take(timing);
     
+    if(_task._valid && _task._complete) {
+        _task._future.get();
+        _task._valid = false;
+    }
+    
     ensure_average_is_ready();
     
     // make timestamp relative to _start_timing
diff --git a/Application/src/grabber/grabber.h b/Application/src/grabber/grabber.h
index 8e587c9..11faec7 100644
--- a/Application/src/grabber/grabber.h
+++ b/Application/src/grabber/grabber.h
@@ -108,9 +108,21 @@ public:
     typedef ImageThreads AnalysisType;
     
     static track::Tracker* tracker_instance();
+    struct Task {
+        std::future<void> _future;
+        std::atomic<bool> _complete = false;
+        std::atomic<bool> _valid = true;
+        
+        Task() = default;
+        Task(Task&& task)
+            : _future(std::move(task._future)), _complete(task._complete.load()), _valid(task._valid.load())
+        {}
+    };
     
 protected:
-    cv::Size _cam_size;
+    Task _task;
+    
+    GETTER(cv::Size, cam_size)
     GETTER(cv::Size, cropped_size)
     GETTER(Bounds, crop_rect)
     
@@ -254,6 +266,7 @@ private:
     
     void crop_and_scale(gpuMat&);
     bool add_image_to_average(const Image_t&);
+    void initialize(std::function<void(FrameGrabber&)>&& callback_before_starting);
 };
 
 #endif
diff --git a/Application/src/grabber/gui.cpp b/Application/src/grabber/gui.cpp
index 946bb40..6d55db0 100644
--- a/Application/src/grabber/gui.cpp
+++ b/Application/src/grabber/gui.cpp
@@ -26,7 +26,7 @@ GUI* GUI::instance() {
 GUI::GUI(FrameGrabber& grabber)
 : _grabber(grabber),
     _crop_offsets(SETTING(crop_offsets).value<CropOffsets>()),
-    _size(grabber.processed().header().resolution),
+    _size(grabber.cam_size().width, grabber.cam_size().height),
     _cropped_size(grabber.cropped_size()),
     //_window_scale(min((sf::VideoMode::getDesktopMode().height - 250 > _cropped_size.height * 1.8f ? 1.8f : ((sf::VideoMode::getDesktopMode().height - 250) / float(_cropped_size.height))),
     //               (sf::VideoMode::getDesktopMode().width - 250 > _cropped_size.width * 1.8f ? 1.8f : ((sf::VideoMode::getDesktopMode().width - 250) / float(_cropped_size.width))))),
diff --git a/Application/src/tracker/VideoOpener.cpp b/Application/src/tracker/VideoOpener.cpp
index 0a03eaa..c7cbbd7 100644
--- a/Application/src/tracker/VideoOpener.cpp
+++ b/Application/src/tracker/VideoOpener.cpp
@@ -74,11 +74,11 @@ VideoOpener::VideoOpener() {
     });
 
     _text_fields["averaging_method"] = LabeledField("averaging_method");
-    _text_fields["averaging_method"]._text_field->set_text(TEMP_SETTING(average_samples).get().valueString());
+    _text_fields["averaging_method"]._text_field->set_text(TEMP_SETTING(averaging_method).get().valueString());
     _text_fields["averaging_method"]._text_field->on_text_changed([this]() {
         try {
-            auto number = Meta::fromStr<grab::averaging_method_t::Class>(_text_fields["averaging_method"]._text_field->text());
-            TEMP_SETTING(average_samples) = number;
+            auto number = Meta::fromStr<averaging_method_t::Class>(_text_fields["averaging_method"]._text_field->text());
+            TEMP_SETTING(averaging_method) = number;
 
             if (_buffer) {
                 _buffer->restart_background();
@@ -262,9 +262,11 @@ void VideoOpener::BufferedVideo::restart_background() {
         resize_image(img, 500 / double(max(img.cols, img.rows)));
     
     img.convertTo(_background_image, CV_32FC1);
-    _background_image.copyTo(_accumulator);
+    _accumulator = std::make_unique<AveragingAccumulator<>>(TEMP_SETTING(averaging_method).value<averaging_method_t::Class>());
+    _accumulator->add(img);
+    //_background_image.copyTo(_accumulator);
     
-    _background_samples = 1;
+    //_background_samples = 1;
     _background_video_index = 0;
     //_accumulator = cv::Mat::zeros(img.rows, img.cols, CV_32FC1);
     
@@ -280,17 +282,20 @@ void VideoOpener::BufferedVideo::restart_background() {
             if(max(img.cols, img.rows) > 500)
                 resize_image(img, 500 / double(max(img.cols, img.rows)));
             
-            img.convertTo(flt, CV_32FC1);
+            /*img.convertTo(flt, CV_32FC1);
             if(!_accumulator.empty())
                 cv::add(_accumulator, flt, _accumulator);
             else
                 flt.copyTo(_accumulator);
-            ++_background_samples;
+            ++_background_samples;*/
+            
+            _accumulator->add(img);
+            
             Debug("%d/%d (%d)", _background_video_index, _background_video->length(), step);
+            auto image = _accumulator->finalize();
             
             std::lock_guard guard(_frame_mutex);
-            cv::divide(_accumulator, cv::Scalar(_background_samples), _background_copy);
-            _set_copy_background = true;
+            _background_copy = std::move(image);
         }
         
         Debug("Done calculating background");
@@ -356,9 +361,9 @@ void VideoOpener::BufferedVideo::update_loop() {
         
         {
             std::lock_guard frame_guard(_frame_mutex);
-            if(_set_copy_background) {
-                _set_copy_background = false;
-                _background_copy.copyTo(_background_image);
+            if(_background_copy) {
+                _background_copy->get().convertTo(_background_image, CV_32FC1);
+                _background_copy = nullptr;
             }
             cv::absdiff(_background_image, _flt, _diff);
         }
diff --git a/Application/src/tracker/VideoOpener.h b/Application/src/tracker/VideoOpener.h
index 70d9572..1f6c2cb 100644
--- a/Application/src/tracker/VideoOpener.h
+++ b/Application/src/tracker/VideoOpener.h
@@ -30,9 +30,9 @@ public:
         gpuMat _background_image;
         cv::Mat _local;
         gpuMat _flt, _img, _mask, _diff, _alpha, _output;
-        cv::Mat _accumulator, _background_copy;
-        bool _set_copy_background = false;
-        uint64_t _background_samples = 0;
+        std::unique_ptr<Image> _background_copy;
+        std::unique_ptr<AveragingAccumulator<>> _accumulator;
+        //uint64_t _background_samples = 0;
         uint64_t _background_video_index = 0;
         
         std::mutex _frame_mutex;
@@ -81,7 +81,7 @@ public:
         
         LabeledField(const std::string& name = "")
             : _text(std::make_shared<gui::Text>(name)),
-              _text_field(std::make_shared<gui::Textfield>("", Bounds(0, 0, 400, 33)))
+              _text_field(std::make_shared<gui::Textfield>("", Bounds(0, 0, 300, 33)))
               //_joint(std::make_shared<gui::HorizontalLayout>(std::vector<Layout::Ptr>{_text, _text_field}))
         {
             _text->set_font(Font(0.75, Style::Bold));
diff --git a/docs/parameters_tgrabs.rst b/docs/parameters_tgrabs.rst
index 9ad1949..b660fa9 100644
--- a/docs/parameters_tgrabs.rst
+++ b/docs/parameters_tgrabs.rst
@@ -39,13 +39,19 @@ TGrabs parameters
 	.. seealso:: :func:`average_method`, 
 
 
-.. function:: averaging_method(string)
+.. function:: averaging_method(averaging_method_t)
 	:noindex:
 
-	**default value:** "mean"
+	**default value:** mean
 
+	**possible values:**
+		- `mean`: Sum all samples and divide by N.
+		- `mode`: Calculate a per-pixel median of the samples.
+		- `max`: Use a per-pixel minimum across samples.
+		- `min`: Use a per-pixel maximum across samples.
+
+	Determines the way in which the background samples are combined. The background generated in the process will be used to subtract background from foreground objects during conversion.
 
-	This can be either 'mean', 'mode', 'min' or 'max'. All accumulated background images (to be used for generating an average background) will be combined using the max or mean function.
 
 
 
-- 
GitLab