From f941c45fc0248095156f0284f0ff29b860108fe9 Mon Sep 17 00:00:00 2001 From: David Pruitt Date: Thu, 15 Mar 2018 15:40:58 -0500 Subject: [PATCH] Initial commit Initial commit --- .../Adafruit_VS1053.cpp | 762 ++++++++++ .../Adafruit_VS1053.h | 163 +++ FearConditioning_Connect.m | 337 +++++ .../FearConditioning_Main.ino | 223 +++ FearConditioning_V3_2.m | 1251 +++++++++++++++++ MusicBoard/MusicBoard.ino | 194 +++ Old versions/FearConditioning_V3_2_20170214.m | 1152 +++++++++++++++ Old versions/FearConditioning_V3_2_20170215.m | 1182 ++++++++++++++++ .../FearConditioning_V3_2_20170215_mmwrite.m | 1189 ++++++++++++++++ .../FearConditioning_V3_2_20170215_qtwriter.m | 1204 ++++++++++++++++ Old versions/FearConditioning_V3_2_20170216.m | 1187 ++++++++++++++++ Old versions/FearConditioning_V3_2_20170227.m | 1187 ++++++++++++++++ Old versions/FearConditioning_V3_2_20170823.m | 1231 ++++++++++++++++ Read_Google_Spreadsheet_Lindsey.m | 61 + Read_Local_Spreadsheet.m | 44 + Save_Local_Spreadsheet.m | 21 + fear_stages.txt | 30 + 17 files changed, 11418 insertions(+) create mode 100644 Adafruit_VS1053_Library-master/Adafruit_VS1053.cpp create mode 100644 Adafruit_VS1053_Library-master/Adafruit_VS1053.h create mode 100644 FearConditioning_Connect.m create mode 100644 FearConditioning_Main/FearConditioning_Main.ino create mode 100644 FearConditioning_V3_2.m create mode 100644 MusicBoard/MusicBoard.ino create mode 100644 Old versions/FearConditioning_V3_2_20170214.m create mode 100644 Old versions/FearConditioning_V3_2_20170215.m create mode 100644 Old versions/FearConditioning_V3_2_20170215_mmwrite.m create mode 100644 Old versions/FearConditioning_V3_2_20170215_qtwriter.m create mode 100644 Old versions/FearConditioning_V3_2_20170216.m create mode 100644 Old versions/FearConditioning_V3_2_20170227.m create mode 100644 Old versions/FearConditioning_V3_2_20170823.m create mode 100644 Read_Google_Spreadsheet_Lindsey.m create mode 100644 Read_Local_Spreadsheet.m create mode 100644 Save_Local_Spreadsheet.m create mode 100644 fear_stages.txt diff --git a/Adafruit_VS1053_Library-master/Adafruit_VS1053.cpp b/Adafruit_VS1053_Library-master/Adafruit_VS1053.cpp new file mode 100644 index 0000000..9fbde4e --- /dev/null +++ b/Adafruit_VS1053_Library-master/Adafruit_VS1053.cpp @@ -0,0 +1,762 @@ +/*************************************************** + This is a library for the Adafruit VS1053 Codec Breakout + + Designed specifically to work with the Adafruit VS1053 Codec Breakout + ----> https://www.adafruit.com/products/1381 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ****************************************************/ + +#include +#include + +static Adafruit_VS1053_FilePlayer *myself; + +#ifndef _BV + #define _BV(x) (1<<(x)) +#endif + +#if defined(__AVR__) +SIGNAL(TIMER0_COMPA_vect) { + myself->feedBuffer(); +} +#endif + +static void feeder(void) { + myself->feedBuffer(); +} + +#define VS1053_CONTROL_SPI_SETTING SPISettings(250000, MSBFIRST, SPI_MODE0) +#define VS1053_DATA_SPI_SETTING SPISettings(8000000, MSBFIRST, SPI_MODE0) + + +static const uint8_t dreqinttable[] = { +#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined(__AVR_ATmega8__) + 2, 0, + 3, 1, +#elif defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) + 2, 0, + 3, 1, + 21, 2, + 20, 3, + 19, 4, + 18, 5, +#elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY) + 5, 0, + 6, 1, + 7, 2, + 8, 3, +#elif defined(__AVR_AT90USB1286__) && defined(CORE_TEENSY) + 0, 0, + 1, 1, + 2, 2, + 3, 3, + 36, 4, + 37, 5, + 18, 6, + 19, 7, +#elif defined(__arm__) && defined(CORE_TEENSY) + 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, + 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, + 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, + 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, + 30, 30, 31, 31, 32, 32, 33, 33, +#elif defined(__AVR_ATmega32U4__) + 3, 0, + 2, 1, + 0, 2, + 1, 3, + 7, 4, +#elif defined(__AVR_ATmega256RFR2__) + 4, 0, + 5, 1, +#elif defined(__SAM3X8E__) || defined(ARDUINO_ARCH_SAMD) + 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, + 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, + 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, + 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, + 30, 30, 31, 31, 32, 32, 33, 33, + +#endif +}; + +boolean Adafruit_VS1053_FilePlayer::useInterrupt(uint8_t type) { + myself = this; // oy vey + + if (type == VS1053_FILEPLAYER_TIMER0_INT) { +#if defined(__AVR__) + OCR0A = 0xAF; + TIMSK0 |= _BV(OCIE0A); + return true; +#elif defined(__arm__) && defined(CORE_TEENSY) + IntervalTimer *t = new IntervalTimer(); + return (t && t->begin(feeder, 1024)) ? true : false; +#else + return false; +#endif + } + if (type == VS1053_FILEPLAYER_PIN_INT) { + for (uint8_t i=0; i= 0) { + uint16_t offsets[] = {0x8000UL, 0x0, 0x4000UL}; + uint16_t addr, len; + + //Serial.print("type: "); Serial.println(type, HEX); + + if (type >= 4) { + plugin.close(); + return 0xFFFF; + } + + len = plugin.read(); len <<= 8; + len |= plugin.read() & ~1; + addr = plugin.read(); addr <<= 8; + addr |= plugin.read(); + //Serial.print("len: "); Serial.print(len); + //Serial.print(" addr: $"); Serial.println(addr, HEX); + + if (type == 3) { + // execute rec! + plugin.close(); + return addr; + } + + // set address + sciWrite(VS1053_REG_WRAMADDR, addr + offsets[type]); + // write data + do { + uint16_t data; + data = plugin.read(); data <<= 8; + data |= plugin.read(); + sciWrite(VS1053_REG_WRAM, data); + } while ((len -=2)); + } + + plugin.close(); + return 0xFFFF; +} + + + + +boolean Adafruit_VS1053::readyForData(void) { + return digitalRead(_dreq); +} + +void Adafruit_VS1053::playData(uint8_t *buffer, uint8_t buffsiz) { + #ifdef SPI_HAS_TRANSACTION + if (useHardwareSPI) SPI.beginTransaction(VS1053_DATA_SPI_SETTING); + #endif + digitalWrite(_dcs, LOW); + for (uint8_t i=0; i= 0) { + digitalWrite(_reset, LOW); + delay(100); + digitalWrite(_reset, HIGH); + } + digitalWrite(_cs, HIGH); + digitalWrite(_dcs, HIGH); + delay(100); + softReset(); + delay(100); + + sciWrite(VS1053_REG_CLOCKF, 0x6000); + + setVolume(40, 40); +} + +uint8_t Adafruit_VS1053::begin(void) { + if (_reset >= 0) { + pinMode(_reset, OUTPUT); + digitalWrite(_reset, LOW); + } + + pinMode(_cs, OUTPUT); + digitalWrite(_cs, HIGH); + pinMode(_dcs, OUTPUT); + digitalWrite(_dcs, HIGH); + pinMode(_dreq, INPUT); + + if (! useHardwareSPI) { + pinMode(_mosi, OUTPUT); + pinMode(_clk, OUTPUT); + pinMode(_miso, INPUT); + } else { + SPI.begin(); + SPI.setDataMode(SPI_MODE0); + SPI.setBitOrder(MSBFIRST); + SPI.setClockDivider(SPI_CLOCK_DIV128); + } + + reset(); + + return (sciRead(VS1053_REG_STATUS) >> 4) & 0x0F; +} + +void Adafruit_VS1053::dumpRegs(void) { + Serial.print("Mode = 0x"); Serial.println(sciRead(VS1053_REG_MODE), HEX); + Serial.print("Stat = 0x"); Serial.println(sciRead(VS1053_REG_STATUS), HEX); + Serial.print("ClkF = 0x"); Serial.println(sciRead(VS1053_REG_CLOCKF), HEX); + Serial.print("Vol. = 0x"); Serial.println(sciRead(VS1053_REG_VOLUME), HEX); +} + + +uint16_t Adafruit_VS1053::recordedWordsWaiting(void) { + return sciRead(VS1053_REG_HDAT1); +} + +uint16_t Adafruit_VS1053::recordedReadWord(void) { + return sciRead(VS1053_REG_HDAT0); +} + + +boolean Adafruit_VS1053::prepareRecordOgg(char *plugname) { + sciWrite(VS1053_REG_CLOCKF, 0xC000); // set max clock + delay(1); while (! readyForData() ); + + sciWrite(VS1053_REG_BASS, 0); // clear Bass + + softReset(); + delay(1); while (! readyForData() ); + + sciWrite(VS1053_SCI_AIADDR, 0); + // disable all interrupts except SCI + sciWrite(VS1053_REG_WRAMADDR, VS1053_INT_ENABLE); + sciWrite(VS1053_REG_WRAM, 0x02); + + int pluginStartAddr = loadPlugin(plugname); + if (pluginStartAddr == 0xFFFF) return false; + Serial.print("Plugin at $"); Serial.println(pluginStartAddr, HEX); + if (pluginStartAddr != 0x34) return false; + + return true; +} + +void Adafruit_VS1053::stopRecordOgg(void) { + sciWrite(VS1053_SCI_AICTRL3, 1); +} + +void Adafruit_VS1053::startRecordOgg(boolean mic) { + /* Set VS1053 mode bits as instructed in the VS1053b Ogg Vorbis Encoder + manual. Note: for microphone input, leave SMF_LINE1 unset! */ + if (mic) { + sciWrite(VS1053_REG_MODE, VS1053_MODE_SM_ADPCM | VS1053_MODE_SM_SDINEW); + } else { + sciWrite(VS1053_REG_MODE, VS1053_MODE_SM_LINE1 | + VS1053_MODE_SM_ADPCM | VS1053_MODE_SM_SDINEW); + } + sciWrite(VS1053_SCI_AICTRL0, 1024); + /* Rec level: 1024 = 1. If 0, use AGC */ + sciWrite(VS1053_SCI_AICTRL1, 1024); + /* Maximum AGC level: 1024 = 1. Only used if SCI_AICTRL1 is set to 0. */ + sciWrite(VS1053_SCI_AICTRL2, 0); + /* Miscellaneous bits that also must be set before recording. */ + sciWrite(VS1053_SCI_AICTRL3, 0); + + sciWrite(VS1053_SCI_AIADDR, 0x34); + delay(1); while (! readyForData() ); +} + +void Adafruit_VS1053::GPIO_pinMode(uint8_t i, uint8_t dir) { + if (i > 7) return; + + sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_DDR); + uint16_t ddr = sciRead(VS1053_REG_WRAM); + + if (dir == INPUT) + ddr &= ~_BV(i); + if (dir == OUTPUT) + ddr |= _BV(i); + + sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_DDR); + sciWrite(VS1053_REG_WRAM, ddr); +} + + +void Adafruit_VS1053::GPIO_digitalWrite(uint8_t val) { + sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_ODATA); + sciWrite(VS1053_REG_WRAM, val); +} + +void Adafruit_VS1053::GPIO_digitalWrite(uint8_t i, uint8_t val) { + if (i > 7) return; + + sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_ODATA); + uint16_t pins = sciRead(VS1053_REG_WRAM); + + if (val == LOW) + pins &= ~_BV(i); + if (val == HIGH) + pins |= _BV(i); + + sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_ODATA); + sciWrite(VS1053_REG_WRAM, pins); +} + +uint16_t Adafruit_VS1053::GPIO_digitalRead(void) { + sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_IDATA); + return sciRead(VS1053_REG_WRAM) & 0xFF; +} + +boolean Adafruit_VS1053::GPIO_digitalRead(uint8_t i) { + if (i > 7) return 0; + + sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_IDATA); + uint16_t val = sciRead(VS1053_REG_WRAM); + if (val & _BV(i)) return true; + return false; +} + +uint16_t Adafruit_VS1053::sciRead(uint8_t addr) { + uint16_t data; + + #ifdef SPI_HAS_TRANSACTION + if (useHardwareSPI) SPI.beginTransaction(VS1053_CONTROL_SPI_SETTING); + #endif + digitalWrite(_cs, LOW); + spiwrite(VS1053_SCI_READ); + spiwrite(addr); + delayMicroseconds(10); + data = spiread(); + data <<= 8; + data |= spiread(); + digitalWrite(_cs, HIGH); + #ifdef SPI_HAS_TRANSACTION + if (useHardwareSPI) SPI.endTransaction(); + #endif + + return data; +} + + +void Adafruit_VS1053::sciWrite(uint8_t addr, uint16_t data) { + #ifdef SPI_HAS_TRANSACTION + if (useHardwareSPI) SPI.beginTransaction(VS1053_CONTROL_SPI_SETTING); + #endif + digitalWrite(_cs, LOW); + spiwrite(VS1053_SCI_WRITE); + spiwrite(addr); + spiwrite(data >> 8); + spiwrite(data & 0xFF); + digitalWrite(_cs, HIGH); + #ifdef SPI_HAS_TRANSACTION + if (useHardwareSPI) SPI.endTransaction(); + #endif +} + + + +uint8_t Adafruit_VS1053::spiread(void) +{ + int8_t i, x; + x = 0; + + // MSB first, clock low when inactive (CPOL 0), data valid on leading edge (CPHA 0) + // Make sure clock starts low + + if (useHardwareSPI) { + x = SPI.transfer(0x00); + } else { + for (i=7; i>=0; i--) { + if ((*misoportreg) & misopin) + x |= (1<=0; i--) { + *clkportreg &= ~clkpin; + if (c & (1< https://www.adafruit.com/products/1381 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ****************************************************/ +#ifndef ADAFRUIT_VS1053_H +#define ADAFRUIT_VS1053_H + +#if (ARDUINO >= 100) + #include +#else + #include + #include +#endif + +#include "pins_arduino.h" +#include "wiring_private.h" +#include +#include + +#ifdef __SAM3X8E__ +typedef volatile RwReg PortReg; +typedef uint32_t PortMask; +#else +typedef volatile uint8_t PortReg; +typedef uint8_t PortMask; +#endif + + +#define VS1053_FILEPLAYER_TIMER0_INT 255 // allows useInterrupt to accept pins 0 to 254 +#define VS1053_FILEPLAYER_PIN_INT 5 + +#define VS1053_SCI_READ 0x03 +#define VS1053_SCI_WRITE 0x02 + +#define VS1053_REG_MODE 0x00 +#define VS1053_REG_STATUS 0x01 +#define VS1053_REG_BASS 0x02 +#define VS1053_REG_CLOCKF 0x03 +#define VS1053_REG_DECODETIME 0x04 +#define VS1053_REG_AUDATA 0x05 +#define VS1053_REG_WRAM 0x06 +#define VS1053_REG_WRAMADDR 0x07 +#define VS1053_REG_HDAT0 0x08 +#define VS1053_REG_HDAT1 0x09 +#define VS1053_REG_VOLUME 0x0B + +#define VS1053_GPIO_DDR 0xC017 +#define VS1053_GPIO_IDATA 0xC018 +#define VS1053_GPIO_ODATA 0xC019 + +#define VS1053_INT_ENABLE 0xC01A + +#define VS1053_MODE_SM_DIFF 0x0001 +#define VS1053_MODE_SM_LAYER12 0x0002 +#define VS1053_MODE_SM_RESET 0x0004 +#define VS1053_MODE_SM_CANCEL 0x0008 +#define VS1053_MODE_SM_EARSPKLO 0x0010 +#define VS1053_MODE_SM_TESTS 0x0020 +#define VS1053_MODE_SM_STREAM 0x0040 +#define VS1053_MODE_SM_SDINEW 0x0800 +#define VS1053_MODE_SM_ADPCM 0x1000 +#define VS1053_MODE_SM_LINE1 0x4000 +#define VS1053_MODE_SM_CLKRANGE 0x8000 + + +#define VS1053_SCI_AIADDR 0x0A +#define VS1053_SCI_AICTRL0 0x0C +#define VS1053_SCI_AICTRL1 0x0D +#define VS1053_SCI_AICTRL2 0x0E +#define VS1053_SCI_AICTRL3 0x0F + +#define VS1053_DATABUFFERLEN 32 + + +class Adafruit_VS1053 { + public: + Adafruit_VS1053(int8_t mosi, int8_t miso, int8_t clk, + int8_t rst, int8_t cs, int8_t dcs, int8_t dreq); + Adafruit_VS1053(int8_t rst, int8_t cs, int8_t dcs, int8_t dreq); + uint8_t begin(void); + void reset(void); + void softReset(void); + uint16_t sciRead(uint8_t addr); + void sciWrite(uint8_t addr, uint16_t data); + void sineTest(uint8_t n, uint16_t ms); + void spiwrite(uint8_t d); + uint8_t spiread(void); + + uint16_t decodeTime(void); + void setVolume(uint8_t left, uint8_t right); + void dumpRegs(void); + + void playData(uint8_t *buffer, uint8_t buffsiz); + boolean readyForData(void); + void applyPatch(const uint16_t *patch, uint16_t patchsize); + uint16_t loadPlugin(char *fn); + + void GPIO_digitalWrite(uint8_t i, uint8_t val); + void GPIO_digitalWrite(uint8_t i); + uint16_t GPIO_digitalRead(void); + boolean GPIO_digitalRead(uint8_t i); + void GPIO_pinMode(uint8_t i, uint8_t dir); + + boolean prepareRecordOgg(char *plugin); + void startRecordOgg(boolean mic); + void stopRecordOgg(void); + uint16_t recordedWordsWaiting(void); + uint16_t recordedReadWord(void); + + uint8_t mp3buffer[VS1053_DATABUFFERLEN]; + +#ifdef ARDUINO_ARCH_SAMD +protected: + uint32_t _dreq; +private: + int32_t _mosi, _miso, _clk, _reset, _cs, _dcs; + boolean useHardwareSPI; +#else + protected: + uint8_t _dreq; + private: + int8_t _mosi, _miso, _clk, _reset, _cs, _dcs; + boolean useHardwareSPI; +#endif +}; + + +class Adafruit_VS1053_FilePlayer : public Adafruit_VS1053 { + public: + Adafruit_VS1053_FilePlayer (int8_t mosi, int8_t miso, int8_t clk, + int8_t rst, int8_t cs, int8_t dcs, int8_t dreq, + int8_t cardCS); + Adafruit_VS1053_FilePlayer (int8_t rst, int8_t cs, int8_t dcs, int8_t dreq, + int8_t cardCS); + Adafruit_VS1053_FilePlayer (int8_t cs, int8_t dcs, int8_t dreq, + int8_t cardCS); + + boolean begin(void); + boolean useInterrupt(uint8_t type); + File currentTrack; + volatile boolean playingMusic; + void feedBuffer(void); + boolean startPlayingFile(const char *trackname); + boolean playFullFile(const char *trackname); + void stopPlaying(void); + boolean paused(void); + boolean stopped(void); + void pausePlaying(boolean pause); + + private: + uint8_t _cardCS; +}; + +#endif // ADAFRUIT_VS1053_H diff --git a/FearConditioning_Connect.m b/FearConditioning_Connect.m new file mode 100644 index 0000000..41968ba --- /dev/null +++ b/FearConditioning_Connect.m @@ -0,0 +1,337 @@ +function ardy = FearConditioning_Connect(varargin) + +port = instrhwinfo('serial'); %Grab information about the available serial ports. +if isempty(port) %If no serial ports were found on this computer... + error(['ERROR IN CONNECT_2AFC: There are no available serial '... + 'ports on this computer.']); %Show an error. +end +port = port.SerialPorts; %Pair down the list of serial ports to only those available. +busyports = instrfind; %Grab all the ports currently in use. +if ~isempty(busyports) %If there are any ports currently in use... + busyports = {busyports.Port}; %Make a list of their port addresses. + if iscell(busyports{1}) %If there's more than one busy port. + busyports = busyports{1}'; %Kick out the extraneous port name parts. + end +else %Otherwise... + busyports = {}; %Make an empty list for comparisons. +end + +[booth_pairings, local, port_matching_file] = Get_Port_Assignments; %Call the subfunction to get the booth-port pairings. + +uiheight = 2; %Set the height for all buttons. +temp = [10,length(port)*(uiheight+0.1)+0.1]; %Set the width and height of the port selection figure. +set(0,'units','centimeters'); %Set the screensize units to centimeters. +pos = get(0,'ScreenSize'); %Grab the screensize. +pos = [pos(3)/2-temp(1)/2,pos(4)/2-temp(2)/2,temp(1),temp(2)]; %Scale a figure position relative to the screensize. +fig1 = figure('units','centimeters',... + 'Position',pos,... + 'resize','off',... + 'MenuBar','none',... + 'name','Select A Serial Port',... + 'numbertitle','off'); %Set the properties of the figure. + +for i = 1:length(port) + + if (~isempty(booth_pairings)) + b = find(strcmpi(local, booth_pairings(:, 1)) & strcmpi(port{i}, booth_pairings(:, 2))); + else + b = []; + end + + if (isempty(b)) + b = '?'; + else + b = num2str(booth_pairings{b(1), 3}); + end + + if any(strcmpi(port{i}, busyports)) + txt = ['Booth ' b ' (' port{i} '): busy (reset?)']; + else + txt = ['Booth ' b ' (' port{i} '): available']; + end + + uicontrol(fig1,'style','pushbutton',... + 'string',txt,... + 'units','centimeters',... + 'position',[0.1 temp(2)-i*(uiheight+0.1) 9.8 uiheight],... + 'fontweight','bold',... + 'fontsize',14,... + 'callback',... + ['guidata(gcbf,' num2str(i) '); uiresume(gcbf);']); %Make a button for the port showing that it is busy. +end + +uiwait(fig1); +if ishandle(fig1) + i = guidata(fig1); + port = port{i}; + close(fig1); + checkreset = 0; +else + port = []; +end + +listbox = []; %Create a variable to hold a listbox handle. + +if ~isempty(port) && checkreset && any(strcmpi(port,busyports)) %If that serial port is busy... + i = questdlg(['Serial port ''' port ''' is busy. Reset and use '... + 'this port?'],['Reset ''' port '''?'],'Reset','Cancel','Reset'); %Ask the user if they want to reset the busy port. + if strcmpi(i,'Cancel') %If the user selected "Cancel"... + port = []; %...set the selected port to empty. + end +end + +if isempty(port) %If no port was selected. + warning(['CONNECT_MOTOTRAK:NoPortChosen','No serial port chosen '... + 'for Ardyardy. Connection to the Arduino was aborted.']); %Show a warning. + ardy = []; %Set the function output to empty. + return; %Exit the Connect_MotoTrak function. +end +if any(strcmpi(port,busyports)) %If the specified port is already busy... + i = find(strcmpi(port,busyports)); %Find the index of the specified ports in the list of all busy ports. + temp = instrfind; %Grab all the open serial connections. + fclose(temp(i)); %Close the busy serial connection. + delete(temp(i)); %Delete the existing serial connection. +end +serialcon = serial(port,'baudrate',115200); %Set up the serial connection on the specified port. +try %Try to open the serial port for communication. + fopen(serialcon); %Open the serial port. +catch err %If no connection could be made to the serial port... + delete(serialcon); %...delete the serial object... + error(['ERROR IN CONNECT_MOTOTRAK: Could not open a serial '... + 'connection on port ''' port ''' (' err.message ').']); %Show an error. +end +message = 'Connecting to the Arduino...'; %Create the beginning of message to show the user. +if isempty(listbox) %If the user didn't specify a listbox... + pos = get(0,'ScreenSize'); %Grab the screensize. + pos = [0.3*pos(3),0.55*pos(4),0.4*pos(3),0.1*pos(4)]; %Scale a figure position relative to the screensize. + fig1 = figure; %Make a figure to show the progress of the Arduino connection. + set(fig1,'Units', get(0, 'Units'), 'Position',pos,'MenuBar','none','name',... + 'Arduino Connection','numbertitle','off'); %Set the properties of the figure. + t = uicontrol(fig1,'style','text',... + 'string',message,... + 'units','normalized',... + 'position',[.01 .01 .98 .98],... + 'fontweight','bold',... + 'horizontalalignment','left',... + 'fontsize',14,... + 'backgroundcolor',get(fig1,'color')); %Make a text label to show the Arduino connection status. +else %Otherwise, if the user specified a listbox... + t = 0; %Create a dummy handle for the non-existent text label. + set(listbox,'string',message,'value',1); %Show the Arduino connection status in the listbox. +end +tic; %Start a timer. +while toc < 10 %Loop for 10 seconds to wait for the Arduino to initialize. + if serialcon.BytesAvailable > 0 %If there's bytes available on the serial line... + break %Break out of the waiting loop. + else %Otherwise... + message(end+1) = '.'; %Add a period to the end of the message. + if ishandle(t) && isempty(listbox) %If the user hasn't closed the figure and hasn't specified a listbox... + set(t,'string',message); %Update the message in the text label on the figure. + elseif ~isempty(listbox) %Or, if the user did specify a listbox... + set(listbox,'string',message,'value',[]); %Update the message in the listbox. + end + pause(0.5); %Pause for 500 milliseconds. + end +end +if serialcon.BytesAvailable > 0 %if there's a reply on the serial line. + temp = fscanf(serialcon,'%c',serialcon.BytesAvailable); %Read the reply into a temporary matrix. +end +tic; %Start a timer. +while toc < 10; %Loop for 10 seconds or until a reply is noted. + fwrite(serialcon,'A','uchar'); %Send the check status code to the Arduino board. + if serialcon.BytesAvailable > 0 %If there's bytes available on the serial line... + message = [message 'Connected!']; %Add to the message to show that the connection was successful. + if ishandle(t) && isempty(listbox) %If the user hasn't closed the figure and hasn't specified a listbox... + set(t,'string',message); %Update the message in the text label on the figure. + elseif ~isempty(listbox) %Or, if the user did specify a listbox... + set(listbox,'string',message,'value',[]); %Update the message in the listbox. + end + break %Break out of the waiting loop. + else %Otherwise... + message(end+1) = '.'; %Add a period to the end of the message. + if ishandle(t) && isempty(listbox) %If the user hasn't closed the figure and hasn't specified a listbox... + set(t,'string',message); %Update the message in the text label on the figure. + elseif ~isempty(listbox) %Or, if the user did specify a listbox... + set(listbox,'string',message,'value',[]); %Update the message in the listbox. + end + pause(0.5); %Pause for 500 milliseconds. + end +end + +while serialcon.BytesAvailable > 0 %Loop through the replies on the serial line. + pause(0.01); %Pause for 50 milliseconds. + temp = fscanf(serialcon,'%d'); %Read each reply, replacing the last. +end +if isempty(temp) || temp(1) ~= 1 %If no status reply was received or the wrong sketch is indicated... + delete(serialcon); %Delete the serial object. + fprintf(1,'COULD NOT CONNECT!\n'); %End the connection message with an error. + error(['ERROR IN Connect_Ardy2AFC: Arduino board is not responding.'... + ' Check to make sure the Arduino is connected to the '... + 'specified serial port and that it is running the '... + 'FearConditioning_Main.ino sketch.']); %Show an error. +else %Otherwise... + fprintf(1,['Arduino is connected and FearConditioning_Main.ino is detected as '... + 'running.']); %Show that the connection was successful. +end + +%Save details about the connection to the output structure. +ardy.port = port; %Save the port address to the output structure. +ardy.serialcon = serialcon; %Save the handle for the serial connection for debugging purposes. + +%Basic functions +ardy.stream_enable = @(i)simple_command(serialcon,'gi',i); %Set the function for enabling or disabling the stream. +ardy.read_stream = @()read_stream(serialcon); %Set the function for reading values from the stream. +ardy.clear = @()clear_stream(serialcon); %Set the function for clearing the serial line prior to streaming. + +%Basic status functions. + +ardy.set_booth = @(int)long_command(serialcon,'Bnn',[],int); %Set the function for setting the booth number saved on the Arduino. +ardy.booth = @()simple_return(serialcon,'b',1); %Set the function for returning the booth number saved on the Arduino. + +%Input / output functions +ardy.shock_enable = @(i)simple_command(serialcon,'Ci', i); %Turn shock on or off +ardy.vns_enable = @()simple_command(serialcon,'D', i); %Turn on vns pulse +ardy.select_tone = @(int)long_command(serialcon,'Enn', [], int); %Select tone +ardy.enable_music = @(i)simple_command(serialcon,'Fi', i); %Turn on/off music +ardy.read_music = @()simple_return(serialcon, 'G', 1); %Read back from Music Player to determine if music is playing +ardy.music_led_disable = @()simple_command(serialcon, 'H', i); %Turn off Music LED +ardy.VNS_led_disable = @()simple_command(serialcon, 'J', i); %Turn off Music LED + +pause(2); +close(fig1); + +%Clean any junk leftover on the serial line +while serialcon.BytesAvailable > 0 + fscanf(serialcon, '%d', serialcon.BytesAvailable); +end + +Set_Port_Assignments(booth_pairings,local,port_matching_file,... + port,ardy.booth()); %Save the port-to-booth pairings for the next start-up. + +end + +%% This function sends the specified command to the Arduino, replacing any "i" characters with the specified input number. +function simple_command(serialcon,command,i) +command(command == 'i') = num2str(i); %Convert the specified input number to a string. +fwrite(serialcon,command,'uchar'); %Send the command to the Arduino board. +end + +%% This function reads in the values from the data stream when streaming is enabled. +function output = read_stream(serialcon) +tic; %Start a timer. +while serialcon.BytesAvailable == 0 && toc < 0.05 %Loop for 50 milliseconds or until there's a reply on the serial line. + pause(0.001); %Pause for 1 millisecond to keep from overwhelming the processor. +end +output = []; %Create an empty matrix to hold the serial line reply. +while serialcon.BytesAvailable > 0 %Loop as long as there's bytes available on the serial line... + try + streamdata = fscanf(serialcon,'%d')'; + output(end+1,:) = streamdata(1:2); %Read each byte and save it to the output matrix. + catch + end +end +end + + +%% This function sends the specified command to the Arduino, replacing any "i" characters with the specified input number. +function output = simple_return(serialcon,command,i) +command(command == 'i') = num2str(i); %Convert the specified input number to a string. +fwrite(serialcon,command,'uchar'); %Send the command to the Arduino board. +output = fscanf(serialcon,'%d'); %Check the serial line for a reply. +end + +%% This function sends commands with 16-bit integers broken up into 2 characters encoding each byte. +function long_command(serialcon,command,i,int) +command(command == 'i') = num2str(i); %Convert the specified input number to a string. +i = dec2bin(int16(int),16); %Convert the 16-bit integer to a 16-bit binary string. +byteA = bin2dec(i(1:8)); %Find the character that codes for the first byte. +byteB = bin2dec(i(9:16)); %Find the character that codes for the second byte. +i = findstr(command,'nn'); %Find the spot for the 16-bit integer bytes in the command. +command(i:i+1) = char([byteA, byteB]); %Insert the byte characters into the command. +fwrite(serialcon,command,'uchar'); %Send the command to the Arduino board. +end + +%% This function clears any residual streaming data from the serial line prior to streaming. +function clear_stream(serialcon) +tic; %Start a timer. +while serialcon.BytesAvailable == 0 && toc < 0.05 %Loop for 50 milliseconds or until there's a reply on the serial line. + pause(0.001); %Pause for 1 millisecond to keep from overwhelming the processor. +end +while serialcon.BytesAvailable > 0 %Loop as long as there's bytes available on the serial line... + fscanf(serialcon,'%d'); %Read each byte and discard it. +end +end + +%% This function writes the port-booth assignment file. +function Set_Port_Assignments(booth_pairings,local,port_matching_file,port,booth) +if ~isempty(booth_pairings) %If a booth-to-port pairings file was found... + i = find(strcmpi(local,booth_pairings(:,1)) & ... + strcmpi(port,booth_pairings(:,2))); %Check to see if this port is already matched to a booth number. +else %Otherwise, if a booth-to-port pairings file wasn't found... + i = []; %Show that no booth-to-port match was found. +end +if ~isempty(i) %If a match was found... + b = booth_pairings{i(1),3}; %Grab the booth number matched to this port on this computer. + if b ~= booth %If the current booth number doesn't match in the file... + booth_pairings{i(1),3} = booth; %Save the new booth number. + if length(i) > 1 %If more than one match was found... + booth_pairings(i(2:end),:) = []; %Kick out the redundant matches. + end + end +else %Otherwise, if a match wasn't found... + b = []; %Set the booth number to an empty matrix. + booth_pairings(end+1,1:3) = {local,port,booth}; %Save the booth-to-port assignment. +end +if isempty(b) || b ~= booth %If the booth on this port isn't yet assigned or the booth number doesn't match... + fid = fopen(port_matching_file,'wt'); %Open a new text file to write the booth-to-port pairing to. + fprintf(fid,'%s\t','COMPUTER:'); %Write the computer column heading to the file. + fprintf(fid,'%s\t','PORT:'); %Write the port column heading to the file. + fprintf(fid,'%s\n','BOOTH:'); %Write the booth column heading to the file. + for i = 1:size(booth_pairings,1) %Step through the listed booth-to-port pairings. + fprintf(fid,'%s\t',booth_pairings{i,1}); %Write the computer name to the file. + fprintf(fid,'%s\t',booth_pairings{i,2}); %Write the port to the file. + fprintf(fid,'%1.0f\n',booth_pairings{i,3}); %Write the booth number to the file. + end + fclose(fid); %Close the pairing file. +end +end + + +%% This function reads in the port-booth assignment file. +function [booth_pairings, local, port_matching_file] = Get_Port_Assignments +if isdeployed %If this is deployed code... + temp = pwd; %Set the destination directory to the current directory. +else %Otherwise, if this isn't deployed code... + temp = mfilename('fullpath'); %Grab the full path and filename of the current *.m file. + temp(find(temp == '\' | temp == '/',1,'last'):end) = []; %Kick out the filename to capture just the path. +end +port_matching_file = [temp '\fear_port_booth_pairings.txt']; %Set the expected name of the pairing file. +if exist(port_matching_file,'file') %If the pairing file exists... + try %Attempt to open and read the pairing file. + fid = fopen(port_matching_file,'rt'); %Open the pairing file for reading. + temp = textscan(fid,'%s'); %Read in the booth-port pairings. + fclose(fid); %Close the pairing file. + if mod(length(temp{1}),3) ~= 0 %If the data in the file isn't formated into 3 columns... + booth_pairings = {}; %Set the pairing cell array to be an empty cell. + else %Otherwise... + booth_pairings = cell(3,length(temp{1})/3-1); %Create a 3-column cell array to hold the booth-to-port assignments. + for i = 4:length(temp{1}) %Step through the elements of the text. + booth_pairings(i-3) = temp{1}(i); %Match each entry to it's correct row and column. + end + booth_pairings = booth_pairings'; %Transpose the pairing cell array. + for i = 1:size(booth_pairings) %Step through each row in the pairing cell array. + booth_pairings{i,3} = str2double(booth_pairings{i,3}); %Convert each string to a number. + end + booth_pairings(isnan([booth_pairings{:,3}]),:) = []; %Kick out any rows where the booth number was unreadable. + end + catch err %If any error occured while reading the pairing file. + booth_pairings = {}; %Set the pairing cell array to be an empty cell. + warning('FearConditioning_Connect:PairingFileReadError',['The '... + 'booth-to-port pairing file was unreadable! ' err.identifier]); %Show that the pairing file couldn't be read. + end +else %Otherwise, if the pairing file doesn't exist. + booth_pairings = {}; %Set the pairing cell array to be an empty cell. +end +[temp, local] = system('hostname'); %Grab the local computer name. +local(local < 33) = []; %Kick out any spaces and carriage returns from the computer name. +end \ No newline at end of file diff --git a/FearConditioning_Main/FearConditioning_Main.ino b/FearConditioning_Main/FearConditioning_Main.ino new file mode 100644 index 0000000..ae66b2c --- /dev/null +++ b/FearConditioning_Main/FearConditioning_Main.ino @@ -0,0 +1,223 @@ +#include +#include +#include +#include //Include the EEPROM Library in this sketch. + +int shock_led = A4; +int shock_pin = 7; + +int vns_pin = A2; +int vns_led= A3; + + +//a is MSB, d is LSB +int music_led = A5; +int tone_select_a = 8; +int tone_select_b = 9; +int tone_select_c = 10; +int tone_select_d = 11; +int music_trigger = 12; +int read_music = 13; + +byte mode[]={0,0,0,0}; +int val; +unsigned int byteA; + +int booth_num = 0; +int input=0; + +long stream_input=0; + +boolean stream_enable=0; +unsigned long stream_period = 10; //Set the time between stream writes, in milliseconds. +unsigned long next_stream, read_time, stream_start; //Create unsigned long variables for timing a periodic data stream. + +boolean input_enabled[] = {1, 0, 0, 0, 0, 0}; + +void setup() +{ + + pinMode(shock_led, OUTPUT); + pinMode(shock_pin, OUTPUT); + pinMode(vns_led, OUTPUT); + pinMode(vns_pin, OUTPUT); + pinMode(music_led, OUTPUT); + pinMode(music_trigger, OUTPUT); + pinMode(tone_select_a, OUTPUT); + pinMode(tone_select_b, OUTPUT); + pinMode(tone_select_c, OUTPUT); + pinMode(tone_select_d, OUTPUT); + pinMode(read_music,INPUT); + + //Initialize the serial connection. + Serial.begin(115200); //Set the baud rate for serial data transmission. + + +} + +void loop() +{ + + if (Serial.available() > 0) //If there's a new incoming command on the serial line... + { + val=Serial.read(); + + switch (mode[0]) + { + + case 0: + //If serial read is an "A" + if(val == 'A') + { + Serial.print(1); + Serial.print("\t"); + Serial.print(0); + Serial.print("\n"); + + } + + //If serial read is a "B": set booth num + else if( val == 'B') + { + Serial.println(booth_num); + } + + //If serial read is a "b": display booth num + else if (val == 'b') + { + booth_num = EEPROM.read(0); + Serial.println(booth_num); + } + + //If serial read is a "C": enable/disable shock + else if (val == 'C') + { + mode[0]=2; + } + + //If serial read is a "D": enable vns pulse of 5ms + else if (val == 'D') + { + digitalWrite(vns_led,1); + digitalWrite(vns_pin,1); + delay(5); + digitalWrite(vns_pin,0); + } + + //If serial read is an "E": select tone outputs + else if (val == 'E') + { + mode[0]=3; + mode[1]=4; + } + + + //If serial read is an "F": enable/disable tone + else if (val == 'F') + { + mode[0]=5; + } + + + //If serial read is a "G": read input from music board + else if (val == 'G') + { + input=digitalRead(read_music); + Serial.println(input); + } + + //If serial read is a "g": enable stream + else if (val == 103) + { + mode[0]=17; + } + + //If serial read is an H: turn off music indicator + else if (val == 'H') + { + digitalWrite(music_led,0); + } + + //If serial read is a J: turrn off VNS indicator + else if (val == 'J') + { + digitalWrite(vns_led,0); + } + break; + + //Enable or disable shock + case 2: + val=val-48; + if (val == 1) + { + digitalWrite(shock_pin,1); + digitalWrite(shock_led,1); + } + else + { + digitalWrite(shock_pin,0); + digitalWrite(shock_led,0); + } + mode[0]=0; + break; + + case 3: //When mode equals 7, the read value is the first byte of a 16-bit integer. + byteA = val; //Save the read value to the byteA variable. + mode[0] = mode[1]; //Set the next mode to the second specified mode. + mode[1] = mode[2]; //Set the second mode to the third specified mode. + mode[2] = mode[3]; //Set the third mode to the fourth specified mode. + break; + + //Enable tone selector pins + case 4: + byteA=val+(byteA<<8); + digitalWrite(tone_select_a, HIGH && (byteA & B00001000)); + digitalWrite(tone_select_b, HIGH && (byteA & B00000100)); + digitalWrite(tone_select_c, HIGH && (byteA & B00000010)); + digitalWrite(tone_select_d, HIGH && (byteA & B00000001)); + mode[0]=0; + break; + + //Enable music + case 5: + + val=val-48; + if(val == 1) + { + digitalWrite(music_trigger,1); + digitalWrite(music_led,1); + } + + else + { + digitalWrite(music_trigger,0); + } + mode[0]=0; + break; + + //Stream Enable + case 17: + stream_enable = val - 48; + stream_start = millis(); + next_stream = stream_start + stream_period; + mode[0] = 0; + break; + + + + } + } + + if ((stream_enable == 1) && (millis() > next_stream)) //If periodic streaming is enabled and it's time for the next stream write... + { + stream_input= analogRead(read_music); + read_time = millis() - stream_start ; //Grab the current microsecond clock time minus the stream start time. + Serial.print(read_time); //Print the current microsecond clock reading to the serial line. + Serial.print("\t"); //Print a tab. + Serial.print(stream_input); //Print the value of the analog input to the serial line. //Print the current value of the stream IR input to the serial line. + Serial.print("\n"); //Print a new line indicator. + next_stream = next_stream + stream_period; //Set the timing for the next stream period. + } + + +} diff --git a/FearConditioning_V3_2.m b/FearConditioning_V3_2.m new file mode 100644 index 0000000..ffbadb1 --- /dev/null +++ b/FearConditioning_V3_2.m @@ -0,0 +1,1251 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + run = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + %Open a file to save the video + if (strcmpi(handles.record_video, 'on')) + %BeginVideo(handles); + end + + %Get the amount of extra time to be appended to both the beginning and the end of the session + extra_time = handles.extra_time; + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Get the VNS duration of this stage + vns_duration = handles.vns_duration; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds) + (extra_time / 1000); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + %For sounds that contain multiple VNS stims, calculate the interval between stims + intervals_between_single_sound_stims = sound_duration / handles.stims_per_sound; + + %Calculate the base VNS timings + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + + %Now calculate the final VNS timings based on sounds that contain multiple stims + final_vns_timings = []; + for i = 1:length(timings_of_paired_vns_sounds) + + new_vns_timings = timings_of_paired_vns_sounds(i) * ones(1, handles.stims_per_sound); + offset_array = 0:(handles.stims_per_sound-1); + offset_array = offset_array .* intervals_between_single_sound_stims; + new_vns_timings = new_vns_timings + offset_array; + + final_vns_timings = [final_vns_timings new_vns_timings]; + end + + timings_of_paired_vns_sounds = sort(final_vns_timings); + + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, handles.vns_duration, extra_time, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + extra_time_started = 0; + extra_time_done = 0; + extra_time_start = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + loop_start = toc; + + %Display image + %colorImage = getsnapshot(vid); + %grayImage = rgb2gray(colorImage); + %imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (strcmpi(handles.record_video, 'on')) + %WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring, current_time); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + + if (~extra_time_started) + extra_time_started = 1; + extra_time_start = current_time; + else + %Check to see if the extra time has been expended + extra_time_expended = current_time - extra_time_start; + if (extra_time_expended > handles.extra_time) + extra_time_done = 1; + end + + if (extra_time_done) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + end + + + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + %disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + if (~handles.no_ardy_debug_mode) + handles.ardy.music_led_disable(); + end + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + if (~handles.no_ardy_debug_mode) + handles.ardy.VNS_led_disable(); + end + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + handles.vns_duration; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, handles.vns_duration, extra_time, current_time); + + loop_end = toc; + + loop_difference = loop_end - loop_start; + disp(num2str(loop_difference)); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (strcmpi(handles.record_video, 'on')) + %StopVideo(); + end + + %Output the total number of seconds that has elapsed during this session. + WriteSessionEvent(fid, 'Total Session Time', num2str(current_time / 1000)); + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.2', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + %handles.webcam_axes = axes('parent', axes_horizontal_panel); + %handles.webcam_image = image(zeros(640, 480, 3), 'Parent', handles.webcam_axes); + %set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + %set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'VNS Duration', 'vns_duration'; ... + 'Record Video', 'record_video'; ... + 'Extra Time', 'extra_time'; ... + 'Stims Per Sound', 'stims_per_sound' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, vns_duration, extra_time, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + vns_duration = vns_duration ./ (1000 * 60); + extra_time = extra_time ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + vns_duration; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + vns_duration)]) + extra_time; + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function BeginVideo ( handles ) + + global vid; + global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.mp4']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + open(vid_writer); + + preview(vid, handles.webcam_image); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring, current_time) + + global vid; + global vid_writer; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + %Return if no frames need to be processed + if (isempty(frames)) + return; + end + + %Return if the number of rows (Y-axis) is less than 480 + if (size(frames, 1) < 480) + return; + end + + %Return if the number of columns (X-axis) is less than 640 + if (size(frames, 2) < 640) + return; + end + + %If the number of rows is greater than 480, then reduce it to 480. + if (size(frames, 1) > 480) + frames = frames(1:480, :, :, :); + end + + %If the number of columns is greater than 640, then reduce it to 640. + if (size(frames, 2) > 640) + frames = frames(:, 1:640, :, :); + end + + for i = 1:size(frames, 4) + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 1:100, 1, i) = 255; + %frames(1:100, 1:100, 2, i) = 0; + %frames(1:100, 1:100, 3, i) = 0; + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 101:200, 1, i) = 0; + %frames(1:100, 101:200, 2, i) = 255; + %frames(1:100, 101:200, 3, i) = 0; + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 201:300, 1, i) = 0; + %frames(1:100, 201:300, 2, i) = 0; + %frames(1:100, 201:300, 3, i) = 255; + end + + frames(:, :, :, i) = insertText(frames(:, :, :, i), [300 1], num2str(current_time), 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + + end + + writeVideo(vid_writer, frames); + +end + +%% This function saves all video frames +function StopVideo ( ) + + global vid; + global vid_writer; + + closepreview(vid); + + stop(vid); + + close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + elseif (strcmpi(event_type_string, 'Total Session Time')) + event_string = [event_type_string ' = ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MusicBoard/MusicBoard.ino b/MusicBoard/MusicBoard.ino new file mode 100644 index 0000000..3ea3f75 --- /dev/null +++ b/MusicBoard/MusicBoard.ino @@ -0,0 +1,194 @@ +#include +#include +#include +#include //Include the EEPROM Library in this sketch. + + + +// These are the pins used for the music maker shield +#define SHIELD_RESET -1 // VS1053 reset pin (unused!) +#define SHIELD_CS 7 // VS1053 chip select pin (output) +#define SHIELD_DCS 6 // VS1053 Data/command select pin (output) + +// These are common pins between breakout and shield +#define CARDCS 4 // Card chip select pin +// DREQ should be an Int pin, see http://arduino.cc/en/Reference/attachInterrupt +#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin + +Adafruit_VS1053_FilePlayer musicPlayer = + Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS); + +//a is MSB, d is LSB +int tone_a = A2; +int tone_b = A3; +int tone_c = A0; +int tone_d = A1; +int trigger = A4; +int music_out = A5; //output from music board to main arduino to state when music is playing + +int read_a=0; +int read_b=0; +int read_c=0; +int read_d=0; +int read_trigger=0; + +int tune_number=0; +String tune=""; + + +String machineGun = "guns.mp3"; +String twitter = "twtr.mp3"; +String ninekhz = "9khz.mp3"; +String fourkhz = "4khz.mp3"; +String twokhzten = "2k10s.mp3"; +String ninekhzten = "9k10s.mp3"; +String war_zone = "warzone.mp3"; + + + +char track[15] = "9khz.mp3"; +char trackb[15] = "guns.mp3"; + + + + +void setup() +{ + pinMode(tone_a,INPUT); + pinMode(tone_b,INPUT); + pinMode(tone_c,INPUT); + pinMode(tone_d,INPUT); + pinMode(trigger,INPUT); + pinMode(music_out,OUTPUT); + + Serial.begin(115200); + + if (! musicPlayer.begin()) { // initialise the music player + //Serial.println(F("Couldn't find VS1053, do you have the right pins defined?")); + while (1); + } + //Serial.println(F("VS1053 found")); + SD.begin(CARDCS); // initialise the SD card + + // Set volume for left, right channels. lower numbers == louder volume! + musicPlayer.setVolume(20,20); + + // If DREQ is on an interrupt pin (on uno, #2 or #3) we can do background + // audio playing + musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT); // DREQ int + randomSeed(analogRead(0)); + +} + +void loop() +{ + digitalWrite(music_out,LOW); + read_trigger=digitalRead(trigger); + if(read_trigger==1) + { + + read_a=digitalRead(tone_a); + read_b=digitalRead(tone_b); + read_c=digitalRead(tone_c); + read_d=digitalRead(tone_d); + + tune_number=pow(2*double(read_a),3) + pow(2*double(read_b),2) + 2*read_c + read_d + 0.01; + + switch(tune_number) + { + case 0: + machineGun.toCharArray(track,15); + musicPlayer.setVolume(20,20); + break; + + case 1: + twitter.toCharArray(track, 15); + musicPlayer.setVolume(20,20); + break; + + case 2: + ninekhz.toCharArray(track, 15); + musicPlayer.setVolume(50,50); + break; + + case 3: + fourkhz.toCharArray(track, 15); + musicPlayer.setVolume(50,50); + break; + + case 4: + twokhzten.toCharArray(track, 15); + musicPlayer.setVolume(50,50); + break; + + case 5: + ninekhzten.toCharArray(track, 15); + musicPlayer.setVolume(50,50); + break; + + case 6: + war_zone.toCharArray(track, 15); + musicPlayer.setVolume(20, 20); + + break; + + case 7: + + break; + + case 8: + + break; + + case 9: + + break; + + case 10: + + break; + + case 11: + + break; + + case 12: + + break; + + case 13: + + break; + + case 14: + + break; + + case 15: + + break; + + + } + Serial.println(tune_number); + + digitalWrite(music_out, HIGH); + + + musicPlayer.startPlayingFile(track); + + while(musicPlayer.playingMusic) + { + + } + + + digitalWrite(music_out, LOW); + + + delay(20); + } + + + +} diff --git a/Old versions/FearConditioning_V3_2_20170214.m b/Old versions/FearConditioning_V3_2_20170214.m new file mode 100644 index 0000000..e515ee3 --- /dev/null +++ b/Old versions/FearConditioning_V3_2_20170214.m @@ -0,0 +1,1152 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + run = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + %Open a file to save the video + if (handles.record_video) + BeginVideo(handles); + end + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + %Display image + colorImage = getsnapshot(vid); + grayImage = rgb2gray(colorImage); + imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (handles.record_video) + WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + 1000; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, current_time); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (handles.record_video) + StopVideo(); + end + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.1', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + handles.webcam_axes = axes('parent', axes_horizontal_panel); + set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'Record Video', 'record_video' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + 0.0167; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + 0.0167)]); + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function BeginVideo ( handles ) + + global vid; + global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.mp4']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + open(vid_writer); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring) + + global vid; + global vid_writer; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + for i = 1:size(frames, 4) + + + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 1:100, 1, i) = 255; + %frames(1:100, 1:100, 2, i) = 0; + %frames(1:100, 1:100, 3, i) = 0; + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 101:200, 1, i) = 0; + %frames(1:100, 101:200, 2, i) = 255; + %frames(1:100, 101:200, 3, i) = 0; + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 201:300, 1, i) = 0; + %frames(1:100, 201:300, 2, i) = 0; + %frames(1:100, 201:300, 3, i) = 255; + end + + end + + writeVideo(vid_writer, frames); + +end + +%% This function saves all video frames +function StopVideo ( ) + + global vid; + global vid_writer; + + stop(vid); + + close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Old versions/FearConditioning_V3_2_20170215.m b/Old versions/FearConditioning_V3_2_20170215.m new file mode 100644 index 0000000..0add4f5 --- /dev/null +++ b/Old versions/FearConditioning_V3_2_20170215.m @@ -0,0 +1,1182 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + run = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + %Open a file to save the video + if (handles.record_video) + BeginVideo(handles); + end + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + loop_start = toc; + + %Display image + colorImage = getsnapshot(vid); + grayImage = rgb2gray(colorImage); + imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (handles.record_video) + WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + 1000; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, current_time); + + loop_end = toc; + + loop_difference = loop_end - loop_start; + disp(num2str(loop_difference)); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (handles.record_video) + StopVideo(); + end + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.1', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + handles.webcam_axes = axes('parent', axes_horizontal_panel); + set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'Record Video', 'record_video' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + 0.0167; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + 0.0167)]); + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function BeginVideo ( handles ) + + global vid; + global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.mp4']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + open(vid_writer); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring) + + global vid; + global vid_writer; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + %Return if no frames need to be processed + if (isempty(frames)) + return; + end + + %Return if the number of rows (Y-axis) is less than 480 + if (size(frames, 1) < 480) + return; + end + + %Return if the number of columns (X-axis) is less than 640 + if (size(frames, 2) < 640) + return; + end + + %If the number of rows is greater than 480, then reduce it to 480. + if (size(frames, 1) > 480) + frames = frames(1:480, :, :, :); + end + + %If the number of columns is greater than 640, then reduce it to 640. + if (size(frames, 2) > 640) + frames = frames(:, 1:640, :, :); + end + + for i = 1:size(frames, 4) + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 1:100, 1, i) = 255; + %frames(1:100, 1:100, 2, i) = 0; + %frames(1:100, 1:100, 3, i) = 0; + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 101:200, 1, i) = 0; + %frames(1:100, 101:200, 2, i) = 255; + %frames(1:100, 101:200, 3, i) = 0; + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 201:300, 1, i) = 0; + %frames(1:100, 201:300, 2, i) = 0; + %frames(1:100, 201:300, 3, i) = 255; + end + + end + + writeVideo(vid_writer, frames); + +end + +%% This function saves all video frames +function StopVideo ( ) + + global vid; + global vid_writer; + + stop(vid); + + close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Old versions/FearConditioning_V3_2_20170215_mmwrite.m b/Old versions/FearConditioning_V3_2_20170215_mmwrite.m new file mode 100644 index 0000000..a1346bd --- /dev/null +++ b/Old versions/FearConditioning_V3_2_20170215_mmwrite.m @@ -0,0 +1,1189 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + run = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + %Open a file to save the video + if (handles.record_video) + BeginVideo(handles); + end + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + loop_start = toc; + + %Display image + colorImage = getsnapshot(vid); + grayImage = rgb2gray(colorImage); + imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (handles.record_video) + WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + 1000; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, current_time); + + loop_end = toc; + + loop_difference = loop_end - loop_start; + disp(num2str(loop_difference)); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (handles.record_video) + StopVideo(); + end + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.1', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + handles.webcam_axes = axes('parent', axes_horizontal_panel); + set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'Record Video', 'record_video' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + 0.0167; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + 0.0167)]); + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function BeginVideo ( handles ) + + global vid; + global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.avi']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + %vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + %open(vid_writer); + vid_writer = [path_name '\' file_name]; + mmwrite(vid_writer, 'Continue'); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring) + + global vid; + global vid_writer; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + %Return if no frames need to be processed + if (isempty(frames)) + return; + end + + %Return if the number of rows (Y-axis) is less than 480 + if (size(frames, 1) < 480) + return; + end + + %Return if the number of columns (X-axis) is less than 640 + if (size(frames, 2) < 640) + return; + end + + %If the number of rows is greater than 480, then reduce it to 480. + if (size(frames, 1) > 480) + frames = frames(1:480, :, :, :); + end + + %If the number of columns is greater than 640, then reduce it to 640. + if (size(frames, 2) > 640) + frames = frames(:, 1:640, :, :); + end + + v = []; + v.frames = struct('cdata', {}); + v.times = []; + v.width = 640; + v.height = 480; + + for i = 1:size(frames, 4) + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + end + + new_frame = []; + new_frame.cdata = uint8(frames(:, :, :, i)); + v.frames(i) = new_frame; + v.times(i) = times(i); + + end + + %writeVideo(vid_writer, frames); + + mmwrite(vid_writer, v, 'Continue', 'Initialized'); + +end + +%% This function saves all video frames +function StopVideo ( ) + + global vid; + global vid_writer; + + stop(vid); + + mmwrite(vid_writer, 'Initialized'); + %close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Old versions/FearConditioning_V3_2_20170215_qtwriter.m b/Old versions/FearConditioning_V3_2_20170215_qtwriter.m new file mode 100644 index 0000000..2d931c6 --- /dev/null +++ b/Old versions/FearConditioning_V3_2_20170215_qtwriter.m @@ -0,0 +1,1204 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + global last_end_time; + run = 0; + last_end_time = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + vid_writer = []; + + %Open a file to save the video + if (handles.record_video) + vid_writer = BeginVideo(handles); + end + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + loop_start = toc; + + %Display image + colorImage = getsnapshot(vid); + grayImage = rgb2gray(colorImage); + imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (handles.record_video) + %WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring, vid_writer); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + 1000; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, current_time); + + loop_end = toc; + + loop_difference = loop_end - loop_start; + disp(num2str(loop_difference)); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (handles.record_video) + StopVideo(vid_writer); + end + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.1', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + handles.webcam_axes = axes('parent', axes_horizontal_panel); + set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'Record Video', 'record_video' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + 0.0167; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + 0.0167)]); + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function vid_writer = BeginVideo ( handles ) + + global vid; + %global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.mov']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + %vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + %open(vid_writer); + vid_writer = QTWriter([path_name '\' file_name]); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring, vid_writer) + + global vid; + global last_end_time; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + %Return if no frames need to be processed + if (isempty(frames)) + return; + end + + %Return if the number of rows (Y-axis) is less than 480 + if (size(frames, 1) < 480) + return; + end + + %Return if the number of columns (X-axis) is less than 640 + if (size(frames, 2) < 640) + return; + end + + %If the number of rows is greater than 480, then reduce it to 480. + if (size(frames, 1) > 480) + frames = frames(1:480, :, :, :); + end + + %If the number of columns is greater than 640, then reduce it to 640. + if (size(frames, 2) > 640) + frames = frames(:, 1:640, :, :); + end + + %v = []; + %v.frames = struct('cdata', {}); + %v.times = []; + %v.width = 640; + %v.height = 480; + + + new_times = [last_end_time; times]; + diff_times = diff(new_times); + last_end_time = times(end); + + for i = 1:size(frames, 4) + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + end + + new_frame = []; + new_frame.cdata = uint8(frames(:, :, :, i)); + new_frame.colormap = []; + + dt = diff_times(i); + fr = 1.0 / dt; + if (~isinf(fr) && ~isnan(fr)) + vid_writer.FrameRate = fr; + end + + writeMovie(vid_writer, new_frame); + + %v.frames(i) = new_frame; + %v.times(i) = times(i); + + end + + %writeVideo(vid_writer, frames); + +end + +%% This function saves all video frames +function StopVideo ( vid_writer ) + + global vid; + + stop(vid); + + %mmwrite(vid_writer, 'Initialized'); + close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Old versions/FearConditioning_V3_2_20170216.m b/Old versions/FearConditioning_V3_2_20170216.m new file mode 100644 index 0000000..b8d54ce --- /dev/null +++ b/Old versions/FearConditioning_V3_2_20170216.m @@ -0,0 +1,1187 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + run = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + %Open a file to save the video + if (handles.record_video) + BeginVideo(handles); + end + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + loop_start = toc; + + %Display image + colorImage = getsnapshot(vid); + grayImage = rgb2gray(colorImage); + imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (handles.record_video) + WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + 1000; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, current_time); + + loop_end = toc; + + loop_difference = loop_end - loop_start; + disp(num2str(loop_difference)); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (handles.record_video) + StopVideo(); + end + + %Output the total number of seconds that has elapsed during this session. + WriteSessionEvent(fid, 'Total Session Time', num2str(current_time / 1000)); + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.1', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + handles.webcam_axes = axes('parent', axes_horizontal_panel); + set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'Record Video', 'record_video' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + 0.0167; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + 0.0167)]); + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function BeginVideo ( handles ) + + global vid; + global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.mp4']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + open(vid_writer); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring) + + global vid; + global vid_writer; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + %Return if no frames need to be processed + if (isempty(frames)) + return; + end + + %Return if the number of rows (Y-axis) is less than 480 + if (size(frames, 1) < 480) + return; + end + + %Return if the number of columns (X-axis) is less than 640 + if (size(frames, 2) < 640) + return; + end + + %If the number of rows is greater than 480, then reduce it to 480. + if (size(frames, 1) > 480) + frames = frames(1:480, :, :, :); + end + + %If the number of columns is greater than 640, then reduce it to 640. + if (size(frames, 2) > 640) + frames = frames(:, 1:640, :, :); + end + + for i = 1:size(frames, 4) + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 1:100, 1, i) = 255; + %frames(1:100, 1:100, 2, i) = 0; + %frames(1:100, 1:100, 3, i) = 0; + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 101:200, 1, i) = 0; + %frames(1:100, 101:200, 2, i) = 255; + %frames(1:100, 101:200, 3, i) = 0; + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 201:300, 1, i) = 0; + %frames(1:100, 201:300, 2, i) = 0; + %frames(1:100, 201:300, 3, i) = 255; + end + + end + + writeVideo(vid_writer, frames); + +end + +%% This function saves all video frames +function StopVideo ( ) + + global vid; + global vid_writer; + + stop(vid); + + close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + elseif (strcmpi(event_type_string, 'Total Session Time')) + event_string = [event_type_string ' = ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Old versions/FearConditioning_V3_2_20170227.m b/Old versions/FearConditioning_V3_2_20170227.m new file mode 100644 index 0000000..b8d54ce --- /dev/null +++ b/Old versions/FearConditioning_V3_2_20170227.m @@ -0,0 +1,1187 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + run = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + %Open a file to save the video + if (handles.record_video) + BeginVideo(handles); + end + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + loop_start = toc; + + %Display image + colorImage = getsnapshot(vid); + grayImage = rgb2gray(colorImage); + imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (handles.record_video) + WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + 1000; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, current_time); + + loop_end = toc; + + loop_difference = loop_end - loop_start; + disp(num2str(loop_difference)); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (handles.record_video) + StopVideo(); + end + + %Output the total number of seconds that has elapsed during this session. + WriteSessionEvent(fid, 'Total Session Time', num2str(current_time / 1000)); + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.1', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + handles.webcam_axes = axes('parent', axes_horizontal_panel); + set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'Record Video', 'record_video' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + 0.0167; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + 0.0167)]); + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function BeginVideo ( handles ) + + global vid; + global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.mp4']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + open(vid_writer); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring) + + global vid; + global vid_writer; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + %Return if no frames need to be processed + if (isempty(frames)) + return; + end + + %Return if the number of rows (Y-axis) is less than 480 + if (size(frames, 1) < 480) + return; + end + + %Return if the number of columns (X-axis) is less than 640 + if (size(frames, 2) < 640) + return; + end + + %If the number of rows is greater than 480, then reduce it to 480. + if (size(frames, 1) > 480) + frames = frames(1:480, :, :, :); + end + + %If the number of columns is greater than 640, then reduce it to 640. + if (size(frames, 2) > 640) + frames = frames(:, 1:640, :, :); + end + + for i = 1:size(frames, 4) + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 1:100, 1, i) = 255; + %frames(1:100, 1:100, 2, i) = 0; + %frames(1:100, 1:100, 3, i) = 0; + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 101:200, 1, i) = 0; + %frames(1:100, 101:200, 2, i) = 255; + %frames(1:100, 101:200, 3, i) = 0; + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 201:300, 1, i) = 0; + %frames(1:100, 201:300, 2, i) = 0; + %frames(1:100, 201:300, 3, i) = 255; + end + + end + + writeVideo(vid_writer, frames); + +end + +%% This function saves all video frames +function StopVideo ( ) + + global vid; + global vid_writer; + + stop(vid); + + close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + elseif (strcmpi(event_type_string, 'Total Session Time')) + event_string = [event_type_string ' = ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Old versions/FearConditioning_V3_2_20170823.m b/Old versions/FearConditioning_V3_2_20170823.m new file mode 100644 index 0000000..0404451 --- /dev/null +++ b/Old versions/FearConditioning_V3_2_20170823.m @@ -0,0 +1,1231 @@ +function FearConditioning_V3_2 + + %Initialize the behavior session state to be 0 (not running) + global run; + run = 0; + + %Set the URL for the Google Docs stage spreadsheet. + %url = 'https://docs.google.com/spreadsheets/d/1dC5XCbhtVAIS2ZHhFVlgdRLpfU5lUbNUcoYgYCt-IzA/pub?output=tsv'; + url = 'https://docs.google.com/spreadsheets/d/1a5Nl9cHM6SL30kVFMivb3iiXoGOKciewjXocmwyJHIs/pub?output=tsv'; + + %Make the graphical user interface + handles = Make_GUI(); + + %Download the stage information from the Google Docs Spreadsheet. + stages = GetStageInfo(url); + if (isempty(stages)) + %If no stages were found, indicate as such on the start button. + set(handles.start_button, 'string', 'No stages found'); + else + %Populate the stage drop-down box + stage_descriptions = cell(1, length(stages)); + for i = 1:length(stages) + stage_descriptions(i) = cellstr(stages(i).description); + end + set(handles.stage_selection_box, 'string', stage_descriptions); + end + + %Set the stages in the handles structure + handles.stages = stages; + + %Connect to the arduino board + handles.ardy = FearConditioning_Connect; + + %Create some variables + handles.rat_name = ''; + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + handles.ardy_connected = ~isempty(handles.ardy); + handles.no_ardy_debug_mode = 0; + + %Initialize some variables and paths here where they'll be easier to find and change. + + %Set the primary local data path for saving data files. + handles.datapath = 'C:\AFC\'; + + %If the primary local data path doesn't already exist... + if ~exist(handles.datapath,'dir') + %Create the directory + mkdir(handles.datapath); + end + + %Set the secondary server data path for saving data files. + handles.serverpath = 'Z:\Konstanty_Behavior_Data\'; + + %If an arduino connection was created + if (handles.ardy_connected) + %Disable music on the arduino + handles.ardy.enable_music(0); + + %Set the name of the booth in the GUI + booth_num = handles.ardy.booth(); + set(handles.boothname, 'string', num2str(booth_num)); + + %Pause for 100ms to allow for initialization + pause(0.1); + else + set(handles.start_button, 'string', 'Not connected'); + end + + %Save the handles to the figure + guidata(handles.fig, handles); + +end + +%% This function executes when the Start button is pressed +function RunBehavior(handles) + + global run; + global vid; + + %Open a file to save the video + if (strcmpi(handles.record_video, 'on')) + BeginVideo(handles); + end + + %Get the amount of extra time to be appended to both the beginning and the end of the session + extra_time = handles.extra_time; + + %Get the sound number for the sound being used during this behavior + %session + sound_number = GetSoundNumber(handles.soundname); + + %Get the approximate duration of the sound being played during this + %session (multiply by 1000 for units of ms) + sound_duration = GetSoundDuration(handles.soundname) * 1000; + + %Get the VNS duration of this stage + vns_duration = handles.vns_duration; + + %Calculate the total number of sounds to play + total_sounds = handles.num_presounds + handles.num_sounds; + + %Decide on times to play each sound + sound_intervals = randi([handles.isimin handles.isimax], 1, total_sounds) + (extra_time / 1000); + + %Create a schedule to play each sound (multiply by 1000 to convert to units of ms) + sound_schedule = cumsum(sound_intervals) * 1000; + + %Create a schedule of END TIMES for each sound + sound_schedule_end_times = sound_schedule + sound_duration; + + %Now, let's choose which sounds to pair with shocks + which_sounds_to_pair = []; + + if (strcmpi(handles.shocktype, 'Random')) + %Randomly choose a set of sounds to pair with shocks + rand_perm_of_sounds = randperm(handles.num_sounds); + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sort(rand_perm_of_sounds(1:total_shocks)); + elseif (strcmpi(handles.shocktype, 'Front')) + %Pair the first N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(1:total_shocks); + elseif (strcmpi(handles.shocktype, 'Back')) + %Pair the last N sounds with shocks + sounds_array = 1:handles.num_sounds; + total_shocks = min(handles.num_shocks, handles.num_sounds); + which_sounds_to_pair = sounds_array(end-total_shocks+1:end); + end + + %Now choose actual times at which to play the sounds + timings_of_non_pre_sounds = sound_schedule(end-handles.num_sounds+1:end); + timings_of_paired_sounds = timings_of_non_pre_sounds(which_sounds_to_pair); + shock_schedule = nan(1, length(timings_of_paired_sounds)); + for t = 1:length(timings_of_paired_sounds) + shock_schedule(t) = timings_of_paired_sounds(t) + randi([handles.shockonsetmin handles.shockonsetmax]); + end + + %Now, let's calculate when to do VNS + vns_schedule = []; + which_sounds_to_pair_vns = []; + is_vns_on = ~strcmpi(handles.vns_type, 'Off'); + if (is_vns_on && handles.vns_stim_count > 0) + + if (strcmpi(handles.vns_type, 'Unpaired')) + + %Decide on times to deliver VNS + vns_intervals = randi([handles.isimin handles.isimax], 1, handles.vns_stim_count); + + %Create a schedule to deliver VNS (multiply by 1000 to convert to units of ms) + vns_schedule = cumsum(vns_intervals) * 1000; + + else + %Check to see if the stage specifies a "paired" form of VNS + if (strcmpi(handles.vns_type, 'Paired Front')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(1:total_stims); + elseif (strcmpi(handles.vns_type, 'Paired Back')) + sounds_array = 1:handles.num_sounds; + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sounds_array(end - total_stims+1:end); + elseif (strcmpi(handles.vns_type, 'Paired Random')) + rand_perm_of_sounds = randperm(handles.num_sounds); + total_stims = min(handles.vns_stim_count, handles.num_sounds); + which_sounds_to_pair_vns = sort(rand_per_of_sounds(1:total_stims)); + end + + timings_of_paired_vns_sounds = timings_of_non_pre_sounds(which_sounds_to_pair_vns); + vns_schedule = nan(1, length(timings_of_paired_vns_sounds)); + for t = 1:length(timings_of_paired_vns_sounds) + vns_schedule(t) = timings_of_paired_vns_sounds(t) + handles.vns_delay; + end + + end + + end + + %Start a timer + tic; + + %Plot the schedule to the GUI window + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, handles.vns_duration, extra_time, 0); + + %Create copies of the sound and shock schedule that we can manipulate + %during the session + sound_schedule_queue = sound_schedule; + sound_schedule_end_time_queue = sound_schedule_end_times; + shock_schedule_queue = shock_schedule; + vns_schedule_queue = vns_schedule; + + is_music_enabled = 0; + time_of_last_music_enable = 0; + is_sound_playing = 0; + current_sound_end_time = 0; + is_shock_occurring = 0; + current_shock_end_time = 0; + is_vns_occurring = 0; + vns_end_time = 0; + current_time = 0; + + extra_time_started = 0; + extra_time_done = 0; + extra_time_start = 0; + + %Open the data file + fid = WriteFileHeader(handles); + + %Loop while the session is running + while (run == 1) + + loop_start = toc; + + %Display image + %colorImage = getsnapshot(vid); + %grayImage = rgb2gray(colorImage); + %imshow(grayImage, 'Parent', handles.webcam_axes, 'Border', 'tight'); + + %Save this frame to the video + if (strcmpi(handles.record_video, 'on')) + WriteFrames(is_sound_playing, is_shock_occurring, is_vns_occurring, current_time); + end + + %If we are completely done playing sounds AND delivering shocks... + if (isempty(sound_schedule_queue) && isempty(shock_schedule_queue) && isempty(vns_schedule_queue) && ... + ~is_sound_playing && ~is_shock_occurring && ~is_vns_occurring) + + if (~extra_time_started) + extra_time_started = 1; + extra_time_start = current_time; + else + %Check to see if the extra time has been expended + extra_time_expended = current_time - extra_time_start; + if (extra_time_expended > handles.extra_time) + extra_time_done = 1; + end + + if (extra_time_done) + %Then set the run-state to be 0. We are finished here. + run = 0; + end + end + + + end + + %Get the current time from the timer (and multiply by 1000 to get + %it in units of ms) + current_time = toc * 1000; + %disp(current_time); + + %Disable sound if it has been enabled for over 200 ms since a sound + %being started (this does not affect sounds currently playing) + if (is_music_enabled && ((current_time - time_of_last_music_enable) > 200)) + if (~handles.no_ardy_debug_mode) + handles.ardy.enable_music(0); + end + is_music_enabled = 0; + end + + %Check to see if a sound is happening right now. + if (is_sound_playing) + %If the sound has expired, toggle the GUI + if (current_time >= current_sound_end_time) + is_sound_playing = 0; + ToggleSoundTextBlockColor(handles, is_sound_playing); + if (~handles.no_ardy_debug_mode) + handles.ardy.music_led_disable(); + end + end + end + + %Check to see if a shock is happening right now + if (is_shock_occurring) + if (current_time >= current_shock_end_time) + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(0); + end + is_shock_occurring = 0; + ToggleShockTextBlockColor(handles, is_shock_occurring); + end + end + + %Check to see if VNS is occurring right now + if (is_vns_occurring) + if (current_time >= vns_end_time) + is_vns_occurring = 0; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + if (~handles.no_ardy_debug_mode) + handles.ardy.VNS_led_disable(); + end + end + end + + %Check to see if it is time to play a new sound + if (~isempty(sound_schedule_queue)) + if (current_time >= sound_schedule_queue(1)) + %Play a sound + if (~handles.no_ardy_debug_mode) + handles.ardy.select_tone(sound_number); + handles.ardy.enable_music(1); + end + + is_music_enabled = 1; + time_of_last_music_enable = current_time; + + %Toggle the text box in the GUI that shows sound is playing + is_sound_playing = 1; + current_sound_end_time = sound_schedule_end_time_queue(1); + ToggleSoundTextBlockColor(handles, is_sound_playing); + WriteSessionEvent(fid, 'Sound', handles.soundname); + + %Dequeue the first element of the list + sound_schedule_queue(1) = []; + sound_schedule_end_time_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new shock + if (~isempty(shock_schedule_queue)) + if (current_time >= shock_schedule_queue(1)) + %Deliver the shock + if (~handles.no_ardy_debug_mode) + handles.ardy.shock_enable(1); + end + is_shock_occurring = 1; + current_shock_end_time = current_time + handles.shockdur; + ToggleShockTextBlockColor(handles, is_shock_occurring); + WriteSessionEvent(fid, 'Shock', ''); + + %Dequeue the first element of the list + shock_schedule_queue(1) = []; + end + end + + %Check to see if it is time to deliver a new VNS stim + if (~isempty(vns_schedule_queue)) + if (current_time >= vns_schedule_queue(1)) + %Deliver the VNS + if (~handles.no_ardy_debug_mode) + handles.ardy.vns_enable(); + end + + is_vns_occurring = 1; + vns_end_time = current_time + handles.vns_duration; + ToggleVNSTextBlockColor(handles, is_vns_occurring); + WriteSessionEvent(fid, 'VNS', ''); + + %Dequeue the first element of the list + vns_schedule_queue(1) = []; + end + end + + %Plot the session as it currently is + PlotSchedule(handles.session_axes, shock_schedule, handles.shockdur, sound_schedule, sound_duration, vns_schedule, handles.vns_duration, extra_time, current_time); + + loop_end = toc; + + loop_difference = loop_end - loop_start; + disp(num2str(loop_difference)); + + %Pause momentarily (33 ms) so we don't hog the processor + pause(0.033); + + end + + %Close the video file for writing + if (strcmpi(handles.record_video, 'on')) + StopVideo(); + end + + %Output the total number of seconds that has elapsed during this session. + WriteSessionEvent(fid, 'Total Session Time', num2str(current_time / 1000)); + + %Close the data file + fclose(fid); + +end + +%% This function is called when the edit rat text box is modified +function EditRat(hObject, eventdata) + + handles = guidata(hObject); + + temp_rat_name = get(handles.rat_edit_box, 'string'); + + %Step through all reserved characters. + for c = '/\?%*:|"<>. ' + %Kick out any reserved characters from the rat name. + temp_rat_name(temp_rat_name == c) = []; + end + + %If the rat's name was changed. + if (~strcmpi(temp_rat_name, handles.rat_name)) + %Save the new rat_name + handles.rat_name = upper(temp_rat_name); + + %Display in the Matlab command window that the rat name has been + %changed + disp([datestr(now,13) ' - Current rat is ' handles.rat_name '.']); + end + + %Set a flag indicating the the rat name has been changed + handles.require_rat_name_change = 0; + SetStartButtonState(handles); + + %Change the name in the GUI + set(handles.rat_edit_box, 'string', handles.rat_name) + + %Save the handles + guidata(handles.fig, handles); + +end + +%% This function is called when you edit the stage +function EditStage(hObject, eventdata) + + handles = guidata(hObject); + + %Get the selected stage index and name + temp = get(handles.stage_selection_box, 'Value'); + temp_str = get(handles.stage_selection_box, 'String'); + temp_str = temp_str{temp}; + + %Now set all variables to selected stage variables + x = handles.stages(temp); + y = fields(handles.stages); + + %Loop through the fields of stage struct + for j = 1:length(y) + handles.(y{j}) = handles.stages(temp).(y{j}); + end + + %Display a message in the Matlab editor indicating that the stage has + %been changed + disp([datestr(now,13) ' - Current stage is ' temp_str '.']); + + %Set a flag indicating the the stage has been changed + handles.require_rat_stage_change = 0; + SetStartButtonState(handles); + + guidata(handles.fig, handles); + +end + +%% This function is called when the start / stop button is pressed +function StartButton(hObject, eventdata) + + global run; + handles = guidata(hObject); + + if run == 0 + + %Verify that we can actually start the behavior session. + go = VerifyStart(handles); + + %If everything looks good, start behavior + if (go) + %Set the text of the button to say "Stop", and change the color to + %red + set(handles.start_button, 'string', 'Stop'); + set(handles.start_button, 'foregroundcolor', [1 0 0]); + + %Disable editing of the rat and stage while the session is running + set(handles.rat_edit_box, 'enable', 'off'); + set(handles.stage_selection_box, 'enable', 'off'); + + %Set the run state + run = 1; + + %Clear the session axes for the upcoming session + cla(handles.session_axes); + hold(handles.session_axes, 'off'); + + %Run the behavior program + RunBehavior(handles); + + %Edit the session axes title to indicate the session has finished + session_axes_title = get(handles.session_axes, 'title'); + title_text = session_axes_title.String; + title(handles.session_axes, ['SESSION ENDED --- ' title_text], 'Color', [1 0 0]); + + end + + end + + % The rest of this code gets executed under 2 conditions: + % (1) The "Stop" button is pressed + % (2) The session finishes due to reaching the end of its sound/shock + % schedule. + + %If the user clicked "Stop", change the text to say "Start", and + %the color to be green + set(handles.start_button, 'string', 'Start'); + set(handles.start_button, 'foregroundcolor', [0 0.7 0]); + + %Allow the rat name and stage to be edited again + set(handles.rat_edit_box, 'enable', 'on'); + set(handles.stage_selection_box, 'enable', 'on'); + + %Set the flags indicating that the rat name and stage must be + %changed + handles.require_rat_name_change = 1; + handles.require_rat_stage_change = 1; + + %Set the start button state + SetStartButtonState(handles); + + %Set the run state to 0, indicating that the session has stopped. + run = 0; + + %Save the handles structure + guidata(handles.fig, handles); + +end + +%% This function makes our GUI +function handles = Make_GUI(handles) + + set(0,'units','centimeters'); + pos = get(0,'screensize'); + h = 22;%0.8*pos(4); + w = 4*h/3; + + figure_color = [1 1 1]; + + %Create the main figure window + handles.fig = figure(... + 'name', 'Auditory Fear Conditioning', ... + 'units', 'centimeters', ... + 'Position', [pos(3)/2-w/2, pos(4)/2-h/2, w*0.7, h*0.7], ... + 'Color', figure_color, ... + 'Menubar', 'none', ... + 'Resize', 'off'); + + %Create the primary vertical stack panel for this figure + primary_panel = uix.VBox( ... + 'parent', handles.fig, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Place a text block at the top with the title of the program + handles.programlabel = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'text', ... + 'string', 'Arduino Fear Conditioning V3.2', ... + 'fontweight', 'bold', ... + 'fontsize', 18, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color, ... + 'foregroundcolor', [0 0 0]); + + %Create a one-row grid for the rat name and booth name + ui_stack_panel = uix.HBox('Parent', primary_panel, ... + 'BackgroundColor', figure_color, ... + 'Spacing', 20, ... + 'Units', 'normalized', ... + 'Spacing', 0.05); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Create labels and text boxes for the rat name and booth name + %Rat name label and text box + handles.ratlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Rat:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.rat_edit_box = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'edit', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16); + + %Booth name label and edit box + handles.boothlabel = uicontrol( ... + 'parent', ui_stack_panel, ... + 'style', 'text', ... + 'string', 'Booth:', ... + 'units', 'normalized', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', get(handles.fig, 'color')); + handles.boothname = uicontrol( ... + 'parent', ui_stack_panel, ... + 'Style', 'text', ... + 'String', '', ... + 'units', 'normalized', ... + 'fontsize', 16, ... + 'backgroundcolor', figure_color); + + uicontrol('parent', ui_stack_panel, 'visible', 'off'); + + %Set the width of each element in the rat/booth stack panel + set(ui_stack_panel, 'Widths', [-0.5 -1 -1.5 -1 -1.5 -0.5]); + + %Create a drop-down box for the stage selection + stage_selection_stack_panel = uix.HBox('parent', primary_panel, ... + 'BackgroundColor', figure_color); + + handles.stagelabel = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'style', 'text', ... + 'string', 'Stage:', ... + 'fontweight', 'bold', ... + 'fontsize', 20, ... + 'horizontalalignment', 'center', ... + 'backgroundcolor', figure_color); + + handles.stage_selection_box = uicontrol( ... + 'parent', stage_selection_stack_panel, ... + 'Style', 'popupmenu', ... + 'String', 'No stages', ... + 'units', 'normalized', ... + 'Value', 1, ... + 'fontsize', 16); + + set(stage_selection_stack_panel, 'Widths', [-1 -4]); + + %Create the start button + handles.start_button = uicontrol( ... + 'parent', primary_panel, ... + 'style', 'pushbutton', ... + 'string', 'Start', ... + 'horizontalalignment', 'center', ... + 'fontsize', 32, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'enable', 'off'); + + %Create text areas that will be used to indicate when VNS, tones, and + %shocks are happening + + vns_tone_shock_stack_panel = uix.HBox('parent', primary_panel, 'Spacing', 10); + + handles.vns_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text',... + 'string', 'VNS',... + 'horizontalalignment', 'center',... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + handles.tone_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Sound', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 .1]); + handles.shock_state_text = uicontrol( ... + 'parent', vns_tone_shock_stack_panel, ... + 'style', 'text', ... + 'string', 'Shock', ... + 'horizontalalignment', 'center', ... + 'fontsize', 24, ... + 'foregroundcolor', [0 0.7 0], ... + 'fontweight', 'bold', ... + 'backgroundcolor', [0.1 0.1 0.1]); + + set(vns_tone_shock_stack_panel, 'Widths', [-1 -1 -1]); + + axes_horizontal_panel = uix.HBox( ... + 'parent', primary_panel, ... + 'Spacing', 10, ... + 'Padding', 10, ... + 'BackgroundColor', figure_color); + + %Create a webcam image slot + %handles.webcam_axes = axes('parent', axes_horizontal_panel); + %handles.webcam_image = image(zeros(640, 480, 3), 'Parent', handles.webcam_axes); + %set(handles.webcam_axes, 'Box', 'off'); + + %Create a session plot + handles.session_axes = axes('parent', axes_horizontal_panel); + set(handles.session_axes, 'YLim', [0.5 3.5]); + set(handles.session_axes, 'YTick', [1 2 3]); + set(handles.session_axes, 'YTickLabel', {'Shocks', 'Sounds', 'VNS'}); + + %Set the height of the axes panel + %set(axes_horizontal_panel, 'Widths', [-1 -2]); + + %Set the heights of each element of the primary vertical stack panel layout + set(primary_panel, 'Heights', [30 30 40 80 40 200]); + + %Set callback functions for the start button, the rat edit box, and the + %stage selection box + set(handles.start_button, 'callback', @StartButton); + set(handles.rat_edit_box, 'callback', @EditRat); + set(handles.stage_selection_box, 'callback', @EditStage); + + %Save all of the panels for future use + handles.panels = [primary_panel ui_stack_panel stage_selection_stack_panel vns_tone_shock_stack_panel]; + +end + +%% This function reads in the stages information for the Google Docs stages spreadsheet. +function stages = GetStageInfo(url) + + %Initialize urldata to be an empty array + urldata = []; + stages = []; + + %Try to read in the stages information from the web. + try + + %Load the stage information from the google spreadsheet + urldata = Read_Google_Spreadsheet_Lindsey(url); + + %Save a backup of the stage information to a local file + try + Save_Local_Spreadsheet(urldata, 'fear_stages.txt'); + catch err3 + disp('Was not able to save backup stage spreadsheet!'); + end + + catch err + %If there's an error, first tell the user + disp('Could not read Google Spreadsheet!'); + + %Attempt to read the local backup spreadsheet + try + backup_spreadsheet = 'fear_stages.txt'; + urldata = Read_Local_Spreadsheet(backup_spreadsheet); + catch err2 + %Warn the user about the error + disp('Could not read the local spreadsheet!'); + + %And then return from the function + return; + end + end + + %List the column headings with their associated stages structure fields. + fields = {'stage','number';... + 'description','description';... + 'Program Type','program';... + 'Sound Name 1', 'soundname';... + 'Sound Name 2', 'soundnameb';... + 'Number of Presounds','num_presounds';... + 'number of sounds','num_sounds';... + 'inter-sound interval minimum (seconds)','isimin';... + 'inter-sound interval maximum (seconds)','isimax';... + 'number of shocks','num_shocks';... + 'shock duration (ms)','shockdur';... + 'Shock Delivery Type', 'shocktype'; ... + 'Shock Onset Minimum (ms)', 'shockonsetmin';... + 'Shock Onset Maximum (ms)', 'shockonsetmax';... + 'Number of VNS Stims','vns_stim_count'; ... + 'Type of VNS', 'vns_type'; ... + 'VNS Delay (ms)', 'vns_delay'; ... + 'VNS Duration', 'vns_duration'; ... + 'Record Video', 'record_video'; ... + 'Extra Time', 'extra_time' ... + }; + + %Step through each column heading. + for c = 1:size(fields,1) + + %Find the column index for this column heading. + a = strncmpi(fields{c,1},urldata(1,:),length(fields{c,1})); + + %Step through each listed stages. + for i = 2:size(urldata,1) + %Grab the entry for this stages. + temp = urldata{i,a}; + + %Kick out any apostrophes in the entry. + temp(temp == 39) = []; + + %If there's any text characters in the entry... + if any(temp > 59) + %Save the field value as a string. + stages(i-1).(fields{c,2}) = temp; + else + %Otherwise, if there's no text characters in the entry. + %Evaluate the entry and save the field value as a number. + stages(i-1).(fields{c,2}) = str2double(temp); + end + end + end + + %Step through the stagess. + for i = 1:length(stages) + %Add the stage number to the stage description. + stages(i).description = [stages(i).number ': ' stages(i).description]; + end + +end + +%% Enable/Disable the start button as necessary +function SetStartButtonState ( handles ) + + if (~handles.require_rat_name_change && ~handles.require_rat_stage_change && ... + ~isempty(handles.stages)) + if (handles.no_ardy_debug_mode || handles.ardy_connected) + set(handles.start_button, 'enable', 'on'); + end + else + set(handles.start_button, 'enable', 'off'); + end + +end + +%% Checks to see if the sound file selected is valid +function valid_sound = IsValidSoundFile ( sound_name ) + + if (any(strcmpi(sound_name, {'war zone (30 seconds)', 'gunfire', 'twitter', '9khz', '4khz', '2khz10s', '9khz10s'}))) + valid_sound = 1; + else + valid_sound = 0; + end + +end + +%% Gets the number associated with a sound file +function sound_number = GetSoundNumber ( sound_name ) + sound_number = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_number = 6; + elseif (strcmpi(sound_name, 'gunfire')) + sound_number = 0; + elseif (strcmpi(sound_name, 'twitter')) + sound_number = 1; + elseif (strcmpi(sound_name, '9khz')) + sound_number = 2; + elseif (strcmpi(sound_name, '4khz')) + sound_number = 3; + elseif (strcmpi(sound_name, '2khz10s')) + sound_number = 4; + elseif (strcmpi(sound_name, '9khz10s')) + sound_number = 5; + end +end + +%% Gets the duration (in units of seconds) of a sound +function sound_duration = GetSoundDuration ( sound_name ) + + %Declare the constants that are the durations for each sound + sound_duration = 0; + if (strcmpi(sound_name, 'war zone (30 seconds)')) + sound_duration = 30; + elseif (strcmpi(sound_name, 'gunfire')) + sound_duration = 6; + elseif (strcmpi(sound_name, 'twitter')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '4khz')) + sound_duration = 6; + elseif (strcmpi(sound_name, '2khz10s')) + sound_duration = 6; + elseif (strcmpi(sound_name, '9khz10s')) + sound_duration = 6; + end + +end + +%% This function verifies some parameters before the behavior session is allowed to start +function go = VerifyStart ( handles ) + + %"Go" by default is a 1 + go = 1; + + %Make sure the sound name for the selected stage is valid + if (~IsValidSoundFile(handles.soundname)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + + %If a second sound is being used... + if (~isnan(handles.soundnameb)) + %Make sure the second sound name is also valid + if (~IsValidSoundFile(handles.soundnameb)) + disp('Please select correct sound type on stage spreadsheet!'); + go = 0; + end + end + + %Display some question dialogs to the user. + %This will force undergrads to confirm the stage and the rat names. + qstring = ['Are you sure animal ' handles.rat_name ' on stage ' handles.description '?']; + choice = questdlg(qstring, 'Confirm Rat and Stage', 'Yes', 'No', 'No'); + if strcmpi(choice, 'No') + go = 0; + end + + if (go == 1) + disp('Beginning behavior session'); + else + disp('Problems have been encountered that must be checked before behavior can begin.'); + end + +end + +%% Toggles the background color of the "tone" text block +function ToggleSoundTextBlockColor ( handles, is_sound_playing ) + + if (is_sound_playing) + set(handles.tone_state_text, 'foregroundcolor', [1 0 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.tone_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.tone_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "shock" text block +function ToggleShockTextBlockColor ( handles, is_shock_happening ) + + if (is_shock_happening) + set(handles.shock_state_text, 'foregroundcolor', [1 0 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.shock_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.shock_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Toggles the background color of the "vns" text block +function ToggleVNSTextBlockColor ( handles, is_vns_happening ) + + if (is_vns_happening) + set(handles.vns_state_text, 'foregroundcolor', [1 0 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0.7 0]); + else + set(handles.vns_state_text, 'foregroundcolor', [0 0.7 0]); + set(handles.vns_state_text, 'backgroundcolor', [0 0 0]); + end + +end + +%% Plots the shock and sounds schedule to the session axes +function PlotSchedule ( session_axes, shock_schedule, shock_duration, sound_schedule, sound_duration, vns_schedule, vns_duration, extra_time, current_time ) + + %Convert everything to minutes + current_time = current_time ./ (1000 * 60); + shock_schedule = shock_schedule ./ (1000 * 60); + shock_duration = shock_duration ./ (1000 * 60); + sound_schedule = sound_schedule ./ (1000 * 60); + sound_duration = sound_duration ./ (1000 * 60); + vns_schedule = vns_schedule ./ (1000 * 60); + vns_duration = vns_duration ./ (1000 * 60); + extra_time = extra_time ./ (1000 * 60); + + %Set the session axes as the current axis + axes(session_axes); + + %Clear the session axes + cla(session_axes); + + %Hold the session axes + hold(session_axes, 'on'); + + %Plot each shock + for i = 1:length(shock_schedule) + x1 = shock_schedule(i); + x2 = shock_schedule(i) + shock_duration; + line([x1 x2], [1 1], 'Color', [0 0.7 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0.7 0], 'MarkerSize', 2); + end + + %Plot each sound + for i = 1:length(sound_schedule) + x1 = sound_schedule(i); + x2 = sound_schedule(i) + sound_duration; + line([x1 x2], [2 2], 'Color', [0 0 1], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [0 0 1], 'MarkerSize', 2); + end + + %Plot each VNS + for i = 1:length(vns_schedule) + x1 = vns_schedule(i); + x2 = vns_schedule(i) + vns_duration; + line([x1 x2], [3 3], 'Color', [1 0 0], 'LineWidth', 2, 'LineStyle', '-', 'Marker', 'o', 'MarkerFaceColor', [1 0 0], 'MarkerSize', 2); + end + + %Plot a line indicating where we currently are in the session + line([current_time current_time], ylim, 'LineWidth', 1, 'LineStyle', '--', 'Marker', 'none', 'Color', [0 0 1]); + + %Calculate the x-axis limits + max_xlim = max([(shock_schedule + shock_duration) (sound_schedule + sound_duration) (vns_schedule + vns_duration)]) + extra_time; + if (isempty(max_xlim)) + max_xlim = 1; + end + set(session_axes, 'XLim', [0 max_xlim]); + xlabel('Time (min)'); + + title_string = ['Session time: ' datestr(current_time/(24*60), 'HH:MM:SS')]; + title(session_axes, title_string, 'Color', [0 0 0]); + +end + +%% This function initializes the video frames array to be empty +function BeginVideo ( handles ) + + global vid; + global vid_writer; + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.mp4']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create a webcam object that saves video to disk + vid = videoinput('winvideo', 1, 'RGB24_640x480'); + set(vid, 'FramesPerTrigger', Inf); + set(vid, 'LoggingMode', 'memory'); + %set(vid, 'LoggingMode', 'disk'); + %set(vid, 'DiskLogger', VideoWriter([path_name '\' file_name], 'MPEG-4')); + + %Start saving data + vid_writer = VideoWriter([path_name '\' file_name], 'MPEG-4'); + open(vid_writer); + + preview(vid, handles.webcam_image); + + %Start the camera recording + start(vid); + +end + +%% This function writes frames to a file +function WriteFrames (is_sound_playing, is_shock_occurring, is_vns_occurring, current_time) + + global vid; + global vid_writer; + + [frames, times] = getdata(vid, get(vid, 'FramesAvailable')); + + %Return if no frames need to be processed + if (isempty(frames)) + return; + end + + %Return if the number of rows (Y-axis) is less than 480 + if (size(frames, 1) < 480) + return; + end + + %Return if the number of columns (X-axis) is less than 640 + if (size(frames, 2) < 640) + return; + end + + %If the number of rows is greater than 480, then reduce it to 480. + if (size(frames, 1) > 480) + frames = frames(1:480, :, :, :); + end + + %If the number of columns is greater than 640, then reduce it to 640. + if (size(frames, 2) > 640) + frames = frames(:, 1:640, :, :); + end + + for i = 1:size(frames, 4) + + if (is_vns_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [1 1], 'VNS', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 1:100, 1, i) = 255; + %frames(1:100, 1:100, 2, i) = 0; + %frames(1:100, 1:100, 3, i) = 0; + end + + if (is_sound_playing) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [100 1], 'Sound', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 101:200, 1, i) = 0; + %frames(1:100, 101:200, 2, i) = 255; + %frames(1:100, 101:200, 3, i) = 0; + end + + if (is_shock_occurring) + frames(:, :, :, i) = insertText(frames(:, :, :, i), [200 1], 'Shock', 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + %frames(1:100, 201:300, 1, i) = 0; + %frames(1:100, 201:300, 2, i) = 0; + %frames(1:100, 201:300, 3, i) = 255; + end + + frames(:, :, :, i) = insertText(frames(:, :, :, i), [300 1], num2str(current_time), 'FontSize', 24, 'BoxColor', 'green', 'BoxOpacity', 0.4, 'TextColor', 'white'); + + end + + writeVideo(vid_writer, frames); + +end + +%% This function saves all video frames +function StopVideo ( ) + + global vid; + global vid_writer; + + closepreview(vid); + + stop(vid); + + close(vid_writer); + +end + +%% Writes a file header for the saved data file +function fid = WriteFileHeader ( handles ) + + %Create a file name + session_date = now; + date_string = datestr(session_date, 'YYYYmmDD_HHMMSS'); + file_name = [handles.rat_name '_' date_string '.ArdyFear']; + path_name = [handles.datapath '\' handles.rat_name '\' handles.number]; + + %Create the path if it doesn't exist + if (~exist(path_name, 'dir')) + mkdir(path_name); + end + + %Create/Open file for writing + fid = fopen([path_name '\' file_name], 'wt'); + + %Write the file header + fprintf(fid, 'Rat Name: %s\n', handles.rat_name); + fprintf(fid, 'Stage Name: %s\n', handles.number); + fprintf(fid, 'Date: %s\n', datestr(session_date)); + +end + +%% Writes an event that occurred in the session out to the data file +function WriteSessionEvent ( fid, event_type_string, sound_name_string ) + + event_time = now; + event_string = [datestr(event_time) ' - ' event_type_string]; + if (strcmpi(event_type_string, 'Sound')) + event_string = [event_string ', ' sound_name_string]; + elseif (strcmpi(event_type_string, 'Total Session Time')) + event_string = [event_type_string ' = ' sound_name_string]; + end + event_string = [event_string '\n']; + + fprintf(fid, event_string); + +end + +%% This function is meant to change the background colors of all UI panels and the main figure +function ChangeFigureColor ( handles, new_color ) + + for i = 1:length(handles.panels) + set(handles.panels(i), 'backgroundcolor', new_color); + end + + set(handles.fig, 'Color', new_color); + +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Read_Google_Spreadsheet_Lindsey.m b/Read_Google_Spreadsheet_Lindsey.m new file mode 100644 index 0000000..4aacc52 --- /dev/null +++ b/Read_Google_Spreadsheet_Lindsey.m @@ -0,0 +1,61 @@ +function [data, structure] = Read_Google_Spreadsheet_Lindsey(url) + +% +%Read_Google_Spreadsheet.m - Rennaker Lab, 2010 +% +% Read_Google_Spreadsheet reads in spreadsheet data from Google Documents +% spreadsheets and returns the data as a 2-D cell array. To use this +% function, you must first publish the document as a webpage with Plain +% Text (TXT) formatting. +% +% data = Read_Google_Spreadsheet(url) reads the spreadsheet data from the +% Google Document link specified by "url" and returns it in the cell +% array "data". +% +% Last updated February 9, 2012, by Drew Sloan. + +if ~any(strfind(url,'tsv')) %Check to make sure the Google Document URL link is text-formatted. + error('Links to Google Documents spreadsheets must be text-formated.'); +end + +urldata = urlread(url); %Read in the data from the Google spreadsheet as a string. + +%% Convert the single string output from urlread into a cell array corresponding to cells in the spreadsheet. +tab = sprintf('\t'); %Make a tab string for finding delimiters. +newline = sprintf('\n'); %Make a new-line string for finding new lines. +a = find(urldata == tab | urldata == newline); %Find all delimiters in the string. +a = [0, a, length(urldata)+1]; %Add indices for the first and last elements of the string. +urldata = [urldata, newline]; %Add a new line to the end of the string to avoid confusing the spreadsheet-reading loop. +column = 1; %Count across columns. +row = 1; %Count down rows. +data = {}; %Make a cell array to hold the spreadsheet-formated data. +for i = 2:length(a) %Step through each entry in the string. + if a(i) == a(i-1)+1 %If there is no entry for this cell... + data{row,column} = []; %...assign an empty matrix. + else %Otherwise... + data{row,column} = urldata((a(i-1)+1):(a(i)-1)); %...read one entry from the string. + end + if urldata(a(i)) == tab %If the delimiter was a tab... + column = column + 1; %...advance the column count. + else %Otherwise, if the delimiter was a new-line... + column = 1; %...reset the column count to 1... + row = row + 1; %...and add one to the row count. + end +end + +%% Make a numeric matrix converting every cell to a number. +checker = zeros(size(data,1),size(data,2)); %Pre-allocate a matrix to hold boolean is-numeric checks. +numdata = nan(size(data,1),size(data,2)); %Pre-allocate a matrix to hold the numeric data. +for i = 1:size(data,1) %Step through each row. + for j = 1:size(data,2) %Step through each column. + numdata(i,j) = str2double(data{i,j}); %Convert the cell contents to a double-precision number. + %If this cell's data is numeric, or if the cell is empty, or contains a placeholder like *, -, or NaN... + if ~isnan(numdata(i,j)) || isempty(data{i,j}) ||... + any(strcmpi(data{i,j},{'*','-','NaN'})) + checker(i,j) = 1; %Indicate that this cell has a numeric entry. + end + end +end +if all(checker(:)) %If all the cells have numeric entries... + data = numdata; %...save the data as a numeric matrix. +end diff --git a/Read_Local_Spreadsheet.m b/Read_Local_Spreadsheet.m new file mode 100644 index 0000000..b377a6b --- /dev/null +++ b/Read_Local_Spreadsheet.m @@ -0,0 +1,44 @@ +function data = Read_Local_Spreadsheet(url) + +fid = fopen(url, 'r'); +urldata = char(fread(fid))'; + +%% Convert the single string output from urlread into a cell array corresponding to cells in the spreadsheet. +tab = sprintf('\t'); %Make a tab string for finding delimiters. +newline = sprintf('\n'); %Make a new-line string for finding new lines. +a = find(urldata == tab | urldata == newline); %Find all delimiters in the string. +a = [0, a, length(urldata)+1]; %Add indices for the first and last elements of the string. +urldata = [urldata, newline]; %Add a new line to the end of the string to avoid confusing the spreadsheet-reading loop. +column = 1; %Count across columns. +row = 1; %Count down rows. +data = {}; %Make a cell array to hold the spreadsheet-formated data. +for i = 2:length(a) %Step through each entry in the string. + if a(i) == a(i-1)+1 %If there is no entry for this cell... + data{row,column} = []; %...assign an empty matrix. + else %Otherwise... + data{row,column} = urldata((a(i-1)+1):(a(i)-1)); %...read one entry from the string. + end + if urldata(a(i)) == tab %If the delimiter was a tab... + column = column + 1; %...advance the column count. + else %Otherwise, if the delimiter was a new-line... + column = 1; %...reset the column count to 1... + row = row + 1; %...and add one to the row count. + end +end + +%% Make a numeric matrix converting every cell to a number. +checker = zeros(size(data,1),size(data,2)); %Pre-allocate a matrix to hold boolean is-numeric checks. +numdata = nan(size(data,1),size(data,2)); %Pre-allocate a matrix to hold the numeric data. +for i = 1:size(data,1) %Step through each row. + for j = 1:size(data,2) %Step through each column. + numdata(i,j) = str2double(data{i,j}); %Convert the cell contents to a double-precision number. + %If this cell's data is numeric, or if the cell is empty, or contains a placeholder like *, -, or NaN... + if ~isnan(numdata(i,j)) || isempty(data{i,j}) ||... + any(strcmpi(data{i,j},{'*','-','NaN'})) + checker(i,j) = 1; %Indicate that this cell has a numeric entry. + end + end +end +if all(checker(:)) %If all the cells have numeric entries... + data = numdata; %...save the data as a numeric matrix. +end diff --git a/Save_Local_Spreadsheet.m b/Save_Local_Spreadsheet.m new file mode 100644 index 0000000..765ebb6 --- /dev/null +++ b/Save_Local_Spreadsheet.m @@ -0,0 +1,21 @@ +function Save_Local_Spreadsheet ( urldata, file_name ) + + %Open a text-formatted configuration file to save the stage information. + fid = fopen(file_name, 'wt'); + + for i = 1:size(urldata, 1) %Step through the rows of the stage data. + for j = 1:size(urldata, 2) %Step through the columns of the stage data. + fprintf(fid, '%s', urldata{i,j}); %Write each element of the stage data as tab-separated values. + if j < size(urldata, 2) %If this isn't the end of a row... + fprintf(fid, '\t'); %Write a tab to the file. + elseif i < size(urldata, 1) %Otherwise, if this isn't the last row... + fprintf(fid, '\n'); %Write a carriage return to the file. + end + end + end + + %Close the file + fclose(fid); + +end + diff --git a/fear_stages.txt b/fear_stages.txt new file mode 100644 index 0000000..d5ab6bf --- /dev/null +++ b/fear_stages.txt @@ -0,0 +1,30 @@ +Stage Description Program Type Sound Name 1 Sound Name 2 Number of Presounds Number of Sounds Inter-sound Interval Minimum (seconds) Inter-sound Interval Maximum (seconds) Number of Shocks Shock Duration (ms) Shock Delivery Type Shock Onset Minimum (ms) Shock Onset Maximum (ms) Number of VNS Stims Type of VNS VNS Delay (ms) VNS Duration Record Video Extra Time Stims Per Sound +NO-AFC NO SHOCK - 4 DAYS Rimenez war zone (30 seconds) 0 8 90 180 0 2000 Front 27950 28050 0 Off 0 30000 Off 30000 1 +AFC Day 1 8CS-US 100% PAIRED Rimenez war zone (30 seconds) 2 8 90 180 8 2000 Front 27950 28050 0 Off 0 30000 Off 30000 1 +AFC Day 2 8CS-US 50% PAIRED Rimenez war zone (30 seconds) 0 8 90 180 4 2000 Random 27950 28050 0 Off 0 30000 Off 30000 1 +AFC Day 3 SHORT TRACE CONDITIONING Rimenez war zone (30 seconds) 0 8 90 180 8 2000 Front 33000 42000 0 Off 0 30000 Off 30000 1 +AFC Day 4 LONG TRACE CONDITIONING Rimenez war zone (30 seconds) 0 8 90 180 8 2000 Front 33000 50000 0 Off 0 30000 Off 30000 1 +AFC Day 5 AVOIDANCE CONDITIONING Rimenez war zone (30 seconds) 0 2 60 120 2 2000 Front 27950 28050 0 Off 0 30000 Off 30000 1 + 1 +AFC REINSTATEMENT 1US - NO SOUND Rimenez 2khz10s 0 1 180 181 1 2000 Front 27950 28050 0 Off 0 30000 Off 60000 1 + 1 + 1 +THERAPY 30S VNS PAIRED VNS Rimenez war zone (30 seconds) 0 5 90 180 0 2000 Front 5000 8000 5 Paired Front -150 30000 Off 30000 1 +MULTIPLE VNS 4x PAIRED VNS 4X Rimenez war zone (30 seconds) 0 5 90 180 0 2000 Front 5000 8000 5 Paired Front -150 500 Off 30000 4 +MULTIPLE VNS 16x PAIRED VNS 16X Rimenez war zone (30 seconds) 0 5 90 180 0 2000 Front 5000 8000 5 Paired Front -150 500 Off 30000 16 +SHAM STIM NO VNS Rimenez war zone (30 seconds) 0 5 90 180 0 2000 Front 5000 8000 0 Paired Front 0 30000 Off 30000 1 +VNS ONLY NO SOUND Rimenez war zone (30 seconds) 0 0 90 180 0 2000 Back 5000 8000 5 Unpaired 0 30000 Off 30000 1 + 1 +SOUND/VNS CHECK 1CS+VNS Rimenez war zone (30 seconds) 0 1 10 15 0 2000 Front 5000 8000 1 Paired Front 0 30000 Off 30000 1 + 1 +SHOCK/SOUND TEST David Test Stage 3 David war zone (30 seconds) 0 1 10 15 1 2000 Random 5000 8000 0 Off 0 30000 Off 15000 1 +DAVID4 David Test Stage 4 - Paired VNS David war zone (30 seconds) 0 8 60 70 0 2000 Front 5000 8000 6 Paired Back 0 30000 Off 0 1 +DAVID5 David Test Stage 5 - Paired VNS Delayed David gunfire 0 8 10 15 0 2000 Front 5000 8000 6 Paired Front 2000 30000 Off 0 1 +DAVID6 David Test Stage 6 - Unpaired VNS David war zone (30 seconds) 0 3 45 60 0 2000 Front 5000 8000 6 Paired Front 0 2000 Off 0 6 + + + + +LINDSEY AFC AFC SEPARATED - SHOCK!! LINDSEY twitter 0 4 5 15 1 1000 Random 1000 2000 0 Off 0 0 Off 0 0 + +LINDSEY EXT SEPARATED - EXTINCTION LINDSEY twitter 0 4 5 15 0 1000 Random 1000 2000 0 Off 0 0 Off 0 0 \ No newline at end of file