Skip to content

Commit

Permalink
Merged latest upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
lawhead committed Oct 24, 2024
2 parents 259f8ef + a635baa commit a12af8e
Show file tree
Hide file tree
Showing 142 changed files with 4,923 additions and 3,111 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ exclude_lines =
raise NotImplementedError
@abstract
if __name__ == .__main__.:
log = logging.getLogger(__name__)
logging.getLogger(__name__)
Binary file removed BciPyReport.pdf
Binary file not shown.
19 changes: 16 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# 2.0.0-rc.4

Our last release candidate before the official 2.0 release!
Our final release candidate before the official 2.0 release!

## Contributions

- Multimodal Acquisition and Querying
- Support for multiple devices in online querying #286
- Support for trigger handling relative to a given device #293
- Session Orchestrator
- New task protocol for orchestrating tasks in a session. This refactors several Task and Cli functionality #339
- Model
- Offline analysis to support multimodal fusion. Initial release of GazeModel, GazeReshaper, and Gaze Visualization #294
- Stimuli
Expand All @@ -18,14 +20,25 @@ Our last release candidate before the official 2.0 release!
- Offset Support
- Add support for determining offsets between timing verification Tasks (Ex. RSVPTimingVerificationCalibration) and RawData with a photodiode trigger column. This is useful for setting up new systems and preventing errors before an experiment begins. #TODO
- Parameters
- Add a Range type parameter #285
- Add a Range type parameter #285 Add editable fields #340 Update parameters.json to seperate relevant parameters by task
- Housekeeping
- Add mypy typing to the codebase #301
- Change default log level to INFO to prevent too many messages in the experiment logs #288
- Upgrade requirements for m1/2 chips #299/#300
- Fix GitHub actions build issues with macOS
- Fix occasionally failing test in `test_stimuli` #326

- GUI Refactor
- Create new `BCIUI` class for simpler more straightforward UI creation.
- Create dedicated external stylesheet for global styling
- Rewrite Experiment Registry to use new GUI code
- Create intertask action UI
- Task Return Object
- Create `TaskData` dataclass to be returned from tasks
- updates task `execute` methods to return an instance of `TaskData`
- Allows for optional storage of a save path and task dictionary in `TaskData`
-Experiment Refactor
- Refactors the Experiment Field Collection GUI to be an action
- Allows task protocol to be defined in the orchestrator

# 2.0.0-rc.3

