Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
0b3c79f
Implement IPC with ZMQ
Aug 5, 2024
6e58e87
check whether GUI process exists before killing
jmxpearson May 27, 2025
eb22093
revamping bubblewrap demo
jmxpearson May 27, 2025
e0561be
WIP: revamping GUI pattern
jmxpearson May 29, 2025
5577036
more tweaks
jmxpearson Jun 3, 2025
aaaf896
most things working now
jmxpearson Jun 3, 2025
194bf43
adjusting fixtures for new actor config
jmxpearson Jun 3, 2025
43938ee
removing tests for GUI special-casing
jmxpearson Jun 4, 2025
2b704a4
fixing link test
jmxpearson Jun 4, 2025
2d1e981
renaming messages from nexus to actors
jmxpearson Jun 4, 2025
b4eab61
adding new message types
jmxpearson Jun 5, 2025
053835d
removing unused testing code
jmxpearson Jun 5, 2025
313162c
using actorsignalmsg instead of state message
jmxpearson Jun 5, 2025
2dc04ec
removing unused code
jmxpearson Jun 5, 2025
5989e7d
change start_nexus to no inputs
jmxpearson Jun 5, 2025
010d7e0
spelling error
jmxpearson Jun 5, 2025
4c22ae1
set actor port to control port
jmxpearson Jun 5, 2025
5484203
streamlining nexus logic
jmxpearson Jun 5, 2025
358686b
fixing nexus/tui communication
jmxpearson Jun 6, 2025
c3379dc
improved tui/nexus comms
jmxpearson Jun 6, 2025
ef65db6
logging tweaks
jmxpearson Jun 9, 2025
bb27a18
making sure tui mocks work
jmxpearson Jun 9, 2025
8289116
testing mode for TUI app
jmxpearson Jun 9, 2025
7ba32fe
fixing error in test_nexus
jmxpearson Jun 9, 2025
874ea0e
updates to enable public log input port
jmxpearson Jun 9, 2025
a1aaa09
WIP: currently broken
jmxpearson Jun 9, 2025
b919293
tests passing, demos working
jmxpearson Jun 10, 2025
175a605
TUI logging up and running
jmxpearson Jun 10, 2025
e6e6c0c
working on bubblewrap demo
jmxpearson Jun 11, 2025
b65ee0f
fixing bubblewrap errors
jmxpearson Jun 11, 2025
254f08d
more WIP getting bubblewrap demo working
jmxpearson Jun 11, 2025
d5452e3
move store interface creation to actors
jmxpearson Jun 12, 2025
c5d622c
cli now asks to keep waiting if ports not read
jmxpearson Jun 12, 2025
b626797
making sure nexus responds to unknown actor msg
jmxpearson Jun 12, 2025
3534773
refactoring actor signaling
jmxpearson Jun 12, 2025
9381491
fixed bug
jmxpearson Jun 12, 2025
299f2d5
failure to call setup_logging now raises exception
jmxpearson Jun 12, 2025
990c7a5
adding send command to link
jmxpearson Jun 12, 2025
382ac83
adding docs for logging files
jmxpearson Jun 12, 2025
7c6af90
formatting changes
jmxpearson Jun 12, 2025
ba1e162
formatting tests
jmxpearson Jun 12, 2025
b330930
formatting and linting
jmxpearson Jun 12, 2025
a4d7e2d
fixing formatting
jmxpearson Jun 12, 2025
8b07883
more format fixes
jmxpearson Jun 12, 2025
ef2b9c9
bubblewrap actor searches for file given relpath
jmxpearson Sep 25, 2025
7bb5d8a
fixing typo
jmxpearson Sep 25, 2025
858a407
run in TUI actually runs GUI
jmxpearson Sep 25, 2025
16c9087
getting rid of dead code
jmxpearson Sep 25, 2025
f0c78c5
rename runStep -> run_step
jmxpearson Sep 25, 2025
14a737b
adding data logging back into sample_processor
jmxpearson Sep 25, 2025
c9e96e8
removing spawn actor in minimal demo
jmxpearson Sep 25, 2025
cebefe9
make tui tests wait longer
jmxpearson Sep 25, 2025
8ef8745
waiting even longer
jmxpearson Sep 25, 2025
6628a25
longer wait for store to die
jmxpearson Sep 25, 2025
77c115e
just try tui tests
jmxpearson Sep 25, 2025
7fe2d4b
even longer store waiting
jmxpearson Sep 25, 2025
cac55f4
attempting to guard against TUI hangs
jmxpearson Sep 25, 2025
b2a5002
reraising canceled task errors
jmxpearson Sep 26, 2025
58a5b90
reducing tests
jmxpearson Sep 26, 2025
59963ff
linger longer
jmxpearson Sep 26, 2025
c47b0d3
try not closing log handler
jmxpearson Sep 26, 2025
675a64e
removing dead line
jmxpearson Sep 26, 2025
ac85f19
slowing down generator
jmxpearson Sep 26, 2025
babf072
Added line for logging allowing run; existing line left (516) but nev…
draelos Nov 17, 2025
fcd01db
Updated scipy.signal.windows import as of scipy v1.13.
draelos Nov 17, 2025
33d983e
Silence millions of jax info messages
draelos Nov 17, 2025
5444a38
Remove unused spawn variant in bubble demo.
draelos Nov 17, 2025
649b469
Remove depricated .py way of launching demos in improv
draelos Nov 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
max-parallel: 1
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest] # [ubuntu-latest, macos-latest, windows-latest]

steps:
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:

- name: Test with pytest
run: |
python -m pytest --cov=improv
python -m pytest -x -s -l --cov=improv

- name: Coveralls
uses: coverallsapp/github-action@v2
Expand Down Expand Up @@ -102,6 +102,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Close parallel build
uses: coverallsapp/github-action@v1
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ dmypy.json
arrow

.vscode/
.idea/

*.code-workspace
improv/_version.py

*venv
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions demos/1p_caiman/1p_demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,3 @@ connections:
Processor.q_out: [Analysis.q_in]
Analysis.q_out: [Visual.q_in]
InputStim.q_out: [Analysis.input_stim_queue]

# settings:
# use_watcher: [Acquirer, Processor, Visual, Analysis]
4 changes: 2 additions & 2 deletions demos/1p_caiman/actors/1p_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ def setup(self):
def stop(self):
super().stop()

def runStep(self):
def run_step(self):
"""Run process. Runs once per frame.
Output is a location in the DS to continually
place the Estimates results, with ref number that
corresponds to the frame number
"""
super().runStep()
super().run_step()

def putEstimates(self):
"""Put whatever estimates we currently have
Expand Down
23 changes: 0 additions & 23 deletions demos/basic/Behavior_demo.py

This file was deleted.

3 changes: 0 additions & 3 deletions demos/basic/Behavior_demo.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
settings:
use_watcher: [Acquirer, Processor, Visual, Analysis, Behavior, Motion]

actors:
GUI:
package: actors.visual
Expand Down
2 changes: 1 addition & 1 deletion demos/basic/actors/basic_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def stop(self):
np.savetxt("output/timing/shape_time.txt", self.shape_time)
np.savetxt("output/timing/detect_time.txt", self.detect_time)

def runStep(self):
def run_step(self):
"""Run process. Runs once per frame.
Output is a location in the DS to continually
place the Estimates results, with ref number that
Expand Down
4 changes: 2 additions & 2 deletions demos/basic/actors/behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def setup(self):
else:
raise FileNotFoundError

def runStep(self):
def run_step(self):
if self.done:
pass
elif self.frame_num < self.data.shape[2]:
Expand Down Expand Up @@ -95,7 +95,7 @@ def setup(self):
else:
raise FileNotFoundError

def runStep(self):
def run_step(self):
if self.done:
pass
elif self.frame_num < len(self.data):
Expand Down
23 changes: 0 additions & 23 deletions demos/basic/basic_demo.py

This file was deleted.

3 changes: 0 additions & 3 deletions demos/basic/basic_demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,3 @@ connections:
Processor.q_out: [Analysis.q_in]
Analysis.q_out: [Visual.q_in]
InputStim.q_out: [Analysis.input_stim_queue]

