From ae9ee0788bb5ca5bc4c6198c3c213df94c846d92 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 6 Sep 2023 17:10:26 -0400 Subject: [PATCH 1/4] use a main function --- README.md | 19 +- .../metadata.yml} | 6 +- conversion_json/edf2bids_json.py | 461 +++++++++--------- .../data}/manual_metadata_example.yml | 6 +- 4 files changed, 256 insertions(+), 236 deletions(-) rename conversion_json/{manual_metadata.yml => config/metadata.yml} (73%) rename {conversion_json => tests/data}/manual_metadata_example.yml (80%) diff --git a/README.md b/README.md index 8df0941..3b023f1 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,28 @@ ### Requirements -- edf file by EyeLink Eye Tracker - Python3 + - EyeLink Developers Kit. Download from [SR-Research support forum] (forum registration required) + +- Input data: + +Those can be: + + - edf file by EyeLink Eye Tracker + +You can install uur test data from OSF by running the following command: + +```bash +python tools/download_test_data.py +``` + - manual_metadata.yml file (find template and an example in conversion_json folder) ### Run code -``` -python3 edf2bids_json.py +```bash +python edf2bids_json.py ``` [SR-Research support forum]: https://www.sr-research.com/support/forum-9.html diff --git a/conversion_json/manual_metadata.yml b/conversion_json/config/metadata.yml similarity index 73% rename from conversion_json/manual_metadata.yml rename to conversion_json/config/metadata.yml index 1760fc5..fcd936c 100644 --- a/conversion_json/manual_metadata.yml +++ b/conversion_json/config/metadata.yml @@ -1,7 +1,7 @@ --- -#manual_metadata +# manual_metadata -#Required, fill according to BIDS specification: +# Required, fill according to BIDS specification: SampleCoordinateUnits: '' SampleCoordinateSystem: '' EnvironmentCoordinates: '' @@ -11,7 +11,7 @@ ScreenSize: InstitutionName: '' InstitutionAddress: '' -#Recommended, leave empty if not available: +# Recommended, leave empty if not available: SoftwareVersion: '' ScreenAOIDefinition: EyeCameraSettings: diff --git a/conversion_json/edf2bids_json.py b/conversion_json/edf2bids_json.py index cfc7086..309b6fb 100644 --- a/conversion_json/edf2bids_json.py +++ b/conversion_json/edf2bids_json.py @@ -8,237 +8,244 @@ import yaml from yaml.loader import SafeLoader -# CONVERSION events - -edf_filepath = input("Enter the edf file path: ") -if os.path.exists(edf_filepath): - print("The file exists") -else: - raise FileNotFoundError("No such file or directory") - - -subprocess.run(["edf2asc", "-y", "-e", edf_filepath, edf_filepath + "_events"]) -asc_file = edf_filepath + "_events.asc" - -with open(asc_file) as f: - events = f.readlines() - -# Read variables from the additional metadata txt file -print( - "Next: Load the manual_metadata.yml file with the additional metadata. \ - This file must contain at least the additional REQUIRED metadata \ - in the format specified in the BIDS specification. \ - Please enter the required metadata manually before loading the file in a next step." -) - -manual_metadata = input("Enter the file path to the manual_metadata.yml file: ") - -if os.path.exists(manual_metadata): - print("The file exists") -else: - raise FileNotFoundError("No such file or directory") - - -with open(manual_metadata) as f: - data = yaml.load(f, Loader=SafeLoader) - -SampleCoordinateUnits = data["SampleCoordinateUnits"] -SampleCoordinateSystem = data["SampleCoordinateSystem"] -EnvironmentCoordinates = data["EnvironmentCoordinates"] -ScreenDistance = data["ScreenDistance"] -ScreenRefreshRate = data["ScreenRefreshRate"] -ScreenSize = data["ScreenSize"] -InstitutionName = data["InstitutionName"] -InstitutionAddress = data["InstitutionAddress"] -SoftwareVersion = data["SoftwareVersion"] -ScreenAOIDefinition = data["ScreenAOIDefinition"] -EyeCameraSettings = data["EyeCameraSettings"] -EyeTrackerDistance = data["EyeTrackerDistance"] -FeatureDetectionSettings = data["FeatureDetectionSettings"] -GazeMappingSettings = data["GazeMappingSettings"] -RawDataFilters = data["RawDataFilters"] - -# Prepare asc file -# dataframe for events, all -df_ms = pd.DataFrame([ms.split() for ms in events if ms.startswith("MSG")]) - -# reduced dataframe without MSG and sample columns -df_ms_reduced = pd.DataFrame(df_ms.iloc[0:, 2:]) - -# Events.json Metadata - -ScreenResolution = list( - map( - int, - ( - df_ms_reduced[df_ms_reduced[2] == "DISPLAY_COORDS"] - .iloc[0:1, 3:5] - .to_string(header=False, index=False) - ).split(" "), + +def main(): + """Convert edf to tsv + json.""" + # CONVERSION events + + edf_filepath = input("Enter the edf file path: ") + if os.path.exists(edf_filepath): + print("The file exists") + else: + raise FileNotFoundError("No such file or directory") + + subprocess.run(["edf2asc", "-y", "-e", edf_filepath, edf_filepath + "_events"]) + asc_file = edf_filepath + "_events.asc" + + with open(asc_file) as f: + events = f.readlines() + + # Read variables from the additional metadata txt file + print( + """Load the manual_metadata.yml file with the additional metadata.\n +This file must contain at least the additional REQUIRED metadata +in the format specified in the BIDS specification.\n +Please enter the required metadata manually +before loading the file in a next step.""" + ) + + manual_metadata = input("Enter the file path to the manual_metadata.yml file: ") + + if os.path.exists(manual_metadata): + print("The file exists") + else: + raise FileNotFoundError("No such file or directory") + + with open(manual_metadata) as f: + data = yaml.load(f, Loader=SafeLoader) + + SampleCoordinateUnits = data["SampleCoordinateUnits"] + SampleCoordinateSystem = data["SampleCoordinateSystem"] + EnvironmentCoordinates = data["EnvironmentCoordinates"] + ScreenDistance = data["ScreenDistance"] + ScreenRefreshRate = data["ScreenRefreshRate"] + ScreenSize = data["ScreenSize"] + InstitutionName = data["InstitutionName"] + InstitutionAddress = data["InstitutionAddress"] + SoftwareVersion = data["SoftwareVersion"] + ScreenAOIDefinition = data["ScreenAOIDefinition"] + EyeCameraSettings = data["EyeCameraSettings"] + EyeTrackerDistance = data["EyeTrackerDistance"] + FeatureDetectionSettings = data["FeatureDetectionSettings"] + GazeMappingSettings = data["GazeMappingSettings"] + RawDataFilters = data["RawDataFilters"] + + # Prepare asc file + # dataframe for events, all + df_ms = pd.DataFrame([ms.split() for ms in events if ms.startswith("MSG")]) + + # reduced dataframe without MSG and sample columns + df_ms_reduced = pd.DataFrame(df_ms.iloc[0:, 2:]) + + # Events.json Metadata + + ScreenResolution = list( + map( + int, + ( + df_ms_reduced[df_ms_reduced[2] == "DISPLAY_COORDS"] + .iloc[0:1, 3:5] + .to_string(header=False, index=False) + ).split(" "), + ) + ) + + TaskName = ( + " ".join([ts for ts in events if ts.startswith("** RECORDED BY")]) + .replace("** RECORDED BY ", "") + .replace("\n", "") ) -) - -TaskName = ( - " ".join([ts for ts in events if ts.startswith("** RECORDED BY")]) - .replace("** RECORDED BY ", "") - .replace("\n", "") -) - -# Eyetrack.json Metadata - -ManufacturersModelName = ( - " ".join([ml for ml in events if ml.startswith("** EYELINK")]) - .replace("** ", "") - .replace("\n", "") -) - -DeviceSerialNumber = ( - " ".join([sl for sl in events if sl.startswith("** SERIAL NUMBER:")]) - .replace("** SERIAL NUMBER: ", "") - .replace("\n", "") -) - -SamplingFrequency = int( - df_ms_reduced[df_ms_reduced[2] == "RECCFG"] - .iloc[0:1, 2:3] - .to_string(header=False, index=False) -) - -eye = ( - df_ms_reduced[df_ms_reduced[2] == "RECCFG"] - .iloc[0:1, 5:6] - .to_string(header=False, index=False) -) -if eye == "L": - RecordedEye = eye.replace("L", "Left") -elif eye == "R": - RecordedEye = eye.replace("R", "Right") -elif eye == "LR": - RecordedEye = eye.replace("LR", "Both") - -if df_ms[df_ms[3] == "VALIDATION"].empty is False: - AverageCalibrationError = ( - np.array(df_ms[df_ms[3] == "VALIDATION"][[9]]).astype(float).tolist() + + # Eyetrack.json Metadata + + ManufacturersModelName = ( + " ".join([ml for ml in events if ml.startswith("** EYELINK")]) + .replace("** ", "") + .replace("\n", "") + ) + + DeviceSerialNumber = ( + " ".join([sl for sl in events if sl.startswith("** SERIAL NUMBER:")]) + .replace("** SERIAL NUMBER: ", "") + .replace("\n", "") ) -else: - AverageCalibrationError = [] - -CalibrationCount = len(df_ms_reduced[df_ms_reduced[3] == "CALIBRATION"]) - -cal_pos = ( - np.array(df_ms_reduced[df_ms_reduced[2] == "VALIDATE"][8].str.split(",", expand=True)) - .astype(int) - .tolist() -) -cal_num = int(len(cal_pos) / CalibrationCount) -CalibrationPosition = list() -if len(cal_pos) != 0: - for i in range(0, len(cal_pos), cal_num): - CalibrationPosition.append(cal_pos[i : i + cal_num]) - -CalibrationType = ( - df_ms_reduced[df_ms_reduced[3] == "CALIBRATION"] - .iloc[0:1, 2:3] - .to_string(header=False, index=False) -) - -if len(cal_pos) != 0: - cal_unit = ( - (df_ms_reduced[df_ms_reduced[2] == "VALIDATE"][[13]]) - .iloc[0:1, 0:1] + + SamplingFrequency = int( + df_ms_reduced[df_ms_reduced[2] == "RECCFG"] + .iloc[0:1, 2:3] .to_string(header=False, index=False) ) - if cal_unit == "pix.": - CalibrationUnit = "pixel" - elif cal_unit == "mm": - CalibrationUnit = "mm" - elif cal_unit == "cm": - CalibrationUnit = "cm" -else: - CalibrationUnit = "" - -EyeTrackingMethod = ( - pd.DataFrame( - " ".join([tm for tm in events if tm.startswith(">>>>>>>")]) - .replace(")", ",") - .split(",") + + eye = ( + df_ms_reduced[df_ms_reduced[2] == "RECCFG"] + .iloc[0:1, 5:6] + .to_string(header=False, index=False) + ) + if eye == "L": + RecordedEye = eye.replace("L", "Left") + elif eye == "R": + RecordedEye = eye.replace("R", "Right") + elif eye == "LR": + RecordedEye = eye.replace("LR", "Both") + + if df_ms[df_ms[3] == "VALIDATION"].empty is False: + AverageCalibrationError = ( + np.array(df_ms[df_ms[3] == "VALIDATION"][[9]]).astype(float).tolist() + ) + else: + AverageCalibrationError = [] + + CalibrationCount = len(df_ms_reduced[df_ms_reduced[3] == "CALIBRATION"]) + + cal_pos = ( + np.array( + df_ms_reduced[df_ms_reduced[2] == "VALIDATE"][8].str.split(",", expand=True) + ) + .astype(int) + .tolist() + ) + cal_num = int(len(cal_pos) / CalibrationCount) + CalibrationPosition = list() + if len(cal_pos) != 0: + for i in range(0, len(cal_pos), cal_num): + CalibrationPosition.append(cal_pos[i : i + cal_num]) + + CalibrationType = ( + df_ms_reduced[df_ms_reduced[3] == "CALIBRATION"] + .iloc[0:1, 2:3] + .to_string(header=False, index=False) ) - .iloc[1:2] - .to_string(header=False, index=False) -) -if df_ms[df_ms[3] == "VALIDATION"].empty is False: - MaximalCalibrationError = ( - np.array(df_ms[df_ms[3] == "VALIDATION"][[11]]).astype(float).tolist() + if len(cal_pos) != 0: + cal_unit = ( + (df_ms_reduced[df_ms_reduced[2] == "VALIDATE"][[13]]) + .iloc[0:1, 0:1] + .to_string(header=False, index=False) + ) + if cal_unit == "pix.": + CalibrationUnit = "pixel" + elif cal_unit == "mm": + CalibrationUnit = "mm" + elif cal_unit == "cm": + CalibrationUnit = "cm" + else: + CalibrationUnit = "" + + EyeTrackingMethod = ( + pd.DataFrame( + " ".join([tm for tm in events if tm.startswith(">>>>>>>")]) + .replace(")", ",") + .split(",") + ) + .iloc[1:2] + .to_string(header=False, index=False) ) -else: - MaximalCalibrationError = [] - - -PupilFitMethod = ( - (df_ms_reduced[df_ms_reduced[2] == "ELCL_PROC"]) - .iloc[0:1, 1:2] - .to_string(header=False, index=False) -) - -StartTime = ( - np.array(pd.DataFrame([st.split() for st in events if st.startswith("START")])[1]) - .astype(int) - .tolist() -) # TODO:figure out if this is actually the StartTime meant by the specification - -StopTime = ( - np.array(pd.DataFrame([so.split() for so in events if so.startswith("END")])[1]) - .astype(int) - .tolist() -) # TODO:figure out if this is actually the StopTime meant by the specification - -# to json - -out_filepath = input("Enter the subject file path: ") - -eyetrack_json = { - "Manufacturer": "SR-Research", - "ManufacturersModelName": ManufacturersModelName, - "DeviceSerialNumber": DeviceSerialNumber, - "SoftwareVersion": SoftwareVersion, - "SamplingFrequency": SamplingFrequency, - "SampleCoordinateUnits": SampleCoordinateUnits, - "SampleCoordinateSystem": SampleCoordinateSystem, - "EnvironmentCoordinates": EnvironmentCoordinates, - "RecordedEye": RecordedEye, - "ScreenAOIDefinition": ScreenAOIDefinition, - "AverageCalibrationError": AverageCalibrationError, - "CalibrationCount": CalibrationCount, - "CalibrationType": CalibrationType, - "CalibrationUnit": CalibrationUnit, - "EyeCameraSettings": EyeCameraSettings, - "EyeTrackerDistance": EyeTrackerDistance, - "CalibrationPosition": CalibrationPosition, - "EyeTrackingMethod": EyeTrackingMethod, - "FeatureDetectionSettings": FeatureDetectionSettings, - "GazeMappingSettings": GazeMappingSettings, - "MaximalCalibrationError": MaximalCalibrationError, - "PupilFitMethod": PupilFitMethod, - "RawDataFilters": RawDataFilters, - "StartTime": StartTime, - "StopTime": StopTime, -} - -with open(out_filepath + "eyetrack.json", "w") as outfile: - json.dump(eyetrack_json, outfile, indent=15) - -events_json = { - "TaskName": TaskName, - "InstitutionName": InstitutionName, - "InstitutionAddress": InstitutionAddress, - "StimulusPresentation": { - "ScreenDistance": ScreenDistance, - "ScreenRefreshRate": ScreenRefreshRate, - "ScreenResolution": ScreenResolution, - "ScreenSize": ScreenSize, - }, -} - -with open(out_filepath + "events.json", "w") as outfile: - json.dump(events_json, outfile, indent=9) + + if df_ms[df_ms[3] == "VALIDATION"].empty is False: + MaximalCalibrationError = ( + np.array(df_ms[df_ms[3] == "VALIDATION"][[11]]).astype(float).tolist() + ) + else: + MaximalCalibrationError = [] + + PupilFitMethod = ( + (df_ms_reduced[df_ms_reduced[2] == "ELCL_PROC"]) + .iloc[0:1, 1:2] + .to_string(header=False, index=False) + ) + + StartTime = ( + np.array(pd.DataFrame([st.split() for st in events if st.startswith("START")])[1]) + .astype(int) + .tolist() + ) # TODO:figure out if this is actually the StartTime meant by the specification + + StopTime = ( + np.array(pd.DataFrame([so.split() for so in events if so.startswith("END")])[1]) + .astype(int) + .tolist() + ) # TODO:figure out if this is actually the StopTime meant by the specification + + # to json + + out_filepath = input("Enter the subject file path: ") + + eyetrack_json = { + "Manufacturer": "SR-Research", + "ManufacturersModelName": ManufacturersModelName, + "DeviceSerialNumber": DeviceSerialNumber, + "SoftwareVersion": SoftwareVersion, + "SamplingFrequency": SamplingFrequency, + "SampleCoordinateUnits": SampleCoordinateUnits, + "SampleCoordinateSystem": SampleCoordinateSystem, + "EnvironmentCoordinates": EnvironmentCoordinates, + "RecordedEye": RecordedEye, + "ScreenAOIDefinition": ScreenAOIDefinition, + "AverageCalibrationError": AverageCalibrationError, + "CalibrationCount": CalibrationCount, + "CalibrationType": CalibrationType, + "CalibrationUnit": CalibrationUnit, + "EyeCameraSettings": EyeCameraSettings, + "EyeTrackerDistance": EyeTrackerDistance, + "CalibrationPosition": CalibrationPosition, + "EyeTrackingMethod": EyeTrackingMethod, + "FeatureDetectionSettings": FeatureDetectionSettings, + "GazeMappingSettings": GazeMappingSettings, + "MaximalCalibrationError": MaximalCalibrationError, + "PupilFitMethod": PupilFitMethod, + "RawDataFilters": RawDataFilters, + "StartTime": StartTime, + "StopTime": StopTime, + } + + with open(out_filepath + "eyetrack.json", "w") as outfile: + json.dump(eyetrack_json, outfile, indent=15) + + events_json = { + "TaskName": TaskName, + "InstitutionName": InstitutionName, + "InstitutionAddress": InstitutionAddress, + "StimulusPresentation": { + "ScreenDistance": ScreenDistance, + "ScreenRefreshRate": ScreenRefreshRate, + "ScreenResolution": ScreenResolution, + "ScreenSize": ScreenSize, + }, + } + + with open(out_filepath + "events.json", "w") as outfile: + json.dump(events_json, outfile, indent=9) + + +if __name__ == "__main__": + main() diff --git a/conversion_json/manual_metadata_example.yml b/tests/data/manual_metadata_example.yml similarity index 80% rename from conversion_json/manual_metadata_example.yml rename to tests/data/manual_metadata_example.yml index c70c94c..79ead08 100644 --- a/conversion_json/manual_metadata_example.yml +++ b/tests/data/manual_metadata_example.yml @@ -1,7 +1,7 @@ --- -#manual_metadata +# manual_metadata -#Required, fill according to BIDS specification: +# Required, fill according to BIDS specification: SampleCoordinateUnits: pixel SampleCoordinateSystem: gaze-on-screen EnvironmentCoordinates: top-left @@ -11,7 +11,7 @@ ScreenSize: [38.6, 29] InstitutionName: Philipps-University Marburg InstitutionAddress: Gutenbergstr. 18 -#Recommended, leave empty if not available: +# Recommended, leave empty if not available: SoftwareVersion: SREB1.10.1630 WIN32 LID:F2AE011 Mod:2017.04.21 15:19 CEST ScreenAOIDefinition: [square, [00, 150, 300, 350]] EyeCameraSettings: From b9ac1d526fd69497599376ecbfb3f1bcbb653ece Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 6 Sep 2023 17:46:58 -0400 Subject: [PATCH 2/4] create docker file with SR research tools --- Dockerfile | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 16 ++++++++++++ README.md | 19 +++++++++++++- 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 Makefile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0661e6d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,73 @@ +# Generated by Neurodocker and Reproenv. + +FROM ubuntu:22.04 +RUN apt-get update -qq \ + && apt-get install -y -q --no-install-recommends \ + ca-certificates \ + curl \ + gcc \ + gnupg2 \ + software-properties-common \ + && rm -rf /var/lib/apt/lists/* +RUN apt-key adv --fetch-keys https://apt.sr-research.com/SRResearch_key && add-apt-repository 'deb [arch=amd64] https://apt.sr-research.com SRResearch main' +RUN apt-get update -qq \ + && apt-get install -y -q --no-install-recommends \ + eyelink-display-software \ + && rm -rf /var/lib/apt/lists/* + +# Save specification to JSON. +RUN printf '{ \ + "pkg_manager": "apt", \ + "existing_users": [ \ + "root" \ + ], \ + "instructions": [ \ + { \ + "name": "from_", \ + "kwds": { \ + "base_image": "ubuntu:22.04" \ + } \ + }, \ + { \ + "name": "install", \ + "kwds": { \ + "pkgs": [ \ + "gnupg2", \ + "curl", \ + "gcc", \ + "ca-certificates", \ + "software-properties-common" \ + ], \ + "opts": null \ + } \ + }, \ + { \ + "name": "run", \ + "kwds": { \ + "command": "apt-get update -qq \\\\\\n && apt-get install -y -q --no-install-recommends \\\\\\n ca-certificates \\\\\\n curl \\\\\\n gcc \\\\\\n gnupg2 \\\\\\n software-properties-common \\\\\\n && rm -rf /var/lib/apt/lists/*" \ + } \ + }, \ + { \ + "name": "run", \ + "kwds": { \ + "command": "apt-key adv --fetch-keys https://apt.sr-research.com/SRResearch_key && add-apt-repository '"'"'deb [arch=amd64] https://apt.sr-research.com SRResearch main'"'"'" \ + } \ + }, \ + { \ + "name": "install", \ + "kwds": { \ + "pkgs": [ \ + "eyelink-display-software" \ + ], \ + "opts": null \ + } \ + }, \ + { \ + "name": "run", \ + "kwds": { \ + "command": "apt-get update -qq \\\\\\n && apt-get install -y -q --no-install-recommends \\\\\\n eyelink-display-software \\\\\\n && rm -rf /var/lib/apt/lists/*" \ + } \ + } \ + ] \ +}' > /.reproenv.json +# End saving to specification to JSON. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..81afffd --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: Dockerfile + +Dockerfile: + docker run --rm repronim/neurodocker:0.9.5 generate docker \ + --pkg-manager apt \ + --base-image ubuntu:22.04 \ + --install gnupg2 curl gcc ca-certificates software-properties-common \ + --run "apt-key adv --fetch-keys https://apt.sr-research.com/SRResearch_key && add-apt-repository 'deb [arch=amd64] https://apt.sr-research.com SRResearch main'" \ + --install eyelink-display-software \ + > Dockerfile + +docker_build: Dockerfile + docker build -t eye2bids . + +test_data: + python tools/download_test_data.py diff --git a/README.md b/README.md index 3b023f1..c59105b 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,24 @@ ### Requirements -- Python3 +- Python >= 3.8 - EyeLink Developers Kit. Download from [SR-Research support forum] (forum registration required) +The installation on Ubuntu can also be done with the following commands: + +Taken from: https://www.sr-research.com/support/docs.php?topic=linuxsoftware + +```bash +sudo add-apt-repository universe +sudo apt update +sudo apt install ca-certificates +sudo apt-key adv --fetch-keys https://apt.sr-research.com/SRResearch_key +sudo add-apt-repository 'deb [arch=amd64] https://apt.sr-research.com SRResearch main' +sudo apt update +sudo apt install eyelink-display-software +``` + - Input data: Those can be: @@ -29,3 +43,6 @@ python edf2bids_json.py ``` [SR-Research support forum]: https://www.sr-research.com/support/forum-9.html + + +## Related projects From c29225e8edad269f129c29b2907e3499da1c6cc7 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 6 Sep 2023 17:59:59 -0400 Subject: [PATCH 3/4] package --- .flake8 | 2 + .gitignore | 1 + eye2bids/__init__.py | 0 .../config/metadata.yml | 0 .../edf2bids_json.py => eye2bids/edf2bids.py | 0 pyproject.toml | 74 +++++++++++++++++++ tests/__init__.py | 0 7 files changed, 77 insertions(+) create mode 100644 eye2bids/__init__.py rename {conversion_json => eye2bids}/config/metadata.yml (100%) rename conversion_json/edf2bids_json.py => eye2bids/edf2bids.py (100%) create mode 100644 tests/__init__.py diff --git a/.flake8 b/.flake8 index cfd079e..c7f4f5e 100644 --- a/.flake8 +++ b/.flake8 @@ -11,6 +11,8 @@ docstring-convention = numpy max-line-length = 90 max_complexity = 15 max_function_length = 100 +per-file-ignores = + **/__init__.py: D104 # for compatibility with black # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8 extend-ignore = E203 diff --git a/.gitignore b/.gitignore index f061a92..ec5449c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ tests/data/test_data.zip **/*.asc **/*.edf **/*.EDF +eye2bids/_version.py # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/eye2bids/__init__.py b/eye2bids/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/conversion_json/config/metadata.yml b/eye2bids/config/metadata.yml similarity index 100% rename from conversion_json/config/metadata.yml rename to eye2bids/config/metadata.yml diff --git a/conversion_json/edf2bids_json.py b/eye2bids/edf2bids.py similarity index 100% rename from conversion_json/edf2bids_json.py rename to eye2bids/edf2bids.py diff --git a/pyproject.toml b/pyproject.toml index a6a0c83..7dd499e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,63 @@ +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling", "hatch-vcs"] + +[project] +authors = [{name = "eye2bids developers"}] +classifiers = [ + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11" +] +dependencies = [ + "numpy", + "pandas" +] +description = "eyetracking data bids converter in Python" +dynamic = ["version"] +license = {text = "MIT"} +name = "eye2bids" +readme = "README.md" +requires-python = ">=3.8" + +[project.optional-dependencies] +# A combination of dependencies useful for developers +dev = [ + "isort", + "black", + "flake8", + "flake8-use-fstring", + "flake8-functions", + "flake8-docstrings", + 'codespell', + 'tomli', + 'pre-commit' +] +# Requirements necessary for building the documentation +doc = [ + "myst-parser", + "numpydoc", + "sphinx", + "sphinx-copybutton" +] +# For running unit and docstring tests +test = [ + "coverage", + "pytest", + "pytest-cov" +] + [tool.black] line-length = 90 @@ -5,8 +65,22 @@ line-length = 90 ignore-words = ".github/codespell_ignore_words.txt" skip = "./.git,.mypy_cache,env,venv,*/tmp,./doc/_build" +[tool.hatch.build.hooks.vcs] +version-file = "eye2bids/_version.py" + +[tool.hatch.build.targets.wheel] +packages = ["eye2bids"] + +[tool.hatch.version] +source = "vcs" + [tool.isort] combine_as_imports = true line_length = 90 profile = "black" skip_gitignore = true + +[tool.pytest.ini_options] +addopts = "-ra -q -vv --cov eye2bids" +norecursedirs = "data" +testpaths = ["tests/"] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 From c097a86fe9c07dd5ab06de1fd4589c0bb545ab43 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 6 Sep 2023 18:39:06 -0400 Subject: [PATCH 4/4] install package in container --- .dockerignore | 23 ++++++++++++++++++++ .github/workflows/docker.yml | 23 ++++++++++++++++++++ .gitignore | 3 +++ Dockerfile | 41 +++++++++++++++++++++++++++++++++-- Makefile | 8 +++++-- README.md | 42 +++++++++++++++++++++++++++++++----- pyproject.toml | 1 + 7 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..174c4c3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +# ignoring this might speed up build +# by preventing passing extra content to the docker daemon + + +# General +.DS_Store + +.github +.vscode + +**/data +**/docs +**/tests +**/*.zip +**/*.asc +**/*.edf +**/*.EDF + +**/build +.pytest_cache +.mypy_cache +coverage_html +.coverage diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..a7c19b7 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,23 @@ +--- +name: docker + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Build docker image + run: docker build -t eye2bids:latest . diff --git a/.gitignore b/.gitignore index ec5449c..11bd6ca 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ tests/data/test_data.zip **/*.EDF eye2bids/_version.py +# General +.DS_Store + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/Dockerfile b/Dockerfile index 0661e6d..15cbd13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,8 @@ RUN apt-get update -qq \ curl \ gcc \ gnupg2 \ + pip \ + python3 \ software-properties-common \ && rm -rf /var/lib/apt/lists/* RUN apt-key adv --fetch-keys https://apt.sr-research.com/SRResearch_key && add-apt-repository 'deb [arch=amd64] https://apt.sr-research.com SRResearch main' @@ -14,6 +16,11 @@ RUN apt-get update -qq \ && apt-get install -y -q --no-install-recommends \ eyelink-display-software \ && rm -rf /var/lib/apt/lists/* +RUN mkdir /eye2bids +COPY [".", \ + "/eye2bids"] +WORKDIR /eye2bids +RUN pip install .[dev] # Save specification to JSON. RUN printf '{ \ @@ -36,7 +43,9 @@ RUN printf '{ \ "curl", \ "gcc", \ "ca-certificates", \ - "software-properties-common" \ + "software-properties-common", \ + "python3", \ + "pip" \ ], \ "opts": null \ } \ @@ -44,7 +53,7 @@ RUN printf '{ \ { \ "name": "run", \ "kwds": { \ - "command": "apt-get update -qq \\\\\\n && apt-get install -y -q --no-install-recommends \\\\\\n ca-certificates \\\\\\n curl \\\\\\n gcc \\\\\\n gnupg2 \\\\\\n software-properties-common \\\\\\n && rm -rf /var/lib/apt/lists/*" \ + "command": "apt-get update -qq \\\\\\n && apt-get install -y -q --no-install-recommends \\\\\\n ca-certificates \\\\\\n curl \\\\\\n gcc \\\\\\n gnupg2 \\\\\\n pip \\\\\\n python3 \\\\\\n software-properties-common \\\\\\n && rm -rf /var/lib/apt/lists/*" \ } \ }, \ { \ @@ -67,6 +76,34 @@ RUN printf '{ \ "kwds": { \ "command": "apt-get update -qq \\\\\\n && apt-get install -y -q --no-install-recommends \\\\\\n eyelink-display-software \\\\\\n && rm -rf /var/lib/apt/lists/*" \ } \ + }, \ + { \ + "name": "run", \ + "kwds": { \ + "command": "mkdir /eye2bids" \ + } \ + }, \ + { \ + "name": "copy", \ + "kwds": { \ + "source": [ \ + ".", \ + "/eye2bids" \ + ], \ + "destination": "/eye2bids" \ + } \ + }, \ + { \ + "name": "workdir", \ + "kwds": { \ + "path": "/eye2bids" \ + } \ + }, \ + { \ + "name": "run", \ + "kwds": { \ + "command": "pip install .[dev]" \ + } \ } \ ] \ }' > /.reproenv.json diff --git a/Makefile b/Makefile index 81afffd..ed62692 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,16 @@ Dockerfile: docker run --rm repronim/neurodocker:0.9.5 generate docker \ --pkg-manager apt \ --base-image ubuntu:22.04 \ - --install gnupg2 curl gcc ca-certificates software-properties-common \ + --install gnupg2 curl gcc ca-certificates software-properties-common python3 pip \ --run "apt-key adv --fetch-keys https://apt.sr-research.com/SRResearch_key && add-apt-repository 'deb [arch=amd64] https://apt.sr-research.com SRResearch main'" \ --install eyelink-display-software \ + --run "mkdir /eye2bids" \ + --copy "." "/eye2bids" \ + --workdir "/eye2bids" \ + --run "pip install .[dev]" \ > Dockerfile -docker_build: Dockerfile +docker_build: docker build -t eye2bids . test_data: diff --git a/README.md b/README.md index c59105b..517e573 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # eye2bids -## Instructions for testing conversion_json +## Installation ### Requirements - Python >= 3.8 -- EyeLink Developers Kit. Download from [SR-Research support forum] (forum registration required) +If you want to use eye2bids to convert EyeLink data, +you will need to install the EyeLink Developers Kit. +It can be downloaded from SR-Research support forum (forum registration required). The installation on Ubuntu can also be done with the following commands: @@ -22,13 +24,28 @@ sudo apt update sudo apt install eyelink-display-software ``` -- Input data: +### Install eye2bids -Those can be: +- Clone the repository + +```bash +git clone https://github.com/bids-standard/eye2bids.git +``` + +- Install the package in editatble mode + +```bash +cd eye2bids +pip install . +``` + +## Using eye2bids + +- Supporeted Input data: - edf file by EyeLink Eye Tracker -You can install uur test data from OSF by running the following command: +To try it, you can install our test data from OSF by running the following command: ```bash python tools/download_test_data.py @@ -44,5 +61,20 @@ python edf2bids_json.py [SR-Research support forum]: https://www.sr-research.com/support/forum-9.html +## Docker + +You can build the docker image with the following command: + +```bash +docker build -t eye2bids:latest . +``` + +## Contributing + +Make sure you install eye2bids in editable mode (see above) and install the development dependencies: + +```bash +pip install --editable .[dev] +``` ## Related projects diff --git a/pyproject.toml b/pyproject.toml index 7dd499e..4396c5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ requires-python = ">=3.8" [project.optional-dependencies] # A combination of dependencies useful for developers dev = [ + "eye2bids[test,doc]", "isort", "black", "flake8",