Expand Down
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,39 @@ Alternately, if [Make](http://www.mingw.org/) is installed, you may run the foll
make dev-install
```

#### Usage Locally

Two ways to get started using BciPy for data collection:
1. Run `python bcipy/gui/BCInterface.py` in your command prompt or terminal from from base BciPy directory. This will execute the main BCI GUI. You may also use the command `make bci-gui`.
2. Invoke the experiment directly using command line utility `bcipy`.
#### Client Usage
Invoke an experiment protocol or task directly using command line utility `bcipy`.
- You can pass it attributes with flags, if desired.
Ex. `bcipy --user "bci_user" --task "RSVP Calibration"`
Running with a User ID and Task: `bcipy --user "bci_user" --task "RSVP Calibration"`
Running with a User ID and Tasks with a registered Protocol: `bcipy --user "bci_user" --experiment "default"`
Running with fake data: `bcipy --fake`
Running without visualizations: `bcipy --noviz`
Running with alerts after each Task execution: `bcipy --alert`
Running with custom parameters: `bcipy --parameters "path/to/valid/parameters.json"`

- Use the help flag to see other available input options: `bcipy --help`

##### Example usage as a package
##### Example Usage as a Package

```python
from bcipy.helpers import system_utils
system_utils.get_system_info()
```

#### Example Usage through the GUI

Run the following command in your terminal to start the BciPy GUI:
```sh
python bcipy/gui/BCInterface.py
```

Alternately, if Make is installed, you may run the follow command to start the GUI from the BciPy root directory:

```sh
make bci-gui
```


#### Simulator Usage

The simulator can be run using the command line utility `bcipy-sim`.
Expand All @@ -101,6 +118,8 @@ Run `bcipy-sim --help` for documentation or see the README in the simulator modu

***Session***: Data collected for a task. Comprised of metadata about the task and a list of Series.

***Protocol***: A collection of tasks and actions to be executed in a session. This is defined as within experiments and can be registered using the BciPy GUI.

***Task***: An experimental design with stimuli, trials, inquiries and series for use in BCI. For instance, "RSVP Calibration" is a task.

***Mode***: Common design elements between task types. For instance, Calibration and Free Spelling are modes.
Expand Down Expand Up @@ -131,6 +150,8 @@ This a list of the major modules and their functionality. Each module will conta
## Paradigms
------------

See `bcipy/task/README.md` for more information on all supported paradigms and modes. The following are the supported and validated paradigms:


> RSVPKeyboard
Expand Down Expand Up @@ -166,7 +187,7 @@ For example, you may run the main BciPy demo by:

`python demo/bci_main_demo.py`

This demo will load in parameters and execute a demo task defined in the file. There are demo files for all modules listed above except helpers and utils. Run them as a python script!
This demo will load in parameters and execute a demo task defined in the file. There are demo files contained in most modules, excepting gui, signal and parameters. Run them as a python script!


## Offset Determination and Correction
Expand Down Expand Up @@ -293,14 +314,14 @@ If you want to be added to the development team slack or have additional questio
We follow and will enforce the contributor's covenant to foster a safe and inclusive environment for this open source software, please reference this link for more information: https://www.contributor-covenant.org/

Other guidelines:
- All features require tests and a demo.
- All modules require tests and a demo.
- All tests must pass to merge, even if they are seemingly unrelated to your work.
- Use Spaces, not Tabs.
- Use informative names for functions and classes.
- Document the input and output of your functions / classes in the code. eg in-line commenting and typing.
- Do not push IDE or other local configuration files.
- All new modules or major functionality should be documented outside of the code with a README.md.
See README.md in repo or go to this site for inspiration: https://github.com/matiassingers/awesome-readme. Always use a Markdown interpreter before pushing. There are many free online or your IDE may come with one.
See README.md in repo or go to this site for inspiration: https://github.com/matiassingers/awesome-readme. Always use a Markdown interpreter before pushing.

See this resource for examples: http://docs.python-guide.org/en/latest/writing/style/

Expand Down
3 changes: 0 additions & 3 deletions bcipy/acquisition/datastream/generator.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""Functions for generating mock data to be used for testing/development."""

import logging
from typing import Optional, Generator, Callable
from past.builtins import range

from bcipy.config import DEFAULT_ENCODING
from bcipy.signal.generator.generator import gen_random_data

log = logging.getLogger(__name__)


def advance_to_row(filehandle, rownum):
"""Utility function to advance a file cursor to the given row."""
Expand Down
5 changes: 2 additions & 3 deletions bcipy/acquisition/datastream/lsl_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@
from bcipy.acquisition.datastream.producer import Producer
from bcipy.acquisition.devices import DeviceSpec
from bcipy.acquisition.util import StoppableThread
from bcipy.config import DEFAULT_ENCODING, MARKER_STREAM_NAME

log = logging.getLogger(__name__)
from bcipy.config import DEFAULT_ENCODING, MARKER_STREAM_NAME, SESSION_LOG_FILENAME

log = logging.getLogger(SESSION_LOG_FILENAME)
# pylint: disable=too-many-arguments


Expand Down
3 changes: 2 additions & 1 deletion bcipy/acquisition/datastream/mock/eye_tracker_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

from bcipy.acquisition.datastream.lsl_server import LslDataServer
from bcipy.acquisition.devices import DeviceSpec
from bcipy.config import SESSION_LOG_FILENAME

log = logging.getLogger(__name__)
log = logging.getLogger(SESSION_LOG_FILENAME)


def eye_tracker_device() -> DeviceSpec:
Expand Down
7 changes: 4 additions & 3 deletions bcipy/acquisition/datastream/mock/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

from bcipy.acquisition.devices import DeviceSpec, IRREGULAR_RATE
from bcipy.gui.main import BCIGui, app
from bcipy.config import SESSION_LOG_FILENAME

log = logging.getLogger(__name__)
log = logging.getLogger(SESSION_LOG_FILENAME)


def switch_device() -> DeviceSpec:
Expand Down Expand Up @@ -41,7 +42,7 @@ def quit(self):
self.outlet = None


class SwitchGui(BCIGui):
class SwitchGui(BCIGui): # pragma: no cover
"""GUI to emulate a switch."""

def __init__(self, switch: Switch, *args, **kwargs):
Expand Down Expand Up @@ -70,7 +71,7 @@ def build_text(self) -> None:
font_size=16)


def main(switch: Switch):
def main(switch: Switch): # pragma: no cover
"""Creates a PyQt5 GUI with a single button in the middle. Performs the
switch action when clicked."""
gui = app(sys.argv)
Expand Down
3 changes: 2 additions & 1 deletion bcipy/acquisition/datastream/producer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import time

from bcipy.acquisition.datastream.generator import random_data_generator
from bcipy.config import SESSION_LOG_FILENAME

log = logging.getLogger(__name__)
log = logging.getLogger(SESSION_LOG_FILENAME)


class Producer(threading.Thread):
Expand Down
1 change: 0 additions & 1 deletion bcipy/acquisition/demo/demo_lsl_acq_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Demo for the LslAcquisitionClient"""

import time

from bcipy.acquisition import LslAcquisitionClient


Expand Down
2 changes: 1 addition & 1 deletion bcipy/acquisition/demo/demo_lsl_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def main():
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Keyboard Interrupt")
log.info("Keyboard Interrupt")
server.stop()


Expand Down
8 changes: 5 additions & 3 deletions bcipy/acquisition/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from pathlib import Path
from typing import Dict, List, NamedTuple, Optional, Union

from bcipy.config import DEFAULT_ENCODING, DEVICE_SPEC_PATH
from bcipy.config import DEFAULT_ENCODING, DEVICE_SPEC_PATH, SESSION_LOG_FILENAME


IRREGULAR_RATE: int = 0
DEFAULT_CONFIG = DEVICE_SPEC_PATH
Expand All @@ -18,7 +19,7 @@
DEFAULT_DEVICE_TYPE = 'EEG'
DEFAULT_STATIC_OFFSET = 0.1

log = logging.getLogger(__name__)
logger = logging.getLogger(SESSION_LOG_FILENAME)


class ChannelSpec(NamedTuple):
Expand Down Expand Up @@ -182,7 +183,7 @@ def _validate_excluded_channels(self):
"""Warn if excluded channels are not in the list of channels"""
for channel in self.excluded_from_analysis:
if channel not in self.channels:
log.warning(
logger.warning(
f"Excluded channel {channel} not found in spec for {self.name}"
)

Expand Down Expand Up @@ -247,6 +248,7 @@ def preconfigured_device(name: str, strict: bool = True) -> DeviceSpec:
"\n"
"You may register new devices using the device module `register` function or in bulk"
" using `load`.")
logger.error(msg)
raise ValueError(msg)
return device

Expand Down
3 changes: 2 additions & 1 deletion bcipy/acquisition/marker_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from typing import Any

import pylsl
from bcipy.config import SESSION_LOG_FILENAME

log = logging.getLogger(__name__)
log = logging.getLogger(SESSION_LOG_FILENAME)


class MarkerWriter():
Expand Down
11 changes: 8 additions & 3 deletions bcipy/acquisition/multimodal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
from bcipy.acquisition.protocols.lsl.lsl_client import LslAcquisitionClient
from bcipy.acquisition.record import Record
from bcipy.helpers.system_utils import AutoNumberEnum
from bcipy.config import SESSION_LOG_FILENAME

log = logging.getLogger(__name__)
logger = logging.getLogger(SESSION_LOG_FILENAME)


class ContentType(AutoNumberEnum):
Expand Down Expand Up @@ -111,11 +112,12 @@ def get_client(
def start_acquisition(self):
"""Start acquiring data for all clients"""
for client in self.clients:
log.info(f"Connecting to {client.device_spec.name}...")
logger.info(f"Connecting to {client.device_spec.name}...")
client.start_acquisition()

def stop_acquisition(self):
"""Stop acquiring data for all clients"""
logger.info("Stopping acquisition...")
for client in self.clients:
client.stop_acquisition()

Expand Down Expand Up @@ -150,12 +152,13 @@ def get_data_by_device(
adjusted_start = start + client.device_spec.static_offset
if client.device_spec.sample_rate > 0:
count = round(seconds * client.device_spec.sample_rate)
log.info(f'Need {count} records for processing {name} data')
logger.info(f'Need {count} records for processing {name} data')
output[content_type] = client.get_data(start=adjusted_start,
limit=count)
data_count = len(output[content_type])
if strict and data_count < count:
msg = f'Needed {count} {name} records but received {data_count}'
logger.error(msg)
raise InsufficientDataException(msg)
else:
# Markers have an IRREGULAR_RATE.
Expand All @@ -174,4 +177,6 @@ def __getattr__(self, name: str) -> Any:
client = self.default_client
if client:
return client.__getattribute__(name)

logger.error(f"Missing attribute: {name}")
raise AttributeError(f"Missing attribute: {name}")
Loading

0 comments on commit a12af8e

Please sign in to comment.