From a650cf08e15538d970ee2c2b2656b1a3662aba46 Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Tue, 14 Jul 2015 14:28:50 -0700 Subject: [PATCH 001/115] replaced class_assoc with a port lookup --- pyoperant/behavior/two_alt_choice.py | 23 +++++++++++++---------- pyoperant/local_vogel.py | 6 +++--- pyoperant/local_zog.py | 6 +++--- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/pyoperant/behavior/two_alt_choice.py b/pyoperant/behavior/two_alt_choice.py index a77fd58c..f5a3d1a8 100755 --- a/pyoperant/behavior/two_alt_choice.py +++ b/pyoperant/behavior/two_alt_choice.py @@ -134,15 +134,18 @@ def session_pre(self): port through `experiment.class_assoc['L']`. """ - assert len(self.parameters['classes'])==2, 'does not currently support > 2 classes' - - self.class_assoc = {} + + self.response_ports = {} for class_, class_params in self.parameters['classes'].items(): try: - self.class_assoc[class_] = getattr(self.panel,class_params['component']) + port_name = class_params['component'] + port = getattr(self.panel,port_name) + self.response_ports.update({port_name:port}) except KeyError: pass + + return 'main' def session_main(self): @@ -427,7 +430,7 @@ def stimulus_post(self): #response flow def response_pre(self): - for class_, port in self.class_assoc.items(): + for port_name, port in self.response_ports.items() port.on() self.log.debug('waiting for response') @@ -441,13 +444,13 @@ def response_main(self): self.this_trial.response = 'none' self.log.info('no response') return - for class_, port in self.class_assoc.items(): + for port_name, port in self.response_ports.items() if port.status(): self.this_trial.rt = (dt.datetime.now() - response_start).total_seconds() self.panel.speaker.stop() - self.this_trial.response = class_ + self.this_trial.response = port_name self.summary['responses'] += 1 - response_event = utils.Event(name=self.parameters['classes'][class_]['component'], + response_event = utils.Event(name=port_name, label='peck', time=elapsed_time, ) @@ -457,7 +460,7 @@ def response_main(self): utils.wait(.015) def response_post(self): - for class_, port in self.class_assoc.items(): + for port_name, port in self.response_ports.items(): port.off() ## consequence flow @@ -466,7 +469,7 @@ def consequence_pre(self): def consequence_main(self): # correct trial - if self.this_trial.response==self.this_trial.class_: + if self.this_trial.response==self.parameters['classes'][self.this_trial.class_]['component']: self.this_trial.correct = True if self.parameters['reinforcement']['secondary']: diff --git a/pyoperant/local_vogel.py b/pyoperant/local_vogel.py index 1fe180a9..4179fafc 100644 --- a/pyoperant/local_vogel.py +++ b/pyoperant/local_vogel.py @@ -41,9 +41,9 @@ def __init__(self,id=None, *args, **kwargs): self.speaker = hwio.AudioOutput(interface=self.interfaces['pyaudio']) # assemble inputs into components - self.left = components.PeckPort(IR=self.inputs[0],LED=self.outputs[0],name='l') - self.center = components.PeckPort(IR=self.inputs[1],LED=self.outputs[1],name='c') - self.right = components.PeckPort(IR=self.inputs[2],LED=self.outputs[2],name='r') + self.left = components.PeckPort(IR=self.inputs[0],LED=self.outputs[0]) + self.center = components.PeckPort(IR=self.inputs[1],LED=self.outputs[1]) + self.right = components.PeckPort(IR=self.inputs[2],LED=self.outputs[2]) self.house_light = components.HouseLight(light=self.outputs[3]) self.hopper = components.Hopper(IR=self.inputs[3],solenoid=self.outputs[4]) diff --git a/pyoperant/local_zog.py b/pyoperant/local_zog.py index 67bfb715..c370a392 100644 --- a/pyoperant/local_zog.py +++ b/pyoperant/local_zog.py @@ -65,9 +65,9 @@ def __init__(self,id=None, *args, **kwargs): self.speaker = hwio.AudioOutput(interface=self.interfaces['pyaudio']) # assemble inputs into components - self.left = components.PeckPort(IR=self.inputs[0],LED=self.outputs[0],name='l') - self.center = components.PeckPort(IR=self.inputs[1],LED=self.outputs[1],name='c') - self.right = components.PeckPort(IR=self.inputs[2],LED=self.outputs[2],name='r') + self.left = components.PeckPort(IR=self.inputs[0],LED=self.outputs[0]) + self.center = components.PeckPort(IR=self.inputs[1],LED=self.outputs[1]) + self.right = components.PeckPort(IR=self.inputs[2],LED=self.outputs[2]) self.house_light = components.HouseLight(light=self.outputs[3]) self.hopper = components.Hopper(IR=self.inputs[3],solenoid=self.outputs[4]) From 69db8bcf0372f55f0a73018195814038b84c9a44 Mon Sep 17 00:00:00 2001 From: siriuslee Date: Fri, 11 Dec 2015 16:33:37 -0800 Subject: [PATCH 002/115] Implemented interface for using an Arduino as a DAQ. --- pyoperant/interfaces/arduino_.py | 231 ++++++++++++++++++++++++++ src/operant_serial/operant_serial.ino | 61 +++++++ 2 files changed, 292 insertions(+) create mode 100644 pyoperant/interfaces/arduino_.py create mode 100644 src/operant_serial/operant_serial.ino diff --git a/pyoperant/interfaces/arduino_.py b/pyoperant/interfaces/arduino_.py new file mode 100644 index 00000000..6e34d744 --- /dev/null +++ b/pyoperant/interfaces/arduino_.py @@ -0,0 +1,231 @@ +import time +import datetime +import serial +import logging +from pyoperant.interfaces import base_ +from pyoperant import utils, InterfaceError + +logger = logging.getLogger(__name__) + +# TODO: Smart find arduinos using something like this: http://stackoverflow.com/questions/19809867/how-to-check-if-serial-port-is-already-open-by-another-process-in-linux-using +# TODO: Attempt to reconnect device if it can't be reached +# TODO: Allow device to be connected to through multiple python instances. This kind of works but needs to be tested thoroughly. + +class ArduinoInterface(base_.BaseInterface): + """Creates a pyserial interface to communicate with an Arduino via the serial connection. + Communication is through two byte messages where the first byte specifies the channel and the second byte specifies the action. + Valid actions are: + 0. Read input value + 1. Set output to ON + 2. Set output to OFF + 3. Sets channel as an output + 4. Sets channel as an input + 5. Sets channel as an input with a pullup resistor (basically inverts the input values) + :param device_name: The address of the device on the local system (e.g. /dev/tty.usbserial) + :param baud_rate: The baud (bits/second) rate for serial communication. If this is changed, then it also needs to be changed in the arduino project code. + """ + + _default_state = dict(invert=False, + held=False, + ) + + def __init__(self, device_name, baud_rate=19200, inputs=None, outputs=None, *args, **kwargs): + + super(ArduinoInterface, self).__init__(*args, **kwargs) + + self.device_name = device_name + self.baud_rate = baud_rate + self.device = None + + self.read_params = ('channel', 'pullup') + self._state = dict() + self.inputs = [] + self.outputs = [] + + self.open() + if inputs is not None: + for input_ in inputs: + self._config_read(*input_) + if outputs is not None: + for output in outputs: + self._config_write(output) + + def __str__(self): + + return "Arduino device at %s: %d input channels and %d output channels configured" % (self.device_name, len(self.inputs), len(self.outputs)) + + def __repr__(self): + # Add inputs and outputs to this + return "ArduinoInterface(%s, baud_rate=%d)" % (self.device_name, self.baud_rate) + + def open(self): + '''Open a serial connection for the device + :return: None + ''' + + logger.debug("Opening device %s" % self) + self.device = serial.Serial(port=self.device_name, + baudrate=self.baud_rate, + timeout=5) + if self.device is None: + raise InterfaceError('Could not open serial device %s' % self.device_name) + + logger.debug("Waiting for device to open") + self.device.readline() + self.device.flushInput() + logger.info("Successfully opened device %s" % self) + + def close(self): + '''Close a serial connection for the device + :return: None + ''' + + logger.debug("Closing %s" % self) + self.device.close() + + def _config_read(self, channel, pullup=False, **kwargs): + ''' Configure the channel to act as an input + :param channel: the channel number to configure + :param pullup: the channel should be configured in pullup mode. On the arduino this has the effect of + returning HIGH when unpressed and LOW when pressed. The returned value will have to be inverted. + :return: None + ''' + + logger.debug("Configuring %s, channel %d as input" % (self.device_name, channel)) + if pullup is False: + self.device.write(self._make_arg(channel, 4)) + else: + self.device.write(self._make_arg(channel, 5)) + + if channel in self.outputs: + self.outputs.remove(channel) + if channel not in self.inputs: + self.inputs.append(channel) + + self._state.setdefault(channel, self._default_state.copy()) + self._state[channel]["invert"] = pullup + + def _config_write(self, channel, **kwargs): + ''' Configure the channel to act as an output + :param channel: the channel number to configure + :return: None + ''' + + logger.debug("Configuring %s, channel %d as output" % (self.device_name, channel)) + self.device.write(self._make_arg(channel, 3)) + if channel in self.inputs: + self.inputs.remove(channel) + if channel not in self.outputs: + self.outputs.append(channel) + self._state.setdefault(channel, self._default_state.copy()) + + def _read_bool(self, channel, **kwargs): + ''' Read a value from the specified channel + :param channel: the channel from which to read + :return: value + + Raises + ------ + ArduinoException + Reading from the device failed. + ''' + + if channel not in self._state: + raise InterfaceError("Channel %d is not configured on device %s" % (channel, self.device_name)) + + if self.device.inWaiting() > 0: # There is currently data in the input buffer + self.device.flushInput() + self.device.write(self._make_arg(channel, 0)) + # Also need to make sure self.device.read() returns something that ord can work with. Possibly except TypeError + while True: + try: + v = ord(self.device.read()) + break + # serial.SerialException("Testing") + except serial.SerialException: + # This is to make it robust in case it accidentally disconnects or you try to access the arduino in + # multiple ways + pass + except TypeError: + ArduinoException("Could not read from arduino device") + + logger.debug("Read value of %d from channel %d on %s" % (v, channel, self)) + if v in [0, 1]: + if self._state[channel]["invert"]: + v = 1 - v + return v == 1 + else: + logger.error("Device %s returned unexpected value of %d on reading channel %d" % (self, v, channel)) + # raise InterfaceError('Could not read from serial device "%s", channel %d' % (self.device, channel)) + + def _poll(self, channel, timeout=None, wait=None, suppress_longpress=True, **kwargs): + """ runs a loop, querying for pecks. returns peck time or None if polling times out + :param channel: the channel from which to read + :param timeout: the time, in seconds, until polling times out. Defaults to no timeout. + :param wait: the time, in seconds, between subsequent reads. Defaults to 0. + :param suppress_longpress: only return a successful read if the previous read was False. This can be helpful when using a button, where a press might trigger multiple times. + + :return: timestamp of True read + """ + + if timeout is not None: + start = time.time() + + logger.debug("Begin polling from device %s" % self.device_name) + while True: + if not self._read_bool(channel): + logger.debug("Polling: %s" % False) + # Read returned False. If the channel was previously "held" then that flag is removed + if self._state[channel]["held"]: + self._state[channel]["held"] = False + else: + logger.debug("Polling: %s" % True) + # As long as the channel is not currently held, or longpresses are not being supressed, register the press + if (not self._state[channel]["held"]) or (not suppress_longpress): + break + + if timeout is not None: + if time.time() - start >= timeout: # Return GoodNite exception? + logger.debug("Polling timed out. Returning") + return None + + # Wait for a specified amount of time before continuing on with the next loop + if wait is not None: + utils.wait(wait) + + self._state[channel]["held"] = True + logger.debug("Input detected. Returning") + return datetime.datetime.now() + + def _write_bool(self, channel, value, **kwargs): + '''Write a value to the specified channel + :param channel: the channel to write to + :param value: the value to write + :return: value written if succeeded + ''' + + if channel not in self._state: + raise InterfaceError("Channel %d is not configured on device %s" % (channel, self)) + + logger.debug("Writing %s to device %s, channel %d" % (value, self, channel)) + if value: + s = self.device.write(self._make_arg(channel, 1)) + else: + s = self.device.write(self._make_arg(channel, 2)) + if s: + return value + else: + raise InterfaceError('Could not write to serial device %s, channel %d' % (self.device, channel)) + + @staticmethod + def _make_arg(channel, value): + """ Turns a channel and boolean value into a 2 byte hex string to be fed to the arduino + :return: 2-byte hex string for input to arduino + """ + + return "".join([chr(channel), chr(value)]) + + +class ArduinoException(Exception): + + pass diff --git a/src/operant_serial/operant_serial.ino b/src/operant_serial/operant_serial.ino new file mode 100644 index 00000000..55794383 --- /dev/null +++ b/src/operant_serial/operant_serial.ino @@ -0,0 +1,61 @@ + +int baudRate = 19200; // 9600 seems common though it can probably be increased significantly if needed. +char ioBytes[2]; +int ioPort = 0; + +void setup() +{ + // start serial port at the specified baud rate + Serial.begin(baudRate); + while (!Serial) { + ; // wait for serial port to connect. Needed for Leonardo only + } + Serial.println("Initialized!"); +} + +void loop() +{ + // All serial communications should be two bytes long + // The first byte specifies the port to act on + // The second byte specifies the action to take + // The actions are: + // 0: Read the specified input + // 1: Write the specified output to HIGH + // 2: Write the specified output to LOW + // 3: Set the specified pin to OUTPUT + // 4: Set the specified pin to INPUT + // 5: Set the specified pin to INPUT_PULLUP + // if we get a valid serial message, read the request: + if (Serial.available() >= 2) { + // get incoming two bytes: + Serial.readBytes(ioBytes, 2); + //Serial.println("I received: "); + //Serial.println(ioBytes[0], DEC); + //Serial.println(ioBytes[1], DEC); + // Extract the specified port + ioPort = (int) ioBytes[0]; + // Switch case on the specified action + switch ((int) ioBytes[1]) { + case 0: // Read an input + Serial.write(digitalRead(ioPort)); + break; + case 1: // Write an output to HIGH + digitalWrite(ioPort, HIGH); + break; + case 2: // Write an output to LOW + digitalWrite(ioPort, LOW); + break; + case 3: // Set a pin to OUTPUT + pinMode(ioPort, OUTPUT); + digitalWrite(ioPort, LOW); + break; + case 4: // Set a pin to INPUT + pinMode(ioPort, INPUT); + break; + case 5: // Set a pin to INPUT_PULLUP + pinMode(ioPort, INPUT_PULLUP); + break; + } + } + //delay(10); // Should probably move to a non-delay based spacing. +} From fa09030662b170fee4077c9d8d151370313ef7bd Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Mon, 18 Jan 2016 15:50:01 -0800 Subject: [PATCH 003/115] added gitter badge back in --- docs/README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/README.rst b/docs/README.rst index 770a904c..966af228 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,6 +1,11 @@ pyoperant ========= + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :alt: Join the chat at https://gitter.im/gentnerlab/pyoperant + :target: https://gitter.im/gentnerlab/pyoperant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + Pyoperant is a framework to easily construct and share new operant behavior paradigms. With PyOperant, you can write a single behavior script that works across different species, different computers, different hardware, different rewards, different modalities. From 854e10e0bf04819425f506d68bb8ab30d852154a Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Tue, 26 Jan 2016 12:43:20 -0800 Subject: [PATCH 004/115] switched to BSD license --- LICENSE.txt | 676 +--------------------------------------------------- setup.py | 2 +- 2 files changed, 8 insertions(+), 670 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 94a9ed02..09789bd6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,674 +1,12 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +Copyright (c) 2016, Regents of the University of California +All rights reserved. - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Preamble +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - The GNU General Public License is a free, copyleft license for -software and other kinds of works. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/setup.py b/setup.py index 6061ddce..6e7523f7 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ 'scripts/pyoperantctl', 'scripts/allsummary.py', ], - license = "GNU Affero General Public License v3", + license = "BSD", classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", From b98257f9b1c773fe1162627a8682ddfaab641ee1 Mon Sep 17 00:00:00 2001 From: Marvin T Date: Mon, 10 Aug 2015 17:39:21 -0700 Subject: [PATCH 005/115] fixes for doublestaircase in queues.py --- pyoperant/queues.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/pyoperant/queues.py b/pyoperant/queues.py index 08d181ed..027b0c68 100644 --- a/pyoperant/queues.py +++ b/pyoperant/queues.py @@ -74,7 +74,7 @@ def update(self, correct, no_resp): def next(self): if not self.updated: #hasn't been updated since last trial - raise Exception(self.update_error_str) + raise Exception(self.update_error_msg()) #TODO: find what causes bug self.updated = False def no_response(self): @@ -88,6 +88,9 @@ def on_load(self): self.updated = True self.no_response() + def update_error_msg(self): + return self.update_error_str + class PersistentBase(object): """ A mixin that allows for the creation of an obj through a load command that @@ -232,6 +235,11 @@ def no_response(self): super(DoubleStaircase, self).no_response() self.trial = {} + def update_error_msg(self): + sup = super(DoubleStaircase, self).update_error_msg() + state = "self.trial.low=%s self.trial.value=%d self.low_idx=%d self.high_idx=%d" % (self.trial['low'], self.trial['value'], self.low_idx, self.high_idx) + return "\n".join([sup, state]) + class DoubleStaircaseReinforced(AdaptiveBase): """ Generates conditions as with DoubleStaircase, but 1-probe_rate proportion of @@ -254,10 +262,9 @@ def __init__(self, stims, rate_constant=.05, probe_rate=.1, sample_log=False, ** self.update_error_str = "reinforced double staircase queue %s hasn't been updated since last trial" % (self.stims[0]) def update(self, correct, no_resp): - super(DoubleStaircaseReinforced, self).update(correct, no_resp) if self.last_probe: self.dblstaircase.update(correct, no_resp) - self.last_probe = False + super(DoubleStaircaseReinforced, self).update(correct, no_resp) def next(self): super(DoubleStaircaseReinforced, self).next() @@ -270,6 +277,7 @@ def next(self): except StopIteration: self.probe_rate = 0 self.last_probe = False + self.updated = True return self.next() else: self.last_probe = False @@ -277,7 +285,7 @@ def next(self): if self.sample_log: val = int((1 - rand_from_log_shape_dist()) * self.dblstaircase.low_idx) else: - val = random.randrange(self.dblstaircase.low_idx) + val = random.randrange(self.dblstaircase.low_idx + 1) return {'class': 'L', 'stim_name': self.stims[val]} else: # probe right if self.sample_log: @@ -288,12 +296,17 @@ def next(self): def no_response(self): super(DoubleStaircaseReinforced, self).no_response() - self.last_probe = False def on_load(self): super(DoubleStaircaseReinforced, self).on_load() self.dblstaircase.on_load() + def update_error_msg(self): + sup = super(DoubleStaircaseReinforced, self).update_error_msg() + state = "self.last_probe=%s" % (self.last_probe) + sub_state = "self.dbs.trial=%s self.dbs.low_idx=%d self.dbs.high_idx=%d" % (self.dblstaircase.trial, self.dblstaircase.low_idx, self.dblstaircase.high_idx) + return "\n".join([sup, state, sub_state]) + class MixedAdaptiveQueue(PersistentBase, AdaptiveBase): """ From 7d96bd4285db827607e24f82776c42f566bb494d Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Wed, 6 Apr 2016 11:33:12 -0700 Subject: [PATCH 006/115] derisking the pyaudio callback --- pyoperant/interfaces/pyaudio_.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pyoperant/interfaces/pyaudio_.py b/pyoperant/interfaces/pyaudio_.py index dae6abcb..c1222a08 100644 --- a/pyoperant/interfaces/pyaudio_.py +++ b/pyoperant/interfaces/pyaudio_.py @@ -22,7 +22,6 @@ def __init__(self,device_name='default',*args,**kwargs): self.device_index = None self.stream = None self.wf = None - self.callback = None self.open() def open(self): @@ -55,20 +54,13 @@ def validate(self): else: raise InterfaceError('there is something wrong with this wav file') - def _get_stream(self,start=False): + def _get_stream(self,start=False,callback=None): """ """ - def _callback(in_data, frame_count, time_info, status): - try: - cont = self.callback() - except TypeError: - cont = True - - if cont: + if callback is None: + def callback(in_data, frame_count, time_info, status): data = self.wf.readframes(frame_count) return (data, pyaudio.paContinue) - else: - return (0, pyaudio.paComplete) self.stream = self.pa.open(format=self.pa.get_format_from_width(self.wf.getsampwidth()), channels=self.wf.getnchannels(), @@ -76,12 +68,12 @@ def _callback(in_data, frame_count, time_info, status): output=True, output_device_index=self.device_index, start=start, - stream_callback=_callback) + stream_callback=callback) - def _queue_wav(self,wav_file,start=False): + def _queue_wav(self,wav_file,start=False,callback=None): self.wf = wave.open(wav_file) self.validate() - self._get_stream(start=start) + self._get_stream(start=start,callback=callback) def _play_wav(self): self.stream.start_stream() From 7e0a9a59d816ff7fe6e7ffc7b580a9cfacf09edb Mon Sep 17 00:00:00 2001 From: Justin Kiggins Date: Tue, 25 Apr 2017 10:17:23 -0700 Subject: [PATCH 007/115] Update README.rst --- docs/README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.rst b/docs/README.rst index 966af228..a61fd67c 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,3 +1,5 @@ +NOTICE!! This package has been renamed to "opyrant" and development has moved to https://github.com/opyrant/opyrant + pyoperant ========= From 80ff648029ea8d5bf27f67325e1bfcf0933fa479 Mon Sep 17 00:00:00 2001 From: Marvin T Date: Wed, 8 Nov 2017 12:50:38 -0800 Subject: [PATCH 008/115] Delete allsummary.py --- scripts/allsummary.py | 75 ------------------------------------------- 1 file changed, 75 deletions(-) delete mode 100755 scripts/allsummary.py diff --git a/scripts/allsummary.py b/scripts/allsummary.py deleted file mode 100755 index 10f73dee..00000000 --- a/scripts/allsummary.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python -import re -import datetime as dt -from glab_common.utils import load_data_pandas -from socket import gethostname -import warnings -from pyoperant.local import DATA_PATH - -process_fname = DATA_PATH + "panel_subject_behavior" - -box_nums = [] -bird_nums = [] -processes = [] - -with open(process_fname, 'rt') as in_f: - for line in in_f.readlines(): - if line.startswith('#') or not line.strip(): - pass # skip comment lines & blank lines - else: - spl_line = line.split() - if spl_line[1] == "1": #box enabled - box_nums.append(int(spl_line[0])) - bird_nums.append(int(spl_line[2])) - processes.append(spl_line[-1]) - -subjects = ['B%d' % (bird_num) for bird_num in bird_nums] -# load all data -with warnings.catch_warnings(): - warnings.simplefilter("ignore") - behav_data = load_data_pandas(subjects, DATA_PATH); - -with open(DATA_PATH+'all.summary','w') as f: - - f.write("this all.summary generated at %s\n" % (dt.datetime.now().strftime('%x %X'))) - - f.write("FeedErr(won't come up, won't go down, already up, resp during feed)\n") - - # Now loop through each bird and grab the error info from each summaryDAT file - for (box, bird, proc) in zip(box_nums, bird_nums, processes): - try: - if proc in ('Lights',): - f.write("box %d\tB%d\t %s\n" % (box, bird, proc)) - else: - summaryfname = "/home/bird/opdat/B%d/%d.summaryDAT" % (bird, bird) - with open(summaryfname, 'rt') as sdat: - sdata = sdat.read() - - m = re.search(r"failures today: (\w+)", sdata) - hopper_failures = m.group(1) - m = re.search(r"down failures today: (\w+)", sdata) - godown_failures = m.group(1) - m = re.search(r"up failures today: (\w+)", sdata) - goup_failures = m.group(1) - m = re.search(r"Responses during feed: (\w+)", sdata) - resp_feed = m.group(1) - - subj = 'B%d' % (bird) - df = behav_data[subj] - todays_data = df[(df.index.date-dt.datetime.today().date()) == dt.timedelta(days=0)] - feeder_ops = sum(todays_data['reward'].values) - trials_run = len(todays_data) - noRs = sum(todays_data['response'].values=='none') - TOs = trials_run-feeder_ops-noRs - last_trial_time = todays_data.sort().tail().index[-1] - if last_trial_time.day != dt.datetime.now().day: - datediff = '(not today)' - else: - minutes_ago = (dt.datetime.now() - last_trial_time).seconds / 60 - datediff = '(%d mins ago)' % (minutes_ago) - - outline = "box %d\tB%d\t %s \ttrls=%s \tfeeds=%d \tTOs=%d \tnoRs=%d \tFeedErrs=(%s,%s,%s,%s) \tlast @ %s %s\n" % (box, bird, proc, trials_run, feeder_ops, TOs, noRs, - hopper_failures, godown_failures, goup_failures, resp_feed, last_trial_time.strftime('%x %X'), datediff) - f.write(outline) - except Exception as e: - f.write("box %d\tB%d\t Error opening SummaryDat or incorrect format\n" % (box, bird)) \ No newline at end of file From 4ad944b3ad508b712186bfea07089f08e37d5747 Mon Sep 17 00:00:00 2001 From: bird Date: Sun, 4 Mar 2018 21:26:41 -0800 Subject: [PATCH 009/115] added new free food block branch to to merge with newer commits of master --- .../pyoperant-checkpoint.ipynb | 518 ++++++++++++++ pyoperant/behavior/base.py | 84 ++- pyoperant/behavior/shape.py | 80 ++- pyoperant/behavior/shape_original.py | 632 ++++++++++++++++++ pyoperant/behavior/two_alt_choice.py | 29 +- pyoperant/interfaces/pyaudio_.py | 7 +- scripts/pyoperantctl | 5 +- 7 files changed, 1338 insertions(+), 17 deletions(-) create mode 100644 notebooks/.ipynb_checkpoints/pyoperant-checkpoint.ipynb create mode 100644 pyoperant/behavior/shape_original.py diff --git a/notebooks/.ipynb_checkpoints/pyoperant-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/pyoperant-checkpoint.ipynb new file mode 100644 index 00000000..988ac737 --- /dev/null +++ b/notebooks/.ipynb_checkpoints/pyoperant-checkpoint.ipynb @@ -0,0 +1,518 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyoperant.local import PANELS\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['1', '3', '2', '4', '7', '6', '8']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "PANELS.keys()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "reset\n", + "output on\n", + "output off\n", + "output on\n", + "output off\n", + "output on\n", + "output off\n", + "output on\n", + "output off\n", + "output on\n", + "output off\n", + "output on\n", + "output off\n", + "output on\n", + "output off\n", + "output on\n", + "output off\n", + "reset\n", + "feed\n", + "timeout\n", + "queue file\n" + ] + }, + { + "ename": "IOError", + "evalue": "[Errno 2] No such file or directory: '/usr/local/stimuli/A1.wav'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mIOError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mbox_num\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mbox\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mPANELS\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'%d'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mbox_num\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtest\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/local_vogel.pyc\u001b[0m in \u001b[0;36mtest\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 75\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpunish\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdur\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 76\u001b[0m \u001b[1;32mprint\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;34m'queue file'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 77\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'/usr/local/stimuli/A1.wav'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 78\u001b[0m \u001b[1;32mprint\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;34m'play file'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 79\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/hwio.pyc\u001b[0m in \u001b[0;36mqueue\u001b[1;34m(self, wav_filename)\u001b[0m\n\u001b[0;32m 107\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 108\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 109\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minterface\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 110\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 111\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/interfaces/pyaudio_.pyc\u001b[0m in \u001b[0;36m_queue_wav\u001b[1;34m(self, wav_file, start, callback)\u001b[0m\n\u001b[0;32m 73\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 74\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mcallback\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 75\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mwave\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 76\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalidate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 77\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_stream\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mcallback\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mcallback\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/home/bird/anaconda2/lib/python2.7/wave.pyc\u001b[0m in \u001b[0;36mopen\u001b[1;34m(f, mode)\u001b[0m\n\u001b[0;32m 509\u001b[0m \u001b[0mmode\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m'rb'\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 510\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mmode\u001b[0m \u001b[1;32min\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;34m'r'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'rb'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 511\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mWave_read\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 512\u001b[0m \u001b[1;32melif\u001b[0m \u001b[0mmode\u001b[0m \u001b[1;32min\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;34m'w'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'wb'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 513\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mWave_write\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/home/bird/anaconda2/lib/python2.7/wave.pyc\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, f)\u001b[0m\n\u001b[0;32m 158\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_i_opened_the_file\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 159\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mbasestring\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 160\u001b[1;33m \u001b[0mf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m__builtin__\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'rb'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 161\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_i_opened_the_file\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 162\u001b[0m \u001b[1;31m# else, assume it is an open file object already\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mIOError\u001b[0m: [Errno 2] No such file or directory: '/usr/local/stimuli/A1.wav'" + ] + } + ], + "source": [ + "box_num = 1\n", + "box = PANELS['%d' % (box_num)]()\n", + "box.test()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "box.house_light.off()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "box.reset()" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "box.speaker.queue('/home/bird/2sec_noise_65dbmean_48kHz.wav')\n", + "box.speaker.play()" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "box.speaker.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "box.speaker.interface._stop_wav()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7\n", + "reset complete\n" + ] + } + ], + "source": [ + "# box_num += 1\n", + "box_num = 7\n", + "print box_num\n", + "box = PANELS['Zog%d' % (box_num)]()\n", + "\n", + "try:\n", + " box.reset()\n", + "except:\n", + " pass\n", + "else:\n", + " print 'reset complete'\n", + "# box.test()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "box.house_light.off()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyoperant.components import RGBLight" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "cue_light = RGBLight(red=box.outputs[7],\n", + " green=box.outputs[5],\n", + " blue=box.outputs[6]\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cue_light._red.write(False)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "InterfaceError", + "evalue": "this wav file must be 48kHz", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mInterfaceError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32mexcept\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;32mpass\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 9\u001b[1;33m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'/home/bird/2sec_noise_65dbmean.wav'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 10\u001b[0m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1.0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/hwio.pyc\u001b[0m in \u001b[0;36mqueue\u001b[1;34m(self, wav_filename)\u001b[0m\n\u001b[0;32m 93\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 94\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 95\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minterface\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 96\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 97\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/interfaces/pyaudio_.pyc\u001b[0m in \u001b[0;36m_queue_wav\u001b[1;34m(self, wav_file, start)\u001b[0m\n\u001b[0;32m 81\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 82\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mwave\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 83\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalidate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 84\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_stream\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 85\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/local_zog.pyc\u001b[0m in \u001b[0;36mvalidate\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 34\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mTrue\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 35\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 36\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mInterfaceError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'this wav file must be 48kHz'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 37\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 38\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mInterfaceError\u001b[0m: this wav file must be 48kHz" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "11\n" + ] + } + ], + "source": [ + "box_num = 11\n", + "print box_num\n", + "box = PANELS['Zog%d' % (box_num)]()\n", + "\n", + "try:\n", + " box.reset()\n", + "except:\n", + " pass\n", + "box.speaker.queue('/home/bird/opdat/B982/stims/triples_abd_48kHz.wavv.wav')\n", + "box.speaker.play()\n", + "time.sleep(1.0)\n", + "box.speaker.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "box_num = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "IOError", + "evalue": "[Errno Device unavailable] -9985", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mIOError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;31m# pass\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 9\u001b[0m \u001b[1;31m# box.speaker.queue('/home/bird/Desktop/test48k.wav')\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 10\u001b[1;33m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'/home/bird/2sec_noise_65dbmean.wav'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 11\u001b[0m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 12\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m2.0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/hwio.pyc\u001b[0m in \u001b[0;36mqueue\u001b[1;34m(self, wav_filename)\u001b[0m\n\u001b[0;32m 93\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 94\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 95\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minterface\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 96\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 97\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/interfaces/pyaudio_.pyc\u001b[0m in \u001b[0;36m_queue_wav\u001b[1;34m(self, wav_file, start)\u001b[0m\n\u001b[0;32m 55\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 56\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_wf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mwave\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 57\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_stream\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 58\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 59\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_play_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/interfaces/pyaudio_.pyc\u001b[0m in \u001b[0;36m_get_stream\u001b[1;34m(self, start)\u001b[0m\n\u001b[0;32m 51\u001b[0m \u001b[0moutput_device_index\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdevice_index\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 52\u001b[0m \u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 53\u001b[1;33m stream_callback=_callback)\n\u001b[0m\u001b[0;32m 54\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 55\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/usr/local/anaconda/lib/python2.7/site-packages/pyaudio.pyc\u001b[0m in \u001b[0;36mopen\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 745\u001b[0m \"\"\"\n\u001b[0;32m 746\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 747\u001b[1;33m \u001b[0mstream\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mStream\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 748\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_streams\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0madd\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstream\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 749\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mstream\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m/usr/local/anaconda/lib/python2.7/site-packages/pyaudio.pyc\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, PA_manager, rate, channels, format, input, output, input_device_index, output_device_index, frames_per_buffer, start, input_host_api_specific_stream_info, output_host_api_specific_stream_info, stream_callback)\u001b[0m\n\u001b[0;32m 440\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 441\u001b[0m \u001b[1;31m# calling pa.open returns a stream object\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 442\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_stream\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mpa\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0marguments\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 443\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 444\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_input_latency\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_stream\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minputLatency\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mIOError\u001b[0m: [Errno Device unavailable] -9985" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n" + ] + } + ], + "source": [ + "box_num = 3\n", + "print box_num\n", + "box = PANELS['Zog%d' % (box_num)]()\n", + "\n", + "# try:\n", + "# box.reset()\n", + "# except:\n", + "# pass\n", + "# box.speaker.queue('/home/bird/Desktop/test48k.wav')\n", + "box.speaker.queue('/home/bird/2sec_noise_65dbmean.wav')\n", + "box.speaker.play()\n", + "time.sleep(2.0)\n", + "box.speaker.stop()" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "Box 3 is very quiet\n", + "Box 10, 14 are very loud\n", + "Box 15 doesn't work" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n", + "False\n", + "False\n", + "False\n" + ] + } + ], + "source": [ + "for ii in box.inputs:\n", + " print ii.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "True\n", + "True\n", + "True\n", + "True\n" + ] + } + ], + "source": [ + "for oo in box.outputs:\n", + " print oo.write(False)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n", + "False\n", + "False\n", + "False\n" + ] + } + ], + "source": [ + "for ii in box_1.inputs:\n", + " print ii.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "iface = box.interfaces['comedi']" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "box = PANELS['Zog6']()\n", + "box.reset()\n", + "box.test()" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(datetime.datetime(2014, 10, 2, 16, 16, 30, 716416),\n", + " datetime.timedelta(0, 2, 2327))" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "box.reward()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.11" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/pyoperant/behavior/base.py b/pyoperant/behavior/base.py index 54a123ea..41accb34 100755 --- a/pyoperant/behavior/base.py +++ b/pyoperant/behavior/base.py @@ -4,7 +4,7 @@ from pyoperant import utils, components, local, hwio from pyoperant import ComponentError, InterfaceError from pyoperant.behavior import shape - +import random try: import simplejson as json @@ -150,12 +150,16 @@ def run(self): error_callback=self.log_error_callback, idle=self._run_idle, sleep=self._run_sleep, - session=self._run_session) + session=self._run_session, + free_food_block=self._free_food + ) def _run_idle(self): + self.log.debug('Starting _run_idle') if self.check_light_schedule() == False: return 'sleep' elif self.check_session_schedule(): + if _check_free_food_block: return 'free_food_block' return 'session' else: self.panel_reset() @@ -196,6 +200,82 @@ def _run_sleep(self): return 'idle' # session + def _wait_block(self, t_min, t_max, next_state): + def temp(): + if t_min == t_max: + t = t_max + else: + t = random.randrange(t_min, t_max) + utils.wait(t) + return next_state + + return temp + + def _check_free_food_block(self): + """ Checks if it is currently a free food block + """ + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return True + return + def free_food_pre(self): + self.log.debug('Buffet starting.') + return 'main' + + def free_food_main(self): + """ reset expal parameters for the next day """ + self.log.debug('Starting Free Food main.') + utils.run_state_machine(start_in='wait', + error_state='wait', + error_callback=self.log_error_callback, + wait=self._wait_block(5, 5, 'food'), + food=self.deliver_free_food(10, 'checker'), + checker=self.food_checker('wait') + ) + + if not utils.check_time(self.parameters['free_food_schedule']): + return 'post' + else: + return 'main' + + def food_checker(self, next_state): + #should we still be giving free food? + def temp(): + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return next_state + return None + return temp + + def free_food_post(self): + self.log.debug('Free food over.') + return None + + def _free_food(self): + self.log.debug('Starting _free_food') + utils.run_state_machine(start_in='pre', + error_state='post', + error_callback=self.log_error_callback, + pre=self.free_food_pre, + main=self.free_food_main, + post=self.free_food_post) + return 'idle' + + def deliver_free_food(self, value, next_state): + """ reward function with no frills + """ + + def temp(): + self.log.debug('Doling out some free food.') + + try: + reward_event = self.panel.reward(value=value) + except: + self.log.warning("Hopper did not drop on free food") + + return next_state + + return temp def session_pre(self): return 'main' diff --git a/pyoperant/behavior/shape.py b/pyoperant/behavior/shape.py index bcb872f6..d23c6491 100644 --- a/pyoperant/behavior/shape.py +++ b/pyoperant/behavior/shape.py @@ -45,7 +45,8 @@ def run_shape(self, start_state='block1'): block3=self.block3, block4=self.block4, block5=self.block5, - sleep_block=self._run_sleep) + sleep_block=self._run_sleep, + free_food_block=self._free_food) self.log.warning('Shaping procedure complete. Remember to disable shaping in your config file') def _null_block(self, block_num): @@ -53,6 +54,15 @@ def temp(): return self.block_name(block_num + 1) return temp + + def _check_free_food_block(self): + """ Checks if it is currently a free food block + """ + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return True + return + def _hopper_block(self, block_num): """ Block 1: Hopper comes up on VI (stays up for 5 s) for the first day @@ -63,6 +73,8 @@ def _hopper_block(self, block_num): def temp(): self.recent_state = block_num self.log.warning('Starting %s'%(self.block_name(block_num))) + if _check_free_food_block: return 'free_food_block' + utils.run_state_machine( start_in='init', error_state='wait', error_callback=self.error_callback, @@ -73,6 +85,8 @@ def temp(): pre_reward=self._pre_reward('reward'), reward=self.reward(5, 'check2'), check2=self._check_block('wait', 1, float('inf'))) + # check if its time for free food + if _check_free_food_block: return 'free_food_block' if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' return self.block_name(block_num + 1) @@ -95,6 +109,7 @@ def temp(): reward=self.reward(4, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' + if _check_free_food_block: return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -123,6 +138,7 @@ def temp(): return None if not utils.check_time(self.parameters['light_schedule']): return None + if _check_free_food_block: return 'free_food_block' return next_state return temp @@ -269,6 +285,63 @@ def _run_sleep(self): post=self.sleep_post) return self.block_name(self.recent_state) + def free_food_pre(self): + self.log.debug('Buffet starting.') + return 'main' + + def free_food_main(self): + """ reset expal parameters for the next day """ + + utils.run_state_machine(start_in='wait', + error_state='wait', + error_callback=self.error_callback, + wait=self._wait_block(5, 5, 'food'), + food=self.deliver_free_food(10, 'checker'), + checker=self.food_checker('wait') + ) + + + if not utils.check_time(self.parameters['free_food_schedule']): + return 'post' + else: + return 'main' + + def food_checker(self, next_state): + # should we still be giving free food? + def temp(): + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return next_state + return None + + return temp + + + def free_food_post(self): + self.log.debug('Free food over.') + self.panel.house_light.on() + # self.init_summary() + return None + + def _free_food(self): + utils.run_state_machine(start_in='pre', + error_state='post', + error_callback=self.error_callback, + pre=self.free_food_pre, + main=self.free_food_main, + post=self.free_food_post) + return self.block_name(self.recent_state) + + def deliver_free_food(self, value, next_state): + """ reward function with no frills + """ + def temp(): + self.log.debug('Doling out some yum yums.') + self.panel.reward(value=value) + return next_state + + return temp + def block_name(self, block_num): if block_num >= 1 and block_num <= 5: return "block%d"%block_num @@ -318,6 +391,7 @@ def temp(): reward=self.reward(3, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' + if _check_free_food_block: return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -345,6 +419,7 @@ def temp(): reward=self.reward(2.5, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' + if _check_free_food_block: return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -443,6 +518,7 @@ def temp(): reward=self.reward(3, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' + if _check_free_food_block: return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -472,6 +548,7 @@ def temp(): reward=self.reward(2.5, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' + if _check_free_food_block: return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -510,6 +587,7 @@ def temp(): reward=self.reward(2.5, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' + if _check_free_food_block: return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: diff --git a/pyoperant/behavior/shape_original.py b/pyoperant/behavior/shape_original.py new file mode 100644 index 00000000..8e68761b --- /dev/null +++ b/pyoperant/behavior/shape_original.py @@ -0,0 +1,632 @@ +import random +import datetime as dt +from pyoperant import panels +from pyoperant import utils + +class Shaper(object): + """ + Run a shaping routine in the operant chamber that will teach an + to peck the center key to hear a stimulus, then peck one of the side keys for reward. + training sequence: + Block 1: Hopper comes up on VI (stays up for 5 s) for the first day + that the animal is in the apparatus. Center key flashes for 5 sec, prior + to the hopper access. If the center key is pressed while flashing, then + the hopper comes up and then the session jumps to block 2 immediately. + Block 2: The center key flashes until pecked. When pecked the hopper comes up for + 4 sec. Run 100 trials. + Block 3: The center key flashes until pecked, then either the right or left (p = .5) + key flashes until pecked, then the hopper comes up for 3 sec. Run 100 trials. + Block 4: Wait for peck to non-flashing center key, then right or left key flashes + until pecked, then food for 2.5 sec. Run 100 trials.""" + + def __init__(self, panel, log, parameters, error_callback=None): + self.panel = panel + assert isinstance(panel, panels.BasePanel) + self.log = log + assert log is not None + self.parameters = parameters + assert 'light_schedule' in self.parameters + self.error_callback = error_callback + self.recent_state = 0 + self.last_response = None + self.block1 = self._null_block(1) + self.block2 = self._null_block(2) + self.block3 = self._null_block(3) + self.block4 = self._null_block(4) + self.block5 = self._null_block(5) + + def run_shape(self, start_state='block1'): + self.log.warning('Starting shaping procedure') + utils.run_state_machine( start_in=start_state, + error_state='block1', + error_callback=self.error_callback, + block1=self.block1, + block2=self.block2, + block3=self.block3, + block4=self.block4, + block5=self.block5, + sleep_block=self._run_sleep, + free_food_block=self._free_food) + self.log.warning('Shaping procedure complete. Remember to disable shaping in your config file') + + def _null_block(self, block_num): + def temp(): + return self.block_name(block_num + 1) + return temp + + def _hopper_block(self, block_num): + """ + Block 1: Hopper comes up on VI (stays up for 5 s) for the first day + that the animal is in the apparatus. Center key flashes for 5 sec, prior + to the hopper access. If the center key is pressed while flashing, then + the hopper comes up and then the session jumps to block 2 immediately""" + + def temp(): + self.recent_state = block_num + self.log.warning('Starting %s'%(self.block_name(block_num))) + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + utils.run_state_machine( start_in='init', + error_state='wait', + error_callback=self.error_callback, + init=self._block_init('wait'), + wait=self._wait_block(10, 40,'check'), + check=self._check_block('flash_mid', 1, float('inf')), + flash_mid=self._flash_poll(self.panel.center, 5, 'reward', 'pre_reward'), + pre_reward=self._pre_reward('reward'), + reward=self.reward(5, 'check2'), + check2=self._check_block('wait', 1, float('inf'))) + + + # check if its time for free food + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + + if not utils.check_time(self.parameters['light_schedule']): + return 'sleep_block' + return self.block_name(block_num + 1) + return temp + + def _center_peck_block(self, block_num, reps=100, revert_timeout=10800): + """Block 2: The center key flashes until pecked. When pecked the hopper comes up for + 4 sec. Run 100 trials. + reverts to revert_state if no response before timeout (60*60*3=10800)""" + def temp(): + self.recent_state = block_num + self.log.warning('Starting %s'%(self.block_name(block_num))) + utils.run_state_machine( start_in='init', + error_state='check', + error_callback=self.error_callback, + init=self._block_init('check'), + check=self._check_block('poll_mid', reps, revert_timeout), + poll_mid=self._flash_poll(self.panel.center, 10, 'check', 'pre_reward'), + pre_reward=self._pre_reward('reward'), + reward=self.reward(4, 'check')) + if not utils.check_time(self.parameters['light_schedule']): + return 'sleep_block' + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + + if self.responded_block: + return self.block_name(block_num + 1) + else: + return self.block_name(block_num - 1) + return temp + + def _block_init(self, next_state): + def temp(): + self.block_start = dt.datetime.now() + self.log.info('Block start time: %s'%(self.block_start.isoformat(' '))) + self.log.info("Blk #\tTrl #\tResp Key\tResp Time") + self.responded_block = False + self.response_counter = 0 + return next_state + return temp + + def _check_block(self, next_state, reps, revert_timeout): + def temp(): + if not self.responded_block: + elapsed_time = (dt.datetime.now() - self.block_start).total_seconds() + if elapsed_time > revert_timeout: + self.log.warning("No response in block %d, reverting to block %d. Time: %s"%(self.recent_state, self.recent_state - 1, dt.datetime.now().isoformat(' '))) + return None + else: + if self.response_counter >= reps: + return None + if not utils.check_time(self.parameters['light_schedule']): + return None + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return None + + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return None + + return next_state + return temp + + def _pre_reward(self, next_state): + def temp(): + self.responded_block = True + self.response_counter = self.response_counter + 1 + return next_state + return temp + + def _wait_block(self, t_min, t_max, next_state): + def temp(): + if t_min == t_max: + t = t_max + else: + t = random.randrange(t_min, t_max) + utils.wait(t) + return next_state + return temp + + def _poll(self, component, duration, next_state, reward_state=None, poll_state=None): + if poll_state == None: + poll_state = self._poll_main + def temp(): + utils.run_state_machine( start_in='init', + init=self._polling_init('main'), + main=poll_state(component, duration)) + if self.responded_poll: + return reward_state + else: + return next_state + return temp + + def _flash_poll(self, component, duration, next_state, reward_state=None): + return self._poll(component, duration, next_state, reward_state, poll_state=self._flashing_main) + + def _light_poll(self, component, duration, next_state, reward_state=None): + return self._poll(component, duration, next_state, reward_state, poll_state=self._light_main) + + + def _polling_init(self, next_state): + def temp(): + self.polling_start = dt.datetime.now() + self.responded_poll = False + self.last_response = None + return next_state + return temp + + # TODO: remake to not hog CPU + def _poll_main(self, component, duration): + def temp(): + elapsed_time = (dt.datetime.now() - self.polling_start).total_seconds() + if elapsed_time <= duration: + if component.status(): + self.responded_poll = True + self.last_response = component.name + return None + utils.wait(.015) + return 'main' + else: + return None + return temp + + def _flashing_main(self, component, duration, period=1): + def temp(): + elapsed_time = (dt.datetime.now() - self.polling_start).total_seconds() + if elapsed_time <= duration: + if ((elapsed_time % period) - (period / 2.0)) < 0: + component.on() + else: + component.off() + if component.status(): + component.off() + self.responded_poll = True + self.last_response = component.name + return None + utils.wait(.015) + return 'main' + else: + component.off() + return None + return temp + + + + def _light_main(self, component, duration): + def temp(): + elapsed_time = (dt.datetime.now() - self.polling_start).total_seconds() + if elapsed_time <= duration: + component.on() + if component.status(): + component.off() + self.responded_poll = True + self.last_response = component.name + return None + utils.wait(.015) + return 'main' + else: + component.off() + return None + return temp + +#TODO: catch errors here + def reward(self, value, next_state): + def temp(): + self.log.info('%d\t%d\t%s\t%s'%(self.recent_state, self.response_counter, self.last_response, dt.datetime.now().isoformat(' '))) + self.panel.reward(value=value) + return next_state + return temp + + def _rand_state(self, states): + def temp(): + return random.choice(states) + return temp + + # defining functions for sleep + #TODO: there should really be a separate sleeper or some better solution + def sleep_pre(self): + self.log.debug('lights off. going to sleep...') + return 'main' + + def sleep_main(self): + """ reset expal parameters for the next day """ + self.log.debug('sleeping...') + self.panel.house_light.off() + utils.wait(self.parameters['idle_poll_interval']) + if not utils.check_time(self.parameters['light_schedule']): + return 'main' + else: + return 'post' + + def sleep_post(self): + self.log.debug('ending sleep') + self.panel.house_light.on() +# self.init_summary() + return None + + def _run_sleep(self): + utils.run_state_machine(start_in='pre', + error_state='post', + error_callback=self.error_callback, + pre=self.sleep_pre, + main=self.sleep_main, + post=self.sleep_post) + return self.block_name(self.recent_state) + + def free_food_pre(self): + self.log.debug('Buffet starting.') + return 'main' + + def free_food_main(self): + """ reset expal parameters for the next day """ + + utils.run_state_machine(start_in='wait', + error_state='wait', + error_callback=self.error_callback, + wait=self._wait_block(5, 5, 'food'), + food=self.deliver_free_food(10, 'checker'), + checker=self.food_checker('wait') + ) + + + if not utils.check_time(self.parameters['free_food_schedule']): + return 'post' + else: + return 'main' + + def food_checker(self, next_state): + # should we still be giving free food? + def temp(): + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return next_state + return None + + return temp + + + def free_food_post(self): + self.log.debug('Free food over.') + self.panel.house_light.on() + # self.init_summary() + return None + + def _free_food(self): + utils.run_state_machine(start_in='pre', + error_state='post', + error_callback=self.error_callback, + pre=self.free_food_pre, + main=self.free_food_main, + post=self.free_food_post) + return self.block_name(self.recent_state) + + def deliver_free_food(self, value, next_state): + """ reward function with no frills + """ + def temp(): + self.log.debug('Doling out some yum yums.') + self.panel.reward(value=value) + return next_state + + return temp + + def block_name(self, block_num): + if block_num >= 1 and block_num <= 5: + return "block%d"%block_num + else: + return None + +class Shaper2AC(Shaper): + """Run a shaping routine in the operant chamber that will teach an + to peck the center key to hear a stimulus, then peck one of the side keys for reward. + training sequence: + Block 1: Hopper comes up on VI (stays up for 5 s) for the first day + that the animal is in the apparatus. Center key flashes for 5 sec, prior + to the hopper access. If the center key is pressed while flashing, then + the hopper comes up and then the session jumps to block 2 immediately. + Block 2: The center key flashes until pecked. When pecked the hopper comes up for + 4 sec. Run 100 trials. + Block 3: The center key flashes until pecked, then either the right or left (p = .5) + key flashes until pecked, then the hopper comes up for 3 sec. Run 100 trials. + Block 4: Wait for peck to non-flashing center key, then right or left key flashes + until pecked, then food for 2.5 sec. Run 100 trials.""" + def __init__(self, panel, log, parameters, error_callback=None): + super(Shaper2AC, self).__init__(panel, log, parameters, error_callback) + self.block1 = self._hopper_block(1) + self.block2 = self._center_peck_block(2) + self.block3 = self._response_2ac_block(3) + self.block4 = self._response_2ac_no_flash_block(4) + + def _response_2ac_block(self, block_num, reps=100, revert_timeout=10800): + + """Block 3: The center key flashes until pecked, then either the right or left (p = .5) + key flashes until pecked, then the hopper comes up for 3 sec. Run 100 trials.""" + def temp(): + self.recent_state = block_num + self.log.warning('Starting %s'%(self.block_name(block_num))) + utils.run_state_machine( start_in='init', + error_state='check', + error_callback=self.error_callback, + init=self._block_init('check'), + check=self._check_block('poll_mid', reps, revert_timeout), + poll_mid=self._flash_poll(self.panel.center, 10, 'check', 'coin_flip'), + coin_flip=self._rand_state(('check_right', 'check_left')), + check_right=self._check_block('poll_right', reps, revert_timeout), + poll_right=self._flash_poll(self.panel.right, 10, 'check_right', 'pre_reward'), + check_left=self._check_block('poll_left', reps, revert_timeout), + poll_left=self._flash_poll(self.panel.left, 10, 'check_left', 'pre_reward'), + pre_reward=self._pre_reward('reward'), + reward=self.reward(3, 'check')) + if not utils.check_time(self.parameters['light_schedule']): + return 'sleep_block' + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + + if self.responded_block: + return self.block_name(block_num + 1) + else: + return self.block_name(block_num - 1) + return temp + + def _response_2ac_no_flash_block(self, block_num, reps=100, revert_timeout=10800): + """Block 4: Wait for peck to non-flashing center key, then right or left key flashes + until pecked, then food for 2.5 sec. Run 100 trials.""" + def temp(): + self.recent_state = block_num + self.log.warning('Starting %s'%(self.block_name(block_num))) + utils.run_state_machine( start_in='init', + error_state='check', + error_callback=self.error_callback, + init=self._block_init('check'), + check=self._check_block('poll_mid', reps, revert_timeout), + poll_mid=self._poll(self.panel.center, 10, 'check', 'coin_flip'), + coin_flip=self._rand_state(('check_right', 'check_left')), + check_right=self._check_block('poll_right', reps, revert_timeout), + poll_right=self._flash_poll(self.panel.right, 10, 'check_right', 'pre_reward'), + check_left=self._check_block('poll_left', reps, revert_timeout), + poll_left=self._flash_poll(self.panel.left, 10, 'check_left', 'pre_reward'), + pre_reward=self._pre_reward('reward'), + reward=self.reward(2.5, 'check')) + if not utils.check_time(self.parameters['light_schedule']): + return 'sleep_block' + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + + if self.responded_block: + return self.block_name(block_num + 1) + else: + return self.block_name(block_num - 1) + return temp + +class ShaperGoNogo(Shaper): + """accomodate go/nogo terminal procedure along with one or two hopper 2choice procedures + Go/Nogo shaping works like this: + Block 1: Hopper comes up on VI (stays up for 5 s) for the first day + that the animal is in the apparatus. Center key flashes for 5 sec, prior + to the hopper access. If the center key is pressed while flashing, then + the hopper comes up and then the session jumps to block 2 immediately. + Block 2: The center key flashes until pecked. When pecked the hopper comes up for + 4 sec. Run 100 trials. + Block 3: Wait for a peck to non-flashing center key, when you get it, the hopper + comes up for 2.5 sec. Run 100 trials. + NOTE: when you run the go/nog procedure in a 2 hopper apparatus, it uses only the + right hand key and hopper. If you do this often, you may want to add the + facility for use of the left hand key and hopper.""" + def __init__(self, panel, log, parameters, error_callback=None): + super(ShaperGoNogo, self).__init__(panel, log, parameters, error_callback) + self.block1 = self._hopper_block(1) + self.block2 = self._center_peck_block(2) + self.block3 = self._center_peck_no_flash_block(3) + + def _center_peck_no_flash_block(self, block_num): + raise NotImplementedError + +class ShaperFemalePref(Shaper): + """run a shaping routine for female pecking preferencein the operant chamber + termial proc: peck one of the side keys for stimulus presentation followed by reward. + Training sequence invoked as: + Block 1: Hopper comes up on VI (stays up for 5 s) for the first day + that the animal is in the apparatus. + Left and right keylights flash for 5 sec, prior + to the hopper access. If either L or R key is pressed while flashing, then + the hopper comes up and the session jumps to block 2 immediately. + Block 2: randomly choose either L or R key to flash until pecked. When pecked the hopper + comes up for 4 sec. + Block 3: Wait for peck to non-flashing L or R key (chosen at random). When pecked, + give food for 2.5 sec.""" + def __init__(self, panel, log, parameters, error_callback=None): + super(ShaperFemalePref, self).__init__(panel, log, parameters, error_callback) + self.block1 = self._hopper_block(1) + self.block2 = self._female_choice_block(2) + self.block3 = self._female_choice_no_flash_block(3) + + def _female_choice_block(self, block_num): + raise NotImplementedError + + def _female_choice_no_flash_block(self, block_num): + raise NotImplementedError + +class Shaper3AC(Shaper): + """run a shaping routine for 3AC the operant chamber + termial proc: peck center key for stimulus presentation then peck one of three keys L-C-R, or give no response. + Training sequence invoked as: + Block 1: Hopper comes up on VI (stays up for 5 s) for the first day + that the animal is in the apparatus. Center key flashes for 5 sec, prior + to the hopper access. If the center key is pressed while flashing, then + the hopper comes up and then the session jumps to block 2 immediately. + Block 2: The center key flashes until pecked. When pecked the hopper comes up for + 4 sec. Run 100 trials. + Block 3: The center key flashes until pecked, then either the right, left, or center + key flashes (p=0.333) until pecked, then the hopper comes up for 3 sec. Run 150 trials. + Block 4: Wait for peck to non-flashing center key, then right, center,or left key flashes + until pecked, then food for 2.5 sec. Run 150 trials.""" + def __init__(self, panel, log, parameters, error_callback=None): + super(Shaper3AC, self).__init__(panel, log, parameters, error_callback) + self.block1 = self._hopper_block(1) + self.block2 = self._center_peck_block(2) + self.block3 = self._response_3ac_block(3) + self.block4 = self._response_3ac_no_flash_block(4) + + def _response_3ac_block(self, block_num, reps=100, revert_timeout=10800): + """Block 3: The center key flashes until pecked, then either the right, left, or center + key flashes (p=0.333) until pecked, then the hopper comes up for 3 sec. Run 150 trials.""" + def temp(): + self.recent_state = block_num + self.log.warning('Starting %s'%(self.block_name(block_num))) + utils.run_state_machine( start_in='init', + error_state='check', + error_callback=self.error_callback, + init=self._block_init('check'), + check=self._check_block('poll_mid', reps, revert_timeout), + poll_mid=self._flash_poll(self.panel.center, 10, 'check', 'coin_flip'), + coin_flip=self._rand_state(('check_right', 'check_center', 'check_left')), + check_right=self._check_block('poll_right', reps, revert_timeout), + poll_right=self._flash_poll(self.panel.right, 10, 'check_right', 'pre_reward'), + check_center=self._check_block('poll_center', reps, revert_timeout), + poll_center=self._flash_poll(self.panel.center, 10, 'check_center', 'pre_reward'), + check_left=self._check_block('poll_left', reps, revert_timeout), + poll_left=self._flash_poll(self.panel.left, 10, 'check_left', 'pre_reward'), + pre_reward=self._pre_reward('reward'), + reward=self.reward(3, 'check')) + if not utils.check_time(self.parameters['light_schedule']): + return 'sleep_block' + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + + if self.responded_block: + return self.block_name(block_num + 1) + else: + return self.block_name(block_num - 1) + return temp + + def _response_3ac_no_flash_block(self, block_num, reps=150, revert_timeout=10800): + """Block 4: Wait for peck to non-flashing center key, then right, center,or left key flashes + until pecked, then food for 2.5 sec. Run 150 trials.""" + def temp(): + self.recent_state = block_num + self.log.warning('Starting %s'%(self.block_name(block_num))) + utils.run_state_machine( start_in='init', + error_state='check', + error_callback=self.error_callback, + init=self._block_init('check'), + check=self._check_block('poll_mid', reps, revert_timeout), + poll_mid=self._poll(self.panel.center, 10, 'check', 'coin_flip'), + coin_flip=self._rand_state(('check_right', 'check_center', 'check_left')), + check_right=self._check_block('poll_right', reps, revert_timeout), + poll_right=self._flash_poll(self.panel.right, 10, 'check_right', 'pre_reward'), + check_center=self._check_block('poll_center', reps, revert_timeout), + poll_center=self._flash_poll(self.panel.center, 10, 'check_center', 'pre_reward'), + check_left=self._check_block('poll_left', reps, revert_timeout), + poll_left=self._flash_poll(self.panel.left, 10, 'check_left', 'pre_reward'), + pre_reward=self._pre_reward('reward'), + reward=self.reward(2.5, 'check')) + if not utils.check_time(self.parameters['light_schedule']): + return 'sleep_block' + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + + if self.responded_block: + return self.block_name(block_num + 1) + else: + return self.block_name(block_num - 1) + return temp + +class Shaper3ACMatching(Shaper3AC): + def __init__(self, panel, log, parameters, get_stimuli, error_callback=None): + super(Shaper3AC, self).__init__(panel, log, parameters, error_callback) + assert hasattr(get_stimuli, '__call__') + self.get_stimuli = get_stimuli + self.block5 = self._response_3ac_matching_audio_block(5) + + def _response_3ac_matching_audio_block(self, block_num, reps=150, revert_timeout=10800): + def temp(): + self.recent_state = block_num + self.log.info('Starting %s'%(self.block_name(block_num))) + utils.run_state_machine( start_in='init', + error_state='check', + error_callback=self.error_callback, + init=self._block_init('check'), + check=self._check_block('poll_mid', reps, revert_timeout), + poll_mid=self._poll(self.panel.center, 10, 'check', 'coin_flip'), + coin_flip=self._rand_state(('check_right', 'check_center', 'check_left')), + check_right=self._check_block('audio_right', reps, revert_timeout), + audio_right=self._play_audio('poll_right', 'R'), + poll_right=self._flash_poll(self.panel.right, 10, 'check_right', 'close_audio'), + check_center=self._check_block('audio_center', reps, revert_timeout), + audio_center=self._play_audio('poll_center', 'C'), + poll_center=self._flash_poll(self.panel.center, 10, 'check_center', 'close_audio'), + check_left=self._check_block('audio_left', reps, revert_timeout), + audio_left=self._play_audio('poll_left', 'L'), + poll_left=self._flash_poll(self.panel.left, 10, 'check_left', 'close_audio'), + close_audio=self._close_audio('pre_reward'), + pre_reward=self._pre_reward('reward'), + reward=self.reward(2.5, 'check')) + if not utils.check_time(self.parameters['light_schedule']): + return 'sleep_block' + if 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + return 'free_food_block' + + if self.responded_block: + return self.block_name(block_num + 1) + else: + return self.block_name(block_num - 1) + return temp + + def _play_audio(self, next_state, trial_class): + def temp(): + trial_stim, trial_motifs = self.get_stimuli(trial_class) + self.log.debug("presenting stimulus %s" % trial_stim.name) + self.panel.speaker.queue(trial_stim.file_origin) + self.panel.speaker.play() + return next_state + return temp + + def _close_audio(self, next_state): + def temp(): + self.panel.speaker.stop() + return next_state + return temp diff --git a/pyoperant/behavior/two_alt_choice.py b/pyoperant/behavior/two_alt_choice.py index 401d71cc..a63bbb53 100755 --- a/pyoperant/behavior/two_alt_choice.py +++ b/pyoperant/behavior/two_alt_choice.py @@ -22,10 +22,10 @@ class TwoAltChoiceExp(base.BaseExp): trials : list all of the trials that have run shaper : Shaper - the protocol for shaping - parameters : dict + the protocol for shaping + parameters : dict all additional parameters for the experiment - data_csv : string + data_csv : string path to csv file to save data reinf_sched : object does logic on reinforcement @@ -110,7 +110,7 @@ def __init__(self, *args, **kwargs): def make_data_csv(self): """ Create the csv file to save trial data - This creates a new csv file at experiment.data_csv and writes a header row + This creates a new csv file at experiment.data_csv and writes a header row with the fields in experiment.fields_to_save """ with open(self.data_csv, 'wb') as data_fh: @@ -132,8 +132,8 @@ def session_pre(self): """ Runs before the session starts For each stimulus class, if there is a component associated with it, that - component is mapped onto `experiment.class_assoc[class]`. For example, - if the `left` port is registered with the 'L' class, you can access the response + component is mapped onto `experiment.class_assoc[class]`. For example, + if the `left` port is registered with the 'L' class, you can access the response port through `experiment.class_assoc['L']`. """ @@ -194,16 +194,16 @@ def run_trial_queue(): elif q_type=='mixedDblStaircase': dbl_staircases = [queues.DoubleStaircaseReinforced(stims) for stims in blk['stim_lists']] self.trial_q = queues.MixedAdaptiveQueue.load(os.path.join(self.parameters['experiment_path'], 'persistentQ.pkl'), dbl_staircases) - try: + try: run_trial_queue() except EndSession: return 'post' self.session_q = None - + else: self.log.info('continuing last session') - try: + try: run_trial_queue() except EndSession: return 'post' @@ -276,7 +276,7 @@ def get_stimuli(self,**conditions): Returns ------- - stim, epochs : Event, list + stim, epochs : Event, list """ @@ -363,6 +363,7 @@ def trial_post(self): if self.check_session_schedule()==False: raise EndSession + if _check_free_food_block: return 'free_food_block' def stimulus_pre(self): # wait for bird to peck @@ -378,6 +379,14 @@ def stimulus_pre(self): self.panel.speaker.stop() self.update_adaptive_queue(presented=False) raise EndSession + elif 'free_food_schedule' in self.parameters: + if utils.check_time(self.parameters['free_food_schedule']): + self.panel.center.off() + self.panel.speaker.stop() + self.update_adaptive_queue(presented=False) + raise EndSession + else: + trial_time = self.panel.center.poll(timeout=60.0) else: trial_time = self.panel.center.poll(timeout=60.0) diff --git a/pyoperant/interfaces/pyaudio_.py b/pyoperant/interfaces/pyaudio_.py index c1222a08..82c45632 100644 --- a/pyoperant/interfaces/pyaudio_.py +++ b/pyoperant/interfaces/pyaudio_.py @@ -1,5 +1,6 @@ import pyaudio import wave +import logging from pyoperant.interfaces import base_ from pyoperant import InterfaceError @@ -42,10 +43,12 @@ def close(self): self.stream.close() except AttributeError: self.stream = None - try: + try: self.wf.close() except AttributeError: self.wf = None + except IOError: + logging.getLogger().error("IOError on _stop_wav") self.pa.terminate() def validate(self): @@ -83,7 +86,7 @@ def _stop_wav(self): self.stream.close() except AttributeError: self.stream = None - try: + try: self.wf.close() except AttributeError: self.wf = None diff --git a/scripts/pyoperantctl b/scripts/pyoperantctl index 4b0374a2..e0b4d652 100755 --- a/scripts/pyoperantctl +++ b/scripts/pyoperantctl @@ -135,8 +135,9 @@ while(scalar(@processes)>0){ $ps=pop(@processes); @info=split(" ",$ps,11); chop($info[10]); - $info[10] =~ s/^(\/usr\/bin\/python \/usr\/local\/bin\/)//; - $info[10] =~ s/^(\/usr\/local\/anaconda\/bin\/python \/usr\/local\/anaconda\/bin\/)//; + $info[10] =~ s/^(.*\/python .*\/bin\/)//; + #$info[10] =~ s/^(\/usr\/local\/anaconda\/bin\/python \/usr\/local\/anaconda\/bin\/)//; + #$info[10] =~ s/^(\/usr\/local\/anaconda\/bin\/python \/usr\/local\/anaconda\/bin\/)//; print($info[10]."\n"); if($info[10] =~ /^($psnames)/){ @pspieces=split(" ",$info[10]); From a29d2b00c249a2104754d79401fc769f3810ab6f Mon Sep 17 00:00:00 2001 From: bird Date: Sun, 4 Mar 2018 21:39:57 -0800 Subject: [PATCH 010/115] fixed return outside function --- pyoperant/behavior/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/base.py b/pyoperant/behavior/base.py index 41accb34..7d84dba5 100755 --- a/pyoperant/behavior/base.py +++ b/pyoperant/behavior/base.py @@ -217,7 +217,7 @@ def _check_free_food_block(self): if 'free_food_schedule' in self.parameters: if utils.check_time(self.parameters['free_food_schedule']): return True - return + def free_food_pre(self): self.log.debug('Buffet starting.') return 'main' From 96a67abbb220a779af203e9a558cea814ec64037 Mon Sep 17 00:00:00 2001 From: bird Date: Mon, 5 Mar 2018 09:11:29 -0800 Subject: [PATCH 011/115] fixed free food call --- pyoperant/behavior/base.py | 2 +- pyoperant/behavior/shape.py | 18 +++++++++--------- pyoperant/behavior/two_alt_choice.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyoperant/behavior/base.py b/pyoperant/behavior/base.py index 7d84dba5..582cd595 100755 --- a/pyoperant/behavior/base.py +++ b/pyoperant/behavior/base.py @@ -159,7 +159,7 @@ def _run_idle(self): if self.check_light_schedule() == False: return 'sleep' elif self.check_session_schedule(): - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' return 'session' else: self.panel_reset() diff --git a/pyoperant/behavior/shape.py b/pyoperant/behavior/shape.py index d23c6491..bee5bca7 100644 --- a/pyoperant/behavior/shape.py +++ b/pyoperant/behavior/shape.py @@ -73,7 +73,7 @@ def _hopper_block(self, block_num): def temp(): self.recent_state = block_num self.log.warning('Starting %s'%(self.block_name(block_num))) - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' utils.run_state_machine( start_in='init', error_state='wait', @@ -86,7 +86,7 @@ def temp(): reward=self.reward(5, 'check2'), check2=self._check_block('wait', 1, float('inf'))) # check if its time for free food - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' return self.block_name(block_num + 1) @@ -109,7 +109,7 @@ def temp(): reward=self.reward(4, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -138,7 +138,7 @@ def temp(): return None if not utils.check_time(self.parameters['light_schedule']): return None - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' return next_state return temp @@ -391,7 +391,7 @@ def temp(): reward=self.reward(3, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -419,7 +419,7 @@ def temp(): reward=self.reward(2.5, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -518,7 +518,7 @@ def temp(): reward=self.reward(3, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -548,7 +548,7 @@ def temp(): reward=self.reward(2.5, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: @@ -587,7 +587,7 @@ def temp(): reward=self.reward(2.5, 'check')) if not utils.check_time(self.parameters['light_schedule']): return 'sleep_block' - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' if self.responded_block: return self.block_name(block_num + 1) else: diff --git a/pyoperant/behavior/two_alt_choice.py b/pyoperant/behavior/two_alt_choice.py index a63bbb53..a2eb902c 100755 --- a/pyoperant/behavior/two_alt_choice.py +++ b/pyoperant/behavior/two_alt_choice.py @@ -363,7 +363,7 @@ def trial_post(self): if self.check_session_schedule()==False: raise EndSession - if _check_free_food_block: return 'free_food_block' + if self._check_free_food_block(): return 'free_food_block' def stimulus_pre(self): # wait for bird to peck From eec9c4a2dff8b494dacea684a6fb9b0be9774c53 Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Mon, 21 Jan 2019 11:33:09 -0800 Subject: [PATCH 012/115] Raspberry Pi Support (#116) * Raspberry pi devices * Hopper/feed * PWM interfaces * PWM houslight * Fixing houselights * minor * audio * sound works * raspi updates * raspi updates * starting development again * updates for raspi behaviors * box rev3 working * Added hostname switch for comedi extension * allsummary * parenthesis * hostname * change readme * add glab_behaviors * houselight update * emails * remove allsummary script from setup.py --- __init__.py | 0 docs/README.rst | 2 - notebooks/pyoperant.ipynb | 814 ++++++++++++---------------- pyoperant/behavior/TargObjObj.py | 122 +++++ pyoperant/behavior/text_markov.py | 432 +++++++++++++++ pyoperant/components.py | 124 ++++- pyoperant/hwio.py | 58 +- pyoperant/interfaces/pyaudio_.py | 19 +- pyoperant/interfaces/raspi_gpio_.py | 264 +++++++++ pyoperant/local.py | 2 + pyoperant/local_pi.py | 135 +++++ scripts/behave | 1 + setup.py | 11 +- 13 files changed, 1492 insertions(+), 492 deletions(-) delete mode 100644 __init__.py create mode 100755 pyoperant/behavior/TargObjObj.py create mode 100755 pyoperant/behavior/text_markov.py create mode 100644 pyoperant/interfaces/raspi_gpio_.py create mode 100644 pyoperant/local_pi.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/README.rst b/docs/README.rst index a61fd67c..966af228 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,5 +1,3 @@ -NOTICE!! This package has been renamed to "opyrant" and development has moved to https://github.com/opyrant/opyrant - pyoperant ========= diff --git a/notebooks/pyoperant.ipynb b/notebooks/pyoperant.ipynb index d8f21ca6..4a6fe1af 100644 --- a/notebooks/pyoperant.ipynb +++ b/notebooks/pyoperant.ipynb @@ -1,482 +1,358 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from pyoperant.local import PANELS\n", - "import time" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "PANELS.keys()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 3, - "text": [ - "['11',\n", - " '10',\n", - " '13',\n", - " '12',\n", - " '15',\n", - " '14',\n", - " '16',\n", - " '3',\n", - " '2',\n", - " '5',\n", - " '4',\n", - " '7',\n", - " '6',\n", - " '9',\n", - " '8']" - ] - } - ], - "prompt_number": 3 - }, + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "box_num = 6\n", - "box = PANELS['%d' % (box_num)]()\n", - "box.test()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 7, - "text": [ - "True" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box.house_light.off()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 23, - "text": [ - "True" - ] - } - ], - "prompt_number": 23 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box.reset()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 22 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box.speaker.queue('/home/bird/2sec_noise_65dbmean_48kHz.wav')\n", - "box.speaker.play()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 70 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box.speaker.stop()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 71 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box.speaker.interface._stop_wav()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 37 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "# box_num += 1\n", - "box_num = 7\n", - "print box_num\n", - "box = PANELS['Zog%d' % (box_num)]()\n", - "\n", - "try:\n", - " box.reset()\n", - "except:\n", - " pass\n", - "else:\n", - " print 'reset complete'\n", - "# box.test()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "7\n", - "reset complete" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n" - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box.house_light.off()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 14, - "text": [ - "True" - ] - } - ], - "prompt_number": 14 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from pyoperant.components import RGBLight" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "cue_light = RGBLight(red=box.outputs[7],\n", - " green=box.outputs[5],\n", - " blue=box.outputs[6]\n", - " )" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 13 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "cue_light._red.write(False)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 19, - "text": [ - "True" - ] - } - ], - "prompt_number": 19 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box_num = 11\n", - "print box_num\n", - "box = PANELS['Zog%d' % (box_num)]()\n", - "\n", - "try:\n", - " box.reset()\n", - "except:\n", - " pass\n", - "box.speaker.queue('/home/bird/opdat/B982/stims/triples_abd_48kHz.wavv.wav')\n", - "box.speaker.play()\n", - "time.sleep(1.0)\n", - "box.speaker.stop()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "InterfaceError", - "evalue": "this wav file must be 48kHz", - "output_type": "pyerr", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mInterfaceError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32mexcept\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;32mpass\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 9\u001b[1;33m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'/home/bird/2sec_noise_65dbmean.wav'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 10\u001b[0m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1.0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/hwio.pyc\u001b[0m in \u001b[0;36mqueue\u001b[1;34m(self, wav_filename)\u001b[0m\n\u001b[0;32m 93\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 94\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 95\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minterface\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 96\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 97\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/interfaces/pyaudio_.pyc\u001b[0m in \u001b[0;36m_queue_wav\u001b[1;34m(self, wav_file, start)\u001b[0m\n\u001b[0;32m 81\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 82\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mwave\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 83\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvalidate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 84\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_stream\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 85\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/local_zog.pyc\u001b[0m in \u001b[0;36mvalidate\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 34\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mTrue\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 35\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 36\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mInterfaceError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'this wav file must be 48kHz'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 37\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 38\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mInterfaceError\u001b[0m: this wav file must be 48kHz" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "11\n" - ] - } - ], - "prompt_number": 2 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box_num = 0" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 11 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "box_num = 3\n", - "print box_num\n", - "box = PANELS['Zog%d' % (box_num)]()\n", - "\n", - "# try:\n", - "# box.reset()\n", - "# except:\n", - "# pass\n", - "# box.speaker.queue('/home/bird/Desktop/test48k.wav')\n", - "box.speaker.queue('/home/bird/2sec_noise_65dbmean.wav')\n", - "box.speaker.play()\n", - "time.sleep(2.0)\n", - "box.speaker.stop()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "IOError", - "evalue": "[Errno Device unavailable] -9985", - "output_type": "pyerr", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mIOError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;31m# pass\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 9\u001b[0m \u001b[1;31m# box.speaker.queue('/home/bird/Desktop/test48k.wav')\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 10\u001b[1;33m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'/home/bird/2sec_noise_65dbmean.wav'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 11\u001b[0m \u001b[0mbox\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 12\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m2.0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/hwio.pyc\u001b[0m in \u001b[0;36mqueue\u001b[1;34m(self, wav_filename)\u001b[0m\n\u001b[0;32m 93\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 94\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mqueue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 95\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minterface\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_filename\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 96\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 97\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mplay\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/interfaces/pyaudio_.pyc\u001b[0m in \u001b[0;36m_queue_wav\u001b[1;34m(self, wav_file, start)\u001b[0m\n\u001b[0;32m 55\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 56\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_wf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mwave\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 57\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_get_stream\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 58\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 59\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_play_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/local/home/bird/Code/pyoperant/pyoperant/interfaces/pyaudio_.pyc\u001b[0m in \u001b[0;36m_get_stream\u001b[1;34m(self, start)\u001b[0m\n\u001b[0;32m 51\u001b[0m \u001b[0moutput_device_index\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdevice_index\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 52\u001b[0m \u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 53\u001b[1;33m stream_callback=_callback)\n\u001b[0m\u001b[0;32m 54\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 55\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_queue_wav\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mwav_file\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/usr/local/anaconda/lib/python2.7/site-packages/pyaudio.pyc\u001b[0m in \u001b[0;36mopen\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 745\u001b[0m \"\"\"\n\u001b[0;32m 746\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 747\u001b[1;33m \u001b[0mstream\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mStream\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 748\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_streams\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0madd\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstream\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 749\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mstream\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/usr/local/anaconda/lib/python2.7/site-packages/pyaudio.pyc\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, PA_manager, rate, channels, format, input, output, input_device_index, output_device_index, frames_per_buffer, start, input_host_api_specific_stream_info, output_host_api_specific_stream_info, stream_callback)\u001b[0m\n\u001b[0;32m 440\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 441\u001b[0m \u001b[1;31m# calling pa.open returns a stream object\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 442\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_stream\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mpa\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0marguments\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 443\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 444\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_input_latency\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_stream\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minputLatency\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mIOError\u001b[0m: [Errno Device unavailable] -9985" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "3\n" - ] - } - ], - "prompt_number": 23 - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "Box 3 is very quiet\n", - "Box 10, 14 are very loud\n", - "Box 15 doesn't work" + "name": "stdout", + "output_type": "stream", + "text": [ + "/usr/bin/python\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for ii in box.inputs:\n", - " print ii.read()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "False\n", - "False\n", - "False\n", - "False\n" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for oo in box.outputs:\n", - " print oo.write(False)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "True\n", - "True\n", - "True\n", - "True\n", - "True\n" - ] - } - ], - "prompt_number": 21 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for ii in box_1.inputs:\n", - " print ii.read()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "False\n", - "False\n", - "False\n", - "False\n" - ] - } - ], - "prompt_number": 27 - }, + } + ], + "source": [ + "import sys\n", + "print(sys.executable)\n", + "import numpy as np\n", + "import scipy as sp" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#import sys\n", + "#sys.path.append('../')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from pyoperant.local import PANELS\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "iface = box.interfaces['comedi']" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 29 - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "/home/pi/pyoperant/pyoperant/__init__.pyc\n" + ] + } + ], + "source": [ + "import pyoperant\n", + "print(pyoperant.__file__)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "box = PANELS['Zog6']()\n", - "box.reset()\n", - "box.test()" - ], - "language": "python", + "data": { + "text/plain": [ + "['1']" + ] + }, + "execution_count": 5, "metadata": {}, - "outputs": [], - "prompt_number": 3 - }, + "output_type": "execute_result" + } + ], + "source": [ + "PANELS.keys()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "box_num = 1\n", + "box = PANELS['%d' % (box_num)]()\n", + "#box.test()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "box.reward()" - ], - "language": "python", + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 17, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 36, - "text": [ - "(datetime.datetime(2014, 10, 2, 16, 16, 30, 716416),\n", - " datetime.timedelta(0, 2, 2327))" - ] - } - ], - "prompt_number": 36 - }, + "output_type": "execute_result" + } + ], + "source": [ + "box.left.status()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [] + "ename": "AttributeError", + "evalue": "'Pi1' object has no attribute 'speaker'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0mTraceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mbox\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mqueue\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'/home/bird/2sec_noise_65dbmean_48kHz.wav'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mbox\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mspeaker\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplay\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'Pi1' object has no attribute 'speaker'" + ] } ], - "metadata": {} + "source": [ + "box.speaker.queue('/home/bird/2sec_noise_65dbmean_48kHz.wav')\n", + "box.speaker.play()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box.speaker.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box.speaker.interface._stop_wav()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# box_num += 1\n", + "box_num = 7\n", + "print box_num\n", + "box = PANELS['Zog%d' % (box_num)]()\n", + "\n", + "try:\n", + " box.reset()\n", + "except:\n", + " pass\n", + "else:\n", + " print 'reset complete'\n", + "# box.test()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box.house_light.off()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pyoperant.components import RGBLight" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cue_light = RGBLight(red=box.outputs[7],\n", + " green=box.outputs[5],\n", + " blue=box.outputs[6]\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cue_light._red.write(False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box_num = 11\n", + "print box_num\n", + "box = PANELS['Zog%d' % (box_num)]()\n", + "\n", + "try:\n", + " box.reset()\n", + "except:\n", + " pass\n", + "box.speaker.queue('/home/bird/opdat/B982/stims/triples_abd_48kHz.wavv.wav')\n", + "box.speaker.play()\n", + "time.sleep(1.0)\n", + "box.speaker.stop()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box_num = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box_num = 3\n", + "print box_num\n", + "box = PANELS['Zog%d' % (box_num)]()\n", + "\n", + "# try:\n", + "# box.reset()\n", + "# except:\n", + "# pass\n", + "# box.speaker.queue('/home/bird/Desktop/test48k.wav')\n", + "box.speaker.queue('/home/bird/2sec_noise_65dbmean.wav')\n", + "box.speaker.play()\n", + "time.sleep(2.0)\n", + "box.speaker.stop()" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "Box 3 is very quiet\n", + "Box 10, 14 are very loud\n", + "Box 15 doesn't work" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for ii in box.inputs:\n", + " print ii.read()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for oo in box.outputs:\n", + " print oo.write(False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for ii in box_1.inputs:\n", + " print ii.read()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "iface = box.interfaces['comedi']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box = PANELS['Zog6']()\n", + "box.reset()\n", + "box.test()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "box.reward()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.13" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/pyoperant/behavior/TargObjObj.py b/pyoperant/behavior/TargObjObj.py new file mode 100755 index 00000000..c2eb25e3 --- /dev/null +++ b/pyoperant/behavior/TargObjObj.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jan 19 16:17:02 2016 + +@author: michael +""" + +#!/usr/bin/env python + +import os, sys, random, csv, shutil, string +import numpy as np +from pyoperant import utils, components, local, hwio +from pyoperant.behavior import two_alt_choice +import copy +import string +import pandas as pd + +try: + import simplejson as json +except ImportError: + import json + #json needs to have a + +class TargObjObj(two_alt_choice.TwoAltChoiceExp): + """docstring for Experiment""" + def __init__(self, *args, **kwargs): + + super(TargObjObj, self).__init__(*args, **kwargs) + self.starting_params = copy.deepcopy(self.parameters) # save beginning parameters to reset each session + self.clear_song_folder() # removes everything in the current song folder + self.get_conditions() # get conditions for trial in block (e.g. ABA, BAB, BBA, ...) + self.get_motifs() # builds specific motif sequences (e.g. [A12, B1, A40]) + self.build_wavs() # build wav files from the generated sequences + + def clear_song_folder(self): + """ + deletes everything in the song folder + """ + folder = self.parameters["stim_path"]+"/Generated_Songs/" + for the_file in os.listdir(folder): + file_path = os.path.join(folder, the_file) + try: + if os.path.isfile(file_path): + os.unlink(file_path) + #elif os.path.isdir(file_path): shutil.rmtree(file_path) + except Exception, e: + print e + + def get_conditions(self): + """ + generates a random 100 trial block (ABA, AAB, BAB, etc) + """ + self.trial_types = np.matrix('0; 1'); # + self.trials=[] + for i in xrange(100): + self.trials.append(random.randrange(0,2,1)) + self.trial_output = [self.parameters["category_conditions"][i]["class"] for i in self.trials] + + def get_motifs(self): + """ + 2. generate specific stim sequence e.g. [A12, B1, A40] + """ + self.song_motifs = [] + molen = self.parameters["current_available_motifs"] + stims = self.parameters["stims"] + repeats = self.parameters["repeats"] + motif_seq = self.trials + + for i in motif_seq: + thisstim = [] + + first_motif = int(random.randrange(0,molen,1)) + second_motif = int(np.random.uniform(float(first_motif)-3, float(first_motif+3)))%molen + while second_motif == first_motif: + second_motif = int(np.random.uniform(float(first_motif)-3, float(first_motif+3)))%molen + + # if self.parameters["curblock"] == "pos1": + # thisstim.append((self.parameters["lowtones"][int(np.random.uniform(0,12))], 0)) + # if self.parameters["curblock"] == "pos2": + # thisstim.append((self.parameters["hightones"][int(np.random.uniform(0,12))], 0)) + + [thisstim.append((stims[str(first_motif)], 0)) for thing in xrange(repeats)] + + if (i == 1 and self.parameters["curblock"] == "pos1") or (i == 0 and self.parameters["curblock"] == "pos2"): + #if match and pos1 is target or nonmatch and pos2 is target + [thisstim.append((stims[str(first_motif)], 0)) for thing in xrange(repeats)] + [thisstim.append((stims[str(second_motif)], 0)) for thing in xrange(repeats)] + elif (i == 1 and self.parameters["curblock"] == "pos2") or (i == 0 and self.parameters["curblock"] == "pos1"): + #if match and pos2 is target or nonmatch and pos1 is target + [thisstim.append((stims[str(second_motif)], 0)) for thing in xrange(repeats)] + [thisstim.append((stims[str(first_motif)], 0)) for thing in xrange(repeats)] + + self.song_motifs.append(thisstim) + + def build_wavs(self): + """ + Creates wav files for each trial and puts them in a destination folder + """ + for i,j in zip(self.song_motifs, self.trials): #number of trials per epoch + #jitter = random.uniform(0.05,0.1) + inputnames = ''.join(str(elem) for elem in [os.path.split(thing[0])[-1] for thing in i]) #generates a name that's just a string of the names of the files used in this seq. + song_wav = utils.concat_wav(i,output_filename=str(self.parameters["stim_path"]+"/Generated_Songs/"+inputnames+".wav")) #generates wavs (hopefully) + + self.parameters["stims"][inputnames] = self.parameters["stim_path"]+"/Generated_Songs/"+inputnames+".wav" + + cur_dict = {} + cur_dict["class"] = "L" if j==1 else "R" + cur_dict["stim_name"] = inputnames + self.parameters["block_design"]["blocks"]["default"]["conditions"].append(cur_dict) + + def session_post(self): + """ + Closes out the sessions + """ + self.parameters = copy.deepcopy(self.starting_params) # copies the original conditions every new session + self.clear_song_folder() # removes everything in the current song folder + self.get_conditions() # get conditions for trial in block (e.g. ABA, BAB, BBA, ...) + self.get_motifs() # builds specific motif sequences (e.g. [A12, B1, A40]) + self.build_wavs() # build wav files from the generated sequences + self.log.info('ending session') + self.trial_q = None + return None diff --git a/pyoperant/behavior/text_markov.py b/pyoperant/behavior/text_markov.py new file mode 100755 index 00000000..5a15a622 --- /dev/null +++ b/pyoperant/behavior/text_markov.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python +import glob +import os, sys, random, csv, shutil +import numpy as np +import datetime as dt +from pyoperant import utils, components, local, hwio +from pyoperant.behavior import two_alt_choice +# my imports +import scipy.io.wavfile as wav +from scipy import stats +import copy +import pandas as pd +from pyoperant.errors import EndSession, EndBlock +import math +import types +import time +import cPickle +sys.path.append('/home/pi/') +from sparray import * +import sys +from pyoperant import components, utils, reinf, queues + + + +try: + import simplejson as json +except ImportError: + import json + +class text_markov(two_alt_choice.TwoAltChoiceExp): + """docstring for Experiment""" + def __init__(self, *args, **kwargs): + super(text_markov, self).__init__(*args, **kwargs) + + #self.panel.speaker.interface.callback = self.callback # add a callback to audio output + self.fields_to_save += ['order', 'sequence'] # add information about the trial + self.make_data_csv() # recreate titles for csv (to include scale, etc) + self.starting_params = copy.deepcopy(self.parameters) # save beginning parameters to reset each session + self.pre_trial_cue() + + + def pre_trial_cue(self): + """ + Before each block, build a distribution + """ + clear_song_folder(folder=self.parameters["stim_path"]) # removes everything in the current song folder + self.determine_markov_orders_to_use() # look through past blocks and determine what Markov orders to play back + self.generate_sequences() # both generates markov sequences and samples from real sequences + + def determine_markov_orders_to_use(self): + """ + Stored in the json we have parameters: + num_blocks_above_thresh: how many previous blocks in a row have been above some threshold percentage for Performance + current_order: the current highest order of the markov model (potentially updated here) + pct_markov: what percent of trials to make Markov generated + pct_cur_model: the percentage of Markov trials using the current (highest order) model. + E.g. if pct_cur_model is .5 and the current order is 3, the full model will use 50% 3rd order, + 25% 2nd order, 12.5 % first order and 12.5% 0th order - default to 1, meaning that all trials are the highest order model + + + Returns a list of 100 trials markov order, vs full sequences. e.g. [0,-1,0,1,0,-1,1], where -1 is full + """ + self.current_order = self.parameters["current_order"] + # Determine the probabilities of each order model being played back + order_prob = [] + for order in np.arange(self.current_order + 1)[::-1]: # loop through each markov orders + remaining_prob = 1. - np.sum(order_prob) + if order ==0: + order_prob.append(remaining_prob) # append all of the remaining probability to the lowest order + else: + order_prob.append(remaining_prob * self.parameters["pct_cur_model"]) + + order_prob = order_prob[::-1] # flip back to go from 0th to nth order + #print(np.arange(self.current_order + 1), order_prob) + + self.trial_order_list = [] # a list of the Markov orders to use for the experiment + for trial_i in range(self.parameters["trials_per_block"]): + if np.random.binomial(1, .5): # flip a coin for markov vs readline + self.trial_order_list.append(-1) + else: + # determine markov order and append to trial_order_list + self.trial_order_list.append(np.random.choice(np.arange(self.current_order+1), p=order_prob)) + #print(order_prob) + + def generate_sequences(self): + """ + 1. Load up Markov models (up to the corresponding order) + 2. Walk through each trial and generate a sequence of length markov_seq_len + """ + # Load up Markov models - up to the max used + MMs = [load_MMs(self.parameters['experiment_path']+'/MarkovModels/'+str(order)+'.pickle') for order in np.arange(self.current_order + 1)] + # Load up true sequences + sentences = np.load(self.parameters['experiment_path']+ '/sentences_numerical.npy') + # compute the distribution of true sequence lengths + sentence_lens = [len(i) for i in sentences] + #self.syll_time_lens = [] # a list holding for each sequence the lengths of the corresponding syllables, so that we can know at what time each syllable occurs + #self.sequences = [] + for trial_num, order in enumerate(self.trial_order_list): + stim_dict = {} # a dictionary that holds onto stimuli info. eg. {"class": "L", "stim_name": "a"}, + if np.random.binomial(1, self.parameters['prob_fixed_example']): # at some probability, make the model use a fixed example + # Choose which fixed example + if order == -1: # if this is a true sentence + stim_dict[unicode("class", "utf-8")] = self.parameters['reinforce_real_side'] + seq = self.parameters['real_exemplars'][np.random.choice(self.parameters['n_exemplars'])] # chose a pregenerated real sequence + else: + stim_dict[unicode("class", "utf-8")] = self.parameters['reinforce_markov_side'] + seq = self.parameters['generated_exemplars'][np.random.choice(self.parameters['n_exemplars'])] # choose a pregenerated fake sequence + else: + if order == -1: # if this is a true sentence + stim_dict[unicode("class", "utf-8")] = self.parameters['reinforce_real_side'] # either L or R, this represents the reinforcement + seq = sentences[np.random.choice(len(sentences))] + else: # if this is a Markov generated sentence + stim_dict[unicode("class", "utf-8")] = self.parameters['reinforce_markov_side'] + sequence_length = sentence_lens[np.random.choice(len(sentence_lens))] # length of the sentence to be generated + seq = generate_MM_seq(sequence_length, MMs[:order+1], self.parameters['n_symbols'], use_tqdm=False) + seq_str = '-'.join([str(i) for i in seq]) # create a string representing the sequence + stim_dict[unicode("stim_name", "utf-8")] = seq_str + stim_dict[unicode("seq", "utf-8")] = list(seq) + stim_dict[unicode("order", "utf-8")] = str(order) + #self.sequences.append(seq) + self.parameters["stims"][seq_str] = self.parameters["stim_path"] + str(trial_num) + '.wav' + # generate the wav + + stim_dict[unicode("syll_time_lens", "utf-8")] = self.build_wav(seq, self.parameters["stim_path"] + str(trial_num) + '.wav') + + + self.parameters["block_design"]["blocks"]["default"]["conditions"].append(stim_dict) # add to the list of stims + + #print(['seqs: ']+ [i[:5] for i in self.sequences[:3]]) + #print(self.parameters["block_design"]["blocks"]["default"]["conditions"][:3]) + + + def build_wav(self, seq, wav_name): + """ + 2. Create a list of the corresponding build_wavs and concatenate them (surrounded by an isi) + 3. Save wavs to stim folder + """ + song_wav = [np.concatenate( + [wav.read(self.parameters["experiment_path"]+"/stim_lib/1201_bout_"+self.parameters["text_to_syllable"][self.parameters["num_to_char"][str(num)]]+".wav")[1] , + np.zeros(int(self.parameters["rate"]*self.parameters["isi"])) + ]) for num in seq] + syll_time_lens = [0] + list(np.cumsum([len(i)/float(self.parameters["rate"]) for i in song_wav]))+[100000] + wav.write(wav_name,self.parameters["rate"],np.concatenate(song_wav).astype('int16')) + return syll_time_lens + + def session_post(self): + """ Closes out the sessions + """ + self.parameters = copy.deepcopy(self.starting_params) # copies the original conditions every new session + + self.pre_trial_cue() # build a new trial + + self.log.info('ending session') + self.trial_q = None + # get a new distrubtion ready for the next session + ###### NEEDS TO BE FIXED ###### + #self.determine_stim_distribution() # determines distribution scale for motifs + return None + + def run_trial(self): + self.trial_pre() # adds time to log ##### WILL NEED TO CHANGE MIN_WAIT, MAX_WAIT + + self.stimulus_pre() # Poll for center peck, append info, turn on peck ports + + self.stimulus_main() # repeatedly play stimulus + + self.response_post() # turns off ports + + self.consequence_pre() + self.consequence_main() + self.consequence_post() + + self.trial_post() + + + def stimulus_pre(self): + """ + Prepares stimuli to be presented + ADDED: turn on peck ports + """ + + # wait for bird to peck + self.log.debug("presenting stimulus %s" % self.this_trial.stimulus) + self.log.debug("from file %s" % self.this_trial.stimulus_event.file_origin) + self.log.debug('waiting for peck...') + self.panel.center.on() + trial_time = None + #print(self.check_session_schedule()) + while trial_time is None: + + if self.check_session_schedule()==False: + self.panel.center.off() + self.panel.speaker.stop() + self.update_adaptive_queue(presented=False) + raise EndSession + else: + trial_time = self.panel.center.poll(timeout=60.0) + + self.this_trial.time = trial_time + self.this_trial.sequence = self.this_trial.annotations['seq']#self.sequences[self.this_trial.index] + self.this_trial.order = self.this_trial.annotations['order'] #self.trial_order_list[self.this_trial.index] + self.this_trial.syll_time_lens = self.this_trial.annotations['syll_time_lens'] #self.trial_order_list[self.this_trial.index] + #print(vars(self.this_trial)) + #print(self.this_trial.sequence) + #print(self.this_trial.index) + + #print('stim_name: ' + ''.join([self.parameters["num_to_char"][str(i)] for i in self.this_trial.annotations['stim_name'].split('-')])) + #print('sequence: ' + ''.join([self.parameters["num_to_char"][str(num)] for num in self.this_trial.sequence])) + self.log.info('sequence: ' + ''.join([self.parameters["num_to_char"][str(num)] for num in self.this_trial.sequence])) + + #print(breakme) + self.panel.center.off() + self.this_trial.events.append(utils.Event(name='center', + label='peck', + time=0.0, + ) + ) + + # record trial initiation + self.summary['trials'] += 1 + self.summary['last_trial_time'] = self.this_trial.time.ctime() + self.log.info("trial started at %s" % self.this_trial.time.ctime()) + + #print(vars(self.this_trial)) + #self.this_trial.scale = self.scale # set distribution scale each trial (so that the output can see it) + #self.this_trial.left_pecks = 0 + #self.this_trial.right_pecks = 0 + #self.this_trial.center_pecks = 0 + #self.log.debug("scale: %s" % self.scale) + + + def stimulus_main(self): + """ + Continuously plays stimulus until the correct port port is pecked + """ + self.log.debug("stimulus main") + self.this_trial.response = 'none' + # the length of the stimulus in seconds + stim_length_s = self.this_trial.stimulus_event.annotations['annotations']['nframes'] / \ + float(self.this_trial.stimulus_event.annotations['annotations']['framerate']) + + self.stim_start = dt.datetime.now() + wait_period = .0075 # number of seconds to wait between checking ports + self.this_trial.stimulus_event.time = (self.stim_start - self.this_trial.time).total_seconds() # Not sure what this does... + self.this_trial.correct = False #### Make sure this is OK with no response trials + self.this_trial.incorrect = False + self.this_trial.early_end = False + + self.panel.speaker.queue(self.this_trial.stimulus_event.file_origin) # que stimulus + self.panel.speaker.play() # play stimulus + #self.log.info(self.parameters["num_to_char"][str(self.this_trial.sequence[cur_sym])]) + + # wait to start checking ports until a threshold time is set + #cur_sym = 0 # this is for printing out one symbol at a time + while True: + if ( dt.datetime.now() - self.stim_start).total_seconds() > self.parameters["min_seconds_before_peck"]: + """if (((dt.datetime.now()- self.stim_start).total_seconds() > self.this_trial.syll_time_lens[cur_sym]) \ + & (cur_sym self.this_trial.syll_time_lens[cur_sym]) \ + & (cur_sym 0: + break + order -=1 + return prob_next + +def generate_MM_seq(sequence_length, MMs, n_elements, use_tqdm=True): + """ + Generates a sequence of length `sequence_length` given a transition matrix + """ + #print('prepping seq list') + sequence = np.zeros(sequence_length) + #print('prepping iter') + seqlist = tqdm(range(sequence_length), leave=False) if use_tqdm else range(sequence_length) + for i in seqlist: + prediction = MM_make_prediction(prev_seq = np.array(sequence)[:i], + MMs = MMs, + n_elements = n_elements) + sequence[i] = np.random.choice(len(prediction), p=prediction, + size = 1)[0] + return sequence.astype('int') + +def change_json_parameter(file_location, param, value): + """ + changes parameter on a JSON + warning: Using this function will reorder your json. + """ + jsonFile = open(file_location, "r") + data = json.load(jsonFile) + data[param] = value + jsonFile = open(file_location, "w+") + jsonFile.write(json.dumps(data, indent=4, sort_keys=True)) + jsonFile.close() + +def find_json_param(file_location, param): + """ + goes into a json a finds a parameter value + """ + jsonFile = open(file_location, "r") + data = json.load(jsonFile) + param_value = data[param] + return(param_value) + + +def clear_song_folder(folder= 'Generated_Songs/'): + """ + deletes everything in the song folder + """ + for the_file in os.listdir(folder): + file_path = os.path.join(folder, the_file) + #try: + if os.path.isfile(file_path): + os.unlink(file_path) + #elif os.path.isdir(file_path): shutil.rmtree(file_path) + #except Exception, e: + #self.log.error(e) + + +def load_MMs(loc): + with open(loc, 'rb') as f: + return cPickle.load(f) + + +def save_MMs(MMs, loc): + with open(loc, "wb") as f: + cPickle.dump(MMs, f) diff --git a/pyoperant/components.py b/pyoperant/components.py index cef3f698..75474985 100644 --- a/pyoperant/components.py +++ b/pyoperant/components.py @@ -1,5 +1,8 @@ import datetime from pyoperant import hwio, utils, ComponentError +import logging + +logger = logging.getLogger() class BaseComponent(object): """Base class for physcal component""" @@ -52,7 +55,7 @@ class Hopper(BaseComponent): time in seconds to wait before checking to make sure the hopper is up """ - def __init__(self,IR,solenoid,max_lag=0.3,*args,**kwargs): + def __init__(self,IR,solenoid,max_lag=0.3, inverted=False,*args,**kwargs): super(Hopper, self).__init__(*args,**kwargs) self.max_lag = max_lag if isinstance(IR,hwio.BooleanInput): @@ -63,6 +66,10 @@ def __init__(self,IR,solenoid,max_lag=0.3,*args,**kwargs): self.solenoid = solenoid else: raise ValueError('%s is not an output channel' % solenoid) + if inverted: + self.inverted=True + else: + self.inverted=False def check(self): """reads the status of solenoid & IR beam, then throws an error if they don't match @@ -80,7 +87,10 @@ def check(self): The Hopper is down and it shouldn't be. (The IR beam is not tripped, but the solenoid is active.) """ + return True IR_status = self.IR.read() + if self.inverted: + IR_status = not IR_status solenoid_status = self.solenoid.read() if IR_status != solenoid_status: if IR_status: @@ -110,7 +120,7 @@ def up(self): time_up = self.IR.poll(timeout=self.max_lag) if time_up is None: # poll timed out - self.solenoid.write(False) + #self.solenoid.write(False) raise HopperWontComeUpError else: return time_up @@ -161,6 +171,7 @@ def feed(self,dur=2.0,error_check=True): The Hopper did not drop fater the feed. """ + logger.debug("Feeding..") assert self.max_lag < dur, "max_lag (%ss) must be shorter than duration (%ss)" % (self.max_lag,dur) try: self.check() @@ -197,7 +208,7 @@ class PeckPort(BaseComponent): input channel for the IR beam to check for a peck """ - def __init__(self,IR,LED,*args,**kwargs): + def __init__(self,IR,LED, inverted=False,*args,**kwargs): super(PeckPort, self).__init__(*args,**kwargs) if isinstance(IR,hwio.BooleanInput): self.IR = IR @@ -205,8 +216,16 @@ def __init__(self,IR,LED,*args,**kwargs): raise ValueError('%s is not an input channel' % IR) if isinstance(LED,hwio.BooleanOutput): self.LED = LED + self.LEDtype = "boolean" + if isinstance(LED, hwio.PWMOutput): + self.LED = LED + self.LEDtype = "pwm" else: raise ValueError('%s is not an output channel' % LED) + if inverted: + self.inverted=True + else: + self.inverted=False def status(self): """reads the status of the IR beam @@ -216,6 +235,8 @@ def status(self): bool True if beam is broken """ + if self.inverted: + return not self.IR.read() return self.IR.read() def off(self): @@ -226,10 +247,13 @@ def off(self): bool True if successful """ - self.LED.write(False) + if self.LEDtype == "boolean": + self.LED.write(False) + else: + self.LED.write(0.0); return True - def on(self): + def on(self, val=100.0): """Turns the LED on Returns @@ -237,7 +261,10 @@ def on(self): bool True if successful """ - self.LED.write(True) + if self.LEDtype == "boolean": + self.LED.write(True) + else: + self.LED.write(val); return True def flash(self,dur=1.0,isi=0.1): @@ -423,7 +450,92 @@ def off(self): self._blue.write(False) return True +## House Light ## +class LEDStripHouseLight(BaseComponent): + """ Class which holds information about the RGBW LED Strip PWM house light + + Keywords + -------- + light : hwio.PWMOutputs + [R, G, B, W] + output channels to turn the light on and off + + Methods: + on() -- + off() -- + set_color() -- set the color + change_color -- sets color and turns on light + timeout(dur) -- turns off the house light for 'dur' seconds (default=10.0) + punish() -- calls timeout() for 'value' as 'dur' + + """ + def __init__(self,lights,color=[100.0,100.0,100.0,100.0],*args,**kwargs): + super(LEDStripHouseLight, self).__init__(*args,**kwargs) + self.lights = [] + for light in lights: + if isinstance(light,hwio.PWMOutput): + self.lights.append(light) + else: + raise ValueError('%s is not an output channel' % light) + self.color = color + + def off(self): + """Turns the house light off. + + Returns + ------- + bool + True if successful. + + """ + for light in self.lights: + light.write(0.0) + return True + + def on(self): + """Turns the house light on. + + Returns + ------- + bool + True if successful. + """ + for ind in range(4): + self.lights[ind].write(self.color[ind]) + return True + + def timeout(self,dur=10.0): + """Turn off the light for *dur* seconds + + Keywords + ------- + dur : float, optional + The amount of time (in seconds) to turn off the light. + + Returns + ------- + (datetime, float) + Timestamp of the timeout and the timeout duration + + """ + timeout_time = datetime.datetime.now() + self.off() + utils.wait(dur) + timeout_duration = datetime.datetime.now() - timeout_time + self.on() + return (timeout_time,timeout_duration) + + def punish(self,value=10.0): + """Calls `timeout(dur)` with *value* as *dur* """ + return self.timeout(dur=value) + + def set_color(self, color): + self.color = color + def change_color(self, color): + self.color = color + self.on() + # ## Perch ## # class Perch(BaseComponent): diff --git a/pyoperant/hwio.py b/pyoperant/hwio.py index e5702ab0..5421151c 100644 --- a/pyoperant/hwio.py +++ b/pyoperant/hwio.py @@ -1,4 +1,5 @@ - +import time +import datetime # Classes of operant components class BaseIO(object): """any type of IO device. maintains info on interface for query IO device""" @@ -24,7 +25,17 @@ def __init__(self,interface=None,params={},*args,**kwargs): assert hasattr(self.interface,'_read_bool') self.config() + def _clbk(self, gpio, level, tick): + self.last_time = datetime.datetime.now() + self.tally += 1 + def config(self): + self.tally = 0 + try: + self.interface._callback(func=self._clbk, **self.params) + except: + print('callback error') + return False try: return self.interface._config_read(**self.params) except AttributeError: @@ -36,8 +47,18 @@ def read(self): def poll(self,timeout=None): """ runs a loop, querying for pecks. returns peck time or "GoodNite" exception """ + #orig = self.tally + #if timeout is not None: + # start = time.time() + #while time.time() - start < timeout: + # if self.tally - orig > 0: + # return datetime.datetime.now() + #return None return self.interface._poll(timeout=timeout,**self.params) + def callback(self, func): + return self.interface._callback(func=func, **self.params) + class BooleanOutput(BaseIO): """Class which holds information about outputs and abstracts the methods of writing to them @@ -114,8 +135,43 @@ def play(self): def stop(self): return self.interface._stop_wav() +class PWMOutput(BaseIO): + """Class which abstracts the writing to PWM outputs + + Keyword arguments: + interface -- Interface() instance. Must have '_write_bool' method. + params -- dictionary of keyword:value pairs needed by the interface + + Methods: + write(value) -- writes a value to the output. Returns the value + read() -- if the interface supports '_read_bool' for this output, returns + the current value of the output from the interface. Otherwise this + returns the last passed by write(value) + """ + def __init__(self,interface=None,params={},*args,**kwargs): + super(PWMOutput, self).__init__(interface=interface,params=params,*args,**kwargs) + + assert hasattr(self.interface,'_write_pwm') + self.last_value = None + self.config() + def config(self): + self.write(0.0) + return True + def read(self): + """read status""" + return self.last_value + + def write(self,val=0.0): + """write status""" + self.last_value = self.interface._write_pwm(value=val, **self.params) + return self.last_value + def toggle(self): + """ flip value """ + new_val = abs(100.0 - self.last_value) + self.write(new_val) + return new_val diff --git a/pyoperant/interfaces/pyaudio_.py b/pyoperant/interfaces/pyaudio_.py index 82c45632..73d1c85a 100644 --- a/pyoperant/interfaces/pyaudio_.py +++ b/pyoperant/interfaces/pyaudio_.py @@ -27,16 +27,16 @@ def __init__(self,device_name='default',*args,**kwargs): def open(self): self.pa = pyaudio.PyAudio() - for index in range(self.pa.get_device_count()): - if self.device_name == self.pa.get_device_info_by_index(index)['name']: - self.device_index = index - break - else: - self.device_index = None - if self.device_index == None: - raise InterfaceError('could not find pyaudio device %s' % (self.device_name)) + #for index in range(self.pa.get_device_count()): + # if self.device_name == self.pa.get_device_info_by_index(index)['name']: + # self.device_index = index + # break + # else: + # self.device_index = None + #if self.device_index == None: + # raise InterfaceError('could not find pyaudio device %s' % (self.device_name)) - self.device_info = self.pa.get_device_info_by_index(self.device_index) + #self.device_info = self.pa.get_device_info_by_index(self.device_index) def close(self): try: @@ -69,7 +69,6 @@ def callback(in_data, frame_count, time_info, status): channels=self.wf.getnchannels(), rate=self.wf.getframerate(), output=True, - output_device_index=self.device_index, start=start, stream_callback=callback) diff --git a/pyoperant/interfaces/raspi_gpio_.py b/pyoperant/interfaces/raspi_gpio_.py new file mode 100644 index 00000000..b368f411 --- /dev/null +++ b/pyoperant/interfaces/raspi_gpio_.py @@ -0,0 +1,264 @@ +import time +import datetime +import logging +#import RPi.GPIO as GPIO + +from pyoperant.interfaces import base_ +from pyoperant import utils, InterfaceError + +logger = logging.getLogger(__name__) + +#!/usr/bin/env python + +# PCA9685.py +# 2016-01-31 +# Public Domain + +import time + +import pigpio + +PCA9685_ADDRESS = 0x55 + +class PWM: + + """ + This class provides an interface to the I2C PCA9685 PWM chip. + + The chip provides 16 PWM channels. + + All channels use the same frequency which may be set in the + range 24 to 1526 Hz. + + If used to drive servos the frequency should normally be set + in the range 50 to 60 Hz. + + The duty cycle for each channel may be independently set + between 0 and 100%. + + It is also possible to specify the desired pulse width in + microseconds rather than the duty cycle. This may be more + convenient when the chip is used to drive servos. + + The chip has 12 bit resolution, i.e. there are 4096 steps + between off and full on. + """ + + _MODE1 = 0x00 + _MODE2 = 0x01 + _SUBADR1 = 0x02 + _SUBADR2 = 0x03 + _SUBADR3 = 0x04 + _PRESCALE = 0xFE + _LED0_ON_L = 0x06 + _LED0_ON_H = 0x07 + _LED0_OFF_L = 0x08 + _LED0_OFF_H = 0x09 + _ALL_LED_ON_L = 0xFA + _ALL_LED_ON_H = 0xFB + _ALL_LED_OFF_L = 0xFC + _ALL_LED_OFF_H = 0xFD + + _RESTART = 1<<7 + _AI = 1<<5 + _SLEEP = 1<<4 + _ALLCALL = 1<<0 + + _OCH = 1<<3 + _OUTDRV = 1<<2 + + def __init__(self, pi, bus=1, address=0x40): + + self.pi = pi + self.bus = bus + self.address = address + + self.h = pi.i2c_open(bus, address) + + self._write_reg(self._MODE1, self._AI | self._ALLCALL) + self._write_reg(self._MODE2, self._OCH | self._OUTDRV) + + time.sleep(0.0005) + + mode = self._read_reg(self._MODE1) + self._write_reg(self._MODE1, mode & ~self._SLEEP) + + time.sleep(0.0005) + + self.set_duty_cycle(-1, 0) + self.set_frequency(200) + + def get_frequency(self): + + "Returns the PWM frequency." + + return self._frequency + + def set_frequency(self, frequency): + + "Sets the PWM frequency." + + prescale = int(round(25000000.0 / (4096.0 * frequency)) - 1) + + if prescale < 3: + prescale = 3 + elif prescale > 255: + prescale = 255 + + mode = self._read_reg(self._MODE1); + self._write_reg(self._MODE1, (mode & ~self._SLEEP) | self._SLEEP) + self._write_reg(self._PRESCALE, prescale) + self._write_reg(self._MODE1, mode) + + time.sleep(0.0005) + + self._write_reg(self._MODE1, mode | self._RESTART) + + self._frequency = (25000000.0 / 4096.0) / (prescale + 1) + self._pulse_width = (1000000.0 / self._frequency) + + def set_duty_cycle(self, channel, percent): + + "Sets the duty cycle for a channel. Use -1 for all channels." + + steps = int(round(percent * (4096.0 / 100.0))) + + if steps < 0: + on = 0 + off = 4096 + elif steps > 4095: + on = 4096 + off = 0 + else: + on = 0 + off = steps + + if (channel >= 0) and (channel <= 15): + self.pi.i2c_write_i2c_block_data(self.h, self._LED0_ON_L+4*channel, + [on & 0xFF, on >> 8, off & 0xFF, off >> 8]) + + else: + self.pi.i2c_write_i2c_block_data(self.h, self._ALL_LED_ON_L, + [on & 0xFF, on >> 8, off & 0xFF, off >> 8]) + + def set_pulse_width(self, channel, width): + + "Sets the pulse width for a channel. Use -1 for all channels." + + self.set_duty_cycle(channel, (float(width) / self._pulse_width) * 100.0) + + def cancel(self): + + "Switches all PWM channels off and releases resources." + + self.set_duty_cycle(-1, 0) + self.pi.i2c_close(self.h) + + def _write_reg(self, reg, byte): + self.pi.i2c_write_byte_data(self.h, reg, byte) + + def _read_reg(self, reg): + return self.pi.i2c_read_byte_data(self.h, reg) + + +# Raspberry Pi GPIO Interface for Pyoperant + +class RaspberryPiInterface(base_.BaseInterface): + """ Opens Raspberry Pi GPIO ports for operant interface """ + + def __init__(self, device_name, inputs=None, outputs=None, *args, **kwargs): + super(RaspberryPiInterface, self).__init__(*args, **kwargs) + + self.device_name = device_name + self.pi = pigpio.pi(port=7777) + + if not self.pi.connected: + logger.debug("PIGPIO Not Connected...") + + self.open() + self.inputs = [] + self.outputs = [] + + if inputs is not None: + for input_ in inputs: + self._config_read(*input_) + if outputs is not None: + for output in outputs: + self._config_write(output) + + def __str__(self): + return "Raspberry Pi device at %s" % (self.device_name) + + def open(self): + logger.debug("Opening device %s") + #GPIO.setmode(GPIO.BCM) + # Setup PWM + self.pwm = PWM(self.pi, address=PCA9685_ADDRESS) + self.pwm.set_frequency(240) + + + def close(self): + logger.debug("Closing %s") + self.pi.stop() + + def _config_read(self, channel, **kwargs): + self.pi.set_mode(channel, pigpio.INPUT) + + def _config_write(self, channel, **kwargs): + self.pi.set_mode(channel, pigpio.OUTPUT) + + def _read_bool(self, channel, **kwargs): + while True: + try: + v = self.pi.read(channel) + break + except: + RaspberryPiException("Could not read GPIO") + + return v == 1 + + def _write_bool(self, channel, value, **kwargs): + if value: + self.pi.write(channel, 1) + else: + self.pi.write(channel, 0) + + def _write_pwm(self, channel, value, **kwargs): + self.pwm.set_duty_cycle(channel, value) + return value + + def _poll2(self, channel, timeout=None, suppress_longpress=True, **kwargs): + ''' runs a loop, querying for transitions ''' + date_fmt = '%Y-%m-%d %H:%M:%S.%f' + cb1 = self.pi.callback(channel, pigpio.RISING_EDGE) + if timeout is not None: + start = time.time() + print('Starting poll') + while True: + if cb1.tally() > 0: + print(cb1.tally()) + cb1.reset_tally() + cb1.cancel() + return datetime.datetime.now() + if timeout is not None: + if time.time() - start >= timeout: + cb1.reset_tally() + cb1.cancel() + return None + + def _poll(self, channel, timeout=None, suppress_longpress=True, **kwargs): + date_fmt = '%Y-%m-%d %H:%M:%S.%f' + if timeout is not None: + start = time.time() + if self.pi.wait_for_edge(channel, pigpio.RISING_EDGE, timeout): + return datetime.datetime.now() + else: + return None + + def _callback(self, channel, func=None, **kwargs): + date_fmt = '%Y-%m-%d %H:%M:%S.%f' + if func: + return self.pi.callback(channel, pigpio.RISING_EDGE, func) + else: + return self.pi.callback(channel, pigpio.RISING_EDGE) + diff --git a/pyoperant/local.py b/pyoperant/local.py index 8e52526e..69eecaa5 100644 --- a/pyoperant/local.py +++ b/pyoperant/local.py @@ -6,3 +6,5 @@ from local_vogel import * elif 'zog' in hostname: from local_zog import * +elif 'pi' in hostname: + from local_pi import * diff --git a/pyoperant/local_pi.py b/pyoperant/local_pi.py new file mode 100644 index 00000000..e6914ea6 --- /dev/null +++ b/pyoperant/local_pi.py @@ -0,0 +1,135 @@ +from pyoperant import hwio, components, panels, utils +from pyoperant.interfaces import raspi_gpio_, pyaudio_ +from pyoperant import InterfaceError +import time + +INPUTS = [5, # Hopper IR + 6, # Left IR + 13, # Center IR + 26, # Right IR + 23, # IR 1 + 24, # IR 2 + 25, # IR 3 + 9, # IR 4 + 11, # IR 5 + 10, # IR 6 + ] + +OUTPUTS = [16, # Hopper Trigger + ] + + +PWM_OUTPUTS = [0, #Red + 1, #Green + 2, #Blue + 3, #White + 4, #Left + 5, #Center + 6, #Right + 7, #LED 1 + 8, #LED 2 + 9, #LED 3 + 10, #LED 4 + 11, #LED 5 + 12, #LED 6 + 13, #RGB Cue R + 14, #RGB Cue G + 15 #RGB Cue B + ] + +class PiPanel(panels.BasePanel): + """class for zog boxes """ + def __init__(self,id=None, *args, **kwargs): + super(PiPanel, self).__init__(*args, **kwargs) + self.id = id + self.pwm_outputs = [] + + # define interfaces + self.interfaces['raspi_gpio_'] = raspi_gpio_.RaspberryPiInterface(device_name='zog0') + self.interfaces['pyaudio'] = pyaudio_.PyAudioInterface() + + # define inputs + for in_chan in INPUTS: + self.inputs.append(hwio.BooleanInput(interface=self.interfaces['raspi_gpio_'], + params = {'channel': in_chan})) + for out_chan in OUTPUTS: + self.outputs.append(hwio.BooleanOutput(interface=self.interfaces['raspi_gpio_'], + params = {'channel': out_chan})) + + for pwm_out_chan in PWM_OUTPUTS: + self.pwm_outputs.append(hwio.PWMOutput(interface=self.interfaces['raspi_gpio_'], + params = {'channel': pwm_out_chan})) + + self.speaker = hwio.AudioOutput(interface=self.interfaces['pyaudio']) + + # assemble inputs into components + # Standard Peckports + self.left = components.PeckPort(IR=self.inputs[1],LED=self.pwm_outputs[4],name='l', inverted=False) + self.center = components.PeckPort(IR=self.inputs[2],LED=self.pwm_outputs[5],name='c', inverted=False) + self.right = components.PeckPort(IR=self.inputs[3],LED=self.pwm_outputs[6],name='r', inverted=False) + + # Hopper + self.hopper = components.Hopper(IR=self.inputs[0],solenoid=self.outputs[0], inverted=True) + + + # House Light + self.house_light = components.LEDStripHouseLight(lights=[self.pwm_outputs[0], + self.pwm_outputs[1], + self.pwm_outputs[2], + self.pwm_outputs[3]]) + # define reward & punishment methods + self.reward = self.hopper.reward + self.punish = self.house_light.punish + + def reset(self): + for output in self.outputs: + output.write(False) + self.house_light.on() + self.hopper.down() + # self.speaker.stop() + + def test(self): + self.reset() + dur = 2.0 + for output in self.outputs: + output.write(True) + utils.wait(dur) + output.write(False) + self.reset() + self.reward(value=dur) + self.punish(value=dur) + self.speaker.queue('/home/pi/test.wav') + self.speaker.play() + time.sleep(1.0) + self.speaker.stop() + return True + + + +class Pi1(PiPanel): + """ Pi1panel""" + def __init__(self): + super(Pi1, self).__init__(id=1) + + +PANELS = { + "1": Pi1, + } + +BEHAVIORS = ['pyoperant.behavior', + 'glab_behaviors' + ] + +DATA_PATH = '/home/pi/opdat/' + +# SMTP_CONFIG + +DEFAULT_EMAIL = 'bradtheilman@gmail.com' + +SMTP_CONFIG = {'mailhost': 'localhost', + 'toaddrs': [DEFAULT_EMAIL], + 'fromaddr': 'bird@magpi.ucsd.edu', + 'subject': '[pyoperant notice] on magpi', + 'credentials': None, + 'secure': None, + } diff --git a/scripts/behave b/scripts/behave index a2bbf3f9..a3122142 100755 --- a/scripts/behave +++ b/scripts/behave @@ -109,6 +109,7 @@ def main(): parameters = {} BehaviorProtocol = find_protocol(cmd_line['protocol']) + print(parameters) if ('debug' in parameters) and parameters['debug']: print parameters diff --git a/setup.py b/setup.py index 6e7523f7..866e0032 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,17 @@ from distutils.core import setup, Extension import os +import socket + +hnm = socket.gethostname() # def read(fname): # return open(os.path.join(os.path.dirname(__file__), fname)).read() -comedi_poll = Extension('comedi_poll', - # include_dirs = ['/usr/local/include'], +if 'zog' in hnm: + comedi_poll = Extension('comedi_poll', + include_dirs = ['/usr/local/include'], libraries = ['comedi'], - # library_dirs = ['/usr/local/lib'], + library_dirs = ['/usr/local/lib'], sources = ['src/comedi_poll.c']) setup( @@ -22,7 +26,6 @@ scripts = [ 'scripts/behave', 'scripts/pyoperantctl', - 'scripts/allsummary.py', ], license = "BSD", classifiers = [ From 20491f98519fb2a6b969eb3e937268055627f3c9 Mon Sep 17 00:00:00 2001 From: acmai Date: Fri, 25 Jan 2019 16:34:58 -0800 Subject: [PATCH 013/115] Update conf.py --- docs/conf.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 3b59f4b5..19878bc7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -110,7 +110,18 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +#html_theme_options = { + 'logo_only': False, + 'display_version': True, + 'prev_next_buttons_location': 'bottom', + 'style_external_links': False, + 'vcs_pageview_mode': '', + # Toc options + 'collapse_navigation': True, + 'sticky_navigation': True, + 'navigation_depth': 4, + 'includehidden': True, + 'titles_only': False} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] From 7d2bfc55aa1697473d15df6b9630baf85766445c Mon Sep 17 00:00:00 2001 From: acmai Date: Fri, 25 Jan 2019 16:43:40 -0800 Subject: [PATCH 014/115] Update index.rst --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index ff751ba0..caf8fb66 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ Welcome to pyoperant's documentation! ===================================== -.. include:: ../README.rst +.. include:: README.rst Contents ======== From 14fb100835e5fe11a0bd3e983425c7dd18cff88a Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 11:08:27 -0800 Subject: [PATCH 015/115] Update conf.py --- docs/conf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 19878bc7..be698fba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -110,7 +110,7 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = { +html_theme_options = { 'logo_only': False, 'display_version': True, 'prev_next_buttons_location': 'bottom', @@ -121,7 +121,8 @@ 'sticky_navigation': True, 'navigation_depth': 4, 'includehidden': True, - 'titles_only': False} + 'titles_only': False +} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] From 3fa188d4bb0ebec1d45284aa62f6a26ae84001ee Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 11:23:27 -0800 Subject: [PATCH 016/115] Update index.rst --- docs/index.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index caf8fb66..f5600819 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,9 @@ Contents :maxdepth: 4 pyoperant + + The Interfaces Package + The Behavior Package Indices and tables From 2e374778806c22201c97ed58b361a932b9143ce4 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 11:34:59 -0800 Subject: [PATCH 017/115] Update index.rst --- docs/index.rst | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index f5600819..ccc6ebca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,9 +10,18 @@ Contents :maxdepth: 4 pyoperant - - The Interfaces Package - The Behavior Package + The Interfaces Sub-Package + base module + comedi module + console module + pyaudio module + spike2 module + The Behavior Sub-Package + base module + lights module + shape module + three_ac_matching module + two_alt_choice module Indices and tables From e50444559315864d2befd600b652116ce3c61189 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 11:42:02 -0800 Subject: [PATCH 018/115] Update pyoperant.rst --- docs/pyoperant.rst | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/pyoperant.rst b/docs/pyoperant.rst index a4db4f21..67ae4c76 100644 --- a/docs/pyoperant.rst +++ b/docs/pyoperant.rst @@ -6,25 +6,24 @@ Subpackages .. toctree:: - pyoperant.interfaces - pyoperant.behavior + The Interfaces Subpackage + The Behavior Subpackage Submodules ---------- .. toctree:: - pyoperant.behavior - pyoperant.components - pyoperant.errors - pyoperant.hwio - pyoperant.local - pyoperant.local_vogel - pyoperant.local_zog - pyoperant.panels - pyoperant.queues - pyoperant.reinf - pyoperant.utils + components submodule + errors submodule + hwio submodule + local submodule + local_vogel submodule + local_zog submodule + panels submodule + queues submodule + reinf submodule + utils submodule Module contents --------------- From 4945b7be909eb5d29eb1e907b39fbc81fdc1e192 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 11:42:38 -0800 Subject: [PATCH 019/115] Update index.rst --- docs/index.rst | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index ccc6ebca..caf8fb66 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,18 +10,6 @@ Contents :maxdepth: 4 pyoperant - The Interfaces Sub-Package - base module - comedi module - console module - pyaudio module - spike2 module - The Behavior Sub-Package - base module - lights module - shape module - three_ac_matching module - two_alt_choice module Indices and tables From b09f2736e3f9d2f5a49c10912e14eb98b2367e2c Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 12:30:45 -0800 Subject: [PATCH 020/115] Update pyoperant.behavior.rst --- docs/pyoperant.behavior.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pyoperant.behavior.rst b/docs/pyoperant.behavior.rst index 5a5d948d..3667e31e 100644 --- a/docs/pyoperant.behavior.rst +++ b/docs/pyoperant.behavior.rst @@ -6,11 +6,11 @@ Submodules .. toctree:: - pyoperant.behavior.base - pyoperant.behavior.lights - pyoperant.behavior.shape - pyoperant.behavior.three_ac_matching - pyoperant.behavior.two_alt_choice + base + lights + shape + three_ac_matching + two_alt_choice Module contents --------------- From 5d0f5eb30ca5465859ebc0f9564373e851b58957 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 12:31:46 -0800 Subject: [PATCH 021/115] Update pyoperant.interfaces.rst --- docs/pyoperant.interfaces.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pyoperant.interfaces.rst b/docs/pyoperant.interfaces.rst index 1b608a01..f5c32ebd 100644 --- a/docs/pyoperant.interfaces.rst +++ b/docs/pyoperant.interfaces.rst @@ -6,11 +6,11 @@ Submodules .. toctree:: - pyoperant.interfaces.base_ - pyoperant.interfaces.comedi_ - pyoperant.interfaces.console_ - pyoperant.interfaces.pyaudio_ - pyoperant.interfaces.spike2_ + base + comedi + console + pyaudio + spike2 Module contents --------------- From fbd2f35f89cd9d34fb14179a1d7bf428d0333773 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 12:33:08 -0800 Subject: [PATCH 022/115] Update pyoperant.interfaces.rst --- docs/pyoperant.interfaces.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pyoperant.interfaces.rst b/docs/pyoperant.interfaces.rst index f5c32ebd..75dac94e 100644 --- a/docs/pyoperant.interfaces.rst +++ b/docs/pyoperant.interfaces.rst @@ -6,11 +6,11 @@ Submodules .. toctree:: - base - comedi - console - pyaudio - spike2 + base + comedi + console + pyaudio + spike2 Module contents --------------- From 098297ff0b87a0ec5968e632f9b80fc094f35d99 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 12:37:57 -0800 Subject: [PATCH 023/115] Update pyoperant.rst --- docs/pyoperant.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pyoperant.rst b/docs/pyoperant.rst index 67ae4c76..9e42c710 100644 --- a/docs/pyoperant.rst +++ b/docs/pyoperant.rst @@ -9,7 +9,7 @@ Subpackages The Interfaces Subpackage The Behavior Subpackage -Submodules +Modules ---------- .. toctree:: From 5902b930072fcc43625e31af9c9214f16251db91 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 12:38:53 -0800 Subject: [PATCH 024/115] Update pyoperant.rst --- docs/pyoperant.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/pyoperant.rst b/docs/pyoperant.rst index 9e42c710..cfcf72c6 100644 --- a/docs/pyoperant.rst +++ b/docs/pyoperant.rst @@ -14,16 +14,16 @@ Modules .. toctree:: - components submodule - errors submodule - hwio submodule - local submodule - local_vogel submodule - local_zog submodule - panels submodule - queues submodule - reinf submodule - utils submodule + components + errors + hwio + local + local_vogel + local_zog + panels + queues + reinf + utils Module contents --------------- From ee6a3aff920d27cf923ea1aec489972475a04769 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 12:49:36 -0800 Subject: [PATCH 025/115] Update lights.py --- pyoperant/behavior/lights.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyoperant/behavior/lights.py b/pyoperant/behavior/lights.py index 72231f2d..04d6fc41 100755 --- a/pyoperant/behavior/lights.py +++ b/pyoperant/behavior/lights.py @@ -1,3 +1,9 @@ +# -*- coding: utf-8 -*- +""" +This submodule controls the light schedule in the animal's environment. +It is documented in more detail main pyoperant module $pyoperant.components +""" + from pyoperant import utils, components from pyoperant.behavior import base From 4c96c8dfba9269c35447e5b457f7d65d050aacee Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 12:53:50 -0800 Subject: [PATCH 026/115] Update lights.py --- pyoperant/behavior/lights.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/lights.py b/pyoperant/behavior/lights.py index 04d6fc41..680afa82 100755 --- a/pyoperant/behavior/lights.py +++ b/pyoperant/behavior/lights.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ This submodule controls the light schedule in the animal's environment. -It is documented in more detail main pyoperant module $pyoperant.components +It is documented in more detail in the main pyoperant module $pyoperant.components """ from pyoperant import utils, components From c37d422febcae81d66b8770eecd5034a05e28a43 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 13:17:58 -0800 Subject: [PATCH 027/115] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index be698fba..4439801b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,7 +32,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.viewcode', - # 'sphinx.ext.autosummary', + 'sphinx.ext.autosummary', 'sphinxcontrib.napoleon', # 'numpydoc', ] From 2bb4577c3a6de78a78cfc0378f382531059cd71f Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 13:24:33 -0800 Subject: [PATCH 028/115] Update conf.py --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 4439801b..1888fbed 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,6 +18,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('../')) # -- General configuration ------------------------------------------------ From f0b93a672f2c92b9f9ed05c480775b9c044f17d5 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 13:48:00 -0800 Subject: [PATCH 029/115] Update two_alt_choice.py --- pyoperant/behavior/two_alt_choice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/two_alt_choice.py b/pyoperant/behavior/two_alt_choice.py index 9e01317e..3cb339e1 100755 --- a/pyoperant/behavior/two_alt_choice.py +++ b/pyoperant/behavior/two_alt_choice.py @@ -442,7 +442,7 @@ def stimulus_post(self): #response flow def response_pre(self): - for port_name, port in self.response_ports.items() + for port_name, port in self.response_ports.items(): port.on() self.log.debug('waiting for response') From 32b47a0f25e432f95a0cbc6ea09921e24213b464 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 13:53:18 -0800 Subject: [PATCH 030/115] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 1888fbed..9c0d7dfe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -300,7 +300,7 @@ def __getattr__(cls, name): else: return Mock() -MOCK_MODULES = ['comedi','comedi_poll', 'numpy', 'ephem'] +MOCK_MODULES = ['comedi','comedi_poll', 'numpy', 'ephem', 'scipy', 'pyaudio'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() From 58f893a5f42c0e02d059da3a0d4fd8324ae582ce Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 13:55:20 -0800 Subject: [PATCH 031/115] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 9c0d7dfe..86986563 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -300,7 +300,7 @@ def __getattr__(cls, name): else: return Mock() -MOCK_MODULES = ['comedi','comedi_poll', 'numpy', 'ephem', 'scipy', 'pyaudio'] +MOCK_MODULES = ['comedi','comedi_poll', 'numpy', 'ephem', 'scipy', 'pyaudio', 'special'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() From 951102ce84c1c94d267e0a204f28e8b6e7143e72 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 13:56:17 -0800 Subject: [PATCH 032/115] Update two_alt_choice.py --- pyoperant/behavior/two_alt_choice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/two_alt_choice.py b/pyoperant/behavior/two_alt_choice.py index 3cb339e1..02fca3e5 100755 --- a/pyoperant/behavior/two_alt_choice.py +++ b/pyoperant/behavior/two_alt_choice.py @@ -456,7 +456,7 @@ def response_main(self): self.this_trial.response = 'none' self.log.info('no response') return - for port_name, port in self.response_ports.items() + for port_name, port in self.response_ports.items(): if port.status(): self.this_trial.rt = (dt.datetime.now() - response_start).total_seconds() self.panel.speaker.stop() From 9021b1047890eea1a33c6a1f6ff2fa9c38c46dbb Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 15:30:21 -0800 Subject: [PATCH 033/115] Update pyoperant.behavior.base.rst --- docs/pyoperant.behavior.base.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pyoperant.behavior.base.rst b/docs/pyoperant.behavior.base.rst index cc48b47c..1a1dd514 100644 --- a/docs/pyoperant.behavior.base.rst +++ b/docs/pyoperant.behavior.base.rst @@ -1,5 +1,5 @@ pyoperant.behavior.base module -============================ +============================== .. automodule:: pyoperant.behavior.base :members: From 4d822e60c06f431eccdbfaa6307993392ec2cb51 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 15:30:51 -0800 Subject: [PATCH 034/115] Update pyoperant.behavior.rst --- docs/pyoperant.behavior.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pyoperant.behavior.rst b/docs/pyoperant.behavior.rst index 3667e31e..1c285f9a 100644 --- a/docs/pyoperant.behavior.rst +++ b/docs/pyoperant.behavior.rst @@ -1,5 +1,5 @@ pyoperant.behavior package -======================== +========================== Submodules ---------- From 6d50102fac7e066d861d412e2924245c33639fbc Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 15:31:11 -0800 Subject: [PATCH 035/115] Update pyoperant.behavior.shape.rst --- docs/pyoperant.behavior.shape.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pyoperant.behavior.shape.rst b/docs/pyoperant.behavior.shape.rst index 24faf679..9a53202a 100644 --- a/docs/pyoperant.behavior.shape.rst +++ b/docs/pyoperant.behavior.shape.rst @@ -1,5 +1,5 @@ pyoperant.behavior.shape module -============================= +=============================== .. automodule:: pyoperant.behavior.shape :members: From a5e50be13b00cee4211f85b9e48151965d9b3713 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 15:31:43 -0800 Subject: [PATCH 036/115] Update pyoperant.behavior.three_ac_matching.rst --- docs/pyoperant.behavior.three_ac_matching.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pyoperant.behavior.three_ac_matching.rst b/docs/pyoperant.behavior.three_ac_matching.rst index 86e6a4c0..9a2f1171 100644 --- a/docs/pyoperant.behavior.three_ac_matching.rst +++ b/docs/pyoperant.behavior.three_ac_matching.rst @@ -1,5 +1,5 @@ pyoperant.behavior.three_ac_matching module -========================================= +=========================================== .. automodule:: pyoperant.behavior.three_ac_matching :members: From 599a22155b1cda61af3cfe9cab47c0602b741467 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 15:32:11 -0800 Subject: [PATCH 037/115] Update pyoperant.behavior.two_alt_choice.rst --- docs/pyoperant.behavior.two_alt_choice.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pyoperant.behavior.two_alt_choice.rst b/docs/pyoperant.behavior.two_alt_choice.rst index d9543341..ec722663 100644 --- a/docs/pyoperant.behavior.two_alt_choice.rst +++ b/docs/pyoperant.behavior.two_alt_choice.rst @@ -1,5 +1,5 @@ pyoperant.behavior.two_alt_choice module -====================================== +======================================== .. automodule:: pyoperant.behavior.two_alt_choice :members: From 8b9dff5fd4326fe654c9167657715516802bc52c Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 15:35:29 -0800 Subject: [PATCH 038/115] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 86986563..6bbe1d4f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -300,7 +300,7 @@ def __getattr__(cls, name): else: return Mock() -MOCK_MODULES = ['comedi','comedi_poll', 'numpy', 'ephem', 'scipy', 'pyaudio', 'special'] +MOCK_MODULES = ['comedi','comedi_poll', 'numpy', 'ephem', 'scipy', 'pyaudio', 'scipy.special'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() From 43cd61480fabed415939bfe396380974f244e3d8 Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 16:20:08 -0800 Subject: [PATCH 039/115] Update utils.py --- pyoperant/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyoperant/utils.py b/pyoperant/utils.py index bc2ceda3..6d15fe52 100644 --- a/pyoperant/utils.py +++ b/pyoperant/utils.py @@ -219,11 +219,9 @@ def time_in_range(start, end, x): def is_day((latitude, longitude) = ('32.82', '-117.14')): """Is it daytime? - (lat,long) -- latitude and longitude of location to check (default is San Diego*) + (lat,long) -- latitude and longitude of location to check (default is San Diego) Returns True if it is daytime - * Discovered by the Germans in 1904, they named it San Diego, - which of course in German means a whale's vagina. (Burgundy, 2004) """ import ephem obs = ephem.Observer() From 4f1c0a49957f1db07655f38312195bc129c3358b Mon Sep 17 00:00:00 2001 From: acmai Date: Wed, 30 Jan 2019 16:43:16 -0800 Subject: [PATCH 040/115] Update utils.py --- pyoperant/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/utils.py b/pyoperant/utils.py index 6d15fe52..3f60060a 100644 --- a/pyoperant/utils.py +++ b/pyoperant/utils.py @@ -216,7 +216,7 @@ def time_in_range(start, end, x): else: return start <= x or x <= end -def is_day((latitude, longitude) = ('32.82', '-117.14')): +def is_day(latitude = '32.82', longitude = '-117.14'): """Is it daytime? (lat,long) -- latitude and longitude of location to check (default is San Diego) From 4b93b4346b6d818a45acfad6079200785518b1ae Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Thu, 14 Mar 2019 17:18:42 -0700 Subject: [PATCH 041/115] Change PWM frequency --- pyoperant/interfaces/raspi_gpio_.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoperant/interfaces/raspi_gpio_.py b/pyoperant/interfaces/raspi_gpio_.py index b368f411..d10b442e 100644 --- a/pyoperant/interfaces/raspi_gpio_.py +++ b/pyoperant/interfaces/raspi_gpio_.py @@ -86,7 +86,7 @@ def __init__(self, pi, bus=1, address=0x40): time.sleep(0.0005) self.set_duty_cycle(-1, 0) - self.set_frequency(200) + self.set_frequency(1000) def get_frequency(self): @@ -194,7 +194,7 @@ def open(self): #GPIO.setmode(GPIO.BCM) # Setup PWM self.pwm = PWM(self.pi, address=PCA9685_ADDRESS) - self.pwm.set_frequency(240) + self.pwm.set_frequency(1000) #used to be 240 def close(self): From c534cc3ca67890750d3d23c191b955754e755f4f Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Wed, 22 May 2019 09:42:15 -0700 Subject: [PATCH 042/115] mutate config --- scripts/mutate_config_file | 39 ++++++++++++++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 40 insertions(+) create mode 100755 scripts/mutate_config_file diff --git a/scripts/mutate_config_file b/scripts/mutate_config_file new file mode 100755 index 00000000..322db943 --- /dev/null +++ b/scripts/mutate_config_file @@ -0,0 +1,39 @@ +#!/usr/bin/env python +""" +Execute this script on the directory of the config file you'd like to change +""" +import argparse +import json + + +parser = argparse.ArgumentParser() +parser.add_argument('--config', dest='config', type=str, default='') +parser.add_argument('--bird', dest='bird', type=str, default='') +parser.add_argument('--panel', dest='panel', type=str, default='') +parser.add_argument('--person', dest='person', type=str, default='') +parser.add_argument('--email', dest='email', type=str, default='') + +def mutate(): + args = parser.parse_args() + if args.config != '': + config_path = '/home/bird/opdat/{}/config.json'.format(args.config) + with open(config_path, 'r') as f: + config_dat = json.load(f) + if args.bird != '': + config_dat["subject"] = args.bird + config_dat["experiment_path"] = "/home/bird/opdat/{}".format(args.bird) + config_dat["stim_path"] = "/home/bird/opdat/{}/stims".format(args.bird) + + if args.panel != '': + config_dat["panel_name"] = args.panel + if args.person != '': + config_dat["experimenter"]["name"] = args.person + if args.email != '': + config_dat["experimenter"]["email"] = args.email + + with open(config_path, 'w') as f: + json.dump(config_dat, f) + +if __name__ == "__main__": + mutate() + diff --git a/setup.py b/setup.py index 866e0032..14954c66 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ scripts = [ 'scripts/behave', 'scripts/pyoperantctl', + 'scripts/mutate_config_file', ], license = "BSD", classifiers = [ From bca0b8c28d7c35f995b16c679a5ca2aefea1b1ea Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Wed, 22 May 2019 09:47:19 -0700 Subject: [PATCH 043/115] json structur --- scripts/mutate_config_file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mutate_config_file b/scripts/mutate_config_file index 322db943..eb102562 100755 --- a/scripts/mutate_config_file +++ b/scripts/mutate_config_file @@ -32,7 +32,7 @@ def mutate(): config_dat["experimenter"]["email"] = args.email with open(config_path, 'w') as f: - json.dump(config_dat, f) + json.dumps(config_dat, f) if __name__ == "__main__": mutate() From b4a4ef3628319cb15f34a5fb5ca4dd49baa716b7 Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Wed, 22 May 2019 09:52:46 -0700 Subject: [PATCH 044/115] indent --- scripts/mutate_config_file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mutate_config_file b/scripts/mutate_config_file index eb102562..5be0ed14 100755 --- a/scripts/mutate_config_file +++ b/scripts/mutate_config_file @@ -32,7 +32,7 @@ def mutate(): config_dat["experimenter"]["email"] = args.email with open(config_path, 'w') as f: - json.dumps(config_dat, f) + json.dumps(config_dat, f, indent=4) if __name__ == "__main__": mutate() From 04a2aed2e617cfd228a0fc2dfc1b5a75f4c812e6 Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Wed, 22 May 2019 09:55:35 -0700 Subject: [PATCH 045/115] dump vs dumps --- scripts/mutate_config_file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mutate_config_file b/scripts/mutate_config_file index 5be0ed14..26acc57d 100755 --- a/scripts/mutate_config_file +++ b/scripts/mutate_config_file @@ -32,7 +32,7 @@ def mutate(): config_dat["experimenter"]["email"] = args.email with open(config_path, 'w') as f: - json.dumps(config_dat, f, indent=4) + json.dump(config_dat, f, indent=4) if __name__ == "__main__": mutate() From 8dfaf3ba0db4e54f8008d1593fb9cfde0d2baa25 Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Wed, 22 May 2019 10:03:02 -0700 Subject: [PATCH 046/115] ordered dict --- scripts/mutate_config_file | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/mutate_config_file b/scripts/mutate_config_file index 26acc57d..3240315d 100755 --- a/scripts/mutate_config_file +++ b/scripts/mutate_config_file @@ -4,6 +4,7 @@ Execute this script on the directory of the config file you'd like to change """ import argparse import json +import collections parser = argparse.ArgumentParser() @@ -18,7 +19,7 @@ def mutate(): if args.config != '': config_path = '/home/bird/opdat/{}/config.json'.format(args.config) with open(config_path, 'r') as f: - config_dat = json.load(f) + config_dat = json.load(f, object_pairs_hook=collections.OrderedDict) if args.bird != '': config_dat["subject"] = args.bird config_dat["experiment_path"] = "/home/bird/opdat/{}".format(args.bird) From 35bcb1e97fd482bd055da0cb4cf71764dc3c590d Mon Sep 17 00:00:00 2001 From: Brad Theilman Date: Sat, 1 Jun 2019 17:33:19 -0700 Subject: [PATCH 047/115] revert during shape fix --- pyoperant/behavior/shape.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyoperant/behavior/shape.py b/pyoperant/behavior/shape.py index bee5bca7..0edbc42d 100644 --- a/pyoperant/behavior/shape.py +++ b/pyoperant/behavior/shape.py @@ -128,7 +128,12 @@ def temp(): def _check_block(self, next_state, reps, revert_timeout): def temp(): - if not self.responded_block: + if 'revert' in self.parameters.keys(): + check_timeout = self.parameters['revert'] + else: + check_timeout = True + + if check_timeout and not self.responded_block: elapsed_time = (dt.datetime.now() - self.block_start).total_seconds() if elapsed_time > revert_timeout: self.log.warning("No response in block %d, reverting to block %d. Time: %s"%(self.recent_state, self.recent_state - 1, dt.datetime.now().isoformat(' '))) From 9abfe2c373a19aec04c04be6ad73a0c1965ef926 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 14 Jul 2023 14:12:35 -0700 Subject: [PATCH 048/115] Add place preference experiment --- pyoperant/behavior/place_pref.py | 408 +++++++++++++++++++++++++++ pyoperant/behavior/two_alt_choice.py | 2 +- pyoperant/errors.py | 6 +- pyoperant/utils.py | 28 +- 4 files changed, 434 insertions(+), 10 deletions(-) create mode 100644 pyoperant/behavior/place_pref.py diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py new file mode 100644 index 00000000..66242935 --- /dev/null +++ b/pyoperant/behavior/place_pref.py @@ -0,0 +1,408 @@ +import os +import csv +import copy +import datetime as dt +from pyoperant.behavior import base, shape +from pyoperant.errors import EndSession, EndBlock +from pyoperant import components, utils, reinf, queues + +import serial +import random + +class PlacePrefExp(base.BaseExp): + ''' + A place preference experiment. + + In this paradigm, a bird is released into a cage with three nestbox perches. + The logic of this experiment is that when a bird lands on a perch for + sufficiently long enough, a trial will start and the stimulus will start + playing from a library, shuffling through the library as the bird continues + to perch. When the bird leaves the perch, the song will stop. + The data that need to be recorded are + 1. When did the bird land on the perch? + 2. Which perch did the bird land on? + 3. How long did the bird land? + 4. Did this bird land long enough for the songs to trigger? + 5. Which category of song was the perch playing? + 6. What actual stimuli were playing? + + + Parameters + ---------- + + Attributes + ---------- + req_panel_attr : list + list of the panel attributes that are required for this behavior + fields_to_save : list + list of the fields of the Trial object that will be saved + trials : list + all the trials that have run + parameter : dict + all additional parameters for the experiment + data_csv : string + path to csv file to save data_csv + ''' + + def __init__(self, *args, **kwargs): + super(PlacePrefExp, self).__init__(*args, **kwargs) + + ## assign stim files full names + for name, filename in self.parameters['stims_A'].items(): + filename_full = os.path.join(self.parameters['stims_A_path'], filename) + self.parameters['stims_A'][name] = filename_full + + ## assign stim files full names + for name, filename in self.parameters['stims_B'].items(): + filename_full = os.path.join(self.parameters['stims_B_path'], filename) + self.parameters['stims_B'][name] = filename_full + + self.req_panel_attr += [ + 'speaker', + 'left', + 'center', + 'right', + 'house_light', + 'reset' + ] + + self.fields_to_save = [ + 'perch_strt', + 'perch_end', + 'perch_dur', + 'perch_loc', + 'valid', + 'class_' + 'stimuli', + 'events' + ] + + if 'add_fields_to_save' in self.parameters.keys(): + self.fields_to_save += self.parameters['add_fields_to_save'] + + self.exp_strt_date = dt.date( + year = self.parameters['experiment_start_year'], + month = self.parameters['experiment_start_month'], + day = self.parameters['experiment_start_day'] + ) + + self.current_perch = {'IR': None, 'speaker': None} + self.current_visit = None + self.stimulus_event = None + + self.arduino = serial.Serial(self.parameters['arduino_address'], self.parameters['arduino_baud_rate'], timeout = 1) + self.arduino.reset_input_buffer() + + self.data_csv = os.path.join(self.parameters['experiment_path'], + self.parameters['subject']+'_trialdata_'+self.timestamp+'.csv') + self.make_data_csv() + + def make_data_csv(self): + """ Create the csv file to save trial data + + This creates a new csv file at experiment.data_csv and writes a header row + with the fields in experiment.fields_to_save + """ + with open(self.data_csv, 'wb') as data_fh: + trialWriter = csv.writer(data_fh) + trialWriter.writerow(self.fields_to_save) + + def open_all_perches(self): + """ + At down state (no active trials), open IR on all perches + """ + self.log.debug("Opening all perches!") + + ## normally IR status returns false if a beam is not broken. + ## If one of them is broken, return true + while self.current_perch['IR'] == None: + + ## check if time of day is appropriate for a trial to commence + if self.check_session_schedule() == False: + raise EndSession + + ## for each iteration, check for perching behavior. + left_perched = self.panel.left.status() + center_perched = self.panel.center.status() + right_perched = self.panel.right.status() + + if left_perched: + self.current_perch['IR'] = self.panel.left + self.current_perch['speaker'] = 0 + break + if center_perched: + self.current_perch['IR'] = self.panel.center + self.current_perch['speaker'] = 1 + break + if right_perched: + self.current_perch['IR'] = self.panel.right + self.current_perch['speaker'] = 2 + break + + ## Record time of break + self.log.debug("Beam-break sensed on %s" % (self.current_perch)) + self.current_visit = utils.Visit() + self.current_visit.perch_strt = dt.datetime.now() + self.current_visit.perch_loc = self.current_perch['IR'] + self.current_visit.class_ = self.current_perch_stim_class() + + ## if validate perching fails, reset perches and open + if (self.validate_perching() == False): + self.end_visit() + self.reset_perches() + self.open_all_perches() + else: + self.log.debug("Perch valid at %s" % (self.current_perch)) + + def current_perch_stim_class(self): + """ + Look in self.parameters for the order of randomized perch orders + depending on what day of experiment we are on + Can be "S" for silence, "A" for category A, "B" for category B + """ + + return self.parameters['perch_sequence'][self.experiment_day()][self.current_perch['speaker']] + + def experiment_day(self): + """ + Figure out what day of experiment we are on. + """ + return (dt.datetime.today() - self.exp_strt_date).days + + + def validate_perching(self): + """ + For any perching behavior to be valid, + the IR needs to be broken for at least one second. + """ + ## validate current perch + self.log.debug("Trying to validate perch...") + + ## for each perch validation effort, the beam-break has a certain amount of graces + ## + grace_tokens = self.parameters['grace_num'] + + while True: + elapsed_time = (dt.datetime.now() - self.current_visit.perch_strt).total_seconds() + ## keep asking if the current perch is still broken + if (self.current_perch['IR'].status() == True): + ## if elapsed time is more than a minimum perch time, perching is valid + if elapsed_time > self.parameters['min_perch_time']: + return True + ## if time is not up, continue the loop + else: + continue + ## if current perch is no longer broken, give a grace period + else: + ## if there is no grace_tokens left, immediately terminate visit + if grace_tokens <= 0: + self.log.debug("Perching not valid. Out of grace tokens.") + return False + + ## set a time marker for the grace period. + grace_onset = dt.datetime.now() + + ## while the current perch is unperched, + while (self.current_perch.status() == False): + ## if the grace period has ended + grace_period = (dt.datetime.now() - grace_onset).total_seconds + if grace_period > self.parameters['perch_grace_period']: + self.log.debug("Perching not valid. Exceed grace period.") + return False + else: + grace_tokens = grace_tokens - 1 + continue + + def validate_deperching(self): + """ + For any deperching behavior to be valid, + the IR needs to be unbroken for at least one second. + """ + grace_tokens = self.parameters['grace_num'] + + while True: + ## keep asking if the current perch is still broken + if (self.current_perch['IR'].status() == True): + ## if the IR is still broken, no deperch + return False + ## if the current perch is no longer broken, give a grace period + else: + ## if there is no grace_tokens left, immediately terminate visit + if grace_tokens <= 0: + self.log.debug("Perching Unstable. Out of grace tokens.") + return True + + ## set a time marker for the grace period. + grace_onset = dt.datetime.now() + + ## while the current perch is unperched, + while (self.current_perch.status() == False): + ## if the grace period has ended, bird has deperched + grace_period = (dt.datetime.now() - grace_onset).total_seconds + if grace_period > self.parameters['perch_grace_period']: + self.log.debug("Perching not valid. Exceed grace period.") + return True + else: + grace_tokens = grace_tokens - 1 + continue + + + + def switch_speaker(self): + """ + Use serial communication with the connected Arduino to switch + """ + + self.arduino.write(str(self.current_perch['speaker']).encode('utf-8')) + + def stimulus_shuffle(self): + """ + While perched, shuffle stimuli from a library + """ + + ## if the current class is silence, don't do shit + if self.current_visit.class_ == "S": + self.log.debug("Silence Perching") + pass + ## if the current class is not silence, prep and play stimuli + else: + self.prep_stimuli() + self.play_stimuli() + + while True: + ## if deperching has been detected, quit this function + if self.validate_deperching == True: + self.stop_stimuli() + return + ## else, play audio until its length runs out, + else: + elapsed_time = (dt.datetime.now() - self.stimulus_event.time).total_seconds() + if elapsed_time < self.stimulus_event.duration: + continue + # when it does, give an inter_stim_interval, and recurse on the function + else: + if elapsed_time < (self.stimulus_event.duration + self.parameters['inter_stim_interval']): + continue + ## when inter_stim_interval runs out, stop stimuli and go back to the stimulus_shuffle + else: + self.stop_stimuli() + self.stimulus_shuffle() + + def perch_playlist(self): + """ + depending on the perch and the perch_sequence, find the correct list + """ + if self.current_perch_stim_class == "A": + return self.parameters['stims_A'] + if self.current_perch_stim_class == "B": + return self.parameters['stims_B'] + + def prep_stimuli(self): + """ + Prep stimuli and generate a stimulus event + """ + ## randomly shuffle from the current perch_playlist + stim_file = random.sample(self.perch_playlist().items(), k = 1)[1] + stim = utils.auditory_stim_from_wav(stim_file) + self.log.debug(stim_file) + + self.stimulus_event = utils.Event( + time = dt.datetime.now(), + duration = stim.duration, + file_origin = stim.file_origin, + ) + + self.log.debug("Queuing stimulus %s" % ) + self.panel.speaker.queue(self.stimulus_event.file_origin) + + def play_stimuli(self): + """ + Play stimuli through current_perch speaker + """ + + self.log.debug("Playing %s" % (self.stimulus_event.file_origin)) + ## trigger speaker + self.panel.speaker.play() + + def stop_stimuli(self): + """ + Stop stimuli, record event, and clear out event + """ + + self.panel.speaker.stop() + self.current_visit.stimuli.append(self.stimulus_event) + self.stimulus_event = None + + def end_visit(self): + """ + End visit and write data of current visit to csv + """ + self.log.debug("Ending visit and record end time.") + self.current_visit.perch_end = dt.datetime.now() + self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() + self.current_visit.valid = (self.current_visit.perch_dur >= self.parameters['min_perch_time']) + self.save_visit() + + def save_visit(self): + """ + write visit results to CSV + """ + + self.log.debug("Writing data to %s" % (self.data_csv)) + visit_dict = {} + for field in self.fields_to_save: + try: + visit_dict[field] = getattr(trial,field) + except AttributeError: + visit_dict[field] = visit.annotations[field] ## it might be in annotations for some reason + + with open(self.data_csv, 'ab') as data_fh: + visitWriter = csv.DictWriter(data_fh, fieldnames = self.fields_to_save, extrasaction = 'ignore') + visitWriter.writerow(visit_dict) + + def reset_perches(self): + """ + Reset perches + """ + + self.current_perch == None + self.stimulus_event == None + self.current_visit == None + + def check_session_schedule(self): + """ + Check if perches should be open + + returns + ------- + bool + True, if sessions should be running + """ + return utils.check_time(self.parameters['session_schedule']) + + def session_main(self): + """ + Inside session_main, maintain a loop that controls paradigm behavior + """ + + while True: + ''' + Try to open all perches. The program loops in open_all_perches + until a valid perching has been detected. + ''' + self.open_all_perches() + + ''' + Once perching has been detected, switch speaker to the current + perch, and start stimuli shuffle. The program loops in stimulus_shuffle() + until a valid deperching has been detected() + ''' + self.switch_speaker() + self.stimulus_shuffle() + + ''' + Once deperched, end visit and reset + ''' + self.end_visit() + self.reset_perches() + ## diff --git a/pyoperant/behavior/two_alt_choice.py b/pyoperant/behavior/two_alt_choice.py index 02fca3e5..e0849372 100755 --- a/pyoperant/behavior/two_alt_choice.py +++ b/pyoperant/behavior/two_alt_choice.py @@ -137,7 +137,7 @@ def session_pre(self): port through `experiment.class_assoc['L']`. """ - + self.response_ports = {} for class_, class_params in self.parameters['classes'].items(): try: diff --git a/pyoperant/errors.py b/pyoperant/errors.py index 9986471e..f9c818d2 100644 --- a/pyoperant/errors.py +++ b/pyoperant/errors.py @@ -19,7 +19,7 @@ class Error(Exception): class InterfaceError(Exception): '''raised for errors with an interface. - this should indicate a software error, like difficulty + this should indicate a software error, like difficulty connecting to an interface ''' pass @@ -27,10 +27,10 @@ class InterfaceError(Exception): class ComponentError(Exception): '''raised for errors with a component. - this should indicate a hardware error in the physical world, + this should indicate a hardware error in the physical world, like a problem with a feeder. - this should be raised by components when doing any internal + this should be raised by components when doing any internal validation that they are working properly ''' diff --git a/pyoperant/utils.py b/pyoperant/utils.py index 3f60060a..7c0207e7 100644 --- a/pyoperant/utils.py +++ b/pyoperant/utils.py @@ -132,27 +132,43 @@ def __init__(self, self.events = [] self.stim_event = None - +class Visit(Event): + """docstring for Visit, a variant of trial for place preference paradigm """ + def __init__(self, + class_=None, + *args, **kwargs): + super(Visit, self).__init__(*args, **kwargs) + self.label = 'visit' + self.perch_strt = None + self.perch_end = None + self.perch_loc = None + self.perch_dur = None + self.valid = None + self.class_ = class_ + self.stimuli = [] + self.events = [] + + class Command(object): """ Enables to run subprocess commands in a different thread with TIMEOUT option. - + via https://gist.github.com/kirpit/1306188 - + Based on jcollado's solution: http://stackoverflow.com/questions/1191374/subprocess-with-timeout/4825933#4825933 - + """ command = None process = None status = None output, error = '', '' - + def __init__(self, command): if isinstance(command, basestring): command = shlex.split(command) self.command = command - + def run(self, timeout=None, **kwargs): """ Run a command then return: (status, output, error). """ def target(**kwargs): From d9c0303d9140414aa57518064bb3fff16663d5c5 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 14 Jul 2023 14:19:47 -0700 Subject: [PATCH 049/115] Update place pref visibility --- pyoperant/behavior/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyoperant/behavior/__init__.py b/pyoperant/behavior/__init__.py index 03ca9402..c690ae25 100755 --- a/pyoperant/behavior/__init__.py +++ b/pyoperant/behavior/__init__.py @@ -1,2 +1,3 @@ from two_alt_choice import * from lights import * +from place_pref import * From 192f4cd75e86279ed01ffde5761c15ac041c754b Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 14 Jul 2023 14:23:26 -0700 Subject: [PATCH 050/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 66242935..ab87e8ff 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -312,7 +312,7 @@ def prep_stimuli(self): file_origin = stim.file_origin, ) - self.log.debug("Queuing stimulus %s" % ) + self.log.debug("Queuing stimulus %s" % stim.file_origin) self.panel.speaker.queue(self.stimulus_event.file_origin) def play_stimuli(self): From a685b8bb420bcf88e3aa424e004fa7a654fbd3d9 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 14 Jul 2023 16:21:14 -0700 Subject: [PATCH 051/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index ab87e8ff..8bba56b3 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -6,7 +6,7 @@ from pyoperant.errors import EndSession, EndBlock from pyoperant import components, utils, reinf, queues -import serial +#import serial import random class PlacePrefExp(base.BaseExp): From 2e10d0a86cf11dc4f1ae1856016878cd60620fda Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 14 Jul 2023 16:37:53 -0700 Subject: [PATCH 052/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 8bba56b3..ab87e8ff 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -6,7 +6,7 @@ from pyoperant.errors import EndSession, EndBlock from pyoperant import components, utils, reinf, queues -#import serial +import serial import random class PlacePrefExp(base.BaseExp): From d812c18db34f794b170a704a7c82d9b5eca64c1f Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 14 Jul 2023 16:40:58 -0700 Subject: [PATCH 053/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index ab87e8ff..92d28d9d 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -6,7 +6,7 @@ from pyoperant.errors import EndSession, EndBlock from pyoperant import components, utils, reinf, queues -import serial +from serial import serial import random class PlacePrefExp(base.BaseExp): From 2bf8275129425708455dc00b9d6f9267f0e99bf4 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 14 Jul 2023 16:47:04 -0700 Subject: [PATCH 054/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 92d28d9d..ab87e8ff 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -6,7 +6,7 @@ from pyoperant.errors import EndSession, EndBlock from pyoperant import components, utils, reinf, queues -from serial import serial +import serial import random class PlacePrefExp(base.BaseExp): From 0a839b1c36269af3761123b0e38d848837a46f97 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 10:53:24 -0700 Subject: [PATCH 055/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index ab87e8ff..21dd3ff2 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -378,7 +378,7 @@ def check_session_schedule(self): bool True, if sessions should be running """ - return utils.check_time(self.parameters['session_schedule']) + return utils.check_time(self.parameters['light_schedule']) def session_main(self): """ From dd81ab8d33ac341afc93063735575ddf2f6dbdb1 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:18:09 -0700 Subject: [PATCH 056/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 21dd3ff2..d7688278 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -167,7 +167,7 @@ def experiment_day(self): """ Figure out what day of experiment we are on. """ - return (dt.datetime.today() - self.exp_strt_date).days + return (dt.date.today() - self.exp_strt_date).days def validate_perching(self): From 668b729a4fb4019d7f7c8f698b13611428720ade Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:26:27 -0700 Subject: [PATCH 057/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index d7688278..cf56a4a6 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -161,7 +161,7 @@ def current_perch_stim_class(self): Can be "S" for silence, "A" for category A, "B" for category B """ - return self.parameters['perch_sequence'][self.experiment_day()][self.current_perch['speaker']] + return self.parameters['perch_sequence'][str(self.experiment_day())][self.current_perch['speaker']] def experiment_day(self): """ From e2d6c6774d5bb5d6c0b51ca99fcb27ee8e96d555 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:33:58 -0700 Subject: [PATCH 058/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index cf56a4a6..b4a93463 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -293,8 +293,10 @@ def perch_playlist(self): depending on the perch and the perch_sequence, find the correct list """ if self.current_perch_stim_class == "A": + self.log.debug("Perch stim class A") return self.parameters['stims_A'] if self.current_perch_stim_class == "B": + self.log.debug("Perch stim class B") return self.parameters['stims_B'] def prep_stimuli(self): From 89e5f67510878542085470b61f53b9d8b975688a Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:36:34 -0700 Subject: [PATCH 059/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index b4a93463..2da3f62a 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -292,10 +292,10 @@ def perch_playlist(self): """ depending on the perch and the perch_sequence, find the correct list """ - if self.current_perch_stim_class == "A": + if self.current_perch_stim_class() == "A": self.log.debug("Perch stim class A") return self.parameters['stims_A'] - if self.current_perch_stim_class == "B": + if self.current_perch_stim_class() == "B": self.log.debug("Perch stim class B") return self.parameters['stims_B'] From b51aff1b52c55e6a8023602bf0adc223f4512a22 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:45:31 -0700 Subject: [PATCH 060/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 2da3f62a..2cf042a0 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -304,7 +304,7 @@ def prep_stimuli(self): Prep stimuli and generate a stimulus event """ ## randomly shuffle from the current perch_playlist - stim_file = random.sample(self.perch_playlist().items(), k = 1)[1] + stim_file = random.sample(self.perch_playlist().items(), k = 1)[0] stim = utils.auditory_stim_from_wav(stim_file) self.log.debug(stim_file) From a44cd177da3feb5c01d8d7e5c407b36c4d619303 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:49:54 -0700 Subject: [PATCH 061/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 2cf042a0..82fc2c81 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -305,6 +305,7 @@ def prep_stimuli(self): """ ## randomly shuffle from the current perch_playlist stim_file = random.sample(self.perch_playlist().items(), k = 1)[0] + print(stim_file) stim = utils.auditory_stim_from_wav(stim_file) self.log.debug(stim_file) From d5c4acc7af954dc5ed8262078fd062cdde0e5500 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:53:02 -0700 Subject: [PATCH 062/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 82fc2c81..3ab85632 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -203,7 +203,7 @@ def validate_perching(self): grace_onset = dt.datetime.now() ## while the current perch is unperched, - while (self.current_perch.status() == False): + while (self.current_perch["IR"].status() == False): ## if the grace period has ended grace_period = (dt.datetime.now() - grace_onset).total_seconds if grace_period > self.parameters['perch_grace_period']: @@ -236,7 +236,7 @@ def validate_deperching(self): grace_onset = dt.datetime.now() ## while the current perch is unperched, - while (self.current_perch.status() == False): + while (self.current_perch['IR'].status() == False): ## if the grace period has ended, bird has deperched grace_period = (dt.datetime.now() - grace_onset).total_seconds if grace_period > self.parameters['perch_grace_period']: @@ -368,7 +368,7 @@ def reset_perches(self): Reset perches """ - self.current_perch == None + self.current_perch == {'IR': None, 'speaker': None} self.stimulus_event == None self.current_visit == None From e5e2c5211700d25cf8d1df6a08184ee9d7b838ee Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 11:59:24 -0700 Subject: [PATCH 063/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 3ab85632..fedabc5b 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -344,9 +344,9 @@ def end_visit(self): self.current_visit.perch_end = dt.datetime.now() self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() self.current_visit.valid = (self.current_visit.perch_dur >= self.parameters['min_perch_time']) - self.save_visit() + self.save_visit(self.current_visit) - def save_visit(self): + def save_visit(self, visit): """ write visit results to CSV """ @@ -355,7 +355,7 @@ def save_visit(self): visit_dict = {} for field in self.fields_to_save: try: - visit_dict[field] = getattr(trial,field) + visit_dict[field] = getattr(visit,field) except AttributeError: visit_dict[field] = visit.annotations[field] ## it might be in annotations for some reason From 1331850b8ea45dd211913c54b1d87068197231d1 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 12:05:37 -0700 Subject: [PATCH 064/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index fedabc5b..39653e57 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -77,8 +77,8 @@ def __init__(self, *args, **kwargs): 'events' ] - if 'add_fields_to_save' in self.parameters.keys(): - self.fields_to_save += self.parameters['add_fields_to_save'] + #if 'add_fields_to_save' in self.parameters.keys(): + # self.fields_to_save += self.parameters['add_fields_to_save'] self.exp_strt_date = dt.date( year = self.parameters['experiment_start_year'], From 8d6d22ff1756f76212694d46afaf78779f51ca2f Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 12:11:30 -0700 Subject: [PATCH 065/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 39653e57..b0473ba2 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -77,8 +77,8 @@ def __init__(self, *args, **kwargs): 'events' ] - #if 'add_fields_to_save' in self.parameters.keys(): - # self.fields_to_save += self.parameters['add_fields_to_save'] + if 'add_fields_to_save' in self.parameters.keys(): + self.fields_to_save += self.parameters['add_fields_to_save'] self.exp_strt_date = dt.date( year = self.parameters['experiment_start_year'], @@ -353,6 +353,7 @@ def save_visit(self, visit): self.log.debug("Writing data to %s" % (self.data_csv)) visit_dict = {} + self.log.debug(str(self.fields_to_save)) for field in self.fields_to_save: try: visit_dict[field] = getattr(visit,field) From 211da1fbbcad7bed8dcf42b3b439b82226e5257a Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 12:13:38 -0700 Subject: [PATCH 066/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index b0473ba2..e27ab021 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -72,7 +72,7 @@ def __init__(self, *args, **kwargs): 'perch_dur', 'perch_loc', 'valid', - 'class_' + 'class_', 'stimuli', 'events' ] @@ -353,7 +353,6 @@ def save_visit(self, visit): self.log.debug("Writing data to %s" % (self.data_csv)) visit_dict = {} - self.log.debug(str(self.fields_to_save)) for field in self.fields_to_save: try: visit_dict[field] = getattr(visit,field) From 6b8036ea76b05dac700a200979ed850fb4078826 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 12:19:15 -0700 Subject: [PATCH 067/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index e27ab021..2bb2eb15 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -150,7 +150,10 @@ def open_all_perches(self): if (self.validate_perching() == False): self.end_visit() self.reset_perches() - self.open_all_perches() + try: + self.open_all_perches() + except: + self.session_main() else: self.log.debug("Perch valid at %s" % (self.current_perch)) From 958688269b814fe04daf67000b65bb5dcd735330 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 12:21:44 -0700 Subject: [PATCH 068/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 33 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 2bb2eb15..849e1e8d 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -153,9 +153,10 @@ def open_all_perches(self): try: self.open_all_perches() except: - self.session_main() + return False else: self.log.debug("Perch valid at %s" % (self.current_perch)) + return True def current_perch_stim_class(self): """ @@ -396,19 +397,21 @@ def session_main(self): Try to open all perches. The program loops in open_all_perches until a valid perching has been detected. ''' - self.open_all_perches() + if (self.open_all_perches()) == True: - ''' - Once perching has been detected, switch speaker to the current - perch, and start stimuli shuffle. The program loops in stimulus_shuffle() - until a valid deperching has been detected() - ''' - self.switch_speaker() - self.stimulus_shuffle() + ''' + Once perching has been detected, switch speaker to the current + perch, and start stimuli shuffle. The program loops in stimulus_shuffle() + until a valid deperching has been detected() + ''' + self.switch_speaker() + self.stimulus_shuffle() - ''' - Once deperched, end visit and reset - ''' - self.end_visit() - self.reset_perches() - ## + else: + + ''' + Once deperched, end visit and reset + ''' + self.end_visit() + self.reset_perches() + ## From 53a90cb641539134e941058c0a2be87afc3a4ada Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 12:26:58 -0700 Subject: [PATCH 069/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 33 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 849e1e8d..2bb2eb15 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -153,10 +153,9 @@ def open_all_perches(self): try: self.open_all_perches() except: - return False + self.session_main() else: self.log.debug("Perch valid at %s" % (self.current_perch)) - return True def current_perch_stim_class(self): """ @@ -397,21 +396,19 @@ def session_main(self): Try to open all perches. The program loops in open_all_perches until a valid perching has been detected. ''' - if (self.open_all_perches()) == True: - - ''' - Once perching has been detected, switch speaker to the current - perch, and start stimuli shuffle. The program loops in stimulus_shuffle() - until a valid deperching has been detected() - ''' - self.switch_speaker() - self.stimulus_shuffle() + self.open_all_perches() - else: + ''' + Once perching has been detected, switch speaker to the current + perch, and start stimuli shuffle. The program loops in stimulus_shuffle() + until a valid deperching has been detected() + ''' + self.switch_speaker() + self.stimulus_shuffle() - ''' - Once deperched, end visit and reset - ''' - self.end_visit() - self.reset_perches() - ## + ''' + Once deperched, end visit and reset + ''' + self.end_visit() + self.reset_perches() + ## From a04d3922fcd92193e8b588f04ec7ab7876cbd1ec Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 12:40:24 -0700 Subject: [PATCH 070/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 2bb2eb15..7da65c98 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -153,7 +153,8 @@ def open_all_perches(self): try: self.open_all_perches() except: - self.session_main() + self.log.debug("Max recursion error") + return else: self.log.debug("Perch valid at %s" % (self.current_perch)) @@ -371,9 +372,9 @@ def reset_perches(self): Reset perches """ - self.current_perch == {'IR': None, 'speaker': None} - self.stimulus_event == None - self.current_visit == None + self.current_perch = {'IR': None, 'speaker': None} + self.stimulus_event = None + self.current_visit = None def check_session_schedule(self): """ @@ -403,8 +404,9 @@ def session_main(self): perch, and start stimuli shuffle. The program loops in stimulus_shuffle() until a valid deperching has been detected() ''' - self.switch_speaker() - self.stimulus_shuffle() + if (self.current_perch['IR'].status() == True): + self.switch_speaker() + self.stimulus_shuffle() ''' Once deperched, end visit and reset From 627b3bdb8f84a3ddf5cbd2ebc92ae10dd332de23 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:14:14 -0700 Subject: [PATCH 071/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 7da65c98..578d0c9f 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -267,7 +267,9 @@ def stimulus_shuffle(self): ## if the current class is silence, don't do shit if self.current_visit.class_ == "S": self.log.debug("Silence Perching") - pass + while True: + if self.validate_deperching() == True: + return ## if the current class is not silence, prep and play stimuli else: self.prep_stimuli() @@ -275,7 +277,7 @@ def stimulus_shuffle(self): while True: ## if deperching has been detected, quit this function - if self.validate_deperching == True: + if self.validate_deperching() == True: self.stop_stimuli() return ## else, play audio until its length runs out, From dec1b89873699973ef373f8c9c4919e01ab4f014 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:22:58 -0700 Subject: [PATCH 072/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 578d0c9f..0760fda6 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -311,9 +311,8 @@ def prep_stimuli(self): """ ## randomly shuffle from the current perch_playlist stim_file = random.sample(self.perch_playlist().items(), k = 1)[0] - print(stim_file) - stim = utils.auditory_stim_from_wav(stim_file) self.log.debug(stim_file) + stim = utils.auditory_stim_from_wav(stim_file) self.stimulus_event = utils.Event( time = dt.datetime.now(), From 9e3bf8f02531179457a2c0309639bff3f7aa7945 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:25:22 -0700 Subject: [PATCH 073/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 0760fda6..ba413440 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -310,7 +310,7 @@ def prep_stimuli(self): Prep stimuli and generate a stimulus event """ ## randomly shuffle from the current perch_playlist - stim_file = random.sample(self.perch_playlist().items(), k = 1)[0] + stim_file = random.sample(self.perch_playlist().items(), k = 1)[0][1] self.log.debug(stim_file) stim = utils.auditory_stim_from_wav(stim_file) From ee6d98a1f78d74203c44a5d21f7d942fe56038a1 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:32:20 -0700 Subject: [PATCH 074/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index ba413440..30c8179a 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -256,7 +256,7 @@ def switch_speaker(self): """ Use serial communication with the connected Arduino to switch """ - + self.log.debug("Switching speaker relay to %s" % str(self.current_perch['speaker'])) self.arduino.write(str(self.current_perch['speaker']).encode('utf-8')) def stimulus_shuffle(self): From 80091892e9a388930eb07bb2365011c1767afc81 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:44:02 -0700 Subject: [PATCH 075/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 30c8179a..37223d49 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -256,8 +256,8 @@ def switch_speaker(self): """ Use serial communication with the connected Arduino to switch """ - self.log.debug("Switching speaker relay to %s" % str(self.current_perch['speaker'])) - self.arduino.write(str(self.current_perch['speaker']).encode('utf-8')) + self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) + self.arduino.write(str(int(self.current_perch['speaker'])).encode('utf-8')) def stimulus_shuffle(self): """ From 67f4b8ab787c3370ef9f07f7e0ea9ab6964ff001 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:48:42 -0700 Subject: [PATCH 076/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 37223d49..0cc6e7ab 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -257,7 +257,7 @@ def switch_speaker(self): Use serial communication with the connected Arduino to switch """ self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) - self.arduino.write(str(int(self.current_perch['speaker'])).encode('utf-8')) + self.arduino.write(str(self.current_perch['speaker']).decode('utf-8')) def stimulus_shuffle(self): """ From bd95eb5b08c0d6a1ec344151debae5c1ac934c6b Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:50:03 -0700 Subject: [PATCH 077/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 0cc6e7ab..a482356c 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -257,7 +257,7 @@ def switch_speaker(self): Use serial communication with the connected Arduino to switch """ self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) - self.arduino.write(str(self.current_perch['speaker']).decode('utf-8')) + self.arduino.write(str(self.current_perch['speaker']).encode('utf-8').decode('utf-8')) def stimulus_shuffle(self): """ From 34ef08eb90f68438513f97dbf04e4c4d9b51d8f0 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:52:12 -0700 Subject: [PATCH 078/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index a482356c..f30fd1fd 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -257,7 +257,7 @@ def switch_speaker(self): Use serial communication with the connected Arduino to switch """ self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) - self.arduino.write(str(self.current_perch['speaker']).encode('utf-8').decode('utf-8')) + self.arduino.write(str(self.current_perch['speaker']).encode('utf-8').decode('utf-8').encode('utf-8')) def stimulus_shuffle(self): """ From f35454a3d868956e6d4d03fffcad17f38774f6c1 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 13:56:06 -0700 Subject: [PATCH 079/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index f30fd1fd..04ade55e 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -257,7 +257,8 @@ def switch_speaker(self): Use serial communication with the connected Arduino to switch """ self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) - self.arduino.write(str(self.current_perch['speaker']).encode('utf-8').decode('utf-8').encode('utf-8')) + self.arduino.reset_input_buffer() + self.arduino.write(str(self.current_perch['speaker']).encode('utf-8')) def stimulus_shuffle(self): """ From 310772dd34596235131237582ada466a3f36739b Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 14:02:50 -0700 Subject: [PATCH 080/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 04ade55e..b9bdfbe0 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -128,15 +128,15 @@ def open_all_perches(self): if left_perched: self.current_perch['IR'] = self.panel.left - self.current_perch['speaker'] = 0 + self.current_perch['speaker'] = 1 break if center_perched: self.current_perch['IR'] = self.panel.center - self.current_perch['speaker'] = 1 + self.current_perch['speaker'] = 2 break if right_perched: self.current_perch['IR'] = self.panel.right - self.current_perch['speaker'] = 2 + self.current_perch['speaker'] = 3 break ## Record time of break @@ -257,7 +257,6 @@ def switch_speaker(self): Use serial communication with the connected Arduino to switch """ self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) - self.arduino.reset_input_buffer() self.arduino.write(str(self.current_perch['speaker']).encode('utf-8')) def stimulus_shuffle(self): From 141344a6caecab6365f3c9d65c02eac76d9b60b0 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 14:05:08 -0700 Subject: [PATCH 081/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index b9bdfbe0..b4cc92b6 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -128,15 +128,15 @@ def open_all_perches(self): if left_perched: self.current_perch['IR'] = self.panel.left - self.current_perch['speaker'] = 1 + self.current_perch['speaker'] = 0 break if center_perched: self.current_perch['IR'] = self.panel.center - self.current_perch['speaker'] = 2 + self.current_perch['speaker'] = 1 break if right_perched: self.current_perch['IR'] = self.panel.right - self.current_perch['speaker'] = 3 + self.current_perch['speaker'] = 2 break ## Record time of break @@ -257,7 +257,7 @@ def switch_speaker(self): Use serial communication with the connected Arduino to switch """ self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) - self.arduino.write(str(self.current_perch['speaker']).encode('utf-8')) + self.arduino.write(str(self.current_perch['speaker'] + 1).encode('utf-8')) def stimulus_shuffle(self): """ From c04583999e85921fcab4169988914722853414d8 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 14:15:12 -0700 Subject: [PATCH 082/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index b4cc92b6..985e3602 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -86,7 +86,7 @@ def __init__(self, *args, **kwargs): day = self.parameters['experiment_start_day'] ) - self.current_perch = {'IR': None, 'speaker': None} + self.current_perch = {'IR': None, 'IRName': None, 'speaker': None} self.current_visit = None self.stimulus_event = None @@ -128,14 +128,17 @@ def open_all_perches(self): if left_perched: self.current_perch['IR'] = self.panel.left + self.current_perch['IRName'] = 'L' self.current_perch['speaker'] = 0 break if center_perched: self.current_perch['IR'] = self.panel.center + self.current_perch['IRName'] = 'C' self.current_perch['speaker'] = 1 break if right_perched: self.current_perch['IR'] = self.panel.right + self.current_perch['IRName'] = 'R' self.current_perch['speaker'] = 2 break @@ -373,7 +376,7 @@ def reset_perches(self): Reset perches """ - self.current_perch = {'IR': None, 'speaker': None} + self.current_perch = {'IR': None, 'IRName': None, 'speaker': None} self.stimulus_event = None self.current_visit = None From b1062136362a9b8aef0f5b5779688660a88d62c6 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 14:20:33 -0700 Subject: [PATCH 083/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 985e3602..c3c2658d 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -146,7 +146,7 @@ def open_all_perches(self): self.log.debug("Beam-break sensed on %s" % (self.current_perch)) self.current_visit = utils.Visit() self.current_visit.perch_strt = dt.datetime.now() - self.current_visit.perch_loc = self.current_perch['IR'] + self.current_visit.perch_loc = self.current_perch['IRName'] self.current_visit.class_ = self.current_perch_stim_class() ## if validate perching fails, reset perches and open @@ -341,7 +341,8 @@ def stop_stimuli(self): """ self.panel.speaker.stop() - self.current_visit.stimuli.append(self.stimulus_event) + self.current.visit.stimuli.append(self.stimulus_event.file_origin) + self.current_visit.event.append(self.stimulus_event) self.stimulus_event = None def end_visit(self): From 3bc23fec450908ab6afd64f845c7df5c8348ccb8 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 14:24:04 -0700 Subject: [PATCH 084/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index c3c2658d..c0c410c1 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -341,7 +341,7 @@ def stop_stimuli(self): """ self.panel.speaker.stop() - self.current.visit.stimuli.append(self.stimulus_event.file_origin) + self.current_visit.stimuli.append(self.stimulus_event.file_origin) self.current_visit.event.append(self.stimulus_event) self.stimulus_event = None From 2945eff923b3851430ee1004dc0985d609d6b5d9 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 14:25:35 -0700 Subject: [PATCH 085/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index c0c410c1..1c0b6b52 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -342,7 +342,7 @@ def stop_stimuli(self): self.panel.speaker.stop() self.current_visit.stimuli.append(self.stimulus_event.file_origin) - self.current_visit.event.append(self.stimulus_event) + self.current_visit.events.append(self.stimulus_event) self.stimulus_event = None def end_visit(self): From 9af0c43a0461c9d7d6f9b64a7349c1c5aa38d0c2 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 17 Jul 2023 14:28:55 -0700 Subject: [PATCH 086/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 1c0b6b52..17d927fe 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -368,9 +368,9 @@ def save_visit(self, visit): except AttributeError: visit_dict[field] = visit.annotations[field] ## it might be in annotations for some reason - with open(self.data_csv, 'ab') as data_fh: - visitWriter = csv.DictWriter(data_fh, fieldnames = self.fields_to_save, extrasaction = 'ignore') - visitWriter.writerow(visit_dict) + with open(self.data_csv, 'ab') as data_fh: + visitWriter = csv.DictWriter(data_fh, fieldnames = self.fields_to_save, extrasaction = 'ignore') + visitWriter.writerow(visit_dict) def reset_perches(self): """ From 44f4c8d1436fe0cc668d4c58f39e2c2d281515c5 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 18 Jul 2023 13:39:53 -0700 Subject: [PATCH 087/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 17d927fe..e0593169 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -155,8 +155,8 @@ def open_all_perches(self): self.reset_perches() try: self.open_all_perches() - except: - self.log.debug("Max recursion error") + except RuntimeError: + self.log.debug("RuntimeError: max recursion") return else: self.log.debug("Perch valid at %s" % (self.current_perch)) @@ -409,9 +409,10 @@ def session_main(self): perch, and start stimuli shuffle. The program loops in stimulus_shuffle() until a valid deperching has been detected() ''' - if (self.current_perch['IR'].status() == True): - self.switch_speaker() - self.stimulus_shuffle() + if (self.current_perch['IR'] != None): + if (self.current_perch['IR'].status() == True): + self.switch_speaker() + self.stimulus_shuffle() ''' Once deperched, end visit and reset From d01c820873daa8cbebeeed789c317e23c199d612 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 18 Jul 2023 14:06:22 -0700 Subject: [PATCH 088/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index e0593169..e2eb30a6 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -117,9 +117,9 @@ def open_all_perches(self): ## If one of them is broken, return true while self.current_perch['IR'] == None: - ## check if time of day is appropriate for a trial to commence + ## check if time of day is appropriate for a visit if self.check_session_schedule() == False: - raise EndSession + return ## for each iteration, check for perching behavior. left_perched = self.panel.left.status() @@ -279,8 +279,8 @@ def stimulus_shuffle(self): self.play_stimuli() while True: - ## if deperching has been detected, quit this function - if self.validate_deperching() == True: + ## if deperching has been detected, or light schedule expires, quit this function + if (self.validate_deperching() == True or self.check_session_schedule() == False): self.stop_stimuli() return ## else, play audio until its length runs out, @@ -398,12 +398,17 @@ def session_main(self): """ while True: + ''' Try to open all perches. The program loops in open_all_perches until a valid perching has been detected. ''' self.open_all_perches() + ## check if time of day is appropriate for a trial to commence + if self.check_session_schedule() == False: + raise EndSession + ''' Once perching has been detected, switch speaker to the current perch, and start stimuli shuffle. The program loops in stimulus_shuffle() @@ -419,4 +424,8 @@ def session_main(self): ''' self.end_visit() self.reset_perches() + + ## check if time of day is appropriate for a trial to commence + if self.check_session_schedule() == False: + raise EndSession ## From 6901ba2c374c0464cfc1bd2f6758cf113f438a0f Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 18 Jul 2023 14:17:46 -0700 Subject: [PATCH 089/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index e2eb30a6..1f14e199 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -407,7 +407,7 @@ def session_main(self): ## check if time of day is appropriate for a trial to commence if self.check_session_schedule() == False: - raise EndSession + return "post" ''' Once perching has been detected, switch speaker to the current @@ -427,5 +427,9 @@ def session_main(self): ## check if time of day is appropriate for a trial to commence if self.check_session_schedule() == False: - raise EndSession + return "post" ## + + def session_post(self): + self.log.info('Paradigm is closed. Proceed to Sleep. ') + return None From aef3827ec49324412982730b3d40d21232f0ab2c Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 18 Jul 2023 14:52:23 -0700 Subject: [PATCH 090/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 1f14e199..68640285 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -294,6 +294,7 @@ def stimulus_shuffle(self): continue ## when inter_stim_interval runs out, stop stimuli and go back to the stimulus_shuffle else: + self.log.debug("Stimuli finished and inter_stim_interval has passed. ") self.stop_stimuli() self.stimulus_shuffle() @@ -339,7 +340,7 @@ def stop_stimuli(self): """ Stop stimuli, record event, and clear out event """ - + self.log.debug("Stop stimuli and flush stimulus event.") self.panel.speaker.stop() self.current_visit.stimuli.append(self.stimulus_event.file_origin) self.current_visit.events.append(self.stimulus_event) From 3301c26c421fa4b929d44e18ebe66bb3dd1bb31c Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 18 Jul 2023 14:57:38 -0700 Subject: [PATCH 091/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 68640285..871bac13 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -297,6 +297,7 @@ def stimulus_shuffle(self): self.log.debug("Stimuli finished and inter_stim_interval has passed. ") self.stop_stimuli() self.stimulus_shuffle() + return def perch_playlist(self): """ From 90a3708c70ec2d50924d8d153976a39cf85cf8ee Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 18 Jul 2023 15:08:31 -0700 Subject: [PATCH 092/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 871bac13..912c10ce 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -296,8 +296,9 @@ def stimulus_shuffle(self): else: self.log.debug("Stimuli finished and inter_stim_interval has passed. ") self.stop_stimuli() - self.stimulus_shuffle() - return + ## find another clip to play + self.prep_stimuli() + self.play_stimuli() def perch_playlist(self): """ From d00066e04865f9b00ecd15bbeebef1ef427c4ddf Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Wed, 19 Jul 2023 11:24:28 -0700 Subject: [PATCH 093/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 912c10ce..aacfa5cb 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -199,20 +199,18 @@ def validate_perching(self): ## if time is not up, continue the loop else: continue - ## if current perch is no longer broken, give a grace period + ## if current perch is no longer broken, record perch end time, give a grace period else: + self.current_visit.perch_end = dt.datetime.now() ## if there is no grace_tokens left, immediately terminate visit if grace_tokens <= 0: self.log.debug("Perching not valid. Out of grace tokens.") return False - ## set a time marker for the grace period. - grace_onset = dt.datetime.now() - ## while the current perch is unperched, while (self.current_perch["IR"].status() == False): ## if the grace period has ended - grace_period = (dt.datetime.now() - grace_onset).total_seconds + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds if grace_period > self.parameters['perch_grace_period']: self.log.debug("Perching not valid. Exceed grace period.") return False @@ -232,22 +230,20 @@ def validate_deperching(self): if (self.current_perch['IR'].status() == True): ## if the IR is still broken, no deperch return False - ## if the current perch is no longer broken, give a grace period + ## if the current perch is no longer broken, record perch_end time, and give a grace period else: + self.current_visit.perch_end = dt.datetime.now() ## if there is no grace_tokens left, immediately terminate visit if grace_tokens <= 0: self.log.debug("Perching Unstable. Out of grace tokens.") return True - ## set a time marker for the grace period. - grace_onset = dt.datetime.now() - ## while the current perch is unperched, while (self.current_perch['IR'].status() == False): ## if the grace period has ended, bird has deperched - grace_period = (dt.datetime.now() - grace_onset).total_seconds + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds if grace_period > self.parameters['perch_grace_period']: - self.log.debug("Perching not valid. Exceed grace period.") + self.log.debug("Subject Deperched. Exceed grace period.") return True else: grace_tokens = grace_tokens - 1 @@ -353,7 +349,6 @@ def end_visit(self): End visit and write data of current visit to csv """ self.log.debug("Ending visit and record end time.") - self.current_visit.perch_end = dt.datetime.now() self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() self.current_visit.valid = (self.current_visit.perch_dur >= self.parameters['min_perch_time']) self.save_visit(self.current_visit) From 7cd3e13430e7a96b44d664904d55752ed08490dc Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Thu, 20 Jul 2023 09:36:51 -0700 Subject: [PATCH 094/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index aacfa5cb..600dfd49 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -210,7 +210,7 @@ def validate_perching(self): ## while the current perch is unperched, while (self.current_perch["IR"].status() == False): ## if the grace period has ended - grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() if grace_period > self.parameters['perch_grace_period']: self.log.debug("Perching not valid. Exceed grace period.") return False @@ -241,7 +241,7 @@ def validate_deperching(self): ## while the current perch is unperched, while (self.current_perch['IR'].status() == False): ## if the grace period has ended, bird has deperched - grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() if grace_period > self.parameters['perch_grace_period']: self.log.debug("Subject Deperched. Exceed grace period.") return True From 7a4a15bc57d2d9adbe9d78dc2ff48eda95d303f1 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 24 Jul 2023 13:44:32 -0700 Subject: [PATCH 095/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 600dfd49..fb75824b 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -277,6 +277,8 @@ def stimulus_shuffle(self): while True: ## if deperching has been detected, or light schedule expires, quit this function if (self.validate_deperching() == True or self.check_session_schedule() == False): + if self.check_session_schedule() == False: + self.current_visit.perch_end = dt.datetime.now() self.stop_stimuli() return ## else, play audio until its length runs out, From e66b4d3af5edc4e57d1fac741153b2ed0b2a4dcf Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 16 Feb 2024 11:23:55 -0800 Subject: [PATCH 096/115] Bug fix pyoperant --- pyoperant/behavior/place_pref.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index fb75824b..39d9189e 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -263,11 +263,14 @@ def stimulus_shuffle(self): While perched, shuffle stimuli from a library """ - ## if the current class is silence, don't do shit + ## if the current class is silence, don't do shit except checking for light schedule if self.current_visit.class_ == "S": self.log.debug("Silence Perching") while True: - if self.validate_deperching() == True: + if (self.validate_deperching() == True or self.check_session_schedule() == False): + if self.check_session_schedule() == False: + self.current_visit.perch_end = dt.datetime.now() + self.stop_stimuli() return ## if the current class is not silence, prep and play stimuli else: @@ -351,7 +354,10 @@ def end_visit(self): End visit and write data of current visit to csv """ self.log.debug("Ending visit and record end time.") - self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() + try: + self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() + except: + self.current_visit.perch_dur = 0 self.current_visit.valid = (self.current_visit.perch_dur >= self.parameters['min_perch_time']) self.save_visit(self.current_visit) From 835bcf975dac6c97ea90f902bea4339e8531bf92 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 16 Feb 2024 11:48:59 -0800 Subject: [PATCH 097/115] hotfix --- pyoperant/behavior/place_pref.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 39d9189e..8576ab2c 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -270,7 +270,6 @@ def stimulus_shuffle(self): if (self.validate_deperching() == True or self.check_session_schedule() == False): if self.check_session_schedule() == False: self.current_visit.perch_end = dt.datetime.now() - self.stop_stimuli() return ## if the current class is not silence, prep and play stimuli else: From 98f4604352dce82ea55608db610672da75b60ff4 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 8 Mar 2024 13:09:40 -0800 Subject: [PATCH 098/115] Update place preference with variable ratio reinforcement --- pyoperant/behavior/place_pref.py | 47 ++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 8576ab2c..8667411f 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -74,7 +74,9 @@ def __init__(self, *args, **kwargs): 'valid', 'class_', 'stimuli', - 'events' + 'events', + 'vr', + 'reinforcement' ] if 'add_fields_to_save' in self.parameters.keys(): @@ -90,6 +92,8 @@ def __init__(self, *args, **kwargs): self.current_visit = None self.stimulus_event = None + self.reinforcement_counter = None + self.arduino = serial.Serial(self.parameters['arduino_address'], self.parameters['arduino_baud_rate'], timeout = 1) self.arduino.reset_input_buffer() @@ -170,6 +174,13 @@ def current_perch_stim_class(self): return self.parameters['perch_sequence'][str(self.experiment_day())][self.current_perch['speaker']] + def current_variable_ratio(self): + """ + Look in self.parameters for current reinforcement ratio of the day + """ + + return self.parameters['perch_sequence'][str(self.experiment_day())][3] ## index 3 is always the variable ratio + def experiment_day(self): """ Figure out what day of experiment we are on. @@ -249,8 +260,6 @@ def validate_deperching(self): grace_tokens = grace_tokens - 1 continue - - def switch_speaker(self): """ Use serial communication with the connected Arduino to switch @@ -258,13 +267,39 @@ def switch_speaker(self): self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) self.arduino.write(str(self.current_perch['speaker'] + 1).encode('utf-8')) + def reinforcement_logic(self): + """ + Figure out if current trial should be reinforced based on + reinforcement schedule of the day and previous reinforcement + """ + + if self.reinforcement_counter == None: + ## start new reinforcement_counter + self.log.info("Reinforcement empty. Calling in reinforcement at ratio %s." %s self.current_variable_ratio()) + self.reinforcement_counter = random.randint(1, 2*self.current_variable_ratio()) - 1 + self.log.info("Reinforcement inbound in %s visits." %s self.reinforcement_counter) + + ## If there are reinforcement counter is 0, reinforce + if self.reinforcement_counter == 0: + self.log.info("Reinforcement available. Reinforcing...") + self.reinforcement_counter = None ## wipe reinforcement + return True + else: + self.log.info("Reinforcement not available, inbound in %s visits. " %s self.reinforcement_counter) + self.reinforcement_counter = self.reinforcement_counter - 1 + return False + def stimulus_shuffle(self): """ While perched, shuffle stimuli from a library """ - ## if the current class is silence, don't do shit except checking for light schedule - if self.current_visit.class_ == "S": + ## decide if reinforcement should be given + self.current_visit.reinforcement = self.reinforcement_logic() + self.current_visit.vr = self.current_variable_ratio() + + ## if the current class is silence, or if reinforcement is not given, don't do shit except checking for light schedule + if (self.current_visit.class_ == "S" or self.current_visit.reinforcement == False): self.log.debug("Silence Perching") while True: if (self.validate_deperching() == True or self.check_session_schedule() == False): @@ -436,5 +471,7 @@ def session_main(self): ## def session_post(self): + self.reinforcement_counter = None ## flush reinforcement + self.log.info('Flushing reinforcement counter. ') self.log.info('Paradigm is closed. Proceed to Sleep. ') return None From f84209545c8fb708221e70ccbe6f1c42dacd86cf Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 8 Mar 2024 13:29:22 -0800 Subject: [PATCH 099/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 8667411f..81ed2980 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -275,7 +275,7 @@ def reinforcement_logic(self): if self.reinforcement_counter == None: ## start new reinforcement_counter - self.log.info("Reinforcement empty. Calling in reinforcement at ratio %s." %s self.current_variable_ratio()) + self.log.info("Reinforcement empty. Calling in reinforcement at ratio %s." %s str(self.current_variable_ratio())) self.reinforcement_counter = random.randint(1, 2*self.current_variable_ratio()) - 1 self.log.info("Reinforcement inbound in %s visits." %s self.reinforcement_counter) From dffdaa6d014694cc73afc589a73fb39d2732c664 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 8 Mar 2024 13:33:17 -0800 Subject: [PATCH 100/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index 81ed2980..b15be439 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -275,9 +275,9 @@ def reinforcement_logic(self): if self.reinforcement_counter == None: ## start new reinforcement_counter - self.log.info("Reinforcement empty. Calling in reinforcement at ratio %s." %s str(self.current_variable_ratio())) + self.log.info("Reinforcement empty. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) self.reinforcement_counter = random.randint(1, 2*self.current_variable_ratio()) - 1 - self.log.info("Reinforcement inbound in %s visits." %s self.reinforcement_counter) + self.log.info("Reinforcement inbound in %s visits." % str(self.reinforcement_counter)) ## If there are reinforcement counter is 0, reinforce if self.reinforcement_counter == 0: @@ -285,7 +285,7 @@ def reinforcement_logic(self): self.reinforcement_counter = None ## wipe reinforcement return True else: - self.log.info("Reinforcement not available, inbound in %s visits. " %s self.reinforcement_counter) + self.log.info("Reinforcement not available, inbound in %s visits. " % str(self.reinforcement_counter)) self.reinforcement_counter = self.reinforcement_counter - 1 return False From 8354cff510ce2c0fb712be3f0268de326f9b66de Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 8 Mar 2024 13:38:38 -0800 Subject: [PATCH 101/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index b15be439..fb7d0496 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -276,7 +276,7 @@ def reinforcement_logic(self): if self.reinforcement_counter == None: ## start new reinforcement_counter self.log.info("Reinforcement empty. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) - self.reinforcement_counter = random.randint(1, 2*self.current_variable_ratio()) - 1 + self.reinforcement_counter = random.randint(1, 2*self.current_variable_ratio() - 1) - 1 self.log.info("Reinforcement inbound in %s visits." % str(self.reinforcement_counter)) ## If there are reinforcement counter is 0, reinforce From eba02a9dec35ef8c9c5cfd19467e8ae885076705 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 8 Mar 2024 13:51:16 -0800 Subject: [PATCH 102/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index fb7d0496..f8dfa942 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -153,6 +153,10 @@ def open_all_perches(self): self.current_visit.perch_loc = self.current_perch['IRName'] self.current_visit.class_ = self.current_perch_stim_class() + ## pre populate + self.current_visit.reinforcement = False + self.current_visit.vr = self.current_variable_ratio() + ## if validate perching fails, reset perches and open if (self.validate_perching() == False): self.end_visit() @@ -296,7 +300,6 @@ def stimulus_shuffle(self): ## decide if reinforcement should be given self.current_visit.reinforcement = self.reinforcement_logic() - self.current_visit.vr = self.current_variable_ratio() ## if the current class is silence, or if reinforcement is not given, don't do shit except checking for light schedule if (self.current_visit.class_ == "S" or self.current_visit.reinforcement == False): From f58d832191fbbdd095c77c92267873c70b1ccf41 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 8 Mar 2024 13:55:55 -0800 Subject: [PATCH 103/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index f8dfa942..a02237ba 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -154,7 +154,7 @@ def open_all_perches(self): self.current_visit.class_ = self.current_perch_stim_class() ## pre populate - self.current_visit.reinforcement = False + self.current_visit.reinforcement = False ## default do nothing self.current_visit.vr = self.current_variable_ratio() ## if validate perching fails, reset perches and open From 075ea046b231afb50730e0983ef80429cb7430d7 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Fri, 8 Mar 2024 14:19:52 -0800 Subject: [PATCH 104/115] Update separate reinforcement counter for each perch --- pyoperant/behavior/place_pref.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index a02237ba..ad0a71bf 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -92,7 +92,7 @@ def __init__(self, *args, **kwargs): self.current_visit = None self.stimulus_event = None - self.reinforcement_counter = None + self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## set up separate reinforcement counters for all perches self.arduino = serial.Serial(self.parameters['arduino_address'], self.parameters['arduino_baud_rate'], timeout = 1) self.arduino.reset_input_buffer() @@ -277,20 +277,20 @@ def reinforcement_logic(self): reinforcement schedule of the day and previous reinforcement """ - if self.reinforcement_counter == None: + if self.reinforcement_counter[self.current_visit.perch_loc] == None: ## start new reinforcement_counter - self.log.info("Reinforcement empty. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) - self.reinforcement_counter = random.randint(1, 2*self.current_variable_ratio() - 1) - 1 - self.log.info("Reinforcement inbound in %s visits." % str(self.reinforcement_counter)) + self.log.info("Reinforcement empty at current perch. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) + self.reinforcement_counter[self.current_visit.perch_loc] = random.randint(1, 2*self.current_variable_ratio() - 1) - 1 + self.log.info("Reinforcement inbound in %s visits." % str(self.reinforcement_counter[self.current_visit.perch_loc])) ## If there are reinforcement counter is 0, reinforce - if self.reinforcement_counter == 0: - self.log.info("Reinforcement available. Reinforcing...") - self.reinforcement_counter = None ## wipe reinforcement + if self.reinforcement_counter[self.current_visit.perch_loc] == 0: + self.log.info("Reinforcement available at current perch. Reinforcing...") + self.reinforcement_counter[self.current_visit.perch_loc] = None ## wipe reinforcement return True else: - self.log.info("Reinforcement not available, inbound in %s visits. " % str(self.reinforcement_counter)) - self.reinforcement_counter = self.reinforcement_counter - 1 + self.log.info("Reinforcement not available at current perch, inbound in %s visits. " % str(self.reinforcement_counter[self.current_visit.perch_loc])) + self.reinforcement_counter[self.current_visit.perch_loc] = self.reinforcement_counter[self.current_visit.perch_loc] - 1 return False def stimulus_shuffle(self): From 40cedd2fadd54760ac505374288688157a276307 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Thu, 21 Mar 2024 10:35:22 -0700 Subject: [PATCH 105/115] Update place_pref.py --- pyoperant/behavior/place_pref.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref.py b/pyoperant/behavior/place_pref.py index ad0a71bf..4ee61c4b 100644 --- a/pyoperant/behavior/place_pref.py +++ b/pyoperant/behavior/place_pref.py @@ -474,7 +474,7 @@ def session_main(self): ## def session_post(self): - self.reinforcement_counter = None ## flush reinforcement + self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## flush reinforcement self.log.info('Flushing reinforcement counter. ') self.log.info('Paradigm is closed. Proceed to Sleep. ') return None From 9aaaf9314ff2b41eead074df73e1778286e00d36 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 19 Aug 2024 11:09:53 -0700 Subject: [PATCH 106/115] Create place_pref_24hr.py --- pyoperant/behavior/place_pref_24hr.py | 486 ++++++++++++++++++++++++++ 1 file changed, 486 insertions(+) create mode 100644 pyoperant/behavior/place_pref_24hr.py diff --git a/pyoperant/behavior/place_pref_24hr.py b/pyoperant/behavior/place_pref_24hr.py new file mode 100644 index 00000000..75cb6763 --- /dev/null +++ b/pyoperant/behavior/place_pref_24hr.py @@ -0,0 +1,486 @@ +import os +import csv +import copy +import datetime as dt +from pyoperant.behavior import base, shape +from pyoperant.errors import EndSession, EndBlock +from pyoperant import components, utils, reinf, queues + +import serial +import random + +class PlacePrefExp24hr(base.BaseExp): + ''' + A place preference experiment. This version is identical to PlacePrefExp, + except that it is modified to record 24/7. + + In this paradigm, a bird is released into a cage with three nestbox perches. + The logic of this experiment is that when a bird lands on a perch for + sufficiently long enough, a trial will start and the stimulus will start + playing from a library, shuffling through the library as the bird continues + to perch. When the bird leaves the perch, the song will stop. + + Nighttime activity is also recorded. + + The data that need to be recorded are + 1. When did the bird land on the perch? + 2. Which perch did the bird land on? + 3. How long did the bird land? + 4. Did this bird land long enough for the songs to trigger? + 5. Which category of song was the perch playing? + 6. What actual stimuli were playing? + + + Parameters + ---------- + + Attributes + ---------- + req_panel_attr : list + list of the panel attributes that are required for this behavior + fields_to_save : list + list of the fields of the Trial object that will be saved + trials : list + all the trials that have run + parameter : dict + all additional parameters for the experiment + data_csv : string + path to csv file to save data_csv + ''' + + def __init__(self, *args, **kwargs): + super(PlacePrefExp, self).__init__(*args, **kwargs) + + ## assign stim files full names + for name, filename in self.parameters['stims_A'].items(): + filename_full = os.path.join(self.parameters['stims_A_path'], filename) + self.parameters['stims_A'][name] = filename_full + + ## assign stim files full names + for name, filename in self.parameters['stims_B'].items(): + filename_full = os.path.join(self.parameters['stims_B_path'], filename) + self.parameters['stims_B'][name] = filename_full + + self.req_panel_attr += [ + 'speaker', + 'left', + 'center', + 'right', + 'house_light', + 'reset' + ] + + self.fields_to_save = [ + 'perch_strt', + 'perch_end', + 'perch_dur', + 'perch_loc', + 'valid', + 'class_', + 'stimuli', + 'events', + 'vr', + 'reinforcement' + ] + + if 'add_fields_to_save' in self.parameters.keys(): + self.fields_to_save += self.parameters['add_fields_to_save'] + + self.exp_strt_date = dt.date( + year = self.parameters['experiment_start_year'], + month = self.parameters['experiment_start_month'], + day = self.parameters['experiment_start_day'] + ) + + self.current_perch = {'IR': None, 'IRName': None, 'speaker': None} + self.current_visit = None + self.stimulus_event = None + + self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## set up separate reinforcement counters for all perches + + self.arduino = serial.Serial(self.parameters['arduino_address'], self.parameters['arduino_baud_rate'], timeout = 1) + self.arduino.reset_input_buffer() + + self.data_csv = os.path.join(self.parameters['experiment_path'], + self.parameters['subject']+'_trialdata_'+self.timestamp+'.csv') + self.make_data_csv() + + def make_data_csv(self): + """ Create the csv file to save trial data + + This creates a new csv file at experiment.data_csv and writes a header row + with the fields in experiment.fields_to_save + """ + with open(self.data_csv, 'wb') as data_fh: + trialWriter = csv.writer(data_fh) + trialWriter.writerow(self.fields_to_save) + + def open_all_perches(self): + """ + At down state (no active trials), open IR on all perches + """ + self.log.debug("Opening all perches!") + + ## normally IR status returns false if a beam is not broken. + ## If one of them is broken, return true + while self.current_perch['IR'] == None: + + ## check if time of day is appropriate for a visit + if self.check_session_schedule() == False: + return + + ## for each iteration, check for perching behavior. + left_perched = self.panel.left.status() + center_perched = self.panel.center.status() + right_perched = self.panel.right.status() + + if left_perched: + self.current_perch['IR'] = self.panel.left + self.current_perch['IRName'] = 'L' + self.current_perch['speaker'] = 0 + break + if center_perched: + self.current_perch['IR'] = self.panel.center + self.current_perch['IRName'] = 'C' + self.current_perch['speaker'] = 1 + break + if right_perched: + self.current_perch['IR'] = self.panel.right + self.current_perch['IRName'] = 'R' + self.current_perch['speaker'] = 2 + break + + ## Record time of break + self.log.debug("Beam-break sensed on %s" % (self.current_perch)) + self.current_visit = utils.Visit() + self.current_visit.perch_strt = dt.datetime.now() + self.current_visit.perch_loc = self.current_perch['IRName'] + self.current_visit.class_ = self.current_perch_stim_class() + + ## pre populate + self.current_visit.reinforcement = False ## default do nothing + self.current_visit.vr = self.current_variable_ratio() + + ## if validate perching fails, reset perches and open + if (self.validate_perching() == False): + self.end_visit() + self.reset_perches() + try: + self.open_all_perches() + except RuntimeError: + self.log.debug("RuntimeError: max recursion") + return + else: + self.log.debug("Perch valid at %s" % (self.current_perch)) + + def current_perch_stim_class(self): + """ + Look in self.parameters for the order of randomized perch orders + depending on what day of experiment we are on + Can be "S" for silence, "A" for category A, "B" for category B + """ + + return self.parameters['perch_sequence'][str(self.experiment_day())][self.current_perch['speaker']] + + def current_variable_ratio(self): + """ + Look in self.parameters for current reinforcement ratio of the day + """ + + return self.parameters['perch_sequence'][str(self.experiment_day())][3] ## index 3 is always the variable ratio + + def experiment_day(self): + """ + Figure out what day of experiment we are on. + """ + return (dt.date.today() - self.exp_strt_date).days + + + def validate_perching(self): + """ + For any perching behavior to be valid, + the IR needs to be broken for at least one second. + """ + ## validate current perch + self.log.debug("Trying to validate perch...") + + ## for each perch validation effort, the beam-break has a certain amount of graces + ## + grace_tokens = self.parameters['grace_num'] + + while True: + elapsed_time = (dt.datetime.now() - self.current_visit.perch_strt).total_seconds() + ## keep asking if the current perch is still broken + if (self.current_perch['IR'].status() == True): + ## if elapsed time is more than a minimum perch time, perching is valid + if elapsed_time > self.parameters['min_perch_time']: + return True + ## if time is not up, continue the loop + else: + continue + ## if current perch is no longer broken, record perch end time, give a grace period + else: + self.current_visit.perch_end = dt.datetime.now() + ## if there is no grace_tokens left, immediately terminate visit + if grace_tokens <= 0: + self.log.debug("Perching not valid. Out of grace tokens.") + return False + + ## while the current perch is unperched, + while (self.current_perch["IR"].status() == False): + ## if the grace period has ended + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() + if grace_period > self.parameters['perch_grace_period']: + self.log.debug("Perching not valid. Exceed grace period.") + return False + else: + grace_tokens = grace_tokens - 1 + continue + + def validate_deperching(self): + """ + For any deperching behavior to be valid, + the IR needs to be unbroken for at least one second. + """ + grace_tokens = self.parameters['grace_num'] + + while True: + ## keep asking if the current perch is still broken + if (self.current_perch['IR'].status() == True): + ## if the IR is still broken, no deperch + return False + ## if the current perch is no longer broken, record perch_end time, and give a grace period + else: + self.current_visit.perch_end = dt.datetime.now() + ## if there is no grace_tokens left, immediately terminate visit + if grace_tokens <= 0: + self.log.debug("Perching Unstable. Out of grace tokens.") + return True + + ## while the current perch is unperched, + while (self.current_perch['IR'].status() == False): + ## if the grace period has ended, bird has deperched + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() + if grace_period > self.parameters['perch_grace_period']: + self.log.debug("Subject Deperched. Exceed grace period.") + return True + else: + grace_tokens = grace_tokens - 1 + continue + + def switch_speaker(self): + """ + Use serial communication with the connected Arduino to switch + """ + self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) + self.arduino.write(str(self.current_perch['speaker'] + 1).encode('utf-8')) + + def reinforcement_logic(self): + """ + Figure out if current trial should be reinforced based on + reinforcement schedule of the day and previous reinforcement + """ + + if self.reinforcement_counter[self.current_visit.perch_loc] == None: + ## start new reinforcement_counter + self.log.info("Reinforcement empty at current perch. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) + self.reinforcement_counter[self.current_visit.perch_loc] = random.randint(1, 2*self.current_variable_ratio() - 1) - 1 + self.log.info("Reinforcement inbound in %s visits." % str(self.reinforcement_counter[self.current_visit.perch_loc])) + + ## If there are reinforcement counter is 0, reinforce + if self.reinforcement_counter[self.current_visit.perch_loc] == 0: + self.log.info("Reinforcement available at current perch. Reinforcing...") + self.reinforcement_counter[self.current_visit.perch_loc] = None ## wipe reinforcement + return True + else: + self.log.info("Reinforcement not available at current perch, inbound in %s visits. " % str(self.reinforcement_counter[self.current_visit.perch_loc])) + self.reinforcement_counter[self.current_visit.perch_loc] = self.reinforcement_counter[self.current_visit.perch_loc] - 1 + return False + + def stimulus_shuffle(self): + """ + While perched, shuffle stimuli from a library + """ + + ## decide if reinforcement should be given + self.current_visit.reinforcement = self.reinforcement_logic() + + ## if the current class is silence, or if reinforcement is not given, don't do shit except checking for light schedule + if (self.current_visit.class_ == "S" or self.current_visit.reinforcement == False): + self.log.debug("Silence Perching") + while True: + if (self.validate_deperching() == True or self.check_session_schedule() == False): + if self.check_session_schedule() == False: + self.current_visit.perch_end = dt.datetime.now() + return + ## if the current class is not silence, prep and play stimuli + else: + self.prep_stimuli() + self.play_stimuli() + + while True: + ## if deperching has been detected, or light schedule expires, quit this function + if (self.validate_deperching() == True or self.check_session_schedule() == False): + if self.check_session_schedule() == False: + self.current_visit.perch_end = dt.datetime.now() + self.stop_stimuli() + return + ## else, play audio until its length runs out, + else: + elapsed_time = (dt.datetime.now() - self.stimulus_event.time).total_seconds() + if elapsed_time < self.stimulus_event.duration: + continue + # when it does, give an inter_stim_interval, and recurse on the function + else: + if elapsed_time < (self.stimulus_event.duration + self.parameters['inter_stim_interval']): + continue + ## when inter_stim_interval runs out, stop stimuli and go back to the stimulus_shuffle + else: + self.log.debug("Stimuli finished and inter_stim_interval has passed. ") + self.stop_stimuli() + ## find another clip to play + self.prep_stimuli() + self.play_stimuli() + + def perch_playlist(self): + """ + depending on the perch and the perch_sequence, find the correct list + """ + if self.current_perch_stim_class() == "A": + self.log.debug("Perch stim class A") + return self.parameters['stims_A'] + if self.current_perch_stim_class() == "B": + self.log.debug("Perch stim class B") + return self.parameters['stims_B'] + + def prep_stimuli(self): + """ + Prep stimuli and generate a stimulus event + """ + ## randomly shuffle from the current perch_playlist + stim_file = random.sample(self.perch_playlist().items(), k = 1)[0][1] + self.log.debug(stim_file) + stim = utils.auditory_stim_from_wav(stim_file) + + self.stimulus_event = utils.Event( + time = dt.datetime.now(), + duration = stim.duration, + file_origin = stim.file_origin, + ) + + self.log.debug("Queuing stimulus %s" % stim.file_origin) + self.panel.speaker.queue(self.stimulus_event.file_origin) + + def play_stimuli(self): + """ + Play stimuli through current_perch speaker + """ + + self.log.debug("Playing %s" % (self.stimulus_event.file_origin)) + ## trigger speaker + self.panel.speaker.play() + + def stop_stimuli(self): + """ + Stop stimuli, record event, and clear out event + """ + self.log.debug("Stop stimuli and flush stimulus event.") + self.panel.speaker.stop() + self.current_visit.stimuli.append(self.stimulus_event.file_origin) + self.current_visit.events.append(self.stimulus_event) + self.stimulus_event = None + + def end_visit(self): + """ + End visit and write data of current visit to csv + """ + self.log.debug("Ending visit and record end time.") + try: + self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() + except: + self.current_visit.perch_dur = 0 + self.current_visit.valid = (self.current_visit.perch_dur >= self.parameters['min_perch_time']) + self.save_visit(self.current_visit) + + def save_visit(self, visit): + """ + write visit results to CSV + """ + + self.log.debug("Writing data to %s" % (self.data_csv)) + visit_dict = {} + for field in self.fields_to_save: + try: + visit_dict[field] = getattr(visit,field) + except AttributeError: + visit_dict[field] = visit.annotations[field] ## it might be in annotations for some reason + + with open(self.data_csv, 'ab') as data_fh: + visitWriter = csv.DictWriter(data_fh, fieldnames = self.fields_to_save, extrasaction = 'ignore') + visitWriter.writerow(visit_dict) + + def reset_perches(self): + """ + Reset perches + """ + + self.current_perch = {'IR': None, 'IRName': None, 'speaker': None} + self.stimulus_event = None + self.current_visit = None + + def check_session_schedule(self): + """ + Check if perches should be open + + returns + ------- + bool + True, if sessions should be running + """ + return utils.check_time(self.parameters['light_schedule']) + + def session_main(self): + """ + Inside session_main, maintain a loop that controls paradigm behavior + """ + + ## while the session is in daylight + while self.check_session_schedule() == True: + ''' + Try to open all perches. The program loops in open_all_perches + until a valid perching has been detected. + ''' + self.open_all_perches() + + ''' + Once perching has been detected, switch speaker to the current + perch, and start stimuli shuffle. The program loops in stimulus_shuffle() + until a valid deperching has been detected() + ''' + if (self.current_perch['IR'] != None): + if (self.current_perch['IR'].status() == True): + self.switch_speaker() + self.stimulus_shuffle() + + ''' + Once deperched, end visit and reset + ''' + self.end_visit() + self.reset_perches() + + ## while it is nighttime, enter alternative nighttime loop + while self.check_session_schedule() == False: + ''' + Try to open all perches. The program loops in open_all_perches + until a valid perching has been detected. + ''' + self.open_all_perches() + + + + + def session_post(self): + self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## flush reinforcement + self.log.info('Flushing reinforcement counter. ') + self.log.info('Paradigm is closed. Proceed to Sleep. ') + return None From ef15a8a156663cb5c29e17d0734ab64453fedc88 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 19 Aug 2024 15:54:54 -0700 Subject: [PATCH 107/115] Update 24hr for testing --- pyoperant/behavior/place_pref_24hr.py | 322 ++------------------------ 1 file changed, 24 insertions(+), 298 deletions(-) diff --git a/pyoperant/behavior/place_pref_24hr.py b/pyoperant/behavior/place_pref_24hr.py index 75cb6763..9966fb1a 100644 --- a/pyoperant/behavior/place_pref_24hr.py +++ b/pyoperant/behavior/place_pref_24hr.py @@ -49,7 +49,7 @@ class PlacePrefExp24hr(base.BaseExp): ''' def __init__(self, *args, **kwargs): - super(PlacePrefExp, self).__init__(*args, **kwargs) + super(PlacePrefExp24hr, self).__init__(*args, **kwargs) ## assign stim files full names for name, filename in self.parameters['stims_A'].items(): @@ -92,17 +92,20 @@ def __init__(self, *args, **kwargs): day = self.parameters['experiment_start_day'] ) + self.daylight = None + self.current_perch = {'IR': None, 'IRName': None, 'speaker': None} self.current_visit = None self.stimulus_event = None - self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## set up separate reinforcement counters for all perches + self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## set up separate reinforcement counters for all perches self.arduino = serial.Serial(self.parameters['arduino_address'], self.parameters['arduino_baud_rate'], timeout = 1) self.arduino.reset_input_buffer() self.data_csv = os.path.join(self.parameters['experiment_path'], self.parameters['subject']+'_trialdata_'+self.timestamp+'.csv') + self.make_data_csv() def make_data_csv(self): @@ -115,7 +118,7 @@ def make_data_csv(self): trialWriter = csv.writer(data_fh) trialWriter.writerow(self.fields_to_save) - def open_all_perches(self): +def open_all_perches(self): """ At down state (no active trials), open IR on all perches """ @@ -125,8 +128,8 @@ def open_all_perches(self): ## If one of them is broken, return true while self.current_perch['IR'] == None: - ## check if time of day is appropriate for a visit - if self.check_session_schedule() == False: + ## check if a daylight cycle shift should occur + if self.check_session_schedule() : return ## for each iteration, check for perching behavior. @@ -173,314 +176,37 @@ def open_all_perches(self): else: self.log.debug("Perch valid at %s" % (self.current_perch)) - def current_perch_stim_class(self): - """ - Look in self.parameters for the order of randomized perch orders - depending on what day of experiment we are on - Can be "S" for silence, "A" for category A, "B" for category B - """ - - return self.parameters['perch_sequence'][str(self.experiment_day())][self.current_perch['speaker']] + def light_switch(): + ''' + This checks if paradigm daylight is incongruent with system time + ''' + return self.daylight != utils.check_time(self.parameters['light_schedule']) - def current_variable_ratio(self): + def session_pre(self): """ - Look in self.parameters for current reinforcement ratio of the day + Check if session should start in daylight or night. + If daylight, state machine will transition to day loop in session_main. + If night, state machine will transition to night loop in session_post. """ - return self.parameters['perch_sequence'][str(self.experiment_day())][3] ## index 3 is always the variable ratio - - def experiment_day(self): - """ - Figure out what day of experiment we are on. - """ - return (dt.date.today() - self.exp_strt_date).days - - - def validate_perching(self): - """ - For any perching behavior to be valid, - the IR needs to be broken for at least one second. - """ - ## validate current perch - self.log.debug("Trying to validate perch...") - - ## for each perch validation effort, the beam-break has a certain amount of graces - ## - grace_tokens = self.parameters['grace_num'] - - while True: - elapsed_time = (dt.datetime.now() - self.current_visit.perch_strt).total_seconds() - ## keep asking if the current perch is still broken - if (self.current_perch['IR'].status() == True): - ## if elapsed time is more than a minimum perch time, perching is valid - if elapsed_time > self.parameters['min_perch_time']: - return True - ## if time is not up, continue the loop - else: - continue - ## if current perch is no longer broken, record perch end time, give a grace period - else: - self.current_visit.perch_end = dt.datetime.now() - ## if there is no grace_tokens left, immediately terminate visit - if grace_tokens <= 0: - self.log.debug("Perching not valid. Out of grace tokens.") - return False - - ## while the current perch is unperched, - while (self.current_perch["IR"].status() == False): - ## if the grace period has ended - grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() - if grace_period > self.parameters['perch_grace_period']: - self.log.debug("Perching not valid. Exceed grace period.") - return False - else: - grace_tokens = grace_tokens - 1 - continue - - def validate_deperching(self): - """ - For any deperching behavior to be valid, - the IR needs to be unbroken for at least one second. - """ - grace_tokens = self.parameters['grace_num'] - - while True: - ## keep asking if the current perch is still broken - if (self.current_perch['IR'].status() == True): - ## if the IR is still broken, no deperch - return False - ## if the current perch is no longer broken, record perch_end time, and give a grace period - else: - self.current_visit.perch_end = dt.datetime.now() - ## if there is no grace_tokens left, immediately terminate visit - if grace_tokens <= 0: - self.log.debug("Perching Unstable. Out of grace tokens.") - return True - - ## while the current perch is unperched, - while (self.current_perch['IR'].status() == False): - ## if the grace period has ended, bird has deperched - grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() - if grace_period > self.parameters['perch_grace_period']: - self.log.debug("Subject Deperched. Exceed grace period.") - return True - else: - grace_tokens = grace_tokens - 1 - continue - - def switch_speaker(self): - """ - Use serial communication with the connected Arduino to switch - """ - self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) - self.arduino.write(str(self.current_perch['speaker'] + 1).encode('utf-8')) + ## assign light schedule to + self.daylight = utils.check_time(self.parameters['light_schedule']) - def reinforcement_logic(self): - """ - Figure out if current trial should be reinforced based on - reinforcement schedule of the day and previous reinforcement - """ - - if self.reinforcement_counter[self.current_visit.perch_loc] == None: - ## start new reinforcement_counter - self.log.info("Reinforcement empty at current perch. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) - self.reinforcement_counter[self.current_visit.perch_loc] = random.randint(1, 2*self.current_variable_ratio() - 1) - 1 - self.log.info("Reinforcement inbound in %s visits." % str(self.reinforcement_counter[self.current_visit.perch_loc])) - - ## If there are reinforcement counter is 0, reinforce - if self.reinforcement_counter[self.current_visit.perch_loc] == 0: - self.log.info("Reinforcement available at current perch. Reinforcing...") - self.reinforcement_counter[self.current_visit.perch_loc] = None ## wipe reinforcement - return True - else: - self.log.info("Reinforcement not available at current perch, inbound in %s visits. " % str(self.reinforcement_counter[self.current_visit.perch_loc])) - self.reinforcement_counter[self.current_visit.perch_loc] = self.reinforcement_counter[self.current_visit.perch_loc] - 1 - return False - - def stimulus_shuffle(self): - """ - While perched, shuffle stimuli from a library - """ - - ## decide if reinforcement should be given - self.current_visit.reinforcement = self.reinforcement_logic() - - ## if the current class is silence, or if reinforcement is not given, don't do shit except checking for light schedule - if (self.current_visit.class_ == "S" or self.current_visit.reinforcement == False): - self.log.debug("Silence Perching") - while True: - if (self.validate_deperching() == True or self.check_session_schedule() == False): - if self.check_session_schedule() == False: - self.current_visit.perch_end = dt.datetime.now() - return - ## if the current class is not silence, prep and play stimuli + if self.daylight: + return "main" else: - self.prep_stimuli() - self.play_stimuli() - - while True: - ## if deperching has been detected, or light schedule expires, quit this function - if (self.validate_deperching() == True or self.check_session_schedule() == False): - if self.check_session_schedule() == False: - self.current_visit.perch_end = dt.datetime.now() - self.stop_stimuli() - return - ## else, play audio until its length runs out, - else: - elapsed_time = (dt.datetime.now() - self.stimulus_event.time).total_seconds() - if elapsed_time < self.stimulus_event.duration: - continue - # when it does, give an inter_stim_interval, and recurse on the function - else: - if elapsed_time < (self.stimulus_event.duration + self.parameters['inter_stim_interval']): - continue - ## when inter_stim_interval runs out, stop stimuli and go back to the stimulus_shuffle - else: - self.log.debug("Stimuli finished and inter_stim_interval has passed. ") - self.stop_stimuli() - ## find another clip to play - self.prep_stimuli() - self.play_stimuli() - - def perch_playlist(self): - """ - depending on the perch and the perch_sequence, find the correct list - """ - if self.current_perch_stim_class() == "A": - self.log.debug("Perch stim class A") - return self.parameters['stims_A'] - if self.current_perch_stim_class() == "B": - self.log.debug("Perch stim class B") - return self.parameters['stims_B'] - - def prep_stimuli(self): - """ - Prep stimuli and generate a stimulus event - """ - ## randomly shuffle from the current perch_playlist - stim_file = random.sample(self.perch_playlist().items(), k = 1)[0][1] - self.log.debug(stim_file) - stim = utils.auditory_stim_from_wav(stim_file) - - self.stimulus_event = utils.Event( - time = dt.datetime.now(), - duration = stim.duration, - file_origin = stim.file_origin, - ) - - self.log.debug("Queuing stimulus %s" % stim.file_origin) - self.panel.speaker.queue(self.stimulus_event.file_origin) - - def play_stimuli(self): - """ - Play stimuli through current_perch speaker - """ + return "post" - self.log.debug("Playing %s" % (self.stimulus_event.file_origin)) - ## trigger speaker - self.panel.speaker.play() - - def stop_stimuli(self): - """ - Stop stimuli, record event, and clear out event - """ - self.log.debug("Stop stimuli and flush stimulus event.") - self.panel.speaker.stop() - self.current_visit.stimuli.append(self.stimulus_event.file_origin) - self.current_visit.events.append(self.stimulus_event) - self.stimulus_event = None - - def end_visit(self): - """ - End visit and write data of current visit to csv - """ - self.log.debug("Ending visit and record end time.") - try: - self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() - except: - self.current_visit.perch_dur = 0 - self.current_visit.valid = (self.current_visit.perch_dur >= self.parameters['min_perch_time']) - self.save_visit(self.current_visit) - - def save_visit(self, visit): - """ - write visit results to CSV - """ - - self.log.debug("Writing data to %s" % (self.data_csv)) - visit_dict = {} - for field in self.fields_to_save: - try: - visit_dict[field] = getattr(visit,field) - except AttributeError: - visit_dict[field] = visit.annotations[field] ## it might be in annotations for some reason - - with open(self.data_csv, 'ab') as data_fh: - visitWriter = csv.DictWriter(data_fh, fieldnames = self.fields_to_save, extrasaction = 'ignore') - visitWriter.writerow(visit_dict) - - def reset_perches(self): - """ - Reset perches - """ - - self.current_perch = {'IR': None, 'IRName': None, 'speaker': None} - self.stimulus_event = None - self.current_visit = None - - def check_session_schedule(self): - """ - Check if perches should be open - - returns - ------- - bool - True, if sessions should be running - """ - return utils.check_time(self.parameters['light_schedule']) def session_main(self): """ - Inside session_main, maintain a loop that controls paradigm behavior + Inside session_pre, maintain a loop that controls *day* behavior. """ - ## while the session is in daylight - while self.check_session_schedule() == True: - ''' - Try to open all perches. The program loops in open_all_perches - until a valid perching has been detected. - ''' - self.open_all_perches() - - ''' - Once perching has been detected, switch speaker to the current - perch, and start stimuli shuffle. The program loops in stimulus_shuffle() - until a valid deperching has been detected() - ''' - if (self.current_perch['IR'] != None): - if (self.current_perch['IR'].status() == True): - self.switch_speaker() - self.stimulus_shuffle() - - ''' - Once deperched, end visit and reset - ''' - self.end_visit() - self.reset_perches() + while True: - ## while it is nighttime, enter alternative nighttime loop - while self.check_session_schedule() == False: ''' Try to open all perches. The program loops in open_all_perches until a valid perching has been detected. ''' self.open_all_perches() - - - - - def session_post(self): - self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## flush reinforcement - self.log.info('Flushing reinforcement counter. ') - self.log.info('Paradigm is closed. Proceed to Sleep. ') - return None From 12ed5c596e565855fb8ffd14133ed2e6d51eff63 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Mon, 19 Aug 2024 15:56:10 -0700 Subject: [PATCH 108/115] Update place_pref_24hr.py --- pyoperant/behavior/place_pref_24hr.py | 408 ++++++++++++++++++++++---- 1 file changed, 350 insertions(+), 58 deletions(-) diff --git a/pyoperant/behavior/place_pref_24hr.py b/pyoperant/behavior/place_pref_24hr.py index 9966fb1a..89145ecd 100644 --- a/pyoperant/behavior/place_pref_24hr.py +++ b/pyoperant/behavior/place_pref_24hr.py @@ -118,65 +118,326 @@ def make_data_csv(self): trialWriter = csv.writer(data_fh) trialWriter.writerow(self.fields_to_save) -def open_all_perches(self): + def open_all_perches(self): + """ + At down state (no active trials), open IR on all perches + """ + self.log.debug("Opening all perches!") + + ## normally IR status returns false if a beam is not broken. + ## If one of them is broken, return true + while self.current_perch['IR'] == None: + + ## check if a daylight cycle shift should occur + if self.light_switch(): + ## exit current loop + return + + ## for each iteration, check for perching behavior. + left_perched = self.panel.left.status() + center_perched = self.panel.center.status() + right_perched = self.panel.right.status() + + if left_perched: + self.current_perch['IR'] = self.panel.left + self.current_perch['IRName'] = 'L' + self.current_perch['speaker'] = 0 + break + if center_perched: + self.current_perch['IR'] = self.panel.center + self.current_perch['IRName'] = 'C' + self.current_perch['speaker'] = 1 + break + if right_perched: + self.current_perch['IR'] = self.panel.right + self.current_perch['IRName'] = 'R' + self.current_perch['speaker'] = 2 + break + + ## Record time of break + self.log.debug("Beam-break sensed on %s" % (self.current_perch)) + self.current_visit = utils.Visit() + self.current_visit.perch_strt = dt.datetime.now() + self.current_visit.perch_loc = self.current_perch['IRName'] + self.current_visit.class_ = self.current_perch_stim_class() + + ## pre populate + self.current_visit.reinforcement = False ## default do nothing + self.current_visit.vr = self.current_variable_ratio() + + ## if validate perching fails, reset perches and open + if (self.validate_perching() == False): + self.end_visit() + self.reset_perches() + try: + self.open_all_perches() + except RuntimeError: + self.log.debug("RuntimeError: max recursion") + return + else: + self.log.debug("Perch valid at %s" % (self.current_perch)) + + def current_perch_stim_class(self): """ - At down state (no active trials), open IR on all perches + Look in self.parameters for the order of randomized perch orders + depending on what day of experiment we are on + Can be "S" for silence, "A" for category A, "B" for category B """ - self.log.debug("Opening all perches!") - ## normally IR status returns false if a beam is not broken. - ## If one of them is broken, return true - while self.current_perch['IR'] == None: + return self.parameters['perch_sequence'][str(self.experiment_day())][self.current_perch['speaker']] - ## check if a daylight cycle shift should occur - if self.check_session_schedule() : + def current_variable_ratio(self): + """ + Look in self.parameters for current reinforcement ratio of the day + """ + + return self.parameters['perch_sequence'][str(self.experiment_day())][3] ## index 3 is always the variable ratio + + def experiment_day(self): + """ + Figure out what day of experiment we are on. + """ + return (dt.date.today() - self.exp_strt_date).days + + def validate_perching(self): + """ + For any perching behavior to be valid, + the IR needs to be broken for at least one second. + """ + ## validate current perch + self.log.debug("Trying to validate perch...") + + ## for each perch validation effort, the beam-break has a certain amount of graces + ## + grace_tokens = self.parameters['grace_num'] + + while True: + elapsed_time = (dt.datetime.now() - self.current_visit.perch_strt).total_seconds() + ## keep asking if the current perch is still broken + if (self.current_perch['IR'].status() == True): + ## if elapsed time is more than a minimum perch time, perching is valid + if elapsed_time > self.parameters['min_perch_time']: + return True + ## if time is not up, continue the loop + else: + continue + ## if current perch is no longer broken, record perch end time, give a grace period + else: + self.current_visit.perch_end = dt.datetime.now() + ## if there is no grace_tokens left, immediately terminate visit + if grace_tokens <= 0: + self.log.debug("Perching not valid. Out of grace tokens.") + return False + + ## while the current perch is unperched, + while (self.current_perch["IR"].status() == False): + ## if the grace period has ended + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() + if grace_period > self.parameters['perch_grace_period']: + self.log.debug("Perching not valid. Exceed grace period.") + return False + else: + grace_tokens = grace_tokens - 1 + continue + + def validate_deperching(self): + """ + For any deperching behavior to be valid, + the IR needs to be unbroken for at least one second. + """ + grace_tokens = self.parameters['grace_num'] + + while True: + ## keep asking if the current perch is still broken + if (self.current_perch['IR'].status() == True): + ## if the IR is still broken, no deperch + return False + ## if the current perch is no longer broken, record perch_end time, and give a grace period + else: + self.current_visit.perch_end = dt.datetime.now() + ## if there is no grace_tokens left, immediately terminate visit + if grace_tokens <= 0: + self.log.debug("Perching Unstable. Out of grace tokens.") + return True + + ## while the current perch is unperched, + while (self.current_perch['IR'].status() == False): + ## if the grace period has ended, bird has deperched + grace_period = (dt.datetime.now() - self.current_visit.perch_end).total_seconds() + if grace_period > self.parameters['perch_grace_period']: + self.log.debug("Subject Deperched. Exceed grace period.") + return True + else: + grace_tokens = grace_tokens - 1 + continue + + def switch_speaker(self): + """ + Use serial communication with the connected Arduino to switch + """ + self.log.debug("Switching speaker relay to %s" % self.current_perch['speaker']) + self.arduino.write(str(self.current_perch['speaker'] + 1).encode('utf-8')) + + def reinforcement_logic(self): + """ + Figure out if current trial should be reinforced based on + reinforcement schedule of the day and previous reinforcement + """ + + if self.reinforcement_counter[self.current_visit.perch_loc] == None: + ## start new reinforcement_counter + self.log.info("Reinforcement empty at current perch. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) + self.reinforcement_counter[self.current_visit.perch_loc] = random.randint(1, 2*self.current_variable_ratio() - 1) - 1 + self.log.info("Reinforcement inbound in %s visits." % str(self.reinforcement_counter[self.current_visit.perch_loc])) + + ## If there are reinforcement counter is 0, reinforce + if self.reinforcement_counter[self.current_visit.perch_loc] == 0: + self.log.info("Reinforcement available at current perch. Reinforcing...") + self.reinforcement_counter[self.current_visit.perch_loc] = None ## wipe reinforcement + return True + else: + self.log.info("Reinforcement not available at current perch, inbound in %s visits. " % str(self.reinforcement_counter[self.current_visit.perch_loc])) + self.reinforcement_counter[self.current_visit.perch_loc] = self.reinforcement_counter[self.current_visit.perch_loc] - 1 + return False + + def stimulus_shuffle(self): + """ + While perched, shuffle stimuli from a library + """ + + ## decide if reinforcement should be given + self.current_visit.reinforcement = self.reinforcement_logic() + + ## if the current class is silence, or if reinforcement is not given, don't do shit except checking for light schedule + if ( + self.current_visit.class_ == "S" or ## silence class + self.current_visit.reinforcement == False or ## don't reinforce + self.daylight == False ## or night-time + ): + self.log.debug("Silence Perching") + while True: + if (self.validate_deperching() == True or self.light_switch()): + if self.light_switch(): + self.current_visit.perch_end = dt.datetime.now() + return + + ## if the current class is not silence, prep and play stimuli + else: + self.prep_stimuli() + self.play_stimuli() + + while True: + ## if deperching has been detected, or light schedule expires, quit this function + if (self.validate_deperching() == True or self.light_switch()): + if self.light_switch: + self.current_visit.perch_end = dt.datetime.now() + self.stop_stimuli() return + ## else, play audio until its length runs out, + else: + elapsed_time = (dt.datetime.now() - self.stimulus_event.time).total_seconds() + if elapsed_time < self.stimulus_event.duration: + continue + # when it does, give an inter_stim_interval, and recurse on the function + else: + if elapsed_time < (self.stimulus_event.duration + self.parameters['inter_stim_interval']): + continue + ## when inter_stim_interval runs out, stop stimuli and go back to the stimulus_shuffle + else: + self.log.debug("Stimuli finished and inter_stim_interval has passed. ") + self.stop_stimuli() + ## find another clip to play + self.prep_stimuli() + self.play_stimuli() + + def perch_playlist(self): + """ + depending on the perch and the perch_sequence, find the correct list + """ + if self.current_perch_stim_class() == "A": + self.log.debug("Perch stim class A") + return self.parameters['stims_A'] + if self.current_perch_stim_class() == "B": + self.log.debug("Perch stim class B") + return self.parameters['stims_B'] + + def prep_stimuli(self): + """ + Prep stimuli and generate a stimulus event + """ + ## randomly shuffle from the current perch_playlist + stim_file = random.sample(self.perch_playlist().items(), k = 1)[0][1] + self.log.debug(stim_file) + stim = utils.auditory_stim_from_wav(stim_file) + + self.stimulus_event = utils.Event( + time = dt.datetime.now(), + duration = stim.duration, + file_origin = stim.file_origin, + ) - ## for each iteration, check for perching behavior. - left_perched = self.panel.left.status() - center_perched = self.panel.center.status() - right_perched = self.panel.right.status() - - if left_perched: - self.current_perch['IR'] = self.panel.left - self.current_perch['IRName'] = 'L' - self.current_perch['speaker'] = 0 - break - if center_perched: - self.current_perch['IR'] = self.panel.center - self.current_perch['IRName'] = 'C' - self.current_perch['speaker'] = 1 - break - if right_perched: - self.current_perch['IR'] = self.panel.right - self.current_perch['IRName'] = 'R' - self.current_perch['speaker'] = 2 - break - - ## Record time of break - self.log.debug("Beam-break sensed on %s" % (self.current_perch)) - self.current_visit = utils.Visit() - self.current_visit.perch_strt = dt.datetime.now() - self.current_visit.perch_loc = self.current_perch['IRName'] - self.current_visit.class_ = self.current_perch_stim_class() - - ## pre populate - self.current_visit.reinforcement = False ## default do nothing - self.current_visit.vr = self.current_variable_ratio() - - ## if validate perching fails, reset perches and open - if (self.validate_perching() == False): - self.end_visit() - self.reset_perches() + self.log.debug("Queuing stimulus %s" % stim.file_origin) + self.panel.speaker.queue(self.stimulus_event.file_origin) + + def play_stimuli(self): + """ + Play stimuli through current_perch speaker + """ + + self.log.debug("Playing %s" % (self.stimulus_event.file_origin)) + ## trigger speaker + self.panel.speaker.play() + + def stop_stimuli(self): + """ + Stop stimuli, record event, and clear out event + """ + self.log.debug("Stop stimuli and flush stimulus event.") + self.panel.speaker.stop() + self.current_visit.stimuli.append(self.stimulus_event.file_origin) + self.current_visit.events.append(self.stimulus_event) + self.stimulus_event = None + + def end_visit(self): + """ + End visit and write data of current visit to csv + """ + self.log.debug("Ending visit and record end time.") + try: + self.current_visit.perch_dur = (self.current_visit.perch_end - self.current_visit.perch_strt).total_seconds() + except: + self.current_visit.perch_dur = 0 + self.current_visit.valid = (self.current_visit.perch_dur >= self.parameters['min_perch_time']) + self.save_visit(self.current_visit) + + def save_visit(self, visit): + """ + write visit results to CSV + """ + + self.log.debug("Writing data to %s" % (self.data_csv)) + visit_dict = {} + for field in self.fields_to_save: try: - self.open_all_perches() - except RuntimeError: - self.log.debug("RuntimeError: max recursion") - return - else: - self.log.debug("Perch valid at %s" % (self.current_perch)) + visit_dict[field] = getattr(visit,field) + except AttributeError: + visit_dict[field] = visit.annotations[field] ## it might be in annotations for some reason + + with open(self.data_csv, 'ab') as data_fh: + visitWriter = csv.DictWriter(data_fh, fieldnames = self.fields_to_save, extrasaction = 'ignore') + visitWriter.writerow(visit_dict) - def light_switch(): + def reset_perches(self): + """ + Reset perches + """ + + self.current_perch = {'IR': None, 'IRName': None, 'speaker': None} + self.stimulus_event = None + self.current_visit = None + + + def light_switch(self): ''' This checks if paradigm daylight is incongruent with system time ''' @@ -185,28 +446,59 @@ def light_switch(): def session_pre(self): """ Check if session should start in daylight or night. - If daylight, state machine will transition to day loop in session_main. - If night, state machine will transition to night loop in session_post. + If daylight, """ + self.log.info('Entering session_pre for daylight logic.') ## assign light schedule to self.daylight = utils.check_time(self.parameters['light_schedule']) + self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## flush reinforcement + self.log.info('Set paradigm to be congruent to system light. Flushing reinforcement counter. ') if self.daylight: - return "main" + self.log.info('Wakey wakey the sun is shining!') + self.panel.house_light.on() else: - return "post" + self.log.info('Sleepy sleepy the moon is rising!') + self.panel.house_light.off() + return "main" def session_main(self): """ - Inside session_pre, maintain a loop that controls *day* behavior. + Inside session_main, maintain a loop that controls looping behavior. """ + self.log.info("Entering session_main for experiment") + while True: ''' - Try to open all perches. The program loops in open_all_perches - until a valid perching has been detected. + Try to open all perches. The program loops in open_all_perches until a valid perching has been detected. ''' self.open_all_perches() + + ## check light switch + if self.light_switch(): + return "pre" + + ''' + Once perching has been detected, switch speaker to the current perch and start stimuli shuffle. + The program loops in stimulus_shuffle() until a valid deperching has been detected. + ''' + if (self.current_perch['IR'] != None): ## + if (self.current_perch['IR'].status() == True): + self.switch_speaker() + self.stimulus_shuffle() + ''' + Once deperched, end visit and reset + ''' + self.end_visit() + self.reset_perches() + + ## check if time of day is appropriate for a trial to commence + if self.light_switch(): + return "pre" + + def session_post(self): + return "pre" \ No newline at end of file From f8c1de32942f090b85887448fba5db8ba42ceac9 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 20 Aug 2024 13:52:39 -0700 Subject: [PATCH 109/115] Load 24 hrs into initialization --- pyoperant/behavior/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyoperant/behavior/__init__.py b/pyoperant/behavior/__init__.py index c690ae25..60cc45f3 100755 --- a/pyoperant/behavior/__init__.py +++ b/pyoperant/behavior/__init__.py @@ -1,3 +1,4 @@ from two_alt_choice import * from lights import * from place_pref import * +from place_pref_24hr import * \ No newline at end of file From 950f8efdbe6e05e16afe8f4b33e7eafa6821373d Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 20 Aug 2024 14:19:23 -0700 Subject: [PATCH 110/115] Debug 24hr place pref --- pyoperant/behavior/place_pref_24hr.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/place_pref_24hr.py b/pyoperant/behavior/place_pref_24hr.py index 89145ecd..f4e00592 100644 --- a/pyoperant/behavior/place_pref_24hr.py +++ b/pyoperant/behavior/place_pref_24hr.py @@ -436,12 +436,22 @@ def reset_perches(self): self.stimulus_event = None self.current_visit = None + def check_session_schedule(self): + """ + Check if perches should be open + + returns + ------- + bool + True, if sessions should be running + """ + return utils.check_time(self.parameters['light_schedule']) def light_switch(self): ''' This checks if paradigm daylight is incongruent with system time ''' - return self.daylight != utils.check_time(self.parameters['light_schedule']) + return self.daylight != self.check_light_schedule() def session_pre(self): """ @@ -451,7 +461,7 @@ def session_pre(self): self.log.info('Entering session_pre for daylight logic.') ## assign light schedule to - self.daylight = utils.check_time(self.parameters['light_schedule']) + self.daylight = self.check_light_schedule() self.reinforcement_counter = {'L': None, 'R': None, 'C': None} ## flush reinforcement self.log.info('Set paradigm to be congruent to system light. Flushing reinforcement counter. ') From 8f527b800b179cc76bc3089d507d84adf974ca42 Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 20 Aug 2024 14:41:37 -0700 Subject: [PATCH 111/115] Update 24hr pref to start from night --- pyoperant/behavior/place_pref_24hr.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pyoperant/behavior/place_pref_24hr.py b/pyoperant/behavior/place_pref_24hr.py index f4e00592..46737f07 100644 --- a/pyoperant/behavior/place_pref_24hr.py +++ b/pyoperant/behavior/place_pref_24hr.py @@ -453,6 +453,30 @@ def light_switch(self): ''' return self.daylight != self.check_light_schedule() + def run(self): + + for attr in self.req_panel_attr: + assert hasattr(self.panel,attr) + self.panel_reset() + self.save() + self.init_summary() + + self.log.info('%s: running %s with parameters in %s' % (self.name, + self.__class__.__name__, + self.snapshot_f, + ) + ) + + while True: # modify to start in pre instead of idle (this script deprecates idle) + utils.run_state_machine( + start_in='pre', + error_state='post', + error_callback=self.log_error_callback, + pre=self.session_pre, + main=self.session_main, + post=self.session_post + ) + def session_pre(self): """ Check if session should start in daylight or night. From 5418bf1b4bc5a7e062642d382a7f42ff85090bbf Mon Sep 17 00:00:00 2001 From: Jeffrey Xing Date: Tue, 20 Aug 2024 15:06:44 -0700 Subject: [PATCH 112/115] update 24hr to have no reinforcement at night --- pyoperant/behavior/place_pref_24hr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyoperant/behavior/place_pref_24hr.py b/pyoperant/behavior/place_pref_24hr.py index 46737f07..fe0b5697 100644 --- a/pyoperant/behavior/place_pref_24hr.py +++ b/pyoperant/behavior/place_pref_24hr.py @@ -284,6 +284,10 @@ def reinforcement_logic(self): reinforcement schedule of the day and previous reinforcement """ + if not self.daylight: + self.log.info("No reinforcement at night.") + return False + if self.reinforcement_counter[self.current_visit.perch_loc] == None: ## start new reinforcement_counter self.log.info("Reinforcement empty at current perch. Calling in reinforcement at ratio %s." % str(self.current_variable_ratio())) From 63b27d6363519fae3634b2abd6dcc8fdf729ab7a Mon Sep 17 00:00:00 2001 From: Jeffrey Xing <76228503+xingjeffrey@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:17:23 -0700 Subject: [PATCH 113/115] Update __init__.py --- pyoperant/behavior/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoperant/behavior/__init__.py b/pyoperant/behavior/__init__.py index 60cc45f3..b34de992 100755 --- a/pyoperant/behavior/__init__.py +++ b/pyoperant/behavior/__init__.py @@ -1,4 +1,4 @@ from two_alt_choice import * from lights import * -from place_pref import * -from place_pref_24hr import * \ No newline at end of file +from place_pref import PlacePrefExp +from place_pref_24hr import PlacePrefExp24hr From e599585a8d42ce9bc897ebc3bf455ca0bfe231ae Mon Sep 17 00:00:00 2001 From: Jeffrey Xing <76228503+xingjeffrey@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:55:37 -0700 Subject: [PATCH 114/115] Update place_pref_24hr.py --- pyoperant/behavior/place_pref_24hr.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pyoperant/behavior/place_pref_24hr.py b/pyoperant/behavior/place_pref_24hr.py index fe0b5697..b8171b46 100644 --- a/pyoperant/behavior/place_pref_24hr.py +++ b/pyoperant/behavior/place_pref_24hr.py @@ -9,6 +9,22 @@ import serial import random +class Visit(utils.Event): + """docstring for Visit, a variant of trial for place preference paradigm """ + def __init__(self, + class_=None, + *args, **kwargs): + super(Visit, self).__init__(*args, **kwargs) + self.label = 'visit' + self.perch_strt = None + self.perch_end = None + self.perch_loc = None + self.perch_dur = None + self.valid = None + self.class_ = class_ + self.stimuli = [] + self.events = [] + class PlacePrefExp24hr(base.BaseExp): ''' A place preference experiment. This version is identical to PlacePrefExp, @@ -539,4 +555,4 @@ def session_main(self): return "pre" def session_post(self): - return "pre" \ No newline at end of file + return "pre" From 31e8866990b452b20b39536f619eba7de9004e9f Mon Sep 17 00:00:00 2001 From: Jeffrey Xing <76228503+xingjeffrey@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:56:16 -0700 Subject: [PATCH 115/115] Update utils.py --- pyoperant/utils.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pyoperant/utils.py b/pyoperant/utils.py index 7c0207e7..11f8de75 100644 --- a/pyoperant/utils.py +++ b/pyoperant/utils.py @@ -132,23 +132,6 @@ def __init__(self, self.events = [] self.stim_event = None -class Visit(Event): - """docstring for Visit, a variant of trial for place preference paradigm """ - def __init__(self, - class_=None, - *args, **kwargs): - super(Visit, self).__init__(*args, **kwargs) - self.label = 'visit' - self.perch_strt = None - self.perch_end = None - self.perch_loc = None - self.perch_dur = None - self.valid = None - self.class_ = class_ - self.stimuli = [] - self.events = [] - - class Command(object): """ Enables to run subprocess commands in a different thread with TIMEOUT option.