diff --git a/.flake8 b/.flake8 deleted file mode 100644 index db1a275..0000000 --- a/.flake8 +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -count = True -show-source = True -statistics = True -exclude = - *build - .git - __pycache__ - _version.py -ignore = D100, D103, W503 -max-line-length = 90 -max_complexity = 15 -max_function_length = 150 -max_parameters_amount = 15 -max_returns_amount = 5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6891c9d..2336b9a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,14 +3,25 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: end-of-file-fixer - - id: check-added-large-files + - id: check-ast - id: check-case-conflict - id: check-json - id: check-merge-conflict + - id: check-toml - id: check-yaml - - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] - id: trailing-whitespace + + # Checks for .rst files +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt rev: 0.2.3 hooks: @@ -22,6 +33,7 @@ repos: - '4' - --offset - '0' + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks rev: v2.16.0 hooks: @@ -30,35 +42,38 @@ repos: - --autofix - --indent - '4' + - repo: https://github.com/asottile/pyupgrade rev: v3.21.2 hooks: - id: pyupgrade args: - --py38-plus + - repo: https://github.com/seddonym/import-linter rev: v2.11 hooks: - id: import-linter -- repo: https://github.com/pycqa/isort - rev: 8.0.1 - hooks: - - id: isort - args: - - --settings-path - - pyproject.toml + args: [--verbose] + language: python + - repo: https://github.com/adamchainz/blacken-docs rev: 1.20.0 hooks: - id: blacken-docs additional_dependencies: - black==24.2.0 -- repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.3.0 + + # Lint and format Python code +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.1 hooks: - - id: black - args: - - --config=pyproject.toml + - id: ruff-check + # args: [--statistics] + args: [--fix, --show-fixes, --unsafe-fixes] + - id: ruff-format + # args: [--diff] + - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.19.1 hooks: @@ -71,28 +86,14 @@ repos: args: - --config-file - pyproject.toml + - repo: https://github.com/codespell-project/codespell rev: v2.4.2 hooks: - id: codespell - args: - - --toml=pyproject.toml - additional_dependencies: - - tomli -- repo: https://github.com/pycqa/flake8 - rev: 7.3.0 - hooks: - - id: flake8 - exclude: tests_.*.py|version.*.py|setup.py - args: - - --config - - .flake8 - - --verbose - additional_dependencies: - - flake8-docstrings - - flake8-use-fstring - - flake8-functions - - flake8-bugbear + args: [--toml=pyproject.toml] + additional_dependencies: [tomli] + ci: autoupdate_commit_msg: 'chore: update pre-commit hooks' autoupdate_schedule: monthly diff --git a/bidsmreye/_parsers.py b/bidsmreye/_parsers.py index d807161..a56793c 100644 --- a/bidsmreye/_parsers.py +++ b/bidsmreye/_parsers.py @@ -10,7 +10,7 @@ def _base_parser(formatter_class: type[HelpFormatter] = HelpFormatter) -> ArgumentParser: parser = ArgumentParser( description=( - "BIDS app using deepMReye to decode " "eye motion for fMRI time series data." + "BIDS app using deepMReye to decode eye motion for fMRI time series data." ), epilog=""" For a more readable version of this help section, diff --git a/bidsmreye/bids_utils.py b/bidsmreye/bids_utils.py index 782a71c..70cd13d 100644 --- a/bidsmreye/bids_utils.py +++ b/bidsmreye/bids_utils.py @@ -36,11 +36,8 @@ def check_layout(cfg: Config, layout: BIDSLayout, for_file: str = "bold") -> Non :raises RuntimeError: _description_ """ desc = layout.get_dataset_description() - if ( - "DatasetType" not in desc - and "PipelineDescription" not in desc - or "DatasetType" in desc - and desc["DatasetType"] != "derivative" + if ("DatasetType" not in desc and "PipelineDescription" not in desc) or ( + "DatasetType" in desc and desc["DatasetType"] != "derivative" ): raise RuntimeError( "DatasetType must be 'derivative' in dataset_description.json\n." diff --git a/bidsmreye/configuration.py b/bidsmreye/configuration.py index 3f44ce7..cbf35c8 100644 --- a/bidsmreye/configuration.py +++ b/bidsmreye/configuration.py @@ -74,7 +74,7 @@ def __attrs_post_init__(self) -> None: self.bids_filter = get_bids_filter_config() self.output_dir = self.output_dir / "bidsmreye" - if not self.output_dir: + if not self.output_dir.exists(): self.output_dir.mkdir(parents=True, exist_ok=True) database_path = self.input_dir / "pybids_db" @@ -136,7 +136,7 @@ def check_argument(self, attribute: str, layout_in: BIDSLayout) -> Config: self.listify(attribute) # convert all run values to integers - if attribute in {"run"}: + if attribute == "run": for i, j in enumerate(value): value[i] = int(j) tmp = [int(j) for j in getattr(self, attribute)] @@ -155,7 +155,7 @@ def check_argument(self, attribute: str, layout_in: BIDSLayout) -> Config: # run and space can be empty if their entity are not used # we will figure out the values for run # in subject / task wise manner later on - if attribute not in ["run"]: + if attribute != "run": setattr(self, attribute, value) if attribute not in ["run", "space"] and not getattr(self, attribute): @@ -236,7 +236,7 @@ def get_config(config_file: Path | None = None, default: str = "") -> dict[str, my_path = Path(__file__).absolute().parent / "config" config_file = my_path / default - if config_file is None or not Path(config_file).exists(): + if not Path(config_file).exists(): raise FileNotFoundError(f"Config file {config_file} not found") with open(config_file) as ff: diff --git a/bidsmreye/methods.py b/bidsmreye/methods.py index fe9c7cb..61e6f14 100644 --- a/bidsmreye/methods.py +++ b/bidsmreye/methods.py @@ -29,7 +29,7 @@ def methods( :rtype: Path """ if output_dir is None: - output_dir = Path(".") + output_dir = Path() if isinstance(output_dir, str): output_dir = Path(output_dir) output_dir = output_dir / "logs" diff --git a/bidsmreye/quality_control.py b/bidsmreye/quality_control.py index 18bb072..ac2d349 100644 --- a/bidsmreye/quality_control.py +++ b/bidsmreye/quality_control.py @@ -325,7 +325,7 @@ def compute_robust_outliers( indices.pop(i) tmp = time_series[indices] - tmp.dropna(inplace=True) + tmp = tmp.dropna() # median of all pair-wise distances distance.append(np.median(abs(this_timepoint - tmp))) diff --git a/bidsmreye/report.py b/bidsmreye/report.py index 30576cf..7cd88b2 100644 --- a/bidsmreye/report.py +++ b/bidsmreye/report.py @@ -66,7 +66,6 @@ def generate_report(output_dir: Path, subject_label: str, action: str) -> None: if __name__ == "__main__": - cwd = Path("/home/remi/github/cpp-lln-lab/bidsMReye") output_dir = cwd / "outputs" / "moae_fmriprep" / "derivatives" / "bidsmreye" diff --git a/bidsmreye/visualize.py b/bidsmreye/visualize.py index 23797ca..6c1e63e 100644 --- a/bidsmreye/visualize.py +++ b/bidsmreye/visualize.py @@ -17,14 +17,14 @@ from bidsmreye.utils import check_if_file_found, set_this_filter LINE_WIDTH = 3 -FONT_SIZE = dict(size=14) +FONT_SIZE = {"size": 14} GRID_COLOR = "grey" LINE_COLOR = "rgb(0, 150, 175)" BG_COLOR = "rgb(255,255,255)" HEAT_MAP_COLOR = "gnbu" MARKER_SIZE = 10 -TICK_FONT = dict(family="arial", color="black", size=14) +TICK_FONT = {"family": "arial", "color": "black", "size": 14} X_POSITION_1 = 1 X_POSITION_2 = 1.5 @@ -113,7 +113,7 @@ def plot_group_boxplot( go.Box( x=np.ones(nb_data_points) * X_POSITION[i], y=qc_data[this_column], - marker=dict(size=MARKER_SIZE, color=COLORS[i]), + marker={"size": MARKER_SIZE, "color": COLORS[i]}, name=trace_names[i], ), row=row, @@ -128,7 +128,7 @@ def plot_group_boxplot( fig.update_yaxes( row=row, col=col, - title=dict(text=yaxes_title, font=FONT_SIZE), + title={"text": yaxes_title, "font": FONT_SIZE}, ) @@ -186,7 +186,7 @@ def group_report(cfg: Config) -> None: ) fig.update_yaxes( - title=dict(standoff=0, font=FONT_SIZE), + title={"standoff": 0, "font": FONT_SIZE}, showline=True, linewidth=LINE_WIDTH - 1, linecolor="black", @@ -215,9 +215,9 @@ def group_report(cfg: Config) -> None: boxmean=True, width=0.2, hovertext=qc_data["filename"], - marker=dict(size=MARKER_SIZE), + marker={"size": MARKER_SIZE}, fillcolor="rgb(200, 200, 200)", - line=dict(color="black"), + line={"color": "black"}, ) fig.update_layout( @@ -226,17 +226,17 @@ def group_report(cfg: Config) -> None: paper_bgcolor=BG_COLOR, height=800, width=800, - title=dict( - text=f"""bidsmreye: group report
+ title={ + "text": f"""bidsmreye: group report
Summary
- Date and time: {datetime.now():%Y-%m-%d, %H:%M}
- bidsmreye version: {__version__}
""", - x=0.05, - y=0.95, - font=dict(size=19, color="black"), - ), - margin=dict(t=150, b=10, l=100, r=10, pad=0), + "x": 0.05, + "y": 0.95, + "font": {"size": 19, "color": "black"}, + }, + margin={"t": 150, "b": 10, "l": 100, "r": 10, "pad": 0}, ) fig.show() @@ -288,7 +288,7 @@ def visualize_eye_gaze_data( fig.update_xaxes( row=3, col=1, - title=dict(text="Time (s)", standoff=16, font=FONT_SIZE), + title={"text": "Time (s)", "standoff": 16, "font": FONT_SIZE}, tickfont=TICK_FONT, ) @@ -376,7 +376,7 @@ def plot_time_series( griddash="dot", gridwidth=0.5, ticksuffix="°", - title=dict(text=title_text, standoff=0, font=FONT_SIZE), + title={"text": title_text, "standoff": 0, "font": FONT_SIZE}, tickfont=FONT_SIZE, ) @@ -428,7 +428,7 @@ def plot_heat_map(fig: Any, eye_gaze_data: pd.DataFrame) -> None: x=X, y=Y, opacity=0.4, - line=dict(color="black", width=1, dash="dash"), + line={"color": "black", "width": 1, "dash": "dash"}, ), row=1, col=3, @@ -448,7 +448,7 @@ def plot_heat_map(fig: Any, eye_gaze_data: pd.DataFrame) -> None: col=3, range=value_range(X), ticksuffix="°", - title=dict(text="X", standoff=16, font=FONT_SIZE), + title={"text": "X", "standoff": 16, "font": FONT_SIZE}, tickfont=TICK_FONT, ) fig.update_yaxes( @@ -456,7 +456,7 @@ def plot_heat_map(fig: Any, eye_gaze_data: pd.DataFrame) -> None: col=3, range=value_range(Y), ticksuffix="°", - title=dict(text="Y", standoff=16, font=FONT_SIZE), + title={"text": "Y", "standoff": 16, "font": FONT_SIZE}, tickfont=TICK_FONT, ) diff --git a/pyproject.toml b/pyproject.toml index 809296c..b06e3bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,22 +113,18 @@ layers = [ name = "Layered architecture" type = "layers" -[tool.isort] -combine_as_imports = true -line_length = 90 -profile = "black" -skip_gitignore = true - [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = false disallow_untyped_defs = false -# enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +enable_error_code = ["redundant-expr", "truthy-bool"] +# enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] # TODO exclude = ['tests/'] no_implicit_optional = true plugins = ["numpy.typing.mypy_plugin", "pydantic.mypy"] warn_redundant_casts = true +# warn_unreachable = true # TODO warn_unused_ignores = true [[tool.mypy.overrides]] @@ -163,6 +159,109 @@ addopts = "-ra --cov bidsmreye --strict-config --strict-markers --doctest-module doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS" junit_family = "xunit2" log_cli_level = "INFO" +log_level = "INFO" minversion = "6.0" testpaths = ["tests"] xfail_strict = true + +[tool.ruff] +extend-exclude = ["bidsmreye/_version.py"] +include = [ + "pyproject.toml", + "bidsmreye/**/*.py", + "tools/**/*.py", + "doc/**/*.py" +] +indent-width = 4 +line-length = 90 + +[tool.ruff.format] +docstring-code-format = true +docstring-code-line-length = "dynamic" +indent-style = "space" +line-ending = "auto" +quote-style = "double" +skip-magic-trailing-comma = false + +[tool.ruff.lint] +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +fixable = ["ALL"] +ignore = [ + "ARG001", + "ARG002", + "ERA001", + "D100", + "D103", + "D203", + "D213", + "N802", + "N803", + "N806", + "N816", + "N815", + "PLR2004", + "PD003", + "PGH003", + "PTH100", + "PTH107", + "PTH103", + "PTH123", + "SIM115", + "NPY002", + # Avoid linter rules conflicting with the formatter + # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "COM812", + "COM819", + "E111", + "E114", + "E117", + "Q000", + "Q001", + "Q002", + "Q003", + "W191" +] +# List of all the ruff rules (includes why the rule matters) +# https://docs.astral.sh/ruff/rules/ +select = [ + "ARG", + "B", + "C4", + "C90", + "D", + "E", + "ERA", + "F", + "FLY", + "FURB", + "I", + "N", + "NPY", + "PERF", + "PIE", + "PTH", + "PD", + "PGH", + "PLR", + "RUF", + "SIM", + "UP", + "W" +] +unfixable = [] + +[tool.ruff.lint.mccabe] +max-complexity = 15 + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["D104"] + +[tool.ruff.lint.pylint] +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-args +max-args = 15 +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-branches +max-branches = 10 +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-returns +max-returns = 6 +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-statements +max-statements = 80