# settings:
# use_watcher: [Acquirer, Processor, Visual, Analysis]
6 changes: 3 additions & 3 deletions demos/bubblewrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ After installing _improv_, install additional dependencies (JAX on CPU by defaul

- `pip install -r requirements.txt`

To download the sample data from the paper, do:
To download the sample data from the paper, run the following from the `improv` directory:

- `python demos/bubblewrap/actors/utils.py`

This may take a few minutes. After data is downloaded, run the GUI with:
This may take a few minutes. After data is downloaded, run the demo with:

- `python demos/bubblewrap/bubble_demo.py`
- `improv run demos/bubblewrap/bubble_demo.yaml`

A GUI will pop up with two buttons named "setup" and "run". First hit "setup" and wait ~5 seconds, then hit "run". Bubblewrap will perform dimensionality reduction of ~180 neurons to 2 dimensions, represented by grey dots popping up on the plot, and coarsely tile the space with red bubbles to represent transitions in the low-dimension space. All in real-time!

Expand Down
37 changes: 31 additions & 6 deletions demos/bubblewrap/actors/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,42 @@
import time
import logging
import traceback
import time
import sys, os


logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

def load_data(filename):
"""
Load filename, which may be a relative path.
Return data.
"""
if os.path.isfile(filename):
full_fname = filename
else:
for p in reversed(sys.path):
# traverse path in reverse order, on the theory that
# the yaml file comes near the end
full_fname = os.path.join(p, filename)
if os.path.isfile(full_fname):
break

try:
data = mat73.loadmat(full_fname)
except FileNotFoundError:
logger.error("Bubblewrap data file not found!")

return data




class Acquirer(Actor):
def __init__(self, *args, filename=None, **kwargs):
super().__init__(*args, **kwargs)
if not filename: logger.error('Error: Filename not specified')
if not filename:
logger.error('Error: Filename not specified')
self.file = filename
self.frame_num = 0
self.done = False
Expand All @@ -28,7 +53,7 @@ def setup(self):
Note: A utility function that downloads the required data file can be found in utils.py
"""
# get unsorted vs sorted units
data_dict = mat73.loadmat(self.file)
data_dict = load_data(self.file)
units_unsorted = []
units_sorted = []
for ch_curr in data_dict['spikes']:
Expand Down Expand Up @@ -58,23 +83,23 @@ def setup(self):
self.num_iters = np.floor((self.data.shape[0] - l1 - self.l)/self.l).astype('int')

#send to dim reduction
init_id = self.client.put([self.data.shape[0], self.data[:l1, :]], "init_data")
init_id = self.client.put([self.data.shape[0], self.data[:l1, :]])
logger.info("Putted init data")
self.q_out.put(init_id)

def stop(self):
logger.info(f"Stopped running Acquire, avg time per frame: {np.mean(self.total_times)}")
logger.info(f"Acquire got through {self.frame_num} frames")

def runStep(self):
def run_step(self):
"""Send data to dim reduction one frame at a time"""
if self.done:
pass
elif self.frame_num < self.num_iters:
start, end = self.t, self.t + 1
frame = self.data[start:end, :]
t = time.time()
id = self.client.put([self.t, frame], "acq_bubble" + str(self.frame_num))
id = self.client.put([self.t, frame])
self.timestamp.append([time.time(), self.frame_num])
try:
self.q_out.put([str(self.frame_num), id])
Expand Down
40 changes: 21 additions & 19 deletions demos/bubblewrap/actors/bubble.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from bubblewrap import Bubblewrap
from improv.actor import Actor
import logging

logging.getLogger("jax").setLevel(logging.WARNING)

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
Expand All @@ -21,7 +21,10 @@ def setup(self):
while shape_id is None:
try:
shape_id = self.q_in.get(timeout=0.0005)
except Empty: pass
except Empty:
pass
except TimeoutError:
pass
dat_shape_0 = self.client.get(shape_id)
# init bubblewrap
M = 20
Expand All @@ -47,23 +50,24 @@ def setup(self):
while id is None:
try:
id = self.q_in.get(timeout = 0.0005)
except Empty: pass
init_data = self.client.getID(id)
except Empty:
pass
except TimeoutError:
pass
init_data = self.client.get(id)

for i in np.arange(0, M):
self.bw.observe(init_data[i])
self.bw.init_nodes()
logger.info("Nodes initialized")

self._getStoreInterface()

def runStep(self):
def run_step(self):
"""Observe new data from dim reduction and update bubblewrap"""
try:
ids = self.q_in.get(timeout=0.0005)

# expect ids of size 2 containing data location and frame number
new_data = self.client.getID(ids[1])
new_data = self.client.get(ids[1])
self.frame_number = ids[0]

self.bw.observe(new_data)
Expand All @@ -72,19 +76,17 @@ def runStep(self):
self.putOutput()
except Empty:
pass
except TimeoutError:
pass

def putOutput(self):
"""Function for putting updated results into the store"""
ids = []
ids.append(self.client.put(np.array(self.bw.A), "A" + str(self.frame_number)))
ids.append(self.client.put(np.array(self.bw.L), "L" + str(self.frame_number)))
ids.append(self.client.put(np.array(self.bw.mu), "mu" + str(self.frame_number)))
ids.append(self.client.put(
np.array(self.bw.n_obs), "n_obs" + str(self.frame_number)))
ids.append(self.client.put(
np.array(self.bw.pred), "pred" + str(self.frame_number)))
ids.append(self.client.put(
np.array(self.bw.entropy_list), "entropy" + str(self.frame_number)))
ids.append(self.client.put(
np.array(self.bw.dead_nodes), "dead_nodes" + str(self.frame_number)))
ids.append(self.client.put(np.array(self.bw.A)))
ids.append(self.client.put(np.array(self.bw.L)))
ids.append(self.client.put(np.array(self.bw.mu)))
ids.append(self.client.put(np.array(self.bw.n_obs)))
ids.append(self.client.put(np.array(self.bw.pred)))
ids.append(self.client.put(np.array(self.bw.entropy_list)))
ids.append(self.client.put(np.array(self.bw.dead_nodes)))
self.q_out.put([self.frame_number, ids])
Loading