view CalciumSignalAnalyser.cpp @ 1:26c75dbfe61c

Update of the documentation and Readme file
author mathieub <mathieu.barthet@eecs.qmul.ac.uk>
date Mon, 20 Jun 2011 22:10:14 +0100
parents cd5e0ac31ff7
children e26a8489c148
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
 Calcium Signal Analyser Vamp Plugin
 
 Description: Detects and characterises the transients in noisy signals.
 
 Authors: Mathieu Barthet.
 Based on the QM Vamp note onset detector plugin by Christian Landone, 
 Chris Duxbury, and Juan Pablo Bello.
 
 Version: 1
 
 Centre for Digital Music, Queen Mary, University of London.
 All rights reserved.
 */

#include "CalciumSignalAnalyser.h"

#include <dsp/onsets/PeakPicking.h>
#include <dsp/tempotracking/TempoTrack.h>

using std::string;
using std::vector;
using std::cerr;
using std::endl;

#define MAX_PEAKFREQ 10; //maximum peak frequency in Hz

//#define VERBOSE //remove comment to have output messages for data display or debugging information

CalciumSignalAnalyser::CalciumSignalAnalyser(float inputSampleRate) :
Vamp::Plugin(inputSampleRate),
m_inputSampleRate(inputSampleRate),
m_blockSize(0),
m_stepSize(0),
data(0),
time(0),
m_sensitivity(50),
m_delta(0.2),
processcounter(0)
{
#ifdef VERBOSE
	cout << "Sample rate: " << inputSampleRate << endl;
#endif
}

CalciumSignalAnalyser::~CalciumSignalAnalyser()
{	
#ifdef VERBOSE
	cout << "Destructor" << endl;
#endif
	
	if (!data.empty()){
#ifdef VERBOSE
		cout << "Data vector reset" << endl;
#endif
		data.clear(); //clears the vector's elements
	}
	
	if (!time.empty()){
#ifdef VERBOSE
		cout << "Time vector reset" << endl;
#endif
		time.clear(); //clears the vector's elements
	}
}

string
CalciumSignalAnalyser::getIdentifier() const
{
    return "peakdetection";
}

string
CalciumSignalAnalyser::getName() const
{
    return "Calcium Signal Analyser";
}

string
CalciumSignalAnalyser::getDescription() const
{
    return "Automatic detection and characterisation of the signal's transients";
}

string
CalciumSignalAnalyser::getMaker() const
{
    return "Mathieu Barthet";
}

int
CalciumSignalAnalyser::getPluginVersion() const
{
    return 1;
}

string
CalciumSignalAnalyser::getCopyright() const
{
    return "Plugin by Mathieu Barthet. Based on the note onset detector plugin by Christian Landone, Chris Duxbury and Juan Pablo Bello. Copyright (c) 2010 QMUL - All Rights Reserved";
}

CalciumSignalAnalyser::ParameterList
CalciumSignalAnalyser::getParameterDescriptors() const
{
    ParameterList list;
	ParameterDescriptor desc;
	
    desc.identifier = "sensitivity";
    desc.name = "Peak-picking sensitivity";
    desc.description = "Sensitivity of the peak detection (criterion based on quadratic interpolation)";
    desc.minValue = 0;
    desc.maxValue = 100;
    desc.defaultValue = 50;
    desc.isQuantized = true;
    desc.quantizeStep = 1;
    desc.unit = "%";
    list.push_back(desc);
	
	desc.identifier = "deltathreshold";
    desc.name = "Peak-picking offset threshold (delta)";
    desc.description = "Offset threshold aiming at improving the peak detection by discarding the noise";
    desc.minValue = 0;
    desc.maxValue = 1;
    desc.defaultValue = 0.2;
    desc.isQuantized = true;
    desc.quantizeStep = 0.05;
    desc.unit = "";
    list.push_back(desc);
	
    return list;
}

float
CalciumSignalAnalyser::getParameter(std::string name) const
{	
	if (name == "sensitivity"){
		return m_sensitivity;
	}
	else if (name == "deltathreshold") {
		return m_delta;	
	}	
	
    return 0.0;
}

void
CalciumSignalAnalyser::setParameter(std::string name, float value)
{	
	if (name == "sensitivity") {
        if (m_sensitivity == value) return;
        m_sensitivity = value;
        
	}
	else if (name == "deltathreshold") {
		if (m_delta == value) return;
        m_delta = value;
	}
}


CalciumSignalAnalyser::ProgramList
CalciumSignalAnalyser::getPrograms() const
{
    ProgramList programs;

    return programs;
}

std::string
CalciumSignalAnalyser::getCurrentProgram() const
{	
	return "";
}

void
CalciumSignalAnalyser::selectProgram(std::string program)
{
}

