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

System directory to support experiments #100

Merged
merged 1 commit into from
Oct 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .bcipy/experiment/experiments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"default": {
"fields": [
{
"age": {
"required": "false"
}
}
],
"summary": "Default experiment to test various BciPy features without registering a full experiment."
}
}
6 changes: 6 additions & 0 deletions .bcipy/field/fields.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"age": {
"help_text": "Age (in years) of user.",
"type": "int"
}
}
13 changes: 10 additions & 3 deletions bci_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from bcipy.helpers.system_utils import get_system_info, configure_logger
from bcipy.helpers.language_model import init_language_model
from bcipy.helpers.load import load_signal_model
from bcipy.helpers.save import init_save_data_structure
from bcipy.helpers.save import init_save_data_structure, DEFAULT_EXPERIMENT_ID
from bcipy.tasks.start_task import start_task
from bcipy.tasks.task_registry import ExperimentType


def bci_main(parameters: dict, user: str, exp_type: int, mode: str) -> bool:
def bci_main(parameters: dict, user: str, exp_type: int, mode: str, experiment: str = DEFAULT_EXPERIMENT_ID) -> bool:
"""BCI Main.

The BCI main function will initialize a save folder, construct needed information
Expand All @@ -27,6 +27,8 @@ def bci_main(parameters: dict, user: str, exp_type: int, mode: str) -> bool:
user (str): name of the user
exp_type (int): type of experiment. Ex. 1 = calibration
mode (str): BCI mode. Ex. RSVP, SHUFFLE, MATRIX
experiment_id (str): Name of the experiment. Default name is DEFAULT_EXPERIMENT_ID.


"""

Expand All @@ -36,7 +38,12 @@ def bci_main(parameters: dict, user: str, exp_type: int, mode: str) -> bool:

# Initialize Save Folder
save_folder = init_save_data_structure(
data_save_location, user, parameter_location, mode, exp_type)
data_save_location,
user,
parameter_location,
mode=mode,
experiment_type=exp_type,
experiment=experiment)

Copy link
Collaborator

Choose a reason for hiding this comment

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

bci_main is passing an extra parameter to init_save_data_structure which causes an error (experiment). Sorry I didn't catch this during the review.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or actually, it looks like maybe this needs to be experiment_id, rather than experiment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah - it should be experiment_id. I'll re-open a PR

