/*
 *       File name:  aghermann/artifact-detection/3in1.cc
 *         Project:  Aghermann
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 * Initial version:  2013-10-14
 *
 *         Purpose:  Comprehensive artifact detection
 *
 *         License:  GPL
 */

#include "3in1.hh"
#include "common/lang.hh"
#include "libsigfile/source-base.hh"
#include "libsigfile/typed-source.hh"
#include "aghermann/globals.hh"
#include "aghermann/rk1968/rk1968.hh"
using namespace std;
using namespace agh::ad;


CComprehensiveArtifactDetector::
CComprehensiveArtifactDetector (const string& name_, agh::TExpDirLevel level_, agh::CExpDesign& ED_, const agh::SExpDirLevelId& level_id_)
      : CStorablePPack (common_subdir, name_, level_, ED_, level_id_)
{
        using agh::confval::SValidator;
        config
                ("do_flat_regions",           &Pp.do_flat_regions)
                ("do_emg_perturbations",      &Pp.do_emg_perturbations)
                ("do_mc_based",               &Pp.do_mc_based)
                ("flat.min_size",             &Pp.flat_min_size,             SValidator<double>::SVFRangeIn (.01, 100.))
                ("flat.pad",                  &Pp.flat_pad,                  SValidator<double>::SVFRangeIn (0., 100.))
                ("EMG.min_steadytone_factor", &Pp.emg_min_steadytone_factor, SValidator<double>::SVFRangeEx (1., 50.))
                ("EMG.min_steadytone_run",    &Pp.emg_min_steadytone_run,    SValidator<double>::SVFRangeIn (0., 60.))
                ("MC.scope",                  &Pp.MC.scope,                  SValidator<double>::SVFRangeIn (0.5, 60.))
                ("MC.upper_thr",              &Pp.MC.upper_thr,              SValidator<double>::SVFRangeIn (0., 100.))
                ("MC.lower_thr",              &Pp.MC.lower_thr,              SValidator<double>::SVFRangeIn (-100., 0.))
                ("MC.f0",                     &Pp.MC.f0,                     SValidator<double>::SVFRangeIn (.1, 80.))
                ("MC.fc",                     &Pp.MC.fc,                     SValidator<double>::SVFRangeIn (.1, 80.))
                ("MC.bandwidth",              &Pp.MC.bandwidth,              SValidator<double>::SVFRangeIn (.1, 40.))
                ("MC.mc_gain",                &Pp.MC.mc_gain,                SValidator<double>::SVFRangeIn (0., 100.))
                ("MC.iir_backpolate",         &Pp.MC.iir_backpolate,         SValidator<double>::SVFRangeIn (0., 1.))
                ("MC.E",                      &Pp.MC.E,                      SValidator<double>::SVFRangeIn (.1, 100.))
                ("MC.dmin",                   &Pp.MC.dmin,                   SValidator<double>::SVFRangeIn (-100., 100.))
                ("MC.dmax",                   &Pp.MC.dmax,                   SValidator<double>::SVFRangeIn (-100., 100.))
                ("MC.sssu_hist_size",         &Pp.MC.sssu_hist_size,         SValidator<size_t>::SVFRangeIn (10, 1000))
                ("MC.smooth_side",            &Pp.MC.smooth_side,            SValidator<size_t>::SVFRangeIn (0, 10))
                ("MC.estimate_E",             &Pp.MC.estimate_E)
                ("MC.use_range",              &Pp.MC.use_range);

        make_default_SComprehensiveArtifactDetectionPPack( Pp);
        load();
}


string
CComprehensiveArtifactDetector::
serialize() const
{
        return move(
                CStorablePPack::serialize() +
                agh::str::sasprintf(
                        "flat.min_size:%g; flat.pad:%g;\n"
                        "emg.min_steadytone_factor:%g; emg.min_steadytone_run:%g;\n"
                        "MC.scope:%g; MC.upper_thr:%g; MC.lower_thr:%g; MC.f0:%g; MC.fc:%g; MC.bandwidth:%g; MC.mc_gain:%g; MC.iir_backpolate:%g; MC.E:%g; MC.dmin:%g; MC.dmax:%g; MC.sssu_hist_size:%zu; MC.smooth_side:%zu; MC.estimate_E:%d; MC.use_range:%d;",
                        Pp.flat_min_size, Pp.flat_pad,
                        Pp.emg_min_steadytone_factor, Pp.emg_min_steadytone_run,
                        Pp.MC.scope, Pp.MC.upper_thr, Pp.MC.lower_thr, Pp.MC.f0, Pp.MC.fc, Pp.MC.bandwidth, Pp.MC.mc_gain, Pp.MC.iir_backpolate,
                        Pp.MC.E, Pp.MC.dmin, Pp.MC.dmax, Pp.MC.sssu_hist_size, Pp.MC.smooth_side,
                        Pp.MC.estimate_E, Pp.MC.use_range));
}



