Skip to content

Commit 0c565ab

Browse files
v0.3.3 0 - Merge pull request #35 from wehr-lab/dev
v0.3.3 Bugfixes * Fix layout in batch reassign from python 3 float division * Cleaner close by catching KeyboardInterrupt in networking modules * Fixing audioserver boot options -- if 'AUDIOSERVER' is set even if 'AUDIO' isn't set in prefs, should still start server. Not full fixed, need to make single plugin handler, single point of enabling/disabling optional services like audio server * Fix conflict between polarity and pull in initializing `pulls` in pilot * Catch `tables.HDF5ExtError` if local .h5 file corrupt in pilot * For some reason 'fs' wasn't being replaced in the jackd string, reinstated. * Fix comparison in LED_RGB that caused '0' to turn on full becuse 'value' was being checked for its truth value (0 is false) rather than checking if value is None. * `obj.next()` to `next(obj)` in jackdserver Improvements * Better internal handling of pigpiod -- you're now able to import and use hardware modules without needing to explicitly start pigpiod!! * Hopefully better killing of processes on exit, though still should work into unified process manager so don't need to reimplement everything (eg. as is done with launching pigpiod and jackd) * Environment scripts have been split out into `setup/scripts.py` and you can now run them with `python -m autopilot.setup.run_script` (use `--help` to see how!) * Informative error when setup is run with too narrow terminal: #23 * More loggers, but increased need to unify logger creation!!! Cleanup * remove unused imports in main `__init__.py` that made cyclical imports happen more frequently than necessary * single-sourcing version number from `__init__.py` * more cleanup of unnecessary meta and header stuff left from early days * more debugging flags * filter `NaturalNameWarning` from pytables * quieter cleanups for hardware objects
2 parents 76d7284 + 697ecb9 commit 0c565ab

File tree

24 files changed

+598
-318
lines changed

24 files changed

+598
-318
lines changed

autopilot/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,2 @@
1-
__version__ = '0.3'
2-
__author__ = 'Jonny Saunders <[email protected]>'
3-
4-
from autopilot import core
5-
from autopilot.tasks import Task
6-
from autopilot.setup import setup_autopilot
1+
__author__ = 'Jonny Saunders <[email protected]>'
2+
__version__ = '0.3.3'

autopilot/core/gui.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import json
2222
import copy
2323
import datetime
24+
import time
2425
from collections import OrderedDict as odict
2526
import numpy as np
2627
import ast
@@ -32,21 +33,17 @@
3233
import threading
3334
import logging
3435
from operator import ior
35-
import pdb
3636

3737
# adding autopilot parent directory to path
38-
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
3938
from autopilot.core.subject import Subject
4039
from autopilot import tasks, prefs
4140
from autopilot.stim.sound import sounds
4241
from autopilot.core.networking import Net_Node
4342
from functools import wraps
4443
from autopilot.core.utils import InvokeEvent
45-
#from autopilot.core.plots import gui_event
4644
from autopilot.core import styles
4745

48-
import pdb
49-
import time
46+
5047

5148

5249
def gui_event(fn):
@@ -2428,9 +2425,9 @@ def init_ui(self):
24282425
step_box.currentIndexChanged.connect(self.set_step)
24292426

24302427
# add to layout
2431-
self.grid.addWidget(subject_lab, i%25, 0+(i/25)*3)
2432-
self.grid.addWidget(protocol_box, i%25, 1+(i/25)*3)
2433-
self.grid.addWidget(step_box, i%25, 2+(i/25)*3)
2428+
self.grid.addWidget(subject_lab, i%25, 0+(np.floor(i/25))*3)
2429+
self.grid.addWidget(protocol_box, i%25, 1+(np.floor(i/25))*3)
2430+
self.grid.addWidget(step_box, i%25, 2+(np.floor(i/25))*3)
24342431

24352432

24362433

