From 95d4c8b3f97163c675f7ec59dc18d61475406bf8 Mon Sep 17 00:00:00 2001 From: Aaron Cuevas Lopez <aacuelo@teleco.upv.es> Date: Mon, 6 Mar 2017 17:12:39 +0100 Subject: [PATCH] Restore experimental plugins to development branch --- .../Plugins/SpikeRaster/SpikeRaster.vcxproj | 132 ++ .../SpikeRaster/SpikeRaster.vcxproj.filters | 36 + Source/Plugins/JuliaProcessor/.Makefile | 49 + Source/Plugins/JuliaProcessor/JuliaEditor.cpp | 151 +++ Source/Plugins/JuliaProcessor/JuliaEditor.h | 64 + .../Plugins/JuliaProcessor/JuliaProcessor.cpp | 169 +++ .../Plugins/JuliaProcessor/JuliaProcessor.h | 68 + .../Plugins/JuliaProcessor/OpenEphysLib.cpp | 70 + .../JuliaProcessor/exampleProcessor.jl | 21 + Source/Plugins/SpikeRaster/Makefile | 45 + Source/Plugins/SpikeRaster/OpenEphysLib.cpp | 97 ++ Source/Plugins/SpikeRaster/SpikeRaster.cpp | 220 +++ Source/Plugins/SpikeRaster/SpikeRaster.h | 120 ++ .../Plugins/SpikeRaster/SpikeRasterEditor.cpp | 1192 +++++++++++++++++ .../Plugins/SpikeRaster/SpikeRasterEditor.h | 281 ++++ 15 files changed, 2715 insertions(+) create mode 100644 Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj create mode 100644 Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj.filters create mode 100644 Source/Plugins/JuliaProcessor/.Makefile create mode 100644 Source/Plugins/JuliaProcessor/JuliaEditor.cpp create mode 100644 Source/Plugins/JuliaProcessor/JuliaEditor.h create mode 100644 Source/Plugins/JuliaProcessor/JuliaProcessor.cpp create mode 100644 Source/Plugins/JuliaProcessor/JuliaProcessor.h create mode 100644 Source/Plugins/JuliaProcessor/OpenEphysLib.cpp create mode 100644 Source/Plugins/JuliaProcessor/exampleProcessor.jl create mode 100644 Source/Plugins/SpikeRaster/Makefile create mode 100644 Source/Plugins/SpikeRaster/OpenEphysLib.cpp create mode 100644 Source/Plugins/SpikeRaster/SpikeRaster.cpp create mode 100644 Source/Plugins/SpikeRaster/SpikeRaster.h create mode 100644 Source/Plugins/SpikeRaster/SpikeRasterEditor.cpp create mode 100644 Source/Plugins/SpikeRaster/SpikeRasterEditor.h diff --git a/Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj b/Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj new file mode 100644 index 000000000..898fba3e7 --- /dev/null +++ b/Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}</ProjectGuid> + <RootNamespace>SpikeRaster</RootNamespace> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Debug32.props" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Debug64.props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Release32.props" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Release64.props" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup /> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <SDLCheck>true</SDLCheck> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\..\..\Source\Plugins\SpikeRaster\OpenEphysLib.cpp" /> + <ClCompile Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRaster.cpp" /> + <ClCompile Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRasterEditor.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRaster.h" /> + <ClInclude Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRasterEditor.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj.filters b/Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj.filters new file mode 100644 index 000000000..3123d5593 --- /dev/null +++ b/Builds/VisualStudio2013/Plugins/SpikeRaster/SpikeRaster.vcxproj.filters @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\..\Source\Plugins\SpikeRaster\OpenEphysLib.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRaster.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRasterEditor.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRaster.h"> + <Filter>Source Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\..\Source\Plugins\SpikeRaster\SpikeRasterEditor.h"> + <Filter>Source Files</Filter> + </ClInclude> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/Source/Plugins/JuliaProcessor/.Makefile b/Source/Plugins/JuliaProcessor/.Makefile new file mode 100644 index 000000000..664cabad4 --- /dev/null +++ b/Source/Plugins/JuliaProcessor/.Makefile @@ -0,0 +1,49 @@ + +LIBNAME := $(notdir $(CURDIR)) +OBJDIR := $(OBJDIR)/$(LIBNAME) +TARGET := $(LIBNAME).so + + +SRC_DIR := ${shell find ./ -type d -print} +VPATH := $(SOURCE_DIRS) + +SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) +OBJ := $(addprefix $(OBJDIR)/,$(notdir $(SRC:.cpp=.o))) + + +JL_SHARE = $(shell julia -e 'print(joinpath(JULIA_HOME,Base.DATAROOTDIR,"julia"))') + +# flags to compile "JuliaProcessor". $(JL_SHARE): toplevel of julia package +#CXXFLAGS += -I $(JL_SHARE)/src -I $(JULIA)/src/support -I $(JULIA)/usr/include +#LDFLAGS += -L $(JL_SHARE)/usr/lib -Wl,-R $(JULIA)/usr/lib -ljulia +CFLAGS += $(shell $(JL_SHARE)/julia-config.jl --cflags) +CXXFLAGS += $(shell $(JL_SHARE)/julia-config.jl --cflags) +LDFLAGS += $(shell $(JL_SHARE)/julia-config.jl --ldflags) +LDLIBS += $(shell $(JL_SHARE)/julia-config.jl --ldlibs) + +BLDCMD := $(CXX) -shared -o $(OUTDIR)/$(TARGET) $(OBJ) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) + +VPATH = $(SRC_DIR) + +.PHONY: objdir + +$(OUTDIR)/$(TARGET): objdir $(OBJ) + -@mkdir -p $(BINDIR) + -@mkdir -p $(LIBDIR) + -@mkdir -p $(OUTDIR) + @echo "Building $(TARGET)" + @$(BLDCMD) + +$(OBJDIR)/%.o : %.cpp + @echo "Compiling $<" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + +objdir: + -@mkdir -p $(OBJDIR) + +clean: + @echo "Cleaning $(LIBNAME)" + -@rm -rf $(OBJDIR) + -@rm -f $(OUTDIR)/$(TARGET) + +-include $(OBJ:%.o=%.d) diff --git a/Source/Plugins/JuliaProcessor/JuliaEditor.cpp b/Source/Plugins/JuliaProcessor/JuliaEditor.cpp new file mode 100644 index 000000000..929d13499 --- /dev/null +++ b/Source/Plugins/JuliaProcessor/JuliaEditor.cpp @@ -0,0 +1,151 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2016 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "JuliaEditor.h" +#include "JuliaProcessor.h" +#include <stdio.h> + +JuliaEditor::JuliaEditor(GenericProcessor* parentNode, bool useDefaultParameterEditors=true) + : GenericEditor(parentNode, useDefaultParameterEditors) +{ + juliaProcessor = (JuliaProcessor*) parentNode; + + lastFilePath = File::getCurrentWorkingDirectory(); + + fileButton = new UtilityButton("Select file",Font("Small Text", 13, Font::plain)); + fileButton->addListener(this); + fileButton->setBounds(10,85,100,25); + addAndMakeVisible(fileButton); + + reloadFileButton = new UtilityButton("refresh",Font("Small Text", 13, Font::plain)); + reloadFileButton->addListener(this); + reloadFileButton->setBounds(100+10,85,60,25); + addAndMakeVisible(reloadFileButton); + + fileNameLabel = new Label("FileNameLabel", "No file selected."); + fileNameLabel->setBounds(10,85+20,140,25); + addAndMakeVisible(fileNameLabel); + + bufferSizeSelection = new Label("Buffer Size","30000"); // this is currently set in RHD2000Thread, the cleaner would be to set it here again + bufferSizeSelection->setEditable(true,false,false); + bufferSizeSelection->addListener(this); + bufferSizeSelection->setBounds(120,60,60,20); + bufferSizeSelection->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(bufferSizeSelection); + + bufferSizeSelectionLabel = new Label("","NBuf.:"); + bufferSizeSelectionLabel->attachToComponent (bufferSizeSelection,true); + addAndMakeVisible(bufferSizeSelectionLabel); + + // Image im; + // im = ImageCache::getFromMemory(BinaryData::JuliaIconActive_png, + // BinaryData::JuliaIconActive_pngSize); + + // icon = new ImageIcon(im); + // addAndMakeVisible(icon); + // icon->setBounds(15,25,61,54); + // icon->setOpacity(0.3f); + + desiredWidth = 200; + + setEnabledState(false); +} + +JuliaEditor::~JuliaEditor() +{ + +} + +void JuliaEditor::setFile(String file) +{ + File fileToRead(file); + lastFilePath = fileToRead.getParentDirectory(); + juliaProcessor->setFile(fileToRead.getFullPathName()); + fileNameLabel->setText(fileToRead.getFileName(), dontSendNotification); + + + // setEnabledState(true); + // icon->setOpacity(1.0f); // tie this to hasJuliaInstance instead of just setting it! + // repaint(); +} + +void JuliaEditor::buttonEvent(Button* button) +{ + if (!acquisitionIsActive) + { + if (button == fileButton) + { + //std::cout << "Button clicked." << std::endl; + //FileChooser chooseJuliaProcessorFile("Please select the file you want to load...", lastFilePath, "*"); + + + // file dialogs are screwed up in current xubuntu, so we'll do this for now. + setFile("/home/jvoigts/Documents/Github/plugin-GUI/Source/Plugins/JuliaProcessor/exampleProcessor.jl"); + + + // if (chooseJuliaProcessorFile.browseForFileToOpen()) + { + // Use the selected file + //setFile(chooseJuliaProcessorFile.getResult().getFullPathName()); + + // lastFilePath = fileToRead.getParentDirectory(); + // thread->setFile(fileToRead.getFullPathName()); + // fileNameLabel->setText(fileToRead.getFileName(),false); + } + } + if (button == reloadFileButton) + { + juliaProcessor->reloadFile(); + } + } +} + +void JuliaEditor::labelTextChanged(Label* label) +{ + if (!acquisitionIsActive) + { + if (label == bufferSizeSelection) + { + Value val = label->getTextValue(); + juliaProcessor->setBuffersize(int(val.getValue())); + } + } +} + +void JuliaEditor::saveEditorParameters(XmlElement* xml) +{ + // XmlElement* fileName = xml->createNewChildElement("FILENAME"); + // fileName->addTextElement(lastFilePath.getFullPathName()); +} + +void JuliaEditor::loadEditorParameters(XmlElement* xml) +{ + // forEachXmlChildElement(*xml, xmlNode) + // { + // if (xmlNode->hasTagName("FILENAME")) + // { + // lastFilePath = File(xmlNode->getText()); + // thread->setFile(lastFilePath.getFullPathName()); + // fileNameLabel->setText(lastFilePath.getFullPathName(),false); + // } + // } +} \ No newline at end of file diff --git a/Source/Plugins/JuliaProcessor/JuliaEditor.h b/Source/Plugins/JuliaProcessor/JuliaEditor.h new file mode 100644 index 000000000..6aec26625 --- /dev/null +++ b/Source/Plugins/JuliaProcessor/JuliaEditor.h @@ -0,0 +1,64 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2016 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef JULIAEDITOR_H_INCLUDED +#define JULIAEDITOR_H_INCLUDED + +#include <EditorHeaders.h> + +//class ImageIcon; +class JuliaProcessor; + +/** + + User interface for the Julia processor. + + @see JuliaProcessor + +*/ + +class JuliaEditor : public GenericEditor, public Label::Listener + +{ +public: + JuliaEditor(GenericProcessor* parentNode, bool useDefaultParameterEditors); + virtual ~JuliaEditor(); + void buttonEvent(Button* button); + void labelTextChanged(Label* te); + void setFile(String file); + void saveEditorParameters(XmlElement*); + void loadEditorParameters(XmlElement*); + ImageIcon* icon; + +private: + ScopedPointer<UtilityButton> fileButton; + ScopedPointer<UtilityButton> reloadFileButton; + ScopedPointer<Label> fileNameLabel; + ScopedPointer<Label> bufferSizeSelection; + ScopedPointer<Label> bufferSizeSelectionLabel; + JuliaProcessor* juliaProcessor; + File lastFilePath; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(JuliaEditor); +}; + +#endif // JULIAEDITOR_H_INCLUDED \ No newline at end of file diff --git a/Source/Plugins/JuliaProcessor/JuliaProcessor.cpp b/Source/Plugins/JuliaProcessor/JuliaProcessor.cpp new file mode 100644 index 000000000..561369399 --- /dev/null +++ b/Source/Plugins/JuliaProcessor/JuliaProcessor.cpp @@ -0,0 +1,169 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2016 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "JuliaProcessor.h" +#include "JuliaEditor.h" +#include <stdio.h> +#include <julia.h> + +JuliaProcessor::JuliaProcessor() + : GenericProcessor("Julia Processor") +{ + hasJuliaInstance = false; + dataHistoryBufferNumChannels = 256; + dataHistoryBuffer = new AudioSampleBuffer(dataHistoryBufferNumChannels, 60000); + dataHistoryBuffer->clear(); +} + +JuliaProcessor::~JuliaProcessor() +{ + jl_atexit_hook(0); + deleteAndZero(dataHistoryBuffer); +} + +AudioProcessorEditor* JuliaProcessor::createEditor() +{ + editor = new JuliaEditor(this, true); + std::cout << "Creating Julia editor." << std::endl; + return editor; +} + +void JuliaProcessor::setFile(String fullpath) +{ + hasJuliaInstance = true; + filePath = fullpath; + + FILE* fp = popen("echo $JULIA", "r"); + char input[255]; + fgets(input, sizeof(input), fp); + + String julia_bin_dir = input; + julia_bin_dir = julia_bin_dir.trimEnd(); + julia_bin_dir += "/home/jvoigts/Documents/Github/julia/usr/bin"; + String julia_sys_dir = input; + julia_sys_dir = julia_sys_dir.trimEnd(); + julia_sys_dir += "/home/jvoigts/Documents/Github/julia/usr/lib/julia/sys.so"; + + const char* jbin = julia_bin_dir.toRawUTF8(); + const char* jsys = julia_sys_dir.toRawUTF8(); + + jl_init_with_image(jbin, jsys); + + String juliaString = "include(\"" + filePath + "\")"; + run_julia_string(juliaString); +} + +void JuliaProcessor::reloadFile() +{ + if (hasJuliaInstance) + { + String juliaString = "reload(\"" + filePath + "\")"; + run_julia_string(juliaString); + } + else + { + std::cout << "No julia instance running - cant refresh" << std::endl; + } +} + + +void JuliaProcessor::setParameter(int parameterIndex, float newValue) +{ + editor->updateParameterButtons(parameterIndex); +} + +void JuliaProcessor::setBuffersize(int bufferSize) +{ + if (bufferSize > 1) + { + dataHistoryBufferSize=bufferSize; + printf("Setting history buffer size to %d samples \n", dataHistoryBufferSize); + dataHistoryBuffer->setSize(dataHistoryBufferNumChannels, dataHistoryBufferSize, false, true, false); + } + else + { + printf("History buffer size has to be at least 1"); + } +} + +void JuliaProcessor::run_julia_string(String juliaString) +{ + // need to convert from juce String to char array + const char* jstr = juliaString.toRawUTF8(); + + printf("executing julia cmd: %s\n", jstr); + + jl_eval_string(jstr); + + if (jl_exception_occurred()) + printf("%s \n", jl_typeof_str(jl_exception_occurred())); +} + + + +String JuliaProcessor::getFile() +{ + return filePath; +} + +void JuliaProcessor::process(AudioSampleBuffer& buffer, MidiBuffer& midiMessages) +{ + if (hasJuliaInstance) + { + jl_function_t *func = jl_get_function(jl_main_module, "oe_process!"); + + // create 2D array of float64 type + jl_value_t *array_type = jl_apply_array_type(jl_float32_type, 1); // last arg is nDims + + for (int n = 0; n < getNumOutputs(); n++) + { + float* ptr = buffer.getWritePointer(n); // to perform in-place edits to the buffer + jl_array_t *x = jl_ptr_to_array_1d(array_type, ptr , buffer.getNumSamples(), 0); + JL_GC_PUSH1(&x); + jl_call1(func, (jl_value_t*)x); + JL_GC_POP(); + } + } +} + +void JuliaProcessor::saveCustomParametersToXml(XmlElement* parentElement) +{ + XmlElement* childNode = parentElement->createNewChildElement("FILENAME"); + childNode->setAttribute("path", getFile()); +} + +void JuliaProcessor::loadCustomParametersFromXml() +{ + if (parametersAsXml != nullptr) + { + // use parametersAsXml to restore state + forEachXmlChildElement(*parametersAsXml, xmlNode) + { + if (xmlNode->hasTagName("FILENAME")) + { + String filepath = xmlNode->getStringAttribute("path"); + JuliaEditor* fre = (JuliaEditor*) getEditor(); + fre->setFile(filepath); + } + } + } +} \ No newline at end of file diff --git a/Source/Plugins/JuliaProcessor/JuliaProcessor.h b/Source/Plugins/JuliaProcessor/JuliaProcessor.h new file mode 100644 index 000000000..eed4a215d --- /dev/null +++ b/Source/Plugins/JuliaProcessor/JuliaProcessor.h @@ -0,0 +1,68 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2016 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef JULIAPROCESSOR_H_INCLUDED +#define JULIAPROCESSOR_H_INCLUDED + +#include <ProcessorHeaders.h> + +/** + Julia Processor. + + Allows the user to select a Julia Programming Language file to use as filter + + @see GenericProcessor, JuliaEditor +*/ + +class JuliaProcessor : public GenericProcessor + +{ +public: + JuliaProcessor(); + ~JuliaProcessor(); + void setFile(String fullpath); + String getFile(); + void reloadFile(); + void process(AudioSampleBuffer& buffer, MidiBuffer& midiMessages); + void setParameter(int parameterIndex, float newValue); + void setBuffersize(int bufferSize); + AudioProcessorEditor* createEditor(); + bool hasEditor() const + { + return true; + } + void saveCustomParametersToXml(XmlElement* parentElement); + void loadCustomParametersFromXml(); + +private: + bool hasJuliaInstance; + String filePath; + int dataHistoryBufferSize; + int dataHistoryBufferNumChannels; + AudioSampleBuffer* dataHistoryBuffer; + void run_julia_string(String juliaString); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(JuliaProcessor); +}; + + +#endif // JULIAPROCESSOR_H_INCLUDED diff --git a/Source/Plugins/JuliaProcessor/OpenEphysLib.cpp b/Source/Plugins/JuliaProcessor/OpenEphysLib.cpp new file mode 100644 index 000000000..189d524e9 --- /dev/null +++ b/Source/Plugins/JuliaProcessor/OpenEphysLib.cpp @@ -0,0 +1,70 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2016 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include <PluginInfo.h> +#include "JuliaProcessor.h" +#include <string> +#ifdef WIN32 +#include <Windows.h> +#define EXPORT __declspec(dllexport) +#else +#define EXPORT __attribute__((visibility("default"))) +#endif + +using namespace Plugin; +#define NUM_PLUGINS 1 + +extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) +{ + info->apiVersion = PLUGIN_API_VER; + info->name = "Julia Processor"; + info->libVersion = 1; + info->numPlugins = NUM_PLUGINS; +} + +extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) +{ + switch (index) + { + case 0: + info->type = Plugin::PLUGIN_TYPE_PROCESSOR; + info->processor.name = "Julia Processor"; + info->processor.type = Plugin::FilterProcessor; + info->processor.creator = &(Plugin::createProcessor<JuliaProcessor>); + break; + default: + return -1; + break; + } + return 0; +} + +#ifdef WIN32 +BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, + IN DWORD nReason, + IN LPVOID Reserved) +{ + return TRUE; +} + +#endif diff --git a/Source/Plugins/JuliaProcessor/exampleProcessor.jl b/Source/Plugins/JuliaProcessor/exampleProcessor.jl new file mode 100644 index 000000000..2efab302f --- /dev/null +++ b/Source/Plugins/JuliaProcessor/exampleProcessor.jl @@ -0,0 +1,21 @@ +# set things up here +#global last=100 + +# this function is called once per buffer upddate and is passed +# the current buffer in data + +#data[:]=sin(data/tscale)*200; + + +function oe_process!(data) + + global last; + + f=0.05; + for i in eachindex(data) + + data[i]=f*data[i] + (1-f)*last; + last = data[i]; + end + +end \ No newline at end of file diff --git a/Source/Plugins/SpikeRaster/Makefile b/Source/Plugins/SpikeRaster/Makefile new file mode 100644 index 000000000..973d9274e --- /dev/null +++ b/Source/Plugins/SpikeRaster/Makefile @@ -0,0 +1,45 @@ + +LIBNAME := $(notdir $(CURDIR)) +OBJDIR := $(OBJDIR)/$(LIBNAME) +TARGET := $(LIBNAME).so +OS := $(shell uname) + +SRC_DIR := ${shell find ./ -type d -print} +VPATH := $(SOURCE_DIRS) + +SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.cpp)) +OBJ := $(addprefix $(OBJDIR)/,$(notdir $(SRC:.cpp=.o))) + +#Extra macros and libraries needed by the plugin +#CXXFLAGS := $(CXXFLAGS) -D EXAMPLE_MACRO +#LDFLAGS := $(LDFLAGS) -lExampleLib + + + +BLDCMD := $(CXX) -shared -o $(OUTDIR)/$(TARGET) $(OBJ) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) + +VPATH = $(SRC_DIR) + +.PHONY: objdir + +$(OUTDIR)/$(TARGET): objdir $(OBJ) + -@mkdir -p $(BINDIR) + -@mkdir -p $(LIBDIR) + -@mkdir -p $(OUTDIR) + @echo "Building $(TARGET)" + @$(BLDCMD) + +$(OBJDIR)/%.o : %.cpp + @echo "Compiling $<" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + + +objdir: + -@mkdir -p $(OBJDIR) + +clean: + @echo "Cleaning $(LIBNAME)" + -@rm -rf $(OBJDIR) + -@rm -f $(OUTDIR)/$(TARGET) + +-include $(OBJ:%.o=%.d) diff --git a/Source/Plugins/SpikeRaster/OpenEphysLib.cpp b/Source/Plugins/SpikeRaster/OpenEphysLib.cpp new file mode 100644 index 000000000..abc7fd079 --- /dev/null +++ b/Source/Plugins/SpikeRaster/OpenEphysLib.cpp @@ -0,0 +1,97 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include <PluginInfo.h> +#include "SpikeRaster.h" +#include <string> +#ifdef WIN32 +#include <Windows.h> +#define EXPORT __declspec(dllexport) +#else +#define EXPORT __attribute__((visibility("default"))) +#endif + +using namespace Plugin; +//Number of plugins defined on the library. Can be of different types (Processors, RecordEngines, etc...) +#define NUM_PLUGINS 1 + +extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) +{ + info->apiVersion = PLUGIN_API_VER; /*API version, defined by the GUI source. + Should not be changed to ensure it is always equal to the one used in the latest codebase. The GUI refueses to load plugins with mismatched API versions */ + info->name = "Example library"; //Name of the Library, used only for information + info->libVersion = 1; //Version of the library, used only for information + info->numPlugins = NUM_PLUGINS; +} + +extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) +{ + switch (index) + { + //one case per plugin. This example is for a processor which connects directly to the signal chain + case 0: + info->type = Plugin::PLUGIN_TYPE_PROCESSOR; //Type of plugin. See "Source/Processors/PluginManager/OpenEphysPlugin.h" for complete info about the different type structures + //For processor + info->processor.name = "Spike Raster"; //Processor name shown in the GUI + info->processor.type = Plugin::FilterProcessor; //Type of processor. Can be FilterProcessor, SourceProcessor, SinkProcessor or UtilityProcessor. Specifies where on the processor list will appear + info->processor.creator = &(Plugin::createProcessor<SpikeRaster>); //Class factory pointer. Replace "ExampleProcessor" with the name of your class. + break; +/** +Examples for other plugin types + +For a RecordEngine, which provides formats for recording data + case x: + info->type = Plugin::RecordEnginePlugin; + info->recordEngine.name = "Record Engine Name"; + info->recordEngine.creator = &(Plugin::createRecordEngine<RecordEngineClassName>); + break; + +For a DataThread, which allows to use the existing SourceNode to connect to an asynchronous data source, such as acquisition hardware + case x: + info->type = Plugin::DatathreadPlugin; + info->dataThread.name = "Source name"; //Name that will appear on the processor list + info->dataThread.creator = &createDataThread<DataThreadClassName>; + +For a FileSource, which allows importing data formats into the FileReader + case x: + info->type = Plugin::FileSourcePlugin; + info->fileSource.name = "File Source Name"; + info->fileSource.extensions = "xxx;xxx;xxx"; //Semicolon separated list of supported extensions. Eg: "txt;dat;info;kwd" + info->fileSource.creator = &(Plugin::createFileSource<FileSourceClassName>); +**/ + default: + return -1; + break; + } + return 0; +} + +#ifdef WIN32 +BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, + IN DWORD nReason, + IN LPVOID Reserved) +{ + return TRUE; +} + +#endif diff --git a/Source/Plugins/SpikeRaster/SpikeRaster.cpp b/Source/Plugins/SpikeRaster/SpikeRaster.cpp new file mode 100644 index 000000000..b3ef325c2 --- /dev/null +++ b/Source/Plugins/SpikeRaster/SpikeRaster.cpp @@ -0,0 +1,220 @@ + +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2016 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#include <stdio.h> +#include "SpikeRaster.h" + +#include "SpikeRasterEditor.h" + +SpikeRaster::SpikeRaster() + : GenericProcessor("Spike Raster"), displayBufferSize(100), redrawRequested(false) + +{ + //Without a custom editor, generic parameter controls can be added + //parameters.add(Parameter("thresh", 0.0, 500.0, 200.0, 0)); + + electrodes.clear(); + +} + +SpikeRaster::~SpikeRaster() +{ + +} + +/** + If the processor uses a custom editor, this method must be present. +*/ + +AudioProcessorEditor* SpikeRaster::createEditor() +{ + editor = new SpikeRasterEditor(this, true); + + //std::cout << "Creating editor." << std::endl; + + return editor; +} + +void SpikeRaster::updateSettings() +{ + //std::cout << "Setting num inputs on SpikeDisplayNode to " << getNumInputs() << std::endl; + + electrodes.clear(); + + for (int i = 0; i < eventChannels.size(); i++) + { + ChannelType type = eventChannels[i]->getType(); + + if (type == ELECTRODE_CHANNEL) + { + + Electrode elec; + elec.numChannels = static_cast<SpikeChannel*>(eventChannels[i]->extraData.get())->numChannels; + + elec.name = eventChannels[i]->getName(); + elec.currentSpikeIndex = 0; + elec.mostRecentSpikes.ensureStorageAllocated(displayBufferSize); + + for (int j = 0; j < elec.numChannels; j++) + { + elec.displayThresholds.add(0); + elec.detectorThresholds.add(0); + } + + electrodes.add(elec); + + } + } + +} + + +void SpikeRaster::setParameter(int param, float newValue) +{ + + + if (param == 2) // redraw + { + redrawRequested = true; + + } +} + +bool SpikeRaster::enable() +{ + std::cout << "SpikeRaster::enable()" << std::endl; + SpikeRasterEditor* editor = (SpikeRasterEditor*) getEditor(); + + editor->enable(); + return true; + +} + +bool SpikeRaster::disable() +{ + std::cout << "SpikeRaster disabled!" << std::endl; + SpikeRasterEditor* editor = (SpikeRasterEditor*) getEditor(); + editor->disable(); + return true; +} + +int SpikeRaster::getNumElectrodes() +{ + return electrodes.size(); +} + +void SpikeRaster::setRasterPlot(RasterPlot* r) +{ + canvas = r; + r->setSampleRate(settings.sampleRate); +} + +void SpikeRaster::process(AudioSampleBuffer& buffer, MidiBuffer& events) +{ + + checkForEvents(events); // automatically calls 'handleEvent + + if (redrawRequested) + { + canvas->setTimestamp(getTimestamp(0)); + + //std::cout << "redraw" << std::endl; + for (int i = 0; i < getNumElectrodes(); i++) + { + + Electrode& e = electrodes.getReference(i); + + // transfer buffered spikes to spike plot + for (int j = 0; j < e.currentSpikeIndex; j++) + { + //std::cout << "Transferring spikes." << std::endl; + canvas->processSpikeObject(e.mostRecentSpikes[j]); + e.currentSpikeIndex = 0; + } + + } + + //redrawRequested = true; + } + + + +} + +void SpikeRaster::handleEvent(int eventType, MidiMessage& event, int samplePosition) +{ + + //std::cout << "Received event of type " << eventType << std::endl; + + if (eventType == SPIKE) + { + + const uint8_t* dataptr = event.getRawData(); + int bufferSize = event.getRawDataSize(); + + if (bufferSize > 0) + { + + SpikeObject newSpike; + + bool isValid = unpackSpike(&newSpike, dataptr, bufferSize); + + if (isValid) + { + int electrodeNum = newSpike.source; + + Electrode& e = electrodes.getReference(electrodeNum); + // std::cout << electrodeNum << std::endl; + + // add to buffer + if (e.currentSpikeIndex < displayBufferSize) + { + // std::cout << "Adding spike " << e.currentSpikeIndex + 1 << std::endl; + e.mostRecentSpikes.set(e.currentSpikeIndex, newSpike); + e.currentSpikeIndex++; + } + + } + + } + + } else if (eventType == TTL) + { + const uint8* dataptr = event.getRawData(); + + //int eventNodeId = *(dataptr+1); + int eventId = *(dataptr+2); + int eventChannel = *(dataptr+3); + uint8 sourceNodeId = event.getNoteNumber(); + + int64 timestamp = timestamps[sourceNodeId] + samplePosition; + + if (eventId == 1) + { + canvas->processEvent(eventChannel, timestamp); + } + } + +} diff --git a/Source/Plugins/SpikeRaster/SpikeRaster.h b/Source/Plugins/SpikeRaster/SpikeRaster.h new file mode 100644 index 000000000..41758a92c --- /dev/null +++ b/Source/Plugins/SpikeRaster/SpikeRaster.h @@ -0,0 +1,120 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2016 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef SPIKERASTER_H_INCLUDED +#define SPIKERASTER_H_INCLUDED + +#ifdef _WIN32 +#include <Windows.h> +#endif + +#include <ProcessorHeaders.h> +#include <SpikeLib.h> + +class RasterPlot; + +/** + + Displays a raster and peri-stimulus time histogram for incoming spikes. + + @see GenericProcessor + +*/ + +class SpikeRaster : public GenericProcessor + +{ +public: + + /** The class constructor, used to initialize any members. */ + SpikeRaster(); + + /** The class destructor, used to deallocate memory */ + ~SpikeRaster(); + + /** Determines whether the processor is treated as a source. */ + bool isSource() + { + return false; + } + + /** Determines whether the processor is treated as a sink. */ + bool isSink() + { + return false; + } + + /** Indicates if the processor has a custom editor. Defaults to false */ + bool hasEditor() const + { + return true; + } + + void updateSettings(); + + int getNumElectrodes(); + + AudioProcessorEditor* createEditor(); + + void process(AudioSampleBuffer& buffer, MidiBuffer& events); + + void handleEvent(int, MidiMessage&, int); + + void setParameter(int parameterIndex, float newValue); + + bool enable(); + bool disable(); + + void setRasterPlot(RasterPlot*); + +private: + + struct Electrode + { + String name; + + int numChannels; + + Array<float> displayThresholds; + Array<float> detectorThresholds; + + Array<SpikeObject> mostRecentSpikes; + int currentSpikeIndex; + + int recordIndex; + + }; + + RasterPlot* canvas; + + + Array<Electrode> electrodes; + + int displayBufferSize; + bool redrawRequested; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SpikeRaster); + +}; + +#endif // SPIKERASTER_H_INCLUDED diff --git a/Source/Plugins/SpikeRaster/SpikeRasterEditor.cpp b/Source/Plugins/SpikeRaster/SpikeRasterEditor.cpp new file mode 100644 index 000000000..04a9c0340 --- /dev/null +++ b/Source/Plugins/SpikeRaster/SpikeRasterEditor.cpp @@ -0,0 +1,1192 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2016 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "SpikeRasterEditor.h" + +SpikeRasterEditor::SpikeRasterEditor(GenericProcessor* parentNode, bool useDefaultParameterEditors=true) +: VisualizerEditor(parentNode, useDefaultParameterEditors) + +{ + + tabText = "Raster"; + + desiredWidth = 180; + +} + +SpikeRasterEditor::~SpikeRasterEditor() +{ +} + + +Visualizer* SpikeRasterEditor::createNewCanvas() +{ + + SpikeRaster* processor = (SpikeRaster*) getProcessor(); + canvas = new SpikeRasterCanvas(processor); + return canvas; + +} + +void SpikeRasterEditor::updateSettings() +{ + updateVisualizer(); +} + +// ============================================================== + + +SpikeRasterCanvas::SpikeRasterCanvas(SpikeRaster* sr) : processor(sr), currentMap(0) +{ + + rasterPlot = new RasterPlot(this); + addAndMakeVisible(rasterPlot); + + ratePlot = new RatePlot(rasterPlot); + addAndMakeVisible(ratePlot); + + psth = new PSTH(rasterPlot); + addAndMakeVisible(psth); + + timescale = new Timescale(rasterPlot); + addAndMakeVisible(timescale); + + for (int i = 0; i < 8; i++) + { + EventChannelButton* ecb = new EventChannelButton(rasterPlot, i, rasterPlot->getColourForChannel(i)); + eventChannelButtons.add(ecb); + addAndMakeVisible(ecb); + } + + triggerLabel = new Label("Triggers:","Triggers:"); + triggerLabel->setFont(Font("Small Text", 11, Font::plain)); + triggerLabel->setColour(Label::textColourId, Colour(200,200,200)); + addAndMakeVisible(triggerLabel); + + viewLabel = new Label("View:","View:"); + viewLabel->setFont(Font("Small Text", 11, Font::plain)); + viewLabel->setColour(Label::textColourId, Colour(200,200,200)); + addAndMakeVisible(viewLabel); + + viewButton = new UtilityButton("Continuous", Font("Small Text", 13, Font::plain)); + viewButton->setRadius(5.0f); + viewButton->setEnabledState(true); + viewButton->setCorners(true, true, true, true); + viewButton->addListener(this); + viewButton->setToggleState(false, dontSendNotification); + addAndMakeVisible(viewButton); + + clearButton = new UtilityButton("Clear", Font("Small Text", 13, Font::plain)); + clearButton->setRadius(5.0f); + clearButton->setEnabledState(true); + clearButton->setCorners(true, true, true, true); + clearButton->addListener(this); + clearButton->setToggleState(false, dontSendNotification); + addAndMakeVisible(clearButton); + + preSecsLabel = new Label("Pre:","Pre:"); + preSecsLabel->setFont(Font("Small Text", 13, Font::plain)); + preSecsLabel->setColour(Label::textColourId, Colour(200, 200, 200)); + addAndMakeVisible(preSecsLabel); + + postSecsLabel = new Label("Post:","Post:"); + postSecsLabel->setFont(Font("Small Text", 13, Font::plain)); + postSecsLabel->setColour(Label::textColourId, Colour(200, 200, 200)); + addAndMakeVisible(postSecsLabel); + + preSecsInput = new Label("0.5","0.5"); + preSecsInput->setFont(Font("Small Text", 13, Font::plain)); + preSecsInput->setColour(Label::textColourId, Colour(250, 250, 250)); + preSecsInput->setEditable(true); + preSecsInput->addListener(this); + addAndMakeVisible(preSecsInput); + + postSecsInput = new Label("1.5","1.5"); + postSecsInput->setFont(Font("Small Text", 13, Font::plain)); + postSecsInput->setColour(Label::textColourId, Colour(250, 250, 250)); + postSecsInput->setEditable(true); + postSecsInput->addListener(this); + addAndMakeVisible(postSecsInput); + + electrodeLayoutLabel = new Label("Layout:","Layout:"); + electrodeLayoutLabel->setFont(Font("Small Text", 11, Font::plain)); + electrodeLayoutLabel->setColour(Label::textColourId, Colour(200,200,200)); + addAndMakeVisible(electrodeLayoutLabel); + + electrodeLayoutSelector = new UtilityButton("Default", Font("Small Text", 13, Font::plain)); + electrodeLayoutSelector->setRadius(5.0f); + electrodeLayoutSelector->setEnabledState(true); + electrodeLayoutSelector->setCorners(true, true, true, true); + electrodeLayoutSelector->addListener(this); + electrodeLayoutSelector->setToggleState(false, dontSendNotification); + addAndMakeVisible(electrodeLayoutSelector); + + resized(); + repaint(); + + processor->setRasterPlot(rasterPlot); + + viewType = 0; + +} + + +SpikeRasterCanvas::~SpikeRasterCanvas(){ + +} + +void SpikeRasterCanvas::beginAnimation() +{ + startCallbacks(); + std::cout << "Spike Raster beginning animation." << std::endl; + + rasterPlot->reset(); + +} + +void SpikeRasterCanvas::endAnimation() +{ + stopCallbacks(); + rasterPlot->resetTimestamps(); +} + +void SpikeRasterCanvas::refreshState() +{ +} + +void SpikeRasterCanvas::update() +{ + rasterPlot->setNumberOfElectrodes(processor->getNumElectrodes()); + ratePlot->setNumberOfElectrodes(processor->getNumElectrodes()); + +} + +void SpikeRasterCanvas::setParameter(int, float) {} + +void SpikeRasterCanvas::paint(Graphics& g) +{ + g.fillAll(Colours::darkgrey); +} + +void SpikeRasterCanvas::refresh() +{ + processor->setParameter(2, 0.0f); // request redraw + + repaint(); +} + +void SpikeRasterCanvas::resized() +{ + rasterPlot->setBounds(140, 10, getWidth()-300, getHeight()-120); + + ratePlot->setBounds(10, 10, 120, getHeight()-120); + + psth->setBounds(140, getHeight()-100, getWidth()-300, 70); + + timescale->setBounds(125, getHeight()-25, getWidth()-270, 20); + + for (int i = 0; i < eventChannelButtons.size(); i++) + { + eventChannelButtons[i]->setBounds(getWidth()-140 + (i % 4) * 20, 40 + (i/4) * 20, 35, 35); + } + + triggerLabel->setBounds(getWidth()-140, 15, 140, 30); + viewLabel->setBounds(getWidth()-140, 100, 140, 20); + viewButton->setBounds(getWidth()-135, 120, 105, 20); + clearButton->setBounds(getWidth()-135, 250, 74, 20); + preSecsLabel->setBounds(getWidth()-140, 160, 105, 15); + preSecsInput->setBounds(getWidth()-90, 160, 45, 15); + postSecsLabel->setBounds(getWidth()-140, 180, 105, 15); + postSecsInput->setBounds(getWidth()-90, 180, 45, 15); + electrodeLayoutLabel->setBounds(10, getHeight()-90, 140, 20); + electrodeLayoutSelector->setBounds(10, getHeight()-70, 105, 20); + +} + +void SpikeRasterCanvas::labelTextChanged(Label* l) +{ + String text = l->getText(); + float value = text.getFloatValue(); + + if (value < 0.05) + value = 0.05; + else if (value > 5) + value = 5; + + if (l == preSecsInput) + rasterPlot->setPreSecs(value); + else if (l == postSecsInput) + rasterPlot->setPostSecs(value); + + l->setText(String(value), dontSendNotification); + + float min = preSecsInput->getText().getFloatValue()*-1; + float max = postSecsInput->getText().getFloatValue(); + + timescale->setRange(min, max); + +} + +void SpikeRasterCanvas::buttonClicked(Button* b) +{ + if (b == viewButton) + { + if (viewType == 0) + { + viewType = 1; + viewButton->setLabel("All"); + } else if (viewType == 1) + { + viewType = 2; + viewButton->setLabel("Single"); + } else { + viewType = 0; + viewButton->setLabel("Continuous"); + } + + rasterPlot->setViewType(viewType); + } + else if (b == clearButton) + { + rasterPlot->clear(); + } else if (b == electrodeLayoutSelector) + { + if (ratePlot->layout == 0) + { + ratePlot->setLayout(1); + electrodeLayoutSelector->setLabel("Neuropix"); + } else if (ratePlot->layout == 1) + { + ratePlot->setLayout(2); + electrodeLayoutSelector->setLabel("Neuroseeker"); + } else { + ratePlot->setLayout(0); + electrodeLayoutSelector->setLabel("Default"); + } + + } + + repaint(); +} + +// ===================================================== + + + +RasterPlot::RasterPlot(SpikeRasterCanvas*) +{ + + rasterWidth = 500; + rasterTimebase = 2.0f; + preStimSecs = 0.5f; + rasterStartTimestamp = 0; + triggerTimestamp = -1; + currentTimestamp = 0; + + viewType = 0; + trialIndex2 = 0; + totalTrials = 0; + + electrodeChannels.add(0); + + spikeBuffer = AudioSampleBuffer(100,rasterWidth); + trialBuffer1 = AudioSampleBuffer(100,rasterWidth); + trialBuffer2 = AudioSampleBuffer(100,rasterWidth); + + spikeBuffer.clear(); + trialBuffer1.clear(); + trialBuffer2.clear(); + + random = Random(); + + reset(); + +} + +RasterPlot::~RasterPlot() +{ + +} + +void RasterPlot::setNumberOfElectrodes(int n) +{ + numElectrodes = n; + + lastBufferPos.clear(); + + for (int i = 0; i < numElectrodes; i++) + { + lastBufferPos.add(0); // 0.0 to 1.0 + } + +} + +void RasterPlot::reset() +{ + spikeBuffer.clear(); + rasterStartTimestamp = 0; + currentTimestamp = 0; + + repaint(); +} + +void RasterPlot::clear() +{ + trialBuffer1.clear(); + trialBuffer2.clear(); + trialIndex1 = 0; + trialIndex2 = 0; + + setNumberOfElectrodes(numElectrodes); // clear last sample +} + +void RasterPlot::setViewType(int v) +{ + viewType = v; +} + +void RasterPlot::paint(Graphics& g) +{ + + //std::cout << "Drawing raster" << std::endl; + + AudioSampleBuffer* buffer; + + switch (viewType) + { + case 0: + buffer = &spikeBuffer; + break; + case 1: + buffer = &trialBuffer1; + break; + case 2: + buffer = &trialBuffer2; + break; + default: + buffer = &spikeBuffer; + break; + } + + g.fillAll(Colours::grey); + + float numYPixels = buffer->getNumChannels(); + float numXPixels = buffer->getNumSamples(); + + float xHeight = getWidth()/numXPixels; + float yHeight = getHeight()/numYPixels; + + for (int n = 0; n < numXPixels; n++) + { + for (int m = 0; m < numYPixels; m++) + { + float colourIndex = buffer->getSample(m,n); + + if (viewType == 1) + { + colourIndex /= (totalTrials); + } + + if (colourIndex > 0) + { + g.setColour(Colour(colourIndex*128+127, colourIndex*128+127, colourIndex*128+127)); + g.fillRect(n*xHeight, getHeight() - m*yHeight - yHeight, xHeight, yHeight); + } + } + } + + //std::cout << lastBufferPos[0] << std::endl; + + g.setColour(Colours::black); + g.fillRect(getMaxBufferPos() * getWidth(), 0.0f, xHeight*2, float(getHeight())); +} + +void RasterPlot::resized() +{ + +} + +void RasterPlot::setPreSecs(float value) +{ + float postStimSecs = rasterTimebase - preStimSecs; + + preStimSecs = value; + rasterTimebase = postStimSecs + preStimSecs; + + clear(); + getParentComponent()->repaint(); +} + +void RasterPlot::setPostSecs(float value) +{ + + rasterTimebase = value + preStimSecs; + + clear(); + + getParentComponent()->repaint(); + +} + +void RasterPlot::setTimestamp(int64 ts) +{ + + //std::cout << "Setting timestamp to " << ts << std::endl; + + if (ts > 10000000000000 || ts < currentTimestamp) + return; + + currentTimestamp = ts; + + float bufferStart = (currentTimestamp - rasterStartTimestamp) / (sampleRate * rasterTimebase); // (0 to 1) + + //std::cout << "Buffer start: " << bufferStart << std::endl; + //std::cout << "Raster start: " << rasterStartTimestamp << std::endl; + //std::cout << "Sample rate: " << sampleRate << std::endl; + //std::cout << "Raster timebase: " << rasterTimebase << std::endl; + + if (bufferStart < 1.0) // no overflow + { + for (int i = 0; i < numElectrodes; i++) + { + spikeBuffer.clear(i, lastBufferPos[i] * rasterWidth + 1, + (bufferStart - lastBufferPos[i]) * rasterWidth); + + lastBufferPos.set(i, bufferStart); + } + + } else { + + float bufferRemaining = bufferStart - 1.0; + + //std::cout << "Buffer remaining: " << bufferRemaining * rasterWidth << std::endl; + + for (int i = 0; i < numElectrodes; i++) + { + int startSample = lastBufferPos[i] * rasterWidth + 1; + int endSample = (1.0 - lastBufferPos[i]) * rasterWidth; + + if (startSample + endSample > spikeBuffer.getNumSamples()) + endSample = spikeBuffer.getNumSamples() - startSample; + + // std::cout << "Clearing " << startSample << " to " << endSample << std::endl; + + spikeBuffer.clear(i, startSample, endSample); + + startSample = 0; + endSample = bufferRemaining * rasterWidth; + + if (startSample + endSample > spikeBuffer.getNumSamples()) + endSample = spikeBuffer.getNumSamples() - startSample; + + // std::cout << "Clearing " << startSample << " to " << endSample << std::endl; + spikeBuffer.clear(i, startSample, endSample); + + lastBufferPos.set(i, bufferRemaining); + } + + rasterStartTimestamp = currentTimestamp - int( bufferRemaining * sampleRate * rasterTimebase ); + } + + if (triggerTimestamp > 0) + { + // copy data + int offset = int((rasterTimebase - preStimSecs) * sampleRate); + + if (currentTimestamp > triggerTimestamp + offset) + { + + std::cout << "Trigger time: " << triggerTimestamp << + ", Current time: " << currentTimestamp << + ", Offset: " << offset << std::endl; + + // copy data to all channels buffer + + for (int n = 0; n < numElectrodes; n++) + { + trialBuffer1.addFrom(n, 0, spikeBuffer, n, 0, spikeBuffer.getNumSamples()); + } + + trialBuffer2.clear(trialIndex2, 0, trialBuffer2.getNumSamples()); + + for (int n = 0; n < electrodeChannels.size(); n++) + { + trialBuffer2.addFrom(trialIndex2, 0, spikeBuffer, electrodeChannels[n], 0, spikeBuffer.getNumSamples()); + } + + trialIndex2++; + totalTrials++; + + if (trialIndex2 == spikeBuffer.getNumChannels()) + trialIndex2 = 0; + + triggerTimestamp = -1; + } + } + +} + +void RasterPlot::resetTimestamps() +{ + + //rasterStartTimestamp = 0; + //setNumberOfElectrodes(numElectrodes); +} + +void RasterPlot::setSampleRate(float sr) +{ + sampleRate = sr; +} + +void RasterPlot::processSpikeObject(const SpikeObject& s) +{ + int electrode = s.source; + //int unit = s.sortedId; + int timestamp = s.timestamp; // absolute time + + float bufferPos = float(timestamp - rasterStartTimestamp) / (sampleRate * rasterTimebase); + + //std::cout << "Spike time: " << bufferPos << std::endl; + + //std::cout << spikeBuffer.getNumSamples() << " " << spikeBuffer.getNumChannels() << " " << (int)(timestamp * 1000) << " " << electrode << std::endl; + + if (bufferPos > 0. && bufferPos < 1.) + { + //std::cout << relativeTimestamp << " " << displayStartTimestamp << std::endl; + + int startSample = lastBufferPos[electrode] * rasterWidth + 1; + int numSamples = (bufferPos - lastBufferPos[electrode]) * rasterWidth; + + if (numSamples > 0) + { + std::cout << electrode << " " << startSample << " " << numSamples << std::endl; + spikeBuffer.clear(electrode, startSample, numSamples); + } else { + spikeBuffer.clear(electrode, 0, bufferPos); + } + + spikeBuffer.setSample(electrode, bufferPos * rasterWidth, 1); + + lastBufferPos.set(electrode, bufferPos); + } +} + +void RasterPlot::processEvent(int chan, int64 timestamp) +{ + //std::cout << "Event chan: " << chan << ", ts: " << timestamp << std::endl; + + std::cout << std::endl; + + if (triggerChannels.contains(chan)) + triggerTimestamp = timestamp; +} + +Array<float> RasterPlot::getPSTH(int numBins) +{ + int samplesPerBin = rasterWidth / numBins; + + AudioSampleBuffer* buffer; + + Array<float> psth; + + switch (viewType) + { + case 0: + { + buffer = &spikeBuffer; + break; + } + case 1: + { + buffer = &trialBuffer1; + break; + } + case 2: + { + buffer = &trialBuffer2; + break; + } + default: + buffer = &spikeBuffer; + break; + } + + int startBin; //= samplesPerBin * i; + + float maxBin = 0; + + for (int i = 0; i < numBins; i++) + { + startBin = samplesPerBin * i; + + float totalSpikes = 0; + + for (int m = startBin; m < startBin + samplesPerBin; m++) + { + for (int n = 0; n < numElectrodes; n++) + { + if (m < spikeBuffer.getNumSamples()) + totalSpikes += buffer->getSample(n,m); + } + } + + float spikeRate = totalSpikes; + + // normalize it! + + switch (viewType) + { + case 0: + { + spikeRate /= float(numElectrodes); + + break; + } + case 1: + { + if (totalTrials > 0) + { + spikeRate /= float(numElectrodes); + spikeRate /= float(totalTrials); + } + + break; + } + case 2: + { + if (trialIndex2 > 0) + { + + spikeRate /= float(jmin(trialIndex2, trialBuffer2.getNumChannels())); + + } + + break; + } + default: + break; + } + + spikeRate /= (rasterTimebase / numBins); + + psth.add(spikeRate); + + maxBin = jmax(maxBin, spikeRate); + + //std::cout << spikeRate << std::endl; + } + + float upperBound = int(maxBin) / int(50) * int(50) + 40; + + for (int i = 0; i < numBins; i++) + { + psth.set(i, psth[i]/upperBound); + } + + psth.add(upperBound); + + return psth; +} + +void RasterPlot::setEventTrigger(int ch, bool trigger) +{ + + if (trigger) + { + triggerChannels.add(ch); + } + else + { + triggerChannels.remove(triggerChannels.indexOf(ch)); + } +} + +void RasterPlot::toggleElectrodeState(int ch) +{ + if (electrodeChannels.contains(ch)) + { + electrodeChannels.remove(electrodeChannels.indexOf(ch)); + } else { + electrodeChannels.add(ch); + } + + trialBuffer2.clear(); + trialIndex2 = 0; +} + + +float RasterPlot::getMaxBufferPos() +{ + float maxBufferPos = 0.0f; + + + + switch (viewType) + { + case 0: + { + for (int i = 0; i < numElectrodes; i++) + maxBufferPos = jmax(lastBufferPos[i], maxBufferPos); + break; + } + + case 1: + { + maxBufferPos = preStimSecs / rasterTimebase; + break; + } + + case 2: + { + maxBufferPos = preStimSecs / rasterTimebase; + break; + } + + default: + break; + } + + + + return maxBufferPos; +} + +Array<float> RasterPlot::getFiringRates() +{ + Array<float> rates; + + for (int n = 0; n < numElectrodes; n++) + { + float totalSpikes = 0; + + for (int m = 0; m < spikeBuffer.getNumSamples(); m++) + { + totalSpikes += spikeBuffer.getSample(n,m); + } + + float spikeRate = totalSpikes / rasterTimebase; + + rates.add(spikeRate); + } + + return rates; +} + +Colour RasterPlot::getColourForChannel(int ch) +{ + + if (ch == 0) + return Colour(224,185,36); + else if (ch == 1) + return Colour(243,119,33); + else if (ch == 2) + return Colour(237,37,36); + else if (ch == 3) + return Colour(217,46,171); + else if (ch == 4) + return Colour(101,31,255); + else if (ch == 5) + return Colour(48,117,255); + else if (ch == 6) + return Colour(116,227,156); + else + return Colour(82,173,0); + +} + + + +//=========================================================== + +PSTH::PSTH(RasterPlot* r) : raster(r) +{ + numBins = 50; +} + +PSTH::~PSTH() +{ + +} + +void PSTH::paint(Graphics& g) +{ + g.fillAll(Colours::lightgrey); + + Array<float> psth = raster->getPSTH(numBins); + + g.setColour(Colours::grey); + + //if (psth[numBins] > 0) + //{ + //std::cout << psth[numBins] << std::endl; + float barHeight = 10.0f; + + while (barHeight < psth[numBins]) + { + float h = getHeight() - (barHeight/psth[numBins])*getHeight(); + g.drawLine(0.0f, h, getWidth(), h); + barHeight += 10.0f; + } + //} + + g.setColour(Colour(225, 54, 125)); + + float maxBufferPos = raster->getMaxBufferPos(); + + float barWidth = float(getWidth())/float(numBins); + + for (int i = 0; i < numBins; i++) + { + + g.fillRect(barWidth*i, float(getHeight()*(1-psth[i])), barWidth+0.5f, psth[i]*getHeight()); + + } + + g.setColour(Colours::black); + g.fillRect(maxBufferPos * getWidth(), 0.0f, 2.0, float(getHeight())); + + float textHeight = 9.0f; + g.setFont(7); + while (textHeight < psth[numBins]) + { + float h = getHeight() - (textHeight/psth[numBins])*getHeight(); + g.drawText(String(textHeight + 1.0f), 2, (int) h, 8, 7, Justification::left, false); + textHeight += 10.0f; + } +} + +void PSTH::resized() +{ + +} + +void PSTH::reset() +{ + +} + +// ========================================================== + + +ElectrodeRateButton::ElectrodeRateButton(RatePlot* r, int chan_) : Button(String(chan_)), ratePlot(r) +{ + std::cout << "created button for channel " << chan_ << std::endl; + chan = chan_; + rate = 0.0f; + + setClickingTogglesState(true); +} + + +ElectrodeRateButton::~ElectrodeRateButton() +{ + +} + +void ElectrodeRateButton::paintButton(Graphics& g, bool isMouseOver, bool isButtonDown) +{ + if (getToggleState()) + { + //std::cout << "hi" << std::endl; + g.fillAll(Colours::white); + } + + Colour fillColour = Colour(jmin(rate/20.0f, 1.0f)*225, 54.0f, jmin(rate/20.0f, 1.0f)*125); + g.setColour(fillColour); + g.fillRect(1,1,getHeight()-2,getWidth()-2); +} + +void ElectrodeRateButton::resized() +{ + +} + +//=========================================================== + +RatePlot::RatePlot(RasterPlot* r) : raster(r) +{ + layout = 0; +} + +RatePlot::~RatePlot() +{ + +} + +void RatePlot::paint(Graphics& g) +{ + g.fillAll(Colours::lightgrey); + + Array<float> rates = raster->getFiringRates(); + + for (int i = 0; i < electrodeButtons.size(); i++) + { + electrodeButtons[i]->rate = rates[i]; + } + + g.setFont(Font("Small Text", 12, Font::plain)); + + float xDist; + if (layout == 0) + xDist = 20; + else + xDist = 38; + + g.drawText("0", 0., getHeight() - 20, xDist, 15., Justification::right, false); + + for (int i = 9; i < electrodeButtons.size(); i += 10) + { + g.drawText(String(i+1), 0., getHeight() - (float(i)/4.*10. + 20.), xDist, 15., Justification::right, false); + } + +} + +void RatePlot::resized() +{ + float electrodeSize = 10.0f; + float xLoc = 0; + float yLoc = 0; + float xDist; + + if (layout == 0) + xDist = 25; + else + xDist = 40; + + float yDist = 20; + + for (int i = 0; i < electrodeButtons.size(); i++) + { + switch (layout) + { + case 0: + { + if (i % 5 == 0){ + xLoc = 0; + yLoc = (i / 5) * electrodeSize; + } + else if (i % 5 == 1){ + xLoc = electrodeSize * 3; + yLoc = (i / 5)*electrodeSize; + } + else if (i % 5 == 2){ + xLoc = electrodeSize * 6; + yLoc = (i / 5) * electrodeSize; + } + else if (i % 5 == 3){ + xLoc = electrodeSize*1.5; + yLoc = (i / 5)*electrodeSize + electrodeSize/2; + } + else{ + xLoc = electrodeSize * 4.5; + yLoc = (i / 5)*electrodeSize + electrodeSize/2; + } + break; + } + case 1: + { + if (i % 4 == 0){ + xLoc = 0; + yLoc = (i / 4)*electrodeSize; + } + else if (i % 4 == 1){ + xLoc = electrodeSize * 2; + yLoc = (i / 4)*electrodeSize; + } + else if (i % 4 == 2){ + xLoc = electrodeSize; + yLoc = (i / 4)*electrodeSize + electrodeSize/2; + } + else{ + xLoc = electrodeSize * 3; + yLoc = (i / 4)*electrodeSize + electrodeSize/2; + } + break; + } + case 2: + { + xLoc = (i % 4) * electrodeSize; + yLoc = (i / 4) * electrodeSize; + break; + } + } + + electrodeButtons[i]->setBounds(xDist + xLoc, getHeight() - yDist - yLoc, electrodeSize, electrodeSize); + + } +} + +void RatePlot::reset() +{ + +} + +void RatePlot::buttonClicked(Button* button) +{ + ElectrodeRateButton* b = (ElectrodeRateButton*) button; + + std::cout << b->chan << " was clicked." << std::endl; + b->repaint(); + + raster->toggleElectrodeState(b->chan); + +} + +void RatePlot::setLayout(int layout_) +{ + layout = layout_; + + resized(); + +} + +void RatePlot::setNumberOfElectrodes(int n) +{ + electrodeButtons.clear(); + + for (int i = 0; i < n; i++) + { + ElectrodeRateButton* button = new ElectrodeRateButton(this, i); + electrodeButtons.add(button); + addAndMakeVisible(button); + button->addListener(this); + } + + if (electrodeButtons.size() > 0) + electrodeButtons[0]->setToggleState(true, dontSendNotification); + + resized(); +} + +// ========================================================= + +EventChannelButton::EventChannelButton(RasterPlot* rp, int chNum, Colour col): + isEnabled(false), colour(col), rasterPlot(rp) +{ + + channelNumber = chNum; + + chButton = new UtilityButton(String(channelNumber+1), Font("Small Text", 13, Font::plain)); + chButton->setRadius(5.0f); + chButton->setBounds(4,4,14,14); + chButton->setEnabledState(true); + chButton->setCorners(true, false, true, false); + + chButton->addListener(this); + addAndMakeVisible(chButton); + +} + +EventChannelButton::~EventChannelButton() +{ + +} + +void EventChannelButton::buttonClicked(Button* button) +{ + + isEnabled = !isEnabled; + + rasterPlot->setEventTrigger(channelNumber, isEnabled); + + repaint(); + +} + + +void EventChannelButton::paint(Graphics& g) +{ + + if (isEnabled) + { + g.setColour(colour); + g.fillRoundedRectangle(2,2,18,18,6.0f); + } + +} + +// ============================================================================ + + + +Timescale::Timescale(RasterPlot* r) : raster(r) +{ + min = -0.5f; + max = 1.5f; + + resolution = 0.5f; + +} + +Timescale::~Timescale() +{ + +} + +void Timescale::paint(Graphics& g) +{ + g.fillAll(Colour(55,55,55)); + g.setColour(Colours::grey); + g.fillRect(2,2,getWidth()-4,getHeight()-4); + g.setColour(Colours::lightgrey); + g.setFont(Font("Small Text", 12, Font::plain)); + + float pt = min; + + //g.drawText("0", 2, (int) h, 8, 7, Justification::left, false); + + //std::cout << "max: " << max << ", min: " << min << std::endl; + + while (pt <= max + resolution) + { + float xLoc = (pt - min)/(max-min); + //std::cout << " x: " << xLoc << " , val: " << pt << std::endl; + g.drawText(String(pt), xLoc*(getWidth()-30)-4, 4, 40, 13, Justification::centred, false); + pt += resolution; + } +} + +void Timescale::resized() +{ + +} + +void Timescale::setRange(float mn, float mx) +{ + min = mn; + max = mx; + + if (max - min > 4) + { + resolution = 1; + } else if (max - min < 4 && max - min > 1) + { + resolution = 0.5f; + } else if (max - min < 1) + { + resolution = 0.25f; + } + + repaint(); +} + diff --git a/Source/Plugins/SpikeRaster/SpikeRasterEditor.h b/Source/Plugins/SpikeRaster/SpikeRasterEditor.h new file mode 100644 index 000000000..050f6672b --- /dev/null +++ b/Source/Plugins/SpikeRaster/SpikeRasterEditor.h @@ -0,0 +1,281 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2014 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef SPIKERASTEREDITOR_H_INCLUDED +#define SPIKERASTEREDITOR_H_INCLUDED + +#include <VisualizerEditorHeaders.h> +#include <VisualizerWindowHeaders.h> +#include <SpikeLib.h> + +#include "SpikeRaster.h" + +class Visualizer; +class ElectrodeRateButton; + +class PSTH : public Component +{ +public: + PSTH(RasterPlot*); + virtual ~PSTH(); + + void paint(Graphics& g); + void resized(); + void reset(); + + int numBins; + RasterPlot* raster; + +}; + + + +class RatePlot : public Component, public Button::Listener +{ +public: + RatePlot(RasterPlot*); + virtual ~RatePlot(); + + void paint(Graphics& g); + void resized(); + void reset(); + + void buttonClicked(Button* button); + + void setNumberOfElectrodes(int); + + void setLayout(int); + + OwnedArray<ElectrodeRateButton> electrodeButtons; + + int layout; + int numElectrodes; + RasterPlot* raster; + +}; + +class ElectrodeRateButton : public Button +{ +public: + ElectrodeRateButton(RatePlot*, int chan); + virtual ~ElectrodeRateButton(); + + void paintButton(Graphics& g, bool, bool); + void resized(); + int chan; + float rate; + bool isSelected; + + RatePlot* ratePlot; + +}; + +class Timescale : public Component +{ +public: + Timescale(RasterPlot*); + virtual ~Timescale(); + + void paint(Graphics& g); + void resized(); + + void setRange(float, float); + + RasterPlot* raster; + + float min, max; + float resolution; + +}; + +class EventChannelButton : public Component, + public Button::Listener +{ +public: + EventChannelButton(RasterPlot*, int chNum, Colour col); + ~EventChannelButton(); + + void paint(Graphics& g); + + void buttonClicked(Button* button); + + bool isEnabled; + +private: + + int channelNumber; + Colour colour; + RasterPlot* rasterPlot; + ScopedPointer<UtilityButton> chButton; + +}; + +/** + + User interface for the SpikeRaster module. + + @see SpikeRaster + + */ + +class SpikeRasterEditor : public VisualizerEditor +{ +public: + SpikeRasterEditor(GenericProcessor*, bool useDefaultParameterEditors); + ~SpikeRasterEditor(); + + void updateSettings(); + + Visualizer* createNewCanvas(); + +private: + + RasterPlot* rasterPlot; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SpikeRasterEditor); +}; + + +class SpikeRasterCanvas : public Visualizer, public Button::Listener, public Label::Listener +{ +public: + SpikeRasterCanvas(SpikeRaster* n); + ~SpikeRasterCanvas(); + + void beginAnimation(); + void endAnimation(); + + void refreshState(); + void update(); + + void setParameter(int, float); + void setParameter(int, int, int, float) {} + + void buttonClicked(Button*); + void labelTextChanged(Label*); + + void paint(Graphics& g); + + void refresh(); + + void resized(); + + RasterPlot* getRasterPlot() {return rasterPlot.get();} + +private: + SpikeRaster* processor; + ScopedPointer<RasterPlot> rasterPlot; + ScopedPointer<PSTH> psth; + ScopedPointer<RatePlot> ratePlot; + ScopedPointer<Timescale> timescale; + + ScopedPointer<Label> triggerLabel; + OwnedArray<EventChannelButton> eventChannelButtons; + + ScopedPointer<Label> viewLabel; + ScopedPointer<UtilityButton> viewButton; + + ScopedPointer<Label> electrodeLayoutLabel; + ScopedPointer<UtilityButton> electrodeLayoutSelector; + + ScopedPointer<UtilityButton> clearButton; + + ScopedPointer<Label> preSecsInput; + ScopedPointer<Label> postSecsInput; + ScopedPointer<Label> preSecsLabel; + ScopedPointer<Label> postSecsLabel; + + int viewType; + + int currentMap; + + float psthHeight; + +}; + +class RasterPlot : public Component +{ +public: + RasterPlot(SpikeRasterCanvas*); + virtual ~RasterPlot(); + + AudioSampleBuffer spikeBuffer; + AudioSampleBuffer trialBuffer1; + AudioSampleBuffer trialBuffer2; + + void paint(Graphics& g); + void resized(); + void reset(); + + void processSpikeObject(const SpikeObject& s); + void processEvent(int eventChan, int64 ts); + + Random random; + + void setNumberOfElectrodes(int); + void setSampleRate(float); + void toggleElectrodeState(int); + + void setTimestamp(int64); + void resetTimestamps(); + + void setPreSecs(float); + void setPostSecs(float); + + void setViewType(int); + + void clear(); + + void setEventTrigger(int, bool); + + Array<float> getPSTH(int numBins); + Array<float> getFiringRates(); + Array<int> triggerChannels; + Array<int> electrodeChannels; + float getMaxBufferPos(); + + int rasterWidth; // number of pixels across raster + float rasterTimebase; // timebase in s + float preStimSecs; // pre-stimulus time + + int64 currentTimestamp; // start time of data buffer (samples) + int64 rasterStartTimestamp; // start time of raster (samples) + int64 triggerTimestamp; // last trigger time + + int numElectrodes; + int viewType; + int trialIndex1; + int trialIndex2; + int totalTrials; + + Array<float> lastBufferPos; + float sampleRate; + + Colour getColourForChannel(int ch); + +}; + + + + +#endif \ No newline at end of file -- GitLab