TDetectArtifactsResult
agh::ad::
detect_artifacts( sigfile::SNamedChannel& N,
                  const SComprehensiveArtifactDetectionPPack& P)
{
        auto S = N.source.get_signal_original( N.sig_no);
        size_t sr = N.source.samplerate( N.sig_no);

        auto AF2 = N.source.artifacts( N.sig_no);
        auto& AF = N.source.artifacts( N.sig_no);
        bool marked_some = false;

        // 1. Flat regions
        if ( P.do_flat_regions ) {
                size_t  last_j = 0;
                for ( size_t i = 0; i < S.size(); ++i ) {
                        if ( agh::dbl_cmp( S[i], S[last_j]) == 0 ) {
                                size_t j = i;
                                while ( j < S.size() && agh::dbl_cmp( S[j], S[last_j]) == 0 )
                                        ++j;
                                if ( j-i > P.flat_min_size * sr ) {
                                        AF.mark_artifact(
                                                (double)i/sr - P.flat_pad,
                                                (double)j/sr + P.flat_pad);
                                        marked_some = true;
                                }
                                i = j;
                        }
                        last_j = i;
                }
        }

      // 2. EMG perturbations
        if ( P.do_emg_perturbations &&
             N.source.signal_type(N.sig_no) != sigfile::SChannel::TType::emg ) {
                // which EMG channels are there?
                list<int> emgRR;
                for ( int h = 0; h < (int)N.source.n_channels(); ++h )
                        if ( N.source.signal_type(h) == sigfile::SChannel::TType::emg )
                                emgRR.push_front( h);

                if ( emgRR.empty() )
                        APPLOG_INFO ("No EMG recordings in %s:%s, skipping EMG perturbation-bound artifact detection", N.source.filename(), N.source.channel_by_id(N.sig_no).name());

                for ( const int h : emgRR ) {
                        sigproc::SSignalRef<TFloat> sigref {N.source.get_signal_original(h), N.source.samplerate(h)};
                        double this_steady_tone;
                        tie (this_steady_tone, ignore) =
                                agh::rk1968::emg_steady_tone(
                                        sigref,
                                        P.emg_steady_secs,
                                        P.emg_max_dev_factor);
                        auto emg_raw_profile =
                                sigproc::raw_signal_profile<TFloat>(
                                        sigref,
                                        1., P.emg_min_steadytone_run);
                        for ( size_t t = 0; t < emg_raw_profile.size(); ++t )
                                if ( emg_raw_profile[t] > this_steady_tone * P.emg_min_steadytone_factor ) {
                                        marked_some = true;
                                        AF.mark_artifact(
                                                (t+0) * P.emg_min_steadytone_run,
                                                (t+1) * P.emg_min_steadytone_run);
                                }
                }
        }

        // 3. MC-based
        if ( P.do_mc_based &&
             N.source.signal_type(N.sig_no) == sigfile::SChannel::TType::eeg ) {
                auto marked =
                        metrics::mc::detect_artifacts( S, sr, P.MC);
                for ( size_t p = 0; p < marked.size(); ++p ) {
                        AF.mark_artifact(
                                (marked[p]+0) * P.MC.scope,
                                (marked[p]+1) * P.MC.scope);
                        marked_some = true;
                }
        }

        return  marked_some
                ? AF() == AF2() ? TDetectArtifactsResult::marked_same : TDetectArtifactsResult::marked_new
                : TDetectArtifactsResult::marked_nothing;
}




// this looks appropriate if we claim to be comprehensive
void
agh::ad::
detect_artifacts( sigfile::CSource& F,
                  const SComprehensiveArtifactDetectionPPack& P)
{
        // it may be reasonable for this function to be or do
        // something more than just a mere channel iterator
        for ( int h = 0; h < (int)F.n_channels(); ++h ) {
                sigfile::SNamedChannel N {F, h};
                agh::ad::detect_artifacts( N, P);
        }
}


// Local Variables:
// Mode: c++
// indent-tabs-mode: nil
// tab-width: 8
// c-basic-offset: 8
// End:
