diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3939b49..8681923 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,14 @@ jobs: - name: Install package run: pip install .[test] + - uses: actions/cache@v4 + id: cache + with: + path: /home/runner/work/eye2bids/eye2bids/tests/data/osf + key: data + - name: Install data + if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: make test_data - name: unit tests diff --git a/README.md b/README.md index db9db57..9571b7e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![test](https://github.com/bids-standard/eye2bids/actions/workflows/tests.yml/badge.svg)](https://github.com/bids-standard/eye2bids/actions/workflows/tests.yml) + # eye2bids ## Installation diff --git a/eye2bids/_cli.py b/eye2bids/_cli.py index 9113fe9..6b156ba 100644 --- a/eye2bids/_cli.py +++ b/eye2bids/_cli.py @@ -55,4 +55,5 @@ def cli(argv: Sequence[str] = sys.argv) -> None: metadata_file=metadata_file, output_dir=output_dir, interactive=args.interactive, + force=args.force, ) diff --git a/eye2bids/_parser.py b/eye2bids/_parser.py index 7d5b958..a652fda 100644 --- a/eye2bids/_parser.py +++ b/eye2bids/_parser.py @@ -70,4 +70,13 @@ def global_parser() -> ArgumentParser: type=int, nargs=2, ) + parser.add_argument( + "-f", + "--force", + help=""" + To run the converter without passing a metadata.yml file.\n + Creates an invalid BIDS dataset. + """, + action="store_true", + ) return parser diff --git a/eye2bids/edf2bids.py b/eye2bids/edf2bids.py index 5a2ccb0..15e989b 100644 --- a/eye2bids/edf2bids.py +++ b/eye2bids/edf2bids.py @@ -26,6 +26,7 @@ def _check_inputs( metadata_file: str | Path | None = None, output_dir: str | Path | None = None, interactive: bool = False, + force: bool = False, ) -> tuple[Path, Path | None, Path]: """Check if inputs are valid.""" if input_file is None: @@ -45,16 +46,34 @@ def _check_inputs( raise FileNotFoundError(f"No such input file: {cheked_input_file}") if metadata_file in [None, ""] and interactive: - e2b_log.info( + e2b_log.warning( """Load the metadata.yml file with the additional metadata.\n - This file must contain at least the additional REQUIRED metadata + You can find a template in the eye2bids GitHub.\n + This file must contain at least the additional REQUIRED metadata\n in the format specified in the BIDS specification.\n""" ) metadata_file = Prompt.ask("Enter the file path to the metadata.yml file") - if metadata_file in ["", None]: - checked_metadata_file = None - elif isinstance(metadata_file, str): + if metadata_file in [None, ""]: + if not force: + e2b_log.error( + """You didn't pass a metadata.yml file. + As this file contains metadata + which is REQUIRED for a valid BIDS dataset, + the conversion process now stops. + Please start again with a metadata.yml file + or run eye2bids in --force mode.\n + This will produce an invalid BIDS dataset.\n""" + ) + raise SystemExit(1) + else: + e2b_log.warning( + """You didn't pass a metadata.yml file. + Note that this will produce an invalid BIDS dataset.\n""" + ) + + checked_metadata_file = None + if isinstance(metadata_file, str): checked_metadata_file = Path(metadata_file) elif isinstance(metadata_file, Path): checked_metadata_file = metadata_file @@ -484,13 +503,14 @@ def edf2bids( metadata_file: str | Path | None = None, output_dir: str | Path | None = None, interactive: bool = False, + force: bool = False, ) -> None: """Convert edf to tsv + json.""" if not _check_edf2asc_present(): return input_file, metadata_file, output_dir = _check_inputs( - input_file, metadata_file, output_dir, interactive + input_file, metadata_file, output_dir, interactive, force ) # CONVERSION events diff --git a/pyproject.toml b/pyproject.toml index ca3feb9..e628dc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,6 +114,6 @@ module = [ ] [tool.pytest.ini_options] -addopts = "-ra -q -vv --cov eye2bids" +addopts = "-ra -q -vv --cov eye2bids --durations=0" norecursedirs = "data" testpaths = ["tests/"] diff --git a/tests/test_cli.py b/tests/test_cli.py index 9ff74c6..508c05a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -16,23 +16,25 @@ def root_dir() -> Path: @pytest.mark.skipif(not _check_edf2asc_present(), reason="edf2asc missing") -@pytest.mark.parametrize("metadata_file", [data_dir() / "metadata.yml", None]) @pytest.mark.parametrize("output_dir", [data_dir() / "output", None]) @pytest.mark.parametrize("use_relative_path", [False, True]) -def test_edf_cli(use_relative_path, metadata_file, output_dir, eyelink_test_data_dir): +def test_edf_cli(use_relative_path, output_dir, eyelink_test_data_dir): + + metadata_file = data_dir() / "metadata.yml" + input_dir = eyelink_test_data_dir / "satf" input_file = edf_test_files(input_dir=input_dir)[0] if use_relative_path: input_file = input_file.relative_to(root_dir()) + metadata_file = metadata_file.relative_to(root_dir()) - command = ["eye2bids", "--input_file", str(input_file)] - if metadata_file is not None: - if use_relative_path: - metadata_file = metadata_file.relative_to(root_dir()) - metadata_file = str(metadata_file) - command.extend(["--metadata_file", metadata_file]) - + command = [ + "eye2bids", + "--input_file", + str(input_file), + *["--metadata_file", str(metadata_file)], + ] if output_dir is not None: if use_relative_path: output_dir = output_dir.relative_to(root_dir()) @@ -53,5 +55,6 @@ def test_all_edf_files(input_file): str(input_file), "--output_dir", str(data_dir() / "output"), + "--force", ] cli(command) diff --git a/tests/test_edf2bids.py b/tests/test_edf2bids.py index a753aa8..d0f4282 100644 --- a/tests/test_edf2bids.py +++ b/tests/test_edf2bids.py @@ -48,8 +48,9 @@ def _check_output_exists(output_dir, input_file, eye=1): @pytest.mark.skipif(not _check_edf2asc_present(), reason="edf2asc missing") -@pytest.mark.parametrize("metadata_file", [data_dir() / "metadata.yml", None]) -def test_edf_end_to_end(metadata_file, eyelink_test_data_dir): +def test_edf_end_to_end(eyelink_test_data_dir): + metadata_file = data_dir() / "metadata.yml" + input_dir = eyelink_test_data_dir / "satf" input_file = edf_test_files(input_dir=input_dir)[0] @@ -75,8 +76,28 @@ def test_edf_end_to_end(metadata_file, eyelink_test_data_dir): @pytest.mark.skipif(not _check_edf2asc_present(), reason="edf2asc missing") -@pytest.mark.parametrize("metadata_file", [data_dir() / "metadata.yml", None]) -def test_edf_end_to_end_2eyes(metadata_file, eyelink_test_data_dir): +def test_edf_end_to_end_error_no_metadata(eyelink_test_data_dir): + input_dir = eyelink_test_data_dir / "2eyes" + input_file = edf_test_files(input_dir=input_dir)[0] + + output_dir = data_dir() / "output" + output_dir.mkdir(exist_ok=True) + + # when force is true no system exit even with no metadata file + edf2bids(input_file=input_file, metadata_file=None, output_dir=output_dir, force=True) + # but when force is false, no metadata file triggers a failure + with pytest.raises(SystemExit) as pytest_wrapped_e: + edf2bids( + input_file=input_file, metadata_file=None, output_dir=output_dir, force=False + ) + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 1 + + +@pytest.mark.skipif(not _check_edf2asc_present(), reason="edf2asc missing") +def test_edf_end_to_end_2eyes(eyelink_test_data_dir): + metadata_file = data_dir() / "metadata.yml" + input_dir = eyelink_test_data_dir / "2eyes" input_file = edf_test_files(input_dir=input_dir)[0] @@ -123,7 +144,7 @@ def test_edf_nan_in_tsv(eyelink_test_data_dir): output_dir = data_dir() / "output" output_dir.mkdir(exist_ok=True) - edf2bids(input_file=input_file, output_dir=output_dir) + edf2bids(input_file=input_file, output_dir=output_dir, force=True) expected_eyetrack_tsv = output_dir / f"{input_file.stem}_recording-eye1_physio.tsv.gz" df = pd.read_csv(expected_eyetrack_tsv, sep="\t", header=None) @@ -144,7 +165,7 @@ def test_number_columns_2eyes_tsv(eyelink_test_data_dir): output_dir = data_dir() / "output" output_dir.mkdir(exist_ok=True) - edf2bids(input_file=input_file, output_dir=output_dir) + edf2bids(input_file=input_file, output_dir=output_dir, force=True) expected_eyetrack_tsv = output_dir / f"{input_file.stem}_recording-eye1_physio.tsv.gz" df = pd.read_csv(expected_eyetrack_tsv, sep="\t") @@ -165,7 +186,7 @@ def test_number_columns_1eye_tsv(eyelink_test_data_dir): output_dir = data_dir() / "output" output_dir.mkdir(exist_ok=True) - edf2bids(input_file=input_file, output_dir=output_dir) + edf2bids(input_file=input_file, output_dir=output_dir, force=True) expected_eyetrack_tsv = output_dir / f"{input_file.stem}_recording-eye1_physio.tsv.gz" df = pd.read_csv(expected_eyetrack_tsv, sep="\t") @@ -469,7 +490,7 @@ def test_number_columns_physioevents_tsv(eyelink_test_data_dir): output_dir = data_dir() / "output" output_dir.mkdir(exist_ok=True) - edf2bids(input_file=input_file, output_dir=output_dir) + edf2bids(input_file=input_file, output_dir=output_dir, force=True) expected_physioevents_tsv = ( output_dir / f"{input_file.stem}_recording-eye2_physioevents.tsv.gz" @@ -528,7 +549,7 @@ def test_physioevents_value(folder, expected, eyelink_test_data_dir): output_dir = data_dir() / "output" output_dir.mkdir(exist_ok=True) - edf2bids(input_file=input_file, output_dir=output_dir) + edf2bids(input_file=input_file, output_dir=output_dir, force=True) expected_eyetrackphysio_tsv = ( output_dir / f"{input_file.stem}_recording-eye1_physioevents.tsv.gz" diff --git a/tools/download_test_data.py b/tools/download_test_data.py index 0cb694b..089e709 100644 --- a/tools/download_test_data.py +++ b/tools/download_test_data.py @@ -15,6 +15,8 @@ output_dir = root_dir / "tests" / "data" / "osf" +print(f"Saving files to {output_dir}") + if output_dir.exists(): shutil.rmtree(output_dir) output_dir.mkdir(parents=True)