autopilot/core/networking.py

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -162,35 +162,41 @@ def run(self):
162162
163163
The process is kept open by the :class:`tornado.IOLoop` .
164164
"""
165-
# init zmq objects
166-
self.context = zmq.Context()
167-
self.loop = IOLoop()
168-
169-
# Our networking topology is treelike:
170-
# each Station object binds one Router to
171-
# send and receive messages from its descendants
172-
# each Station object may have one Dealer that
173-
# connects it with its antecedents.
174-
self.listener = self.context.socket(zmq.ROUTER)
175-
#self.listener.identity = self.id.encode('utf-8')
176-
#self.listener.identity = self.id
177-
self.listener.setsockopt_string(zmq.IDENTITY, self.id)
178-
self.listener.bind('tcp://*:{}'.format(self.listen_port))
179-
self.listener = ZMQStream(self.listener, self.loop)
180-
self.listener.on_recv(self.handle_listen)
181-
182-
if self.pusher is True:
183-
self.pusher = self.context.socket(zmq.DEALER)
184-
#self.pusher.identity = self.id.encode('utf-8')
185-
#self.pusher.identity = self.id
186-
self.pusher.setsockopt_string(zmq.IDENTITY, self.id)
187-
self.pusher.connect('tcp://{}:{}'.format(self.push_ip, self.push_port))
188-
self.pusher = ZMQStream(self.pusher, self.loop)
189-
self.pusher.on_recv(self.handle_listen)
190-
# TODO: Make sure handle_listen knows how to handle ID-less messages
191-
192-
self.logger.info('Starting IOLoop')
193-
self.loop.start()
165+
try:
166+
# init zmq objects
167+
self.context = zmq.Context()
168+
self.loop = IOLoop()
169+
170+
# Our networking topology is treelike:
171+
# each Station object binds one Router to
172+
# send and receive messages from its descendants
173+
# each Station object may have one Dealer that
174+
# connects it with its antecedents.
175+
self.listener = self.context.socket(zmq.ROUTER)
176+
#self.listener.identity = self.id.encode('utf-8')
177+
#self.listener.identity = self.id
178+
self.listener.setsockopt_string(zmq.IDENTITY, self.id)
179+
self.listener.bind('tcp://*:{}'.format(self.listen_port))
180+
self.listener = ZMQStream(self.listener, self.loop)
181+
self.listener.on_recv(self.handle_listen)
182+
183+
if self.pusher is True:
184+
self.pusher = self.context.socket(zmq.DEALER)
185+
#self.pusher.identity = self.id.encode('utf-8')
186+
#self.pusher.identity = self.id
187+
self.pusher.setsockopt_string(zmq.IDENTITY, self.id)
188+
self.pusher.connect('tcp://{}:{}'.format(self.push_ip, self.push_port))
189+
self.pusher = ZMQStream(self.pusher, self.loop)
190+
self.pusher.on_recv(self.handle_listen)
191+
# TODO: Make sure handle_listen knows how to handle ID-less messages
192+
193+
self.logger.info('Starting IOLoop')
194+
self.loop.start()
195+
except KeyboardInterrupt:
196+
# normal quitting behavior
197+
pass
198+
finally:
199+
self.release()
194200

195201
def prepare_message(self, to, key, value, repeat=True, flags=None):
196202
"""
@@ -689,8 +695,12 @@ def get_ip(self):
689695
return unwrap2
690696

691697
def release(self):
692-
self.closing.set()
693-
self.terminate()
698+
try:
699+
self.closing.set()
700+
self.terminate()
701+
except AttributeError:
702+
# already been called, NoneTypes have no attribute terminate
703+
pass
694704

695705
# Stopping the loop should kill the process, as it's what's holding us in run()
696706
#self.loop.stop()

autopilot/core/pilot.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
#!/usr/bin/python2.7
2-
31
"""
42
53
"""
64

7-
__version__ = '0.2'
8-
__author__ = 'Jonny Saunders <[email protected]>'
9-
105
import os
116
import sys
127
import datetime
@@ -18,11 +13,13 @@
1813
import json
1914
import base64
2015
import subprocess
16+
import warnings
2117
import numpy as np
2218
import pandas as pd
2319
from scipy.stats import linregress
2420

2521
import tables
22+
warnings.simplefilter('ignore', category=tables.NaturalNameWarning)
2623

2724
from autopilot import prefs
2825

@@ -46,10 +43,10 @@
4643

4744
prefs.init(prefs_file)
4845

49-
if hasattr(prefs, 'AUDIOSERVER') and 'AUDIO' in prefs.CONFIG:
46+
if hasattr(prefs, 'AUDIOSERVER') or 'AUDIO' in prefs.CONFIG:
5047
if prefs.AUDIOSERVER == 'pyo':
5148
from autopilot.stim.sound import pyoserver
52-
elif prefs.AUDIOSERVER == 'jack':
49+
elif prefs.AUDIOSERVER in ('jack', True):
5350
from autopilot.stim.sound import jackclient
5451

5552
from autopilot.core.networking import Pilot_Station, Net_Node, Message
@@ -172,7 +169,7 @@ def __init__(self, splash=True):
172169
self.init_pigpio()
173170

174171
# Init audio server
175-
if hasattr(prefs, 'AUDIOSERVER') and 'AUDIO' in prefs.CONFIG:
172+
if hasattr(prefs, 'AUDIOSERVER') or 'AUDIO' in prefs.CONFIG:
176173
self.init_audio()
177174

178175
# Init Station
@@ -199,10 +196,10 @@ def __init__(self, splash=True):
199196
self.pulls = []
200197
if hasattr(prefs, 'PULLUPS'):
201198
for pin in prefs.PULLUPS:
202-
self.pulls.append(gpio.Digital_Out(int(pin), pull='U'))
199+
self.pulls.append(gpio.Digital_Out(int(pin), pull='U', polarity=0))
203200
if hasattr(prefs, 'PULLDOWNS'):
204201
for pin in prefs.PULLDOWNS:
205-
self.pulls.append(gpio.Digital_Out(int(pin), pull='D'))
202+
self.pulls.append(gpio.Digital_Out(int(pin), pull='D', polarity=1))
206203

207204
# check if the calibration file needs to be updated
208205

@@ -234,7 +231,11 @@ def init_logging(self):
234231
self.log_formatter = logging.Formatter("%(asctime)s %(levelname)s : %(message)s")
235232
self.log_handler.setFormatter(self.log_formatter)
236233
self.logger.addHandler(self.log_handler)
237-
self.logger.setLevel(logging.INFO)
234+
if hasattr(prefs, 'LOGLEVEL'):
235+
loglevel = getattr(logging, prefs.LOGLEVEL)
236+
else:
237+
loglevel = logging.WARNING
238+
self.logger.setLevel(loglevel)
238239
self.logger.info('Pilot Logging Initiated')
239240

240241
#################################################################
@@ -593,7 +594,7 @@ def init_audio(self):
593594
if prefs.AUDIOSERVER == 'pyo':
594595
self.server = pyoserver.pyo_server()
595596
self.logger.info("pyo server started")
596-
elif prefs.AUDIOSERVER == 'jack':
597+
elif prefs.AUDIOSERVER in ('jack', True):
597598
self.jackd = external.start_jackd()
598599
self.server = jackclient.JackClient()
599600
self.server.start()
@@ -625,14 +626,18 @@ def open_file(self):
625626
Opens `prefs.DATADIR/local.h5`, creates a group for the current subject,
626627
a new table for the current day.
627628
629+
.. todo::
630+
631+
This needs to be unified with a general file constructor abstracted from :class:`.Subject` so it doesn't reimplement file creation!!
632+
628633
Returns:
629634
(:class:`tables.File`, :class:`tables.Table`,
630635
:class:`tables.tableextension.Row`): The file, table, and row for the local data table
631636
"""
632637
local_file = os.path.join(prefs.DATADIR, 'local.h5')
633638
try:
634639
h5f = tables.open_file(local_file, mode='a')
635-
except IOError as e:
640+
except (IOError, tables.HDF5ExtError) as e:
636641
self.logger.warning("local file was broken, making new")
637642
self.logger.warning(e)
638643
os.remove(local_file)
@@ -695,10 +700,11 @@ def run_task(self, task_class, task_params):
695700
h5f, table, row = self.open_file()
696701

697702
# TODO: Init sending continuous data here
698-
703+
self.logger.debug('Starting task loop')
699704
while True:
700705
# Calculate next stage data and prep triggers
701706
data = next(self.task.stages)() # Double parens because next just gives us the function, we still have to call it
707+
self.logger.debug('called stage method')
702708

703709
if data:
704710
data['pilot'] = self.name
@@ -718,16 +724,20 @@ def run_task(self, task_class, task_params):
718724
if 'TRIAL_END' in data.keys():
719725
row.append()
720726
table.flush()
727+
self.logger.debug('sent data')
728+
721729

722730
# Wait on the stage lock to clear
723731
self.stage_block.wait()
732+
self.logger.debug('stage lock passed')
724733

725734
# If the running flag gets set, we're closing.
726735
if not self.running.is_set():
727736
self.task.end()
728737
self.task = None
729738
row.append()
730739
table.flush()
740+
self.logger.debug('stopping task')
731741
break
732742

733743
h5f.flush()

autopilot/core/plots.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import os
1818
import numpy as np
1919
import PySide2 # have to import to tell pyqtgraph to use it
20-
#import PySide
2120
import pandas as pd
2221
from PySide2 import QtGui
2322
from PySide2 import QtCore
@@ -35,7 +34,6 @@
3534
pg.setConfigOptions(antialias=True)
3635
# from pyqtgraph.widgets.RawImageWidget import RawImageWidget, RawImageGLWidget
3736

38-
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
3937
from autopilot import tasks, prefs
4038
from autopilot.core import styles
4139
from .utils import InvokeEvent, Invoker

autopilot/core/subject.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import pandas as pd
1919
import warnings
2020
import typing
21+
import warnings
2122
from copy import copy
22-
# sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
2323
from autopilot.tasks import GRAD_LIST, TASK_LIST
2424
from autopilot import prefs
2525
from autopilot.stim.sound.sounds import STRING_PARAMS
@@ -29,6 +29,9 @@
2929
else:
3030
import Queue as queue
3131

32+
# suppress pytables natural name warnings
33+
warnings.simplefilter('ignore', category=tables.NaturalNameWarning)
34+
3235
import pdb
3336
import numpy as np
3437

@@ -501,7 +504,7 @@ def assign_protocol(self, protocol, step_n=0):
501504
# if this task has sounds, make columns for them
502505
# TODO: Make stim managers return a list of properties for their sounds
503506
if 'stim' in step.keys():
504-
if 'manager' in step['stim'].keys():
507+
if 'groups' in step['stim'].keys():
505508
# managers have stim nested within groups, but this is still really ugly
506509
sound_params = {}
507510
for g in step['stim']['groups']:

autopilot/core/terminal.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
__version__ = '0.3'
2-
__author__ = 'Jonny Saunders <[email protected]>'
3-
41
import argparse
52
import json
63
import sys

autopilot/core/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# import numpy as np
21
from autopilot import prefs
32
# if prefs.AGENT in ("TERMINAL", "DOCS"):
43
HAVE_PYSIDE = False

0 commit comments

Comments
 (0)