Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSVP Copy Phrase Simulator #315

Merged
merged 95 commits into from
Sep 26, 2024
Merged

RSVP Copy Phrase Simulator #315

merged 95 commits into from
Sep 26, 2024

Conversation

sreekaroo
Copy link
Collaborator

@sreekaroo sreekaroo commented Feb 16, 2024

Overview

BciPy Simulator infrastructure.

Issues

https://www.pivotaltracker.com/story/show/185958637

Discussion

See the README for usage information, key components, and current limitations.

@lawhead lawhead marked this pull request as ready for review September 23, 2024 23:15
"""Query the data."""


def convert_trials(data_source: ExtractedExperimentData) -> List[Trial]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this method also provide a list of Inquiries? Or, can it leave the structure as-is if the data is extracted by inquiries already? (Inquiries would later be useful for gaze simulation)

trials = self.sample(state)
return self.reshaped(trials)

def sample_with_context(self,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the context here include the information about where the sample is taken from (e.g. experiment #, trigger #) ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's the idea. I expect some of this will require refinement when we implement the specifics for Gaze.

return model.metadata.evidence_type or DEFAULT_EVIDENCE_TYPE


class SimulatorCopyPhraseTask(RSVPCopyPhraseTask):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the clear method names here.


for model in self.signal_models:
sampler = self.samplers[model]
# This assumes that sampling is independent. Changes to the sampler API are needed if
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean we assume a uniform LM throughout the simulation?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The language model evidence gets added in a different method (in the copy phrase wrapper initialize_series). This method specifically gets evidence from devices. So any language model can be configured for simulation.

def exit_display(self) -> None:
"""Close the UI and cleanup."""

def elapsed_seconds(self) -> float:
Copy link
Member

@celikbasak celikbasak Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to estimate the just the computation time per symbol selection / next symbol querying? I think it would be more realistic if the formula here becomes something like: elapsed_time = inquiry_length + computation_time

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The computation of time is surprisingly complicated. There is an estimate in the acquisition helper for how much time an inquiry will take (for acquisition buffering purposes), but there are some confounding factors. I will create a followup ticket because I want to give this more thought and perhaps provide a way for this to be configured.

Copy link
Member

@celikbasak celikbasak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! The class names and methods are much more clear after cleanup/refactoring.

Copy link
Contributor

@tab-cmd tab-cmd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a considerable feature addition! Thank you for carrying this over for us. It seems beneficial enough to add some documentation to the main README and potentially to the install script itself for top-level client use.

    entry_points={
        'console_scripts': ['bcipy = bcipy.main:bcipy_main', 'bcipy-sim = bcipy.simulator'],
    }

# OR if removing __main__

    entry_points={
        'console_scripts': ['bcipy = bcipy.main:bcipy_main', 'bcipy-sim = bcipy.simulator.task.task_runner:main'],
    },

Then one could use,

bcipy-sim <args>

@@ -401,12 +401,11 @@ def __call__(self,
return np.stack(reshaped_trials, 1), targetness_labels


def update_inquiry_timing(timing: List[List[float]], downsample: int) -> List[List[float]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would want to update the expected return from our reshapers to make this compatible. I think this is still correct, given we expect a sample instead of a time at this stage?


* Only provides EEG support
* Only one sampler maybe provided for all devices. Ideally we should support a different sampling strategy for each device.
* Only the RSVP Copy Phrase may be simulated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You list Matrix CopyPhrase in the documentation, and if this is true, we should update the example above to avoid confusion. It seems like we could do MatrixCopyPhrase?

@@ -0,0 +1,6 @@
""" Entry point to run Simulator """

from bcipy.simulator.task import task_runner
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this behavior needed? I try to avoid putting functionality in init or main as much as possible since it can be harder to trace and understand.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows you to run the module. It's not necessary, but if not here I would change the usage instructions. I don't have a preference here so I'm fine changing it.

eeg - EEG data associated with this trial
"""
source: str
# series_n: int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

for querying the processed data."""

def load(self):
"""Load data from sources."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary, but often we use the ... when no return is set.

'feedback_stim_height': self.parameters['info_height'],
'feedback_duration': self.parameters['feedback_duration']},
self.experiment_clock)
self.feedback = self.init_feedback()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this too!

if save:
self.write_session_data()
if decision_made:
self.session.add_series()

def elapsed_seconds(self) -> float:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

DEFAULT_DEVICE_SPEC_FILENAME,
)
from bcipy.helpers.acquisition import analysis_channels

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that we can still use this script, but I do worry about its long-term survival. Could we add a replay feature to the simulator? It would permit slightly different questions and add a sampling strategy. The visualization was also beneficial (and the most used to communicate outwardly) and, I think, could be used more generally in the simulator output.

I'd be okay with deprecating this script (and most others) before the 2.0 release, even if it can't be rolled in.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't actually make any changes to this script, but saving it organized the imports in my editor. Replicating this behavior should be fairly easy to do in the simulator. I think it's just a matter of having a stateful sampler.


bcipy/simulator/tests/resource/
!bcipy/simulator/data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have /data earlier in the file, but this was too general and ignoring the data module in the simulator directory. The ! indicates not, so don't ignore this one.

`main.py` is the entry point for program. After following `BciPy` readme steps for setup, run the module from terminal:

```
(venv) $ python bcipy/simulator -h
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would want to document other clients here or in a linked README (ex. helpers/state). Otherwise, we could move those clients to a demo if they will not be used often.

@lawhead lawhead merged commit edf838b into 2.0.0rc4 Sep 26, 2024
6 checks passed
@lawhead lawhead deleted the s_simple_sampler_simulator branch September 26, 2024 00:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants