diff --git a/experiments/base/stimulation.py b/experiments/base/stimulation.py index ccc8fdb..08ed030 100644 --- a/experiments/base/stimulation.py +++ b/experiments/base/stimulation.py @@ -19,18 +19,35 @@ def __init__(self): self._name = 'BaseStimulation' self._parameter_dict = dict(TYPE='str', PORT='str', + IP = 'str', STIM_TIME='float') self._settings_dict = get_stimulation_settings(self._name, self._parameter_dict) self._running = False - self._stim_device = self._setup_device(self._settings_dict['TYPE'], self._settings_dict['PORT']) + self._stim_device = self._setup_device(self._settings_dict['TYPE'], self._settings_dict['PORT'], self._settings_dict['IP']) @staticmethod - def _setup_device(type, port): + def _setup_device(type, port, ip): device = None if type == 'NI': from experiments.utils.DAQ_output import DigitalModDevice device = DigitalModDevice(port) + if type == 'RASPBERRY': + from experiments.utils.gpio_control import DigitalPiDevice + device = DigitalPiDevice(port) + + if type == 'RASP_NETWORK': + from experiments.utils.gpio_control import DigitalPiDevice + if ip is not None: + device = DigitalPiDevice(port, ip) + else: + raise ValueError('IP required for remote GPIO control.') + + if type == 'ARDUINO': + from experiments.utils.gpio_control import DigitalArduinoDevice + device = DigitalArduinoDevice(port) + + return device def stimulate(self): @@ -72,23 +89,38 @@ class RewardDispenser(BaseStimulation): def __init__(self): self._name = 'RewardDispenser' self._parameter_dict = dict(TYPE = 'str', + IP = 'str', STIM_PORT= 'str', REMOVAL_PORT = 'str', STIM_TIME = 'float', REMOVAL_TIME = 'float') self._settings_dict = get_stimulation_settings(self._name, self._parameter_dict) self._running = False - self._stim_device = self._setup_device(self._settings_dict['TYPE'], self._settings_dict['STIM_PORT']) - self._removal_device = self._setup_device(self._settings_dict['TYPE'], self._settings_dict['REMOVAL_PORT']) + self._stim_device = self._setup_device(self._settings_dict['TYPE'], self._settings_dict['STIM_PORT'], + self._settings_dict['IP']) + self._removal_device = self._setup_device(self._settings_dict['TYPE'], self._settings_dict['REMOVAL_PORT'], + self._settings_dict['IP']) @staticmethod - def _setup_device(type, port): + def _setup_device(type, port, ip): device = None if type == 'NI': from experiments.utils.DAQ_output import DigitalModDevice device = DigitalModDevice(port) + if type == 'RASPBERRY': + from experiments.utils.gpio_control import DigitialPiBoardDevice + device = DigitialPiBoardDevice(port) + + if type == 'RASP_NETWORK': + from experiments.utils.gpio_control import DigitialPiBoardDevice + if ip is not None: + device = DigitialPiBoardDevice(port, ip) + else: + raise ValueError('IP required for remote GPIO control.') + + return device def stimulate(self): diff --git a/experiments/configs/default_config.ini b/experiments/configs/default_config.ini index e56ec25..ef85c36 100644 --- a/experiments/configs/default_config.ini +++ b/experiments/configs/default_config.ini @@ -98,12 +98,20 @@ STIMULATION = BaseStimulation ;[STIMULATION] [BaseStimulation] +; can be NI, RASPBERRY or RASP_NETWORK TYPE = NI +;only used in RASP_NETWORK +IP = None +;PORT parameter is used for all (Port from DAQ, PIN from Raspberry, or serial port from Arduino) PORT = Dev1/PFI6 STIM_TIME = 3.5 [RewardDispenser] +; can be NI, RASPBERRY, RASP_NETWORK or ARDUINO TYPE = NI +;only used in RASP_NETWORK +IP = None +;PORT parameter is used for all (Port from DAQ, PIN from Raspberry, or serial port from Arduino) STIM_PORT = Dev1/PFI6 REMOVAL_PORT = Dev1/PFI5 STIM_TIME = 3.5 diff --git a/experiments/utils/gpio_control.py b/experiments/utils/gpio_control.py new file mode 100644 index 0000000..ff6a11b --- /dev/null +++ b/experiments/utils/gpio_control.py @@ -0,0 +1,66 @@ +from gpiozero import DigitalOutputDevice +from gpiozero.pins.pigpio import PiGPIOFactory + +import serial + + + +class DigitalPiDevice: + """ + Digital modulated devices in combination with Raspberry Pi GPIO + Setup: https://gpiozero.readthedocs.io/en/stable/remote_gpio.html + """ + + def __init__(self, PIN, BOARD_IP: str = None): + + """ + :param BOARD_IP: IP adress of board connected to the Device + """ + if BOARD_IP is not None: + self._factory = PiGPIOFactory(host = BOARD_IP) + self._device = DigitalOutputDevice(PIN, pin_factory = self._factory) + else: + self._factory = None + self._device = DigitalOutputDevice(PIN) + self._running = False + + def turn_on(self): + self._device.on() + self._running = True + + def turn_off(self): + self._device.off() + self._running = False + + def toggle(self): + self._device.toggle() + self._running = self._device.is_active + + +class DigitalArduinoDevice: + """ + Digital modulated devices in combination with Arduino boards connected via USB + setup: https://pythonforundergradengineers.com/python-arduino-LED.html + + """ + + def __init__(self, PORT): + """ + :param PORT: USB PORT of the arduino board + """ + self._device = serial.Serial(PORT, baudrate=9600) + self._running = False + + def turn_on(self): + self._device.write(b'H') + self._running = True + + def turn_off(self): + self._device.write(b'L') + self._running = False + + def toggle(self): + if self._running: + self.turn_off() + else: + self.turn_on() diff --git a/experiments/utils/readme.md b/experiments/utils/readme.md deleted file mode 100644 index 8d6a845..0000000 --- a/experiments/utils/readme.md +++ /dev/null @@ -1,130 +0,0 @@ -###How to use the predesigned experiment features - -With the new version of DLStream, creating your own experiments has become much more accessible to researchers that do not want to dive into the DLStream code. - -This guide will discuss the new feature as well as our `base` modules that can be used to generate combinations of prebuild `EXPERIMENT`, `TRIGGER`, `STIMULATION` modules and their `parameters` without any need to access the code. - -First the function: `design_experiment()` - -In your console of choice or IDE, run `design_experiment.py` - -You will be asked to enter the base modules that can be used to design a basic experiment in DLStream. - -![Example](docs/design_experiment_gif.gif) - -#### Selecting a designed experiment in DLStream -Once a experiment config file has been created, the experiment can be used in DLStream by entering the filename to `settings.ini`. -```` -[Experiment] -EXP_NAME = CONFIG_NAME -```` - -Starting `app.py` and `Start Experiment` will load the configuration into DLStream and run the experiment based on the entered parameters. - -####General Build-up and custom parameters -Second, let's discuss the general build-up. - -Experiments can now be combined, configured and saved in `.ini` files, very similiar to `settings.ini`: -Each module has a `[Section]` and corresponding `PARAMETERS`, that are used to configure each experiment design. -```` -[EXPERIMENT] -BASE = BaseConditionalExperiment -EXPERIMENTOR = DEFAULT - -[BaseConditionalExperiment] -TRIGGER = BaseHeaddirectionTrigger -PROCESS = BaseProtocolProcess -INTERTRIAL_TIME = 40 -EXP_LENGTH = 40 -EXP_TIME = 3600 -EXP_COMPLETION = 10 - -[BaseHeaddirectionTrigger] - -POINT = 550, 63 -ANLGE = 30 -BODYPARTS = neck -DEBUG = False - -[BaseProtocolProcess] - -TYPE = switch -STIMULATION = ScreenStimulation - -[ScreenStimulation] - -TYPE = image -STIM_PATH = PATH_TO_IMAGE -BACKGROUND_PATH = PATH_TO_BACKGROUND -```` - -#### Changing parameters in an already created config - -If you want to change any parameter, you can do so directly in the experiment config file (and save them). - -```` -[BaseConditionalExperiment] - -INTERTRIAL_TIME = 40 -```` -Change the number and save the file. -```` -[BaseConditionalExperiment] - -INTERTRIAL_TIME = 20 -```` - -When you load the experiment again, it will use the new parameter. Note, that in case of non-sense parameter changes (e.g. `INTERSTIMULUS_TIME = banana`). -DLStream will throw an error and the experiment won't run (as expected). - -Several parameters only except preselected options, so make sure that you check out the available options in the corresponding wiki article. - -#### Changing modules in an already created config - -Modules are found as `PARAMETERS` in their corresponding parent module and as `[Sections]` in the `.ini` file. -```` -[EXPERIMENT] -BASE = BaseConditionalExperiment -... - -[BaseConditionalExperiment] -TRIGGER = BaseHeaddirectionTrigger -... -[BaseHeaddirectionTrigger] -... -```` - -Above you see that in the `[Experiment]` section the base experiment `BaseConditionalExperiment` is named, while underneath a `[Section]` with the same name appears with all relevant `PARAMETERS`. - -If you do not want to manually change the modules, you can use our new functions to do so. -The easiest way is to create a new experiment, with your selection of modules and then copy the parameters into the new config file. - -If you want to change a module manually (e.g. `TRIGGER`), you will need to change the entry under `TRIGGER`, remove the old section `[BaseHeaddirectionTrigger]` and add the new section from `experiment/configs/default_config.ini`. -Afterwards check if all parameters are conclusive and save the file. - -```` -[EXPERIMENT] -BASE = BaseConditionalExperiment -... - -[BaseConditionalExperiment] - -# change to BaseRegionTrigger: -TRIGGER = BaseHeaddirectionTrigger -... -# delete this section completely: -[BaseHeaddirectionTrigger] -POINT = 550, 63 -ANGLE = 30 -BODYPARTS = neck -DEBUG = False - -# add the new section from the default_config.ini: -[BaseRegionTrigger] -TYPE = circle -CENTER= 550, 63 -RADIUS = 30 -BODYPARTS = neck -DEBUG = False -```` - diff --git a/requirements.txt b/requirements.txt index 35eb929..fa08f7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ +gpiozero +pigpio +pyserial nidaqmx>=0.5.7 click>=7.0 opencv-python>=3.4.5.20 @@ -5,4 +8,4 @@ numpy>=1.14.5 pandas>=0.21.0 matplotlib>=3.0.3 scikit-image>=0.14.2 -scipy>=1.1.0 \ No newline at end of file +scipy>=1.1.0