# Register Task Type
task_type = {
Expand Down
55 changes: 22 additions & 33 deletions bcipy/helpers/save.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,41 @@
import json


def init_save_data_structure(data_save_path,
user_information,
parameters_used,
mode=None,
experiment_type=None):
DEFAULT_EXPERIMENT_ID = 'default'


def init_save_data_structure(data_save_path: str,
user_id: str,
parameters_path: str,
experiment_id: str = DEFAULT_EXPERIMENT_ID,
mode: str = None,
experiment_type: int = None):
"""
Initialize Save Data Strucutre.

data_save_path[str]: string of path to save our data in
user_information[str]: string of user name / related information
parameters_used[str]: a path to parameters file for the experiment
user_id[str]: string of user name / related information
parameters_path[str]: a path to parameters file for the experiment
experiment_id[str]: Name of the experiment. Default name is DEFAULT_EXPERIMENT_ID.
mode[str]: BCI mode. Ex. RSVP, SHUFFLE, MATRIX
experiment_type[int]: type of experiment. Ex. 1 = calibration

"""

# make an experiment folder : note datetime is in utc
save_folder_name = data_save_path + user_information
save_folder_name = f'{data_save_path}{experiment_id}/{user_id}'
dt = strftime('%a_%d_%b_%Y_%Hhr%Mmin%Ssec_%z', localtime())

# If there is a mode or experiment type registered add it to the folder structure
if mode and experiment_type:
save_directory = save_folder_name + '/' + \
user_information + '_' + str(mode) + '_' + str(experiment_type) + '_' + strftime(
'%a_%d_%b_%Y_%Hhr%Mmin%Ssec_%z', localtime())
save_directory = f'{save_folder_name}/{user_id}_{mode}_{experiment_type}_{dt}'
else:
save_directory = save_folder_name + '/' + \
user_information + '_' + strftime(
'%a_%d_%b_%Y_%Hhr%Mmin%Ssec_%z', localtime())
helper_folder_name = save_directory + '/helpers/'
save_directory = f'{save_folder_name}/{user_id}_{dt}'

try:
# make a directory to save data to
os.makedirs(save_folder_name)
os.makedirs(save_directory)
os.makedirs(helper_folder_name)
os.makedirs(os.path.join(save_directory, 'logs'), exist_ok=True)

except OSError as error:
Expand All @@ -45,28 +49,13 @@ def init_save_data_structure(data_save_path,

# since this is only called on init, we can make another folder run
os.makedirs(save_directory)
os.makedirs(helper_folder_name)
os.makedirs(os.path.join(save_directory, 'logs'), exist_ok=True)

try:
# Go to folder helpers and list files within it
src_files = os.listdir('bcipy/helpers/')

# Loop through files in helpers and copy important ones over
for file_name in src_files:

# get the full name
full_file_name = os.path.join('bcipy/helpers/', file_name)

# Check that constructed file is a real file and ends in .py
if (os.path.isfile(full_file_name)) and '.py' in file_name:
# Copy over!
copy2(full_file_name, helper_folder_name)

Copy link
Collaborator

Choose a reason for hiding this comment

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

When I was looking into BIDS I meant to ask if these helpers are still needed. I'm glad we're able to clean up the data.

# Check that parameters file given is a real file
if (os.path.isfile(parameters_used)):
if (os.path.isfile(parameters_path)):
# Copy over parameters file
copy2(parameters_used, save_directory)
copy2(parameters_path, save_directory)
else:
raise Exception('Parameter File Not Found!')

Expand Down
37 changes: 21 additions & 16 deletions bcipy/helpers/tests/test_save.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import errno
import os
import shutil
import unittest

from bcipy.helpers.save import init_save_data_structure
from bcipy.helpers import save
from bcipy.helpers.save import init_save_data_structure, DEFAULT_EXPERIMENT_ID
from mockito import any, unstub, when


class TestSave(unittest.TestCase):
Expand All @@ -21,9 +21,13 @@ def setUp(self):
self.user_information,
self.parameters_used)

# mock save modules use of strftime to return an empty string
when(save).strftime(any(), any()).thenReturn('')

def tearDown(self):
# clean up by removing the data folder we used for testing
shutil.rmtree(self.data_save_path)
unstub()

def test_init_save_data_structure_creates_correct_save_folder(self):

Expand All @@ -38,6 +42,20 @@ def test_parameter_file_copies(self):
# assert that the params file was created in the correct location
self.assertTrue(os.path.isfile(param_path))

def test_save_structure_adds_default_experiment(self):
self.assertIn(DEFAULT_EXPERIMENT_ID, self.save_folder_name)

def test_save_structure_adds_experiment_id_when_provided_as_argument(self):
experiment_id = 'test'
response = init_save_data_structure(
self.data_save_path,
self.user_information,
self.parameters_used,
experiment_id=experiment_id)
expected = f'{self.data_save_path}{experiment_id}/{self.user_information}/{self.user_information}_'

self.assertEqual(response, expected)

def test_throws_useful_error_if_given_incorrect_params_path(self):

# try passing a parameters file that does not exist
Expand All @@ -47,19 +65,6 @@ def test_throws_useful_error_if_given_incorrect_params_path(self):
'new_user',
'does_not_exist.json')

def test_init_save_data_structure_makes_helpers_folder(self):

# contruct the path of the helper folder
helper_folder_name = self.save_folder_name + '/helpers/'

# attempt to make that folder
try:
os.makedirs(helper_folder_name)

except OSError as error:
# assert the error returned, is that the dir exists.
self.assertEqual(error.errno, errno.EEXIST)


if __name__ == '__main__':
unittest.main()