bool
CalciumSignalAnalyser::initialise(size_t channels, size_t stepSize, size_t blockSize)
{
	
#ifdef VERBOSE
	cout << "Initialise" << endl;
#endif

	m_blockSize = 1;
	m_stepSize = 1;
	
#ifdef VERBOSE
	cout << m_blockSize << " " << m_stepSize << endl;
#endif
	
    if (channels < getMinChannelCount() ||
		channels > getMaxChannelCount()) {
        std::cerr << "CalciumSignalAnalyser::initialise: Unsupported channel count: "
		<< channels << std::endl;
        return false;
    }
	
    if (stepSize != getPreferredStepSize()) {
		
		std::cerr << "ERROR: CalciumSignalAnalyser::initialise: the step size has to be 1." << std::endl;
		return false;
	}
	
    if (blockSize != getPreferredBlockSize()) {
		
		std::cerr << "ERROR: CalciumSignalAnalyser::initialise: the block size has to be 1." << std::endl;
		return false;
	}
	
    return true;
}

void
CalciumSignalAnalyser::reset()
{	
#ifdef VERBOSE
	cout << "Reset" << endl;
#endif
	
	if (!data.empty()){
#ifdef VERBOSE
		cout << "Data vector reset" << endl;
#endif
		data.clear(); //clears the vector's elements
	}
	
	if (!time.empty()){
#ifdef VERBOSE
		cout << "Time vector reset" << endl;
#endif
		time.clear(); //clears the vector's elements
	}
}

size_t
CalciumSignalAnalyser::getPreferredStepSize() const
{	
	return getPreferredBlockSize();
}

size_t
CalciumSignalAnalyser::getPreferredBlockSize() const
{
	return 1;
}

size_t
CalciumSignalAnalyser::getMinChannelCount() const
{
    return 1;
}

size_t
CalciumSignalAnalyser::getMaxChannelCount() const
{
    return 1;
}


CalciumSignalAnalyser::OutputList
CalciumSignalAnalyser::getOutputDescriptors() const
{	
#ifdef VERBOSE
	cout << "get output desc" << endl;
#endif
	
    OutputList list;
  	
    OutputDescriptor pt;
    pt.identifier = "peaktimes";
    pt.name = "Peaks times";
    pt.description = "Time positions of the peaks (in seconds)";
    pt.unit = "";
    pt.hasFixedBinCount = true;
    pt.binCount = 0; //0 for time only feature
    pt.sampleType = OutputDescriptor::VariableSampleRate;
	pt.hasDuration = false;
	pt.hasKnownExtents = false;
	pt.isQuantized = false;
    pt.sampleRate = 0; //no sensible resolution, the time of the feature are given in the timestamp of the feature
	list.push_back(pt);
	
	OutputDescriptor po;
    po.identifier = "peakonsets";
    po.name = "Peaks onsets";
    po.description = "Time positions of the peak onsets (in seconds)";
    po.unit = "";
    po.hasFixedBinCount = true;
    po.binCount = 0; //0 for time only feature
    po.sampleType = OutputDescriptor::VariableSampleRate;
	po.hasDuration = false;
	po.hasKnownExtents = false;
	po.isQuantized = false;
    po.sampleRate = 0; //no sensible resolution, the time of the feature are given in the timestamp of the feature
	list.push_back(po);
	
	OutputDescriptor sdf;
    sdf.identifier = "smoothed_df";
    sdf.name = "Smoothed Detection Function";
    sdf.description = "Smoothed probability function used for peak-picking";
    sdf.unit = "";
    sdf.hasFixedBinCount = true;
    sdf.binCount = 1;
    sdf.hasKnownExtents = false;
    sdf.isQuantized = false;
    sdf.sampleType = OutputDescriptor::FixedSampleRate;
	sdf.sampleRate = m_inputSampleRate; //the smoothed detection function has the same sample rate as the input signal
    list.push_back(sdf);
		
	OutputDescriptor pf;
    pf.identifier = "peak_frequency";
    pf.name = "Peak frequency";
    pf.description = "Frequency of the peaks (in Hz)";
    //pf.unit = "Hz"; //if unit is Hz then the feature is treated as a pitch which is not relevant in this case
    pf.unit = "";
	pf.hasFixedBinCount = true;
    pf.binCount = 1; //1 corresponds to time and value
    pf.sampleType = OutputDescriptor::VariableSampleRate;
	pf.sampleRate = 0; //no sensible resolution, the time of the feature is given in the timestamp of the feature
	pf.hasKnownExtents = true;
	pf.minValue = 0;
	pf.maxValue = MAX_PEAKFREQ;	
	pf.isQuantized = false;
	pf.hasDuration = true; //duration of the signal
	list.push_back(pf);
    
	OutputDescriptor mipi;
    mipi.identifier = "mean_interpeakinterval";
    mipi.name = "Mean Inter Peak Interval";
    mipi.description = "Mean of the time intervals between peaks (in seconds)";
    mipi.unit = "s";
	mipi.hasFixedBinCount = true;
    mipi.binCount = 1; //1 corresponds to time and value
    mipi.sampleType = OutputDescriptor::VariableSampleRate;
	mipi.sampleRate = 0; //no sensible resolution, the time of the feature is given in the timestamp of the feature
	mipi.hasKnownExtents = false;
	mipi.isQuantized = false;
	mipi.hasDuration = true; //duration of the signal
	list.push_back(mipi);
	
    return list;
}

CalciumSignalAnalyser::FeatureSet
CalciumSignalAnalyser::process(const float *const *inputBuffers,
                       Vamp::RealTime timestamp)
{	
	FeatureSet fs;
	
	if (m_blockSize == 0 || m_stepSize == 0) {
		cerr << "ERROR: CalciumSignalAnalyser::process: "
		<< "CalciumSignalAnalyser has not been initialised."
		<< endl;
		return fs;
    }
	
	data.push_back(inputBuffers[0][0]); //assumes that only one sample is read at a time
	time.push_back(timestamp);
	
	processcounter ++;
	
	fs = FeatureSet(); //empty feature set
	
	return fs;	
}

CalciumSignalAnalyser::FeatureSet
CalciumSignalAnalyser::getRemainingFeatures()
{
   	FeatureSet returnFeatures;
	returnFeatures = FeatureSet(); 
	
#ifdef VERBOSE
	cout << "Get remaining features" << endl << endl;
#endif
	
	/***************************************
	 Peak times
	 ****************************************/
    
	double aCoeffs[] = { 1.0000, -0.5949, 0.2348 };
    double bCoeffs[] = { 0.1600,  0.3200, 0.1600 };
	
    //The signal is directly treated as a detection function for the peak picking stage
	
    PPickParams ppParams;
    ppParams.length = data.size(); //length of the signal
	
#ifdef VERBOSE
	cout << "Length: " << ppParams.length << endl;
#endif
	
	// tau and cutoff appear to be unused in PeakPicking, but I've
    // inserted some moderately plausible values rather than leave
    // them unset.  The QuadThresh values come from trial and error.
    // The rest of these are copied from ttParams in the BeatTracker
    // code: I don't claim to know whether they're good or not --cc
        
	ppParams.tau = m_stepSize / m_inputSampleRate; //not used for peak picking 
	
	ppParams.alpha = 9;
    ppParams.cutoff = m_inputSampleRate/4; //not used for peak picking 
    ppParams.LPOrd = 2;
    ppParams.LPACoeffs = aCoeffs;
    ppParams.LPBCoeffs = bCoeffs;
    
	//TO-DO: the duration of the median filtering window could be another adjustable parameter 
	//of the plugin since the optimal duration may vary according to signals. It should not be 
	//set up in samples but in seconds instead to maintain the same behavior for different signal 
	//sample frequencies.
	
	ppParams.WinT.post = 8; //works well with calcium data (15 samples at 3 Hz --> W = 5 s --> M = 2.5 s)
    ppParams.WinT.pre = 7;
    		
	ppParams.QuadThresh.a = (100 - m_sensitivity) / 1000.0;
    ppParams.QuadThresh.b = 0;
    ppParams.QuadThresh.c = (100 - m_sensitivity) / 1500.0;
	ppParams.delta = m_delta; //delta threshold used as an offset when computing the smoothed detection function
			
    PeakPicking peakPicker(ppParams);
	
	double *ppSrc = new double[ppParams.length];
	if (ppSrc==NULL)	{
		cerr << "ERROR: CalciumSignalAnalyser::getRemainingFeatures:"
		<< "Unable to allocate memory (ppSrc)."
		<< endl;
		return returnFeatures;
	}
		
    for (unsigned int i = 0; i < ppParams.length; ++i) {
        
		ppSrc[i] = (double) data[i];
	}	
	
    vector<int> peaks;
	vector<int> peak_frame;
	vector<int> onsets_frame;
	
	peakPicker.process(ppSrc, ppParams.length, peaks);
	
	for (size_t i = 0; i < peaks.size(); ++i) {
		
		//peak times
        size_t index = peaks[i];
		size_t peakframe = peaks[i] * m_stepSize;
		peak_frame.push_back(peakframe);
		
		Feature featurepeaks;
		featurepeaks.hasTimestamp = true;	
		featurepeaks.timestamp = Vamp::RealTime::frame2RealTime(peakframe, lrintf(m_inputSampleRate));	
		
		returnFeatures[0].push_back(featurepeaks); // peak times are output 0
		
		//algorithm to detect the peak onsets
		 
		double prevDiff = 0.0;
		while (index > 1) {
			double diff = ppSrc[index] - ppSrc[index-1];
			if (diff < prevDiff * 0.9) break;
			prevDiff = diff;
			--index;
		}	
			
		size_t onsetframe = index * m_stepSize;		
		onsets_frame.push_back(onsetframe);
		
		Feature featureonsets;
		featureonsets.hasTimestamp = true;	
		featureonsets.timestamp = Vamp::RealTime::frame2RealTime(onsetframe, lrintf(m_inputSampleRate));	
		
#ifdef VERBOSE
		cout << featureonsets.timestamp << endl;
#endif
		returnFeatures[1].push_back(featureonsets); // peak onsets are output 1
    }
	
    for (unsigned int i = 0; i < ppParams.length; ++i) {
        
        Feature feature;
		feature.hasTimestamp = true;
		size_t frame = i * m_stepSize;
				
		feature.timestamp = Vamp::RealTime::frame2RealTime(frame, lrintf(m_inputSampleRate));
		
        feature.values.push_back(ppSrc[i]);
     	
		returnFeatures[2].push_back(feature); // smoothed df is output 2
    }
	
	/***************************************
	 Peak frequency
	 ****************************************/
	
	double peakfreq, duration;
	int npeaks; //total number of peaks
	
	//last element of the time vector is the duration of the signal - converts the RealTime to a float
	duration = time.back()/Vamp::RealTime(1,0); //division by 1 s to obtain a double
		
	npeaks = peaks.size();
	peakfreq = npeaks/duration;
	
#ifdef VERBOSE
	cout << npeaks << endl;
	cout << duration << endl;	
	cout << peakfreq << endl;
#endif
	
	Feature pf;
	pf.hasTimestamp = true;
	pf.timestamp = time[0];
	pf.hasDuration = true;			
	pf.duration = time.back()-time[0];
	
#ifdef VERBOSE
	cout << "processcounter" << processcounter << endl;
	
	cout << "time.back " << time.back().toText() << endl;
	cout << "time.size " << time.size() << endl;
	cout << "data.size " << data.size() << endl;
	cout << "time[0, 1 ...] " << time[0].toText() << " " << time[1].toText() << " " << time[2].toText() << " " << time[time.size()].toText() << endl;
	
	cout << "time[999] " << time[999].toText() << endl;
	cout << "data[999] " << data[999] << endl;
	cout << "time[1000] " << time[1000].toText() << endl;
	cout << "data[1000] " << data[1000]<< endl;
#endif
	
	pf.values.push_back(peakfreq);
		
	char pflabel[256];
	sprintf(pflabel,"%f Hz",peakfreq);
	pf.label = pflabel;
	
#ifdef VERBOSE
	cout << "label: " << pflabel << endl;
#endif
	
	returnFeatures[3].push_back(pf); //peak frequency is output 3
	
	/***************************************
	 Mean Inter Peak Interval
	 ****************************************/
	
	double o1,o2;
	
	double sumipi_s = 0;
	double meanipi_s;
	
	for (int j = 0; j < npeaks-1; ++j) {
		o2 = Vamp::RealTime::frame2RealTime(peak_frame[j+1], lrintf(m_inputSampleRate))/Vamp::RealTime(1,0);
		o1 = Vamp::RealTime::frame2RealTime(peak_frame[j], lrintf(m_inputSampleRate))/Vamp::RealTime(1,0);
#ifdef VERBOSE
		cout << "o1: " << o1 << endl;
		cout << "o2: " << o2 << endl;
#endif
		sumipi_s = sumipi_s + o2 - o1;	
	}
	
	meanipi_s = sumipi_s/(npeaks-1);
	
#ifdef VERBOSE
	cout << "Sum IPI: " << sumipi_s << endl;
	cout << "Mean IPI: " << meanipi_s << endl;
#endif
	
	Feature mipi;
	mipi.hasTimestamp = true;
	mipi.timestamp = time[0];
	mipi.hasDuration = true;			
	mipi.duration = time.back()-time[0];
	
	mipi.values.push_back(meanipi_s); //mean inter peak interval in secs
	
	returnFeatures[4].push_back(mipi); //mean inter peak interval is output 4
	
	//clear data
	
	delete [] ppSrc;
	
	if (!data.empty()){
#ifdef VERBOSE
		cout << "Data vector reset" << endl;
#endif
		data.clear(); //clears the vector's elements
	}
	
	if (!time.empty()){
#ifdef VERBOSE
		cout << "Time vector reset" << endl;
#endif
		time.clear(); //clears the vector's elements
	}
	
#ifdef VERBOSE
	cout << "End of get remaining features" << endl << endl;
#endif
	return returnFeatures;
}