diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/pyproject.toml b/pyproject.toml index d4e1aef6e781053509e570e7f57a0808d6f51e91..faf01b7944be5b56adc4d3e5b2383117a31e5a2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -476,7 +476,7 @@ convention = "numpy" [tool.ruff.lint.per-file-ignores] "helpers/*.py" = ["T201", "D103"] -"tests/*.py" = ["D", "E501"] +"**/tests/*.py" = ["D", "E501"] "doc/conf.py" = ["D"] "**/scripts/*.py" = ["E501"] diff --git a/tests/data/histograms/models/histograms_alexnet_montgomery_default.json b/src/mednet/libs/classification/tests/data/histograms/models/histograms_alexnet_montgomery_default.json similarity index 100% rename from tests/data/histograms/models/histograms_alexnet_montgomery_default.json rename to src/mednet/libs/classification/tests/data/histograms/models/histograms_alexnet_montgomery_default.json diff --git a/tests/data/histograms/models/histograms_densenet-121_montgomery_default.json b/src/mednet/libs/classification/tests/data/histograms/models/histograms_densenet-121_montgomery_default.json similarity index 100% rename from tests/data/histograms/models/histograms_densenet-121_montgomery_default.json rename to src/mednet/libs/classification/tests/data/histograms/models/histograms_densenet-121_montgomery_default.json diff --git a/tests/data/histograms/models/histograms_pasa_montgomery_default.json b/src/mednet/libs/classification/tests/data/histograms/models/histograms_pasa_montgomery_default.json similarity index 100% rename from tests/data/histograms/models/histograms_pasa_montgomery_default.json rename to src/mednet/libs/classification/tests/data/histograms/models/histograms_pasa_montgomery_default.json diff --git a/tests/data/histograms/raw_data/histograms_hivtb_fold_0.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_hivtb_fold_0.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_hivtb_fold_0.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_hivtb_fold_0.json diff --git a/tests/data/histograms/raw_data/histograms_indian_default.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_indian_default.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_indian_default.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_indian_default.json diff --git a/tests/data/histograms/raw_data/histograms_montgomery_default.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_montgomery_default.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_montgomery_default.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_montgomery_default.json diff --git a/tests/data/histograms/raw_data/histograms_montgomery_preprocessed_default.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_montgomery_preprocessed_default.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_montgomery_preprocessed_default.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_montgomery_preprocessed_default.json diff --git a/tests/data/histograms/raw_data/histograms_nih_cxr14_default.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_nih_cxr14_default.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_nih_cxr14_default.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_nih_cxr14_default.json diff --git a/tests/data/histograms/raw_data/histograms_padchest_idiap.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_padchest_idiap.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_padchest_idiap.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_padchest_idiap.json diff --git a/tests/data/histograms/raw_data/histograms_shenzhen_default.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_shenzhen_default.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_shenzhen_default.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_shenzhen_default.json diff --git a/tests/data/histograms/raw_data/histograms_tbpoc_fold_0.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_tbpoc_fold_0.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_tbpoc_fold_0.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_tbpoc_fold_0.json diff --git a/tests/data/histograms/raw_data/histograms_tbx11k_v1_fold_0.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_tbx11k_v1_fold_0.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_tbx11k_v1_fold_0.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_tbx11k_v1_fold_0.json diff --git a/tests/data/histograms/raw_data/histograms_tbx11k_v2_fold_0.json b/src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_tbx11k_v2_fold_0.json similarity index 100% rename from tests/data/histograms/raw_data/histograms_tbx11k_v2_fold_0.json rename to src/mednet/libs/classification/tests/data/histograms/raw_data/histograms_tbx11k_v2_fold_0.json diff --git a/src/mednet/libs/classification/tests/data/lfs/.gitattributes b/src/mednet/libs/classification/tests/data/lfs/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..ebbbf5f9ef5538de427dbe579433ef3349c20cad --- /dev/null +++ b/src/mednet/libs/classification/tests/data/lfs/.gitattributes @@ -0,0 +1,6 @@ +_test_densenetrs_checkpoint.pth filter=lfs diff=lfs merge=lfs -text +_test_fpasa_checkpoint.pth filter=lfs diff=lfs merge=lfs -text +_test_logreg_checkpoint.pth filter=lfs diff=lfs merge=lfs -text +_test_signstotb_checkpoint.pth filter=lfs diff=lfs merge=lfs -text +_testdb.zip filter=lfs diff=lfs merge=lfs -text +pasa.pth filter=lfs diff=lfs merge=lfs -text diff --git a/src/mednet/libs/classification/tests/data/lfs/.gitignore b/src/mednet/libs/classification/tests/data/lfs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b25c15b81fae06e1c55946ac6270bfdb293870e8 --- /dev/null +++ b/src/mednet/libs/classification/tests/data/lfs/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/src/mednet/libs/classification/tests/data/lfs/README.md b/src/mednet/libs/classification/tests/data/lfs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bc66bf5e69eb8db14bb357d1c3278697f7491e2b --- /dev/null +++ b/src/mednet/libs/classification/tests/data/lfs/README.md @@ -0,0 +1,14 @@ +# Assets for Testing biosignal/software/mednet> + +This package contains test unit assets used by the test suit of +biosignal/software/mednet>. + + +## Updating + +To update the contents of this package, use [git-lfs](https://git-lfs.com), +following the workflow described at the [GitLab support +page](https://docs.gitlab.com/ee/topics/git/lfs/). + +Cloning and updating the repository, in particular, works the same as before +and should not impose any workflow changes. diff --git a/src/mednet/libs/classification/tests/data/lfs/models/logreg.ckpt b/src/mednet/libs/classification/tests/data/lfs/models/logreg.ckpt new file mode 100644 index 0000000000000000000000000000000000000000..b8609b572753ff637cac6edb39d643ec683abebb Binary files /dev/null and b/src/mednet/libs/classification/tests/data/lfs/models/logreg.ckpt differ diff --git a/src/mednet/libs/classification/tests/data/lfs/models/signstotb.ckpt b/src/mednet/libs/classification/tests/data/lfs/models/signstotb.ckpt new file mode 100644 index 0000000000000000000000000000000000000000..59ea4595166b871e031c9664c1e0581081f3412e Binary files /dev/null and b/src/mednet/libs/classification/tests/data/lfs/models/signstotb.ckpt differ diff --git a/tests/data/mednet.toml b/src/mednet/libs/classification/tests/data/mednet.toml similarity index 100% rename from tests/data/mednet.toml rename to src/mednet/libs/classification/tests/data/mednet.toml diff --git a/src/mednet/libs/classification/tests/data/test_predictions.csv b/src/mednet/libs/classification/tests/data/test_predictions.csv new file mode 100644 index 0000000000000000000000000000000000000000..5de618e0b3f567a982d8a63341a6741d738274bc --- /dev/null +++ b/src/mednet/libs/classification/tests/data/test_predictions.csv @@ -0,0 +1,10 @@ +filename,likelihood,ground_truth +file1,"[6.1538161e-01 1.4814811e-03 6.5176686e-05 1.0000000e+00 5.1068876e-01 + 1.8108148e-04 3.6165615e-01 1.4568567e-01 4.8867718e-05 5.0666117e-05 + 5.1114771e-07 1.3583761e-01 6.0767888e-11 1.5170315e-08]",[1.] +file2,"[6.3864078e-04 3.6866156e-03 8.4878616e-08 6.3316641e-01 4.4661388e-01 + 7.6667184e-04 1.1361861e-03 4.6111313e-03 4.3461104e-04 1.1185581e-06 + 1.1631314e-06 3.7814886e-06 6.1658076e-11 3.5506051e-08]",[0.] +file3,"[1.3780616e-05 1.8464373e-05 1.4054011e-07 1.6511037e-03 7.0664110e-01 + 6.0737631e-03 7.3566751e-03 3.0668571e-04 3.1133456e-05 5.0116336e-07 + 1.5665150e-04 1.0710500e-08 4.1036647e-06 1.1140607e-07]",[1.] diff --git a/src/mednet/libs/classification/tests/data/test_vis_metrics.csv b/src/mednet/libs/classification/tests/data/test_vis_metrics.csv new file mode 100644 index 0000000000000000000000000000000000000000..1a144f21b6f21693d56be19ef301c240f30398e3 --- /dev/null +++ b/src/mednet/libs/classification/tests/data/test_vis_metrics.csv @@ -0,0 +1,6 @@ +Image,MoRF,LeRF,Combined Score ((LeRF-MoRF) / 2),IoU,IoDA,propEnergy,ASF +tb0004.png,1,2,3,4,5,6,7 +tb0006.png,2,3,4,5,6,7,8 +tb0009.png,1,2,3,4,5,6,7 +tb0014.png,2,3,4,5,6,7,8 +tb0015.png,1,2,3,4,5,6,7 diff --git a/tests/test_cli.py b/src/mednet/libs/classification/tests/test_cli_classification.py similarity index 96% rename from tests/test_cli.py rename to src/mednet/libs/classification/tests/test_cli_classification.py index bb3c1ceddfa0c09c27b093eda499d702aea85564..f735ae171bfd97dc78b60049423eaa56f650a519 100644 --- a/tests/test_cli.py +++ b/src/mednet/libs/classification/tests/test_cli_classification.py @@ -40,26 +40,6 @@ def _check_help(entry_point): assert result.output.startswith("Usage:") -def test_info_help(): - from mednet.scripts.info import info - - _check_help(info) - - -def test_info(): - from mednet.scripts.info import info - - runner = CliRunner() - result = runner.invoke(info) - _assert_exit_0(result) - assert "platform:" in result.output - assert "accelerators:" in result.output - assert "version:" in result.output - assert "configured databases:" in result.output - assert "dependencies:" in result.output - assert "python:" in result.output - - def test_config_help(): from mednet.libs.classification.scripts.config import config diff --git a/tests/test_evaluator.py b/src/mednet/libs/classification/tests/test_evaluator.py similarity index 100% rename from tests/test_evaluator.py rename to src/mednet/libs/classification/tests/test_evaluator.py diff --git a/tests/test_hivtb.py b/src/mednet/libs/classification/tests/test_hivtb.py similarity index 100% rename from tests/test_hivtb.py rename to src/mednet/libs/classification/tests/test_hivtb.py diff --git a/tests/test_indian.py b/src/mednet/libs/classification/tests/test_indian.py similarity index 100% rename from tests/test_indian.py rename to src/mednet/libs/classification/tests/test_indian.py diff --git a/tests/test_montgomery.py b/src/mednet/libs/classification/tests/test_montgomery.py similarity index 100% rename from tests/test_montgomery.py rename to src/mednet/libs/classification/tests/test_montgomery.py diff --git a/tests/test_montgomery_shenzhen.py b/src/mednet/libs/classification/tests/test_montgomery_shenzhen.py similarity index 100% rename from tests/test_montgomery_shenzhen.py rename to src/mednet/libs/classification/tests/test_montgomery_shenzhen.py diff --git a/tests/test_montgomery_shenzhen_indian.py b/src/mednet/libs/classification/tests/test_montgomery_shenzhen_indian.py similarity index 100% rename from tests/test_montgomery_shenzhen_indian.py rename to src/mednet/libs/classification/tests/test_montgomery_shenzhen_indian.py diff --git a/tests/test_montgomery_shenzhen_indian_padchest.py b/src/mednet/libs/classification/tests/test_montgomery_shenzhen_indian_padchest.py similarity index 100% rename from tests/test_montgomery_shenzhen_indian_padchest.py rename to src/mednet/libs/classification/tests/test_montgomery_shenzhen_indian_padchest.py diff --git a/tests/test_montgomery_shenzhen_indian_tbx11k.py b/src/mednet/libs/classification/tests/test_montgomery_shenzhen_indian_tbx11k.py similarity index 100% rename from tests/test_montgomery_shenzhen_indian_tbx11k.py rename to src/mednet/libs/classification/tests/test_montgomery_shenzhen_indian_tbx11k.py diff --git a/tests/test_nih_cxr14.py b/src/mednet/libs/classification/tests/test_nih_cxr14.py similarity index 100% rename from tests/test_nih_cxr14.py rename to src/mednet/libs/classification/tests/test_nih_cxr14.py diff --git a/tests/test_nih_cxr14_padchest.py b/src/mednet/libs/classification/tests/test_nih_cxr14_padchest.py similarity index 100% rename from tests/test_nih_cxr14_padchest.py rename to src/mednet/libs/classification/tests/test_nih_cxr14_padchest.py diff --git a/tests/test_padchest.py b/src/mednet/libs/classification/tests/test_padchest.py similarity index 100% rename from tests/test_padchest.py rename to src/mednet/libs/classification/tests/test_padchest.py diff --git a/tests/test_saliencymap_interpretability.py b/src/mednet/libs/classification/tests/test_saliencymap_interpretability.py similarity index 100% rename from tests/test_saliencymap_interpretability.py rename to src/mednet/libs/classification/tests/test_saliencymap_interpretability.py diff --git a/tests/test_shenzhen.py b/src/mednet/libs/classification/tests/test_shenzhen.py similarity index 100% rename from tests/test_shenzhen.py rename to src/mednet/libs/classification/tests/test_shenzhen.py diff --git a/tests/test_summary.py b/src/mednet/libs/classification/tests/test_summary.py similarity index 100% rename from tests/test_summary.py rename to src/mednet/libs/classification/tests/test_summary.py diff --git a/tests/test_tbpoc.py b/src/mednet/libs/classification/tests/test_tbpoc.py similarity index 100% rename from tests/test_tbpoc.py rename to src/mednet/libs/classification/tests/test_tbpoc.py diff --git a/tests/test_tbx11k.py b/src/mednet/libs/classification/tests/test_tbx11k.py similarity index 100% rename from tests/test_tbx11k.py rename to src/mednet/libs/classification/tests/test_tbx11k.py diff --git a/tests/test_visceral.py b/src/mednet/libs/classification/tests/test_visceral.py similarity index 90% rename from tests/test_visceral.py rename to src/mednet/libs/classification/tests/test_visceral.py index 9743577e1dc68f819c556177dc2e77dfa616baeb..77d0e94bf2d3b624497e675284d29a98c9439540 100644 --- a/tests/test_visceral.py +++ b/src/mednet/libs/classification/tests/test_visceral.py @@ -25,7 +25,7 @@ def test_protocol_consistency( split: str, lenghts: dict[str, int], ): - from mednet.data.split import make_split + from mednet.libs.common.data.split import make_split database_checkers.check_split( make_split("mednet.config.data.visceral", f"{split}.json"), @@ -37,7 +37,7 @@ def test_protocol_consistency( @pytest.mark.skip_if_rc_var_not_set("datadir.visceral") def test_database_check(): - from mednet.scripts.database import check + from mednet.libs.common.scripts.database import check runner = CliRunner() result = runner.invoke(check, ["visceral"]) diff --git a/src/mednet/libs/common/scripts/upload.py b/src/mednet/libs/common/scripts/upload.py new file mode 100644 index 0000000000000000000000000000000000000000..ccca1ce840f68010bbdb93658733a90cdf2b068c --- /dev/null +++ b/src/mednet/libs/common/scripts/upload.py @@ -0,0 +1,211 @@ +# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch> +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import pathlib + +import click +from clapper.click import ResourceOption, verbosity_option +from clapper.logging import setup + +from .click import ConfigCommand + +logger = setup(__name__.split(".")[0], format="%(levelname)s: %(message)s") + + +@click.command( + entry_point_group="mednet.config", + cls=ConfigCommand, + epilog="""Examples: + +1. Upload an existing experiment result from a path it resides on (with a default experiment name as {model-name}_{database-name} and a default run name as {date-time}): + + .. code:: sh + + mednet upload --experiment-folder=/path/to/results + +2. Upload an existing experiment result with an experiment name: + + .. code:: sh + + mednet upload --experiment-folder=/path/to/results --experiment-name=exp-pasa_mc + +3. Upload an existing experiment result with a run name: + + .. code:: sh + + mednet upload --experiment-folder=/path/to/results --run-name=run-1 + +4. Upload an existing experiment result with defining a size limit of 20MB for each file: + + .. code:: sh + + mednet upload --experiment-folder=/path/to/results --upload-limit-mb=20 + +""", +) +@click.option( + "--project-path", + "-p", + help="Path to the project where to upload model entries", + required=True, + type=str, + default="biosignal/software/mednet", + show_default=True, + cls=ResourceOption, +) +@click.option( + "--experiment-folder", + "-f", + help="Directory in which to upload results from", + required=True, + type=click.Path( + file_okay=False, + dir_okay=True, + path_type=pathlib.Path, + ), + default="results", + show_default=True, + cls=ResourceOption, +) +@click.option( + "--experiment-name", + "-e", + help='A string indicating the experiment name (e.g. "exp-pasa-mc" or "exp-densenet-mc-ch")', + cls=ResourceOption, +) +@click.option( + "--run-name", + "-r", + help='A string indicating the run name (e.g. "run-1")', + cls=ResourceOption, +) +@click.option( + "--upload-limit-mb", + "-l", + help="Maximim upload size in MB (set to 0 for no limit).", + show_default=True, + required=True, + default=10, + type=click.IntRange(min=0), + cls=ResourceOption, +) +@verbosity_option(logger=logger, cls=ResourceOption, expose_value=False) +def upload( + project_path: str, + experiment_folder: pathlib.Path, + experiment_name: str, + run_name: str, + upload_limit_mb: int, + **_, # ignored +) -> None: # numpydoc ignore=PR01 + """Upload results from an experiment folder to GitLab's MLFlow server.""" + + import json + import os + import tempfile + + import mlflow + from mednet.libs.common.utils.checkpointer import ( + get_checkpoint_to_run_inference, + ) + from mednet.libs.common.utils.gitlab import ( + gitlab_instance_and_token, + sanitize_filename, + size_in_mb, + ) + + logger.info( + "Retrieving GitLab credentials for access to hosted MLFlow server..." + ) + gitlab, token = gitlab_instance_and_token() + project = gitlab.projects.get(project_path) + os.environ["MLFLOW_TRACKING_TOKEN"] = token + os.environ["MLFLOW_TRACKING_URI"] = ( + gitlab.api_url + f"/projects/{project.id}/ml/mlflow" + ) + + # get train files + train_folder = experiment_folder / "model" + train_meta_file = train_folder / "meta.json" + train_log_file = train_folder / "trainlog.pdf" + train_model_file = get_checkpoint_to_run_inference(train_folder) + train_files = [train_meta_file, train_model_file, train_log_file] + + # get evaluation files + evaluation_file = experiment_folder / "evaluation.json" + evaluation_meta_file = experiment_folder / "evaluation.meta.json" + evaluation_log_file = experiment_folder / "evaluation.pdf" + evaluation_files = [ + evaluation_file, + evaluation_meta_file, + evaluation_log_file, + ] + + # checks for maximum upload limit + total_size_mb = sum([size_in_mb(f) for f in train_files + evaluation_files]) + if upload_limit_mb != 0 and total_size_mb > upload_limit_mb: + raise RuntimeError( + f"Total size of upload ({total_size_mb:.2f} MB) exceeds " + f"permitted maximum ({upload_limit_mb:.2f} MB)." + ) + + # prepare experiment and run names + with train_meta_file.open("r") as meta_file: + train_data = json.load(meta_file) + + with evaluation_file.open("r") as meta_file: + evaluation_data = json.load(meta_file) + evaluation_data = evaluation_data["test"] + + experiment_name = ( + experiment_name + or f"{train_data['model-name']}-{train_data['database-name']}" + ) + run_name = run_name or train_data["datetime"] + + click.secho( + f"Uploading entry `{run_name}` to experiment `{experiment_name}` " + f"on GitLab project {project_path} (id: {project.id})...", + bold=True, + fg="green", + ) + exp_meta = mlflow.set_experiment(experiment_name=experiment_name) + with mlflow.start_run(run_name=run_name): + click.echo("Uploading package metadata...") + click.echo(f" -> `version` ({train_data['package-version']})") + mlflow.log_param("package version", train_data["package-version"]) + + click.echo("Uploading metrics...") + + for k in [ + "threshold", + "precision", + "recall", + "f1_score", + "average_precision_score", + "specificity", + "auc_score", + "accuracy", + ]: + click.secho(f" -> `{k}` ({evaluation_data[k]:.3g})") + mlflow.log_metric(k, evaluation_data[k]) + + click.echo("Uploading artifacts (files)...") + + with tempfile.TemporaryDirectory() as tmpdir_name: + tmpdir = pathlib.Path(tmpdir_name) + for f in train_files + evaluation_files: + assert f.exists(), f"File `{f}` does not exist - cannot upload!" + clean_path = str(sanitize_filename(tmpdir, f)) + click.secho(f" -> `{clean_path}` ({size_in_mb(f):.2f} MB)") + mlflow.log_artifact(clean_path) + + click.secho( + f"Uploaded {total_size_mb:.2f} MB to server.", bold=True, fg="green" + ) + click.secho( + f"Visit {gitlab.url}/{project.path_with_namespace}/-/ml/experiments/{exp_meta.experiment_id}", + bold=True, + fg="blue", + ) diff --git a/tests/conftest.py b/src/mednet/libs/common/tests/conftest.py similarity index 100% rename from tests/conftest.py rename to src/mednet/libs/common/tests/conftest.py diff --git a/tests/data/16bits.png b/src/mednet/libs/common/tests/data/16bits.png similarity index 100% rename from tests/data/16bits.png rename to src/mednet/libs/common/tests/data/16bits.png diff --git a/src/mednet/libs/common/tests/data/iris-test.csv b/src/mednet/libs/common/tests/data/iris-test.csv new file mode 100644 index 0000000000000000000000000000000000000000..27d1b05a7aa70667844b74778504f3b51c624884 --- /dev/null +++ b/src/mednet/libs/common/tests/data/iris-test.csv @@ -0,0 +1,75 @@ +5,3,1.6,0.2,Iris-setosa +5,3.4,1.6,0.4,Iris-setosa +5.2,3.5,1.5,0.2,Iris-setosa +5.2,3.4,1.4,0.2,Iris-setosa +4.7,3.2,1.6,0.2,Iris-setosa +4.8,3.1,1.6,0.2,Iris-setosa +5.4,3.4,1.5,0.4,Iris-setosa +5.2,4.1,1.5,0.1,Iris-setosa +5.5,4.2,1.4,0.2,Iris-setosa +4.9,3.1,1.5,0.1,Iris-setosa +5,3.2,1.2,0.2,Iris-setosa +5.5,3.5,1.3,0.2,Iris-setosa +4.9,3.1,1.5,0.1,Iris-setosa +4.4,3,1.3,0.2,Iris-setosa +5.1,3.4,1.5,0.2,Iris-setosa +5,3.5,1.3,0.3,Iris-setosa +4.5,2.3,1.3,0.3,Iris-setosa +4.4,3.2,1.3,0.2,Iris-setosa +5,3.5,1.6,0.6,Iris-setosa +5.1,3.8,1.9,0.4,Iris-setosa +4.8,3,1.4,0.3,Iris-setosa +5.1,3.8,1.6,0.2,Iris-setosa +4.6,3.2,1.4,0.2,Iris-setosa +5.3,3.7,1.5,0.2,Iris-setosa +5,3.3,1.4,0.2,Iris-setosa +6.6,3,4.4,1.4,Iris-versicolor +6.8,2.8,4.8,1.4,Iris-versicolor +6.7,3,5,1.7,Iris-versicolor +6,2.9,4.5,1.5,Iris-versicolor +5.7,2.6,3.5,1,Iris-versicolor +5.5,2.4,3.8,1.1,Iris-versicolor +5.5,2.4,3.7,1,Iris-versicolor +5.8,2.7,3.9,1.2,Iris-versicolor +6,2.7,5.1,1.6,Iris-versicolor +5.4,3,4.5,1.5,Iris-versicolor +6,3.4,4.5,1.6,Iris-versicolor +6.7,3.1,4.7,1.5,Iris-versicolor +6.3,2.3,4.4,1.3,Iris-versicolor +5.6,3,4.1,1.3,Iris-versicolor +5.5,2.5,4,1.3,Iris-versicolor +5.5,2.6,4.4,1.2,Iris-versicolor +6.1,3,4.6,1.4,Iris-versicolor +5.8,2.6,4,1.2,Iris-versicolor +5,2.3,3.3,1,Iris-versicolor +5.6,2.7,4.2,1.3,Iris-versicolor +5.7,3,4.2,1.2,Iris-versicolor +5.7,2.9,4.2,1.3,Iris-versicolor +6.2,2.9,4.3,1.3,Iris-versicolor +5.1,2.5,3,1.1,Iris-versicolor +5.7,2.8,4.1,1.3,Iris-versicolor +7.2,3.2,6,1.8,Iris-virginica +6.2,2.8,4.8,1.8,Iris-virginica +6.1,3,4.9,1.8,Iris-virginica +6.4,2.8,5.6,2.1,Iris-virginica +7.2,3,5.8,1.6,Iris-virginica +7.4,2.8,6.1,1.9,Iris-virginica +7.9,3.8,6.4,2,Iris-virginica +6.4,2.8,5.6,2.2,Iris-virginica +6.3,2.8,5.1,1.5,Iris-virginica +6.1,2.6,5.6,1.4,Iris-virginica +7.7,3,6.1,2.3,Iris-virginica +6.3,3.4,5.6,2.4,Iris-virginica +6.4,3.1,5.5,1.8,Iris-virginica +6,3,4.8,1.8,Iris-virginica +6.9,3.1,5.4,2.1,Iris-virginica +6.7,3.1,5.6,2.4,Iris-virginica +6.9,3.1,5.1,2.3,Iris-virginica +5.8,2.7,5.1,1.9,Iris-virginica +6.8,3.2,5.9,2.3,Iris-virginica +6.7,3.3,5.7,2.5,Iris-virginica +6.7,3,5.2,2.3,Iris-virginica +6.3,2.5,5,1.9,Iris-virginica +6.5,3,5.2,2,Iris-virginica +6.2,3.4,5.4,2.3,Iris-virginica +5.9,3,5.1,1.8,Iris-virginica diff --git a/src/mednet/libs/common/tests/data/iris-train.csv b/src/mednet/libs/common/tests/data/iris-train.csv new file mode 100644 index 0000000000000000000000000000000000000000..82d5b134803975463f070aebe6847e7c742749d2 --- /dev/null +++ b/src/mednet/libs/common/tests/data/iris-train.csv @@ -0,0 +1,75 @@ +5.1,3.5,1.4,0.2,Iris-setosa +4.9,3,1.4,0.2,Iris-setosa +4.7,3.2,1.3,0.2,Iris-setosa +4.6,3.1,1.5,0.2,Iris-setosa +5,3.6,1.4,0.2,Iris-setosa +5.4,3.9,1.7,0.4,Iris-setosa +4.6,3.4,1.4,0.3,Iris-setosa +5,3.4,1.5,0.2,Iris-setosa +4.4,2.9,1.4,0.2,Iris-setosa +4.9,3.1,1.5,0.1,Iris-setosa +5.4,3.7,1.5,0.2,Iris-setosa +4.8,3.4,1.6,0.2,Iris-setosa +4.8,3,1.4,0.1,Iris-setosa +4.3,3,1.1,0.1,Iris-setosa +5.8,4,1.2,0.2,Iris-setosa +5.7,4.4,1.5,0.4,Iris-setosa +5.4,3.9,1.3,0.4,Iris-setosa +5.1,3.5,1.4,0.3,Iris-setosa +5.7,3.8,1.7,0.3,Iris-setosa +5.1,3.8,1.5,0.3,Iris-setosa +5.4,3.4,1.7,0.2,Iris-setosa +5.1,3.7,1.5,0.4,Iris-setosa +4.6,3.6,1,0.2,Iris-setosa +5.1,3.3,1.7,0.5,Iris-setosa +4.8,3.4,1.9,0.2,Iris-setosa +7,3.2,4.7,1.4,Iris-versicolor +6.4,3.2,4.5,1.5,Iris-versicolor +6.9,3.1,4.9,1.5,Iris-versicolor +5.5,2.3,4,1.3,Iris-versicolor +6.5,2.8,4.6,1.5,Iris-versicolor +5.7,2.8,4.5,1.3,Iris-versicolor +6.3,3.3,4.7,1.6,Iris-versicolor +4.9,2.4,3.3,1,Iris-versicolor +6.6,2.9,4.6,1.3,Iris-versicolor +5.2,2.7,3.9,1.4,Iris-versicolor +5,2,3.5,1,Iris-versicolor +5.9,3,4.2,1.5,Iris-versicolor +6,2.2,4,1,Iris-versicolor +6.1,2.9,4.7,1.4,Iris-versicolor +5.6,2.9,3.6,1.3,Iris-versicolor +6.7,3.1,4.4,1.4,Iris-versicolor +5.6,3,4.5,1.5,Iris-versicolor +5.8,2.7,4.1,1,Iris-versicolor +6.2,2.2,4.5,1.5,Iris-versicolor +5.6,2.5,3.9,1.1,Iris-versicolor +5.9,3.2,4.8,1.8,Iris-versicolor +6.1,2.8,4,1.3,Iris-versicolor +6.3,2.5,4.9,1.5,Iris-versicolor +6.1,2.8,4.7,1.2,Iris-versicolor +6.4,2.9,4.3,1.3,Iris-versicolor +6.3,3.3,6,2.5,Iris-virginica +5.8,2.7,5.1,1.9,Iris-virginica +7.1,3,5.9,2.1,Iris-virginica +6.3,2.9,5.6,1.8,Iris-virginica +6.5,3,5.8,2.2,Iris-virginica +7.6,3,6.6,2.1,Iris-virginica +4.9,2.5,4.5,1.7,Iris-virginica +7.3,2.9,6.3,1.8,Iris-virginica +6.7,2.5,5.8,1.8,Iris-virginica +7.2,3.6,6.1,2.5,Iris-virginica +6.5,3.2,5.1,2,Iris-virginica +6.4,2.7,5.3,1.9,Iris-virginica +6.8,3,5.5,2.1,Iris-virginica +5.7,2.5,5,2,Iris-virginica +5.8,2.8,5.1,2.4,Iris-virginica +6.4,3.2,5.3,2.3,Iris-virginica +6.5,3,5.5,1.8,Iris-virginica +7.7,3.8,6.7,2.2,Iris-virginica +7.7,2.6,6.9,2.3,Iris-virginica +6,2.2,5,1.5,Iris-virginica +6.9,3.2,5.7,2.3,Iris-virginica +5.6,2.8,4.9,2,Iris-virginica +7.7,2.8,6.7,2,Iris-virginica +6.3,2.7,4.9,1.8,Iris-virginica +6.7,3.3,5.7,2.1,Iris-virginica diff --git a/tests/data/iris.json b/src/mednet/libs/common/tests/data/iris.json similarity index 100% rename from tests/data/iris.json rename to src/mednet/libs/common/tests/data/iris.json diff --git a/tests/data/raw_with_black_border.png b/src/mednet/libs/common/tests/data/raw_with_black_border.png similarity index 100% rename from tests/data/raw_with_black_border.png rename to src/mednet/libs/common/tests/data/raw_with_black_border.png diff --git a/tests/data/raw_with_elastic_deformation.png b/src/mednet/libs/common/tests/data/raw_with_elastic_deformation.png similarity index 100% rename from tests/data/raw_with_elastic_deformation.png rename to src/mednet/libs/common/tests/data/raw_with_elastic_deformation.png diff --git a/tests/data/raw_without_black_border.png b/src/mednet/libs/common/tests/data/raw_without_black_border.png similarity index 100% rename from tests/data/raw_without_black_border.png rename to src/mednet/libs/common/tests/data/raw_without_black_border.png diff --git a/tests/data/raw_without_elastic_deformation.png b/src/mednet/libs/common/tests/data/raw_without_elastic_deformation.png similarity index 100% rename from tests/data/raw_without_elastic_deformation.png rename to src/mednet/libs/common/tests/data/raw_without_elastic_deformation.png diff --git a/tests/test_database_split.py b/src/mednet/libs/common/tests/test_database_split.py similarity index 100% rename from tests/test_database_split.py rename to src/mednet/libs/common/tests/test_database_split.py diff --git a/tests/test_image_utils.py b/src/mednet/libs/common/tests/test_image_utils.py similarity index 100% rename from tests/test_image_utils.py rename to src/mednet/libs/common/tests/test_image_utils.py diff --git a/tests/test_resource_monitor.py b/src/mednet/libs/common/tests/test_resource_monitor.py similarity index 100% rename from tests/test_resource_monitor.py rename to src/mednet/libs/common/tests/test_resource_monitor.py diff --git a/tests/test_transforms.py b/src/mednet/libs/common/tests/test_transforms.py similarity index 100% rename from tests/test_transforms.py rename to src/mednet/libs/common/tests/test_transforms.py diff --git a/src/mednet/libs/common/utils/gitlab.py b/src/mednet/libs/common/utils/gitlab.py new file mode 100644 index 0000000000000000000000000000000000000000..d0b111ddcf1f54ec995ffb0544c0199aa3d4f7b4 --- /dev/null +++ b/src/mednet/libs/common/utils/gitlab.py @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch> +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import configparser +import logging +import pathlib +import shutil + +import gitlab + +logger = logging.getLogger(__name__) + + +def gitlab_instance_and_token() -> tuple[gitlab.Gitlab, str]: + """Return an instance of the Gitlab object for remote operations, and the + user token. + + Returns + ------- + Gitlab main object and user token + """ + + cfg = pathlib.Path("~/.python-gitlab.cfg").expanduser() + if cfg.exists(): + gl = gitlab.Gitlab.from_config("idiap", [str(cfg)]) + config = configparser.ConfigParser() + config.read(cfg) + token = config["idiap"]["private_token"] + + else: # ask the user for a token or use one from the current runner + server = "https://gitlab.idiap.ch" + token = input(f"{server} (user or project) token: ") + gl = gitlab.Gitlab(server, private_token=token, api_version="4") + + # tests authentication with given credential. + gl.auth() + + return gl, token + + +def sanitize_filename(tmpdir: pathlib.Path, path: pathlib.Path) -> pathlib.Path: + """Sanitize the name of a file to be logged. + + This function sanitizes the basename of a file to be logged on the GitLab + MLflow server. It removes unsupported characters (such as ``=``) by + creating a copy of the file to be uploaded, with a modified name, on the + provided temporary directory. It then returns the name of such a temporary + file. + + If the input file path does not need sanitization, it is returned as is. + + Parameters + ---------- + tmpdir + The temporary directory where a copy of the input path, with a + sanitized name will be created. + path + The file that needs its name sanitized. + + Returns + ------- + Path to the temporary folder, and the sanitized copy of the input file + in said temporary folder, or the input ``path``, in case its name does not + need sanitization. + """ + + sanitized_filename = path.parts[-1].replace("=", "-") + if path.parts[-1] == sanitized_filename: + return path + + absolute_sanitized_filename = tmpdir / sanitized_filename + logger.info( + f"Sanitazing filename `{path}` -> `{absolute_sanitized_filename}`" + ) + shutil.copy2(path, absolute_sanitized_filename) + return absolute_sanitized_filename + + +def size_in_mb(path: pathlib.Path) -> float: + """Return the size in megabytes of a file. + + Parameters + ---------- + path + Input path to calculate file size from. + + Returns + ------- + A floating point number for the size of the object in MB. + """ + return path.stat().st_size / (1024**2) diff --git a/src/mednet/tests/test_cli.py b/src/mednet/tests/test_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..e3a7d9a0065d649bfc0738bd03de3b0653da9e42 --- /dev/null +++ b/src/mednet/tests/test_cli.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch> +# +# SPDX-License-Identifier: GPL-3.0-or-later +"""Tests for our CLI applications.""" + +import contextlib + +from click.testing import CliRunner + + +@contextlib.contextmanager +def stdout_logging(): + # copy logging messages to std out + + import io + import logging + + buf = io.StringIO() + ch = logging.StreamHandler(buf) + ch.setFormatter(logging.Formatter("%(message)s")) + ch.setLevel(logging.INFO) + logger = logging.getLogger("mednet") + logger.addHandler(ch) + yield buf + logger.removeHandler(ch) + + +def _assert_exit_0(result): + assert ( + result.exit_code == 0 + ), f"Exit code {result.exit_code} != 0 -- Output:\n{result.output}" + + +def _check_help(entry_point): + runner = CliRunner() + result = runner.invoke(entry_point, ["--help"]) + _assert_exit_0(result) + assert result.output.startswith("Usage:") + + +def test_info_help(): + from mednet.scripts.info import info + + _check_help(info) + + +def test_info(): + from mednet.scripts.info import info + + runner = CliRunner() + result = runner.invoke(info) + _assert_exit_0(result) + assert "platform:" in result.output + assert "accelerators:" in result.output + assert "version:" in result.output + assert "configured classification databases:" in result.output + assert "configured segmentation databases:" in result.output + assert "dependencies:" in result.output + assert "python:" in result.output + + +def test_upload_help(): + from mednet.libs.common.scripts.upload import upload + + _check_help(upload)