From 6dc6aa6a171a8ccf41f345b3756107812caebc3f Mon Sep 17 00:00:00 2001
From: dcarron <daniel.carron@idiap.ch>
Date: Tue, 7 May 2024 16:13:52 +0200
Subject: [PATCH] [tests] Update tests

---
 .gitmodules                                   |   0
 pyproject.toml                                |   2 +-
 ...histograms_alexnet_montgomery_default.json |   0
 ...grams_densenet-121_montgomery_default.json |   0
 .../histograms_pasa_montgomery_default.json   |   0
 .../raw_data/histograms_hivtb_fold_0.json     |   0
 .../raw_data/histograms_indian_default.json   |   0
 .../histograms_montgomery_default.json        |   0
 ...grams_montgomery_preprocessed_default.json |   0
 .../histograms_nih_cxr14_default.json         |   0
 .../raw_data/histograms_padchest_idiap.json   |   0
 .../raw_data/histograms_shenzhen_default.json |   0
 .../raw_data/histograms_tbpoc_fold_0.json     |   0
 .../raw_data/histograms_tbx11k_v1_fold_0.json |   0
 .../raw_data/histograms_tbx11k_v2_fold_0.json |   0
 .../tests/data/lfs/.gitattributes             |   6 +
 .../classification/tests/data/lfs/.gitignore  |   1 +
 .../classification/tests/data/lfs/README.md   |  14 ++
 .../tests/data/lfs/models/logreg.ckpt         | Bin 0 -> 3211 bytes
 .../tests/data/lfs/models/signstotb.ckpt      | Bin 0 -> 6363 bytes
 .../classification/tests}/data/mednet.toml    |   0
 .../tests/data/test_predictions.csv           |  10 +
 .../tests/data/test_vis_metrics.csv           |   6 +
 .../tests/test_cli_classification.py          |  20 --
 .../classification/tests}/test_evaluator.py   |   0
 .../libs/classification/tests}/test_hivtb.py  |   0
 .../libs/classification/tests}/test_indian.py |   0
 .../classification/tests}/test_montgomery.py  |   0
 .../tests}/test_montgomery_shenzhen.py        |   0
 .../tests}/test_montgomery_shenzhen_indian.py |   0
 ...est_montgomery_shenzhen_indian_padchest.py |   0
 .../test_montgomery_shenzhen_indian_tbx11k.py |   0
 .../classification/tests}/test_nih_cxr14.py   |   0
 .../tests}/test_nih_cxr14_padchest.py         |   0
 .../classification/tests}/test_padchest.py    |   0
 .../test_saliencymap_interpretability.py      |   0
 .../classification/tests}/test_shenzhen.py    |   0
 .../classification/tests}/test_summary.py     |   0
 .../libs/classification/tests}/test_tbpoc.py  |   0
 .../libs/classification/tests}/test_tbx11k.py |   0
 .../classification/tests}/test_visceral.py    |   4 +-
 src/mednet/libs/common/scripts/upload.py      | 211 ++++++++++++++++++
 .../mednet/libs/common/tests}/conftest.py     |   0
 .../mednet/libs/common/tests}/data/16bits.png | Bin
 .../libs/common/tests/data/iris-test.csv      |  75 +++++++
 .../libs/common/tests/data/iris-train.csv     |  75 +++++++
 .../mednet/libs/common/tests}/data/iris.json  |   0
 .../tests}/data/raw_with_black_border.png     | Bin
 .../data/raw_with_elastic_deformation.png     | Bin
 .../tests}/data/raw_without_black_border.png  | Bin
 .../data/raw_without_elastic_deformation.png  | Bin
 .../libs/common/tests}/test_database_split.py |   0
 .../libs/common/tests}/test_image_utils.py    |   0
 .../common/tests}/test_resource_monitor.py    |   0
 .../libs/common/tests}/test_transforms.py     |   0
 src/mednet/libs/common/utils/gitlab.py        |  92 ++++++++
 src/mednet/tests/test_cli.py                  |  65 ++++++
 57 files changed, 558 insertions(+), 23 deletions(-)
 create mode 100644 .gitmodules
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/models/histograms_alexnet_montgomery_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/models/histograms_densenet-121_montgomery_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/models/histograms_pasa_montgomery_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_hivtb_fold_0.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_indian_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_montgomery_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_montgomery_preprocessed_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_nih_cxr14_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_padchest_idiap.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_shenzhen_default.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_tbpoc_fold_0.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_tbx11k_v1_fold_0.json (100%)
 rename {tests => src/mednet/libs/classification/tests}/data/histograms/raw_data/histograms_tbx11k_v2_fold_0.json (100%)
 create mode 100644 src/mednet/libs/classification/tests/data/lfs/.gitattributes
 create mode 100644 src/mednet/libs/classification/tests/data/lfs/.gitignore
 create mode 100644 src/mednet/libs/classification/tests/data/lfs/README.md
 create mode 100644 src/mednet/libs/classification/tests/data/lfs/models/logreg.ckpt
 create mode 100644 src/mednet/libs/classification/tests/data/lfs/models/signstotb.ckpt
 rename {tests => src/mednet/libs/classification/tests}/data/mednet.toml (100%)
 create mode 100644 src/mednet/libs/classification/tests/data/test_predictions.csv
 create mode 100644 src/mednet/libs/classification/tests/data/test_vis_metrics.csv
 rename tests/test_cli.py => src/mednet/libs/classification/tests/test_cli_classification.py (96%)
 rename {tests => src/mednet/libs/classification/tests}/test_evaluator.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_hivtb.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_indian.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_montgomery.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_montgomery_shenzhen.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_montgomery_shenzhen_indian.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_montgomery_shenzhen_indian_padchest.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_montgomery_shenzhen_indian_tbx11k.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_nih_cxr14.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_nih_cxr14_padchest.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_padchest.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_saliencymap_interpretability.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_shenzhen.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_summary.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_tbpoc.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_tbx11k.py (100%)
 rename {tests => src/mednet/libs/classification/tests}/test_visceral.py (90%)
 create mode 100644 src/mednet/libs/common/scripts/upload.py
 rename {tests => src/mednet/libs/common/tests}/conftest.py (100%)
 rename {tests => src/mednet/libs/common/tests}/data/16bits.png (100%)
 create mode 100644 src/mednet/libs/common/tests/data/iris-test.csv
 create mode 100644 src/mednet/libs/common/tests/data/iris-train.csv
 rename {tests => src/mednet/libs/common/tests}/data/iris.json (100%)
 rename {tests => src/mednet/libs/common/tests}/data/raw_with_black_border.png (100%)
 rename {tests => src/mednet/libs/common/tests}/data/raw_with_elastic_deformation.png (100%)
 rename {tests => src/mednet/libs/common/tests}/data/raw_without_black_border.png (100%)
 rename {tests => src/mednet/libs/common/tests}/data/raw_without_elastic_deformation.png (100%)
 rename {tests => src/mednet/libs/common/tests}/test_database_split.py (100%)
 rename {tests => src/mednet/libs/common/tests}/test_image_utils.py (100%)
 rename {tests => src/mednet/libs/common/tests}/test_resource_monitor.py (100%)
 rename {tests => src/mednet/libs/common/tests}/test_transforms.py (100%)
 create mode 100644 src/mednet/libs/common/utils/gitlab.py
 create mode 100644 src/mednet/tests/test_cli.py

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..e69de29b
diff --git a/pyproject.toml b/pyproject.toml
index d4e1aef6..faf01b79 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 00000000..ebbbf5f9
--- /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 00000000..b25c15b8
--- /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 00000000..bc66bf5e
--- /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
GIT binary patch
literal 3211
zcmbW3Ym5_B6vwAq=<b%Kyt)gEycO;8=(fAtF6{zc%F+VEqjW`vm&0s3bOzeaoO#q;
zOc4yA_*jiW`GCO~B{6DnHGWuJ$f}7-K#dShkPjM(@skN^NDPT4-g~E8I;EoNO(rv$
zx#xGzz2|@KoEU8#M^SZk)Lo;AYM^vE$!UC&%}7Z;-HM($x4b_c3D;3O3gb{s9w-+x
z9+{I#U`ZsTbed0SqLfwbo0TN5@W~!Aq1mCPB@R{>GZD>Ai&>si+>h~MO3+|jLX(t)
z;AVAAOsjTQ;rnzkon$pWt4a!+YqP_6my4-IhANuLrFgIzCiX~L;<R4lr5$P+3!3AB
zI>w6b30a4F+M$7c3r*1$+Hnjf&<@&i3>uCLIGBkVIN~sgnQ$kfK9N&lvVhzPHo?Go
zVTw>sTQrzTFh^k;Z8-`~1JS5KQyiu<PV7$+HC{pMcFU5=7S=Yy#A<_PgEh0jx(~Bv
z8LZhF%!$KXW@agi&2eclSxTH|BDTYPgSenTydM)EFo+8^crXqR>3MK5^@z%5c#Xqp
zgch;^i(CwPOTpqiEFs&Q;}sPx0G84gm9e2uMxwx-hgJc<)YU#hl4MQHhzEEDJbCaM
z0#G$17HG>uyNjms(BWc`v{1%)8GIXGwGsz>zsz#E6!=Yx=zvbN2$ZR1#Y_c^Oa+aF
zbZM|W4l5Wt0u~Ug3b4{d@j=L-bQdYB3`*Fbtk$3>4r_GUl81;(z(EP-K>QFjc5`iE
zH`n3aTyL1u8f=KeM#1d06E+zpn+ql}Y_i2LiEGdshlh2YX+V+7oWf<;lp^V}3R`!>
zBYD{7VruX<E5JlUM-KcHJ?ng&R8t?ylnUFUp<lnm<Ev=s=AU1G-}KbtW3Yp$n@5bD
z(Uofk25)$t3lhE|IX1~BxB=KjQ)I`vjG9uoB<wy;5=vrF;nCf|qAbxCUgvb;ke8)|
z0DI^~B)=K1pEcfabQ{x*gC8J8x-^YTHY;XR?3h^kh`rbCaeE*^iYb+r`nWVpQlCU|
zOh)#?fXpi_anGYNQGw6HUPG3}!hsYs4NVQNUb9Wqgbh+k)YJ`<s=8W0U?!n2#hFG~
zfG7xMa@<Emw<^hHU_UbjnG}`ECZz0MF{MH}51FxD&lY!GCE^mZvaYczQZ`7mLnrTu
zuGu6y!$20Sx;sYK)?L{=&~ppP8(*=1Jwu(8y}}vV>WPGHg%kAZ@Oz_A4__L6`Ot5n
z**BkG>EMP!4WHf&)edg#j&^@C`kD9l(7qMT-M7!wcYkr@-B9n&x^C;U*FrdkQ@0k%
z%g_q(b*~s~m16Kl!Zj!cG2!?av#$O<%Y~~IU-x1KE3w)lVSn+DF!pEoe|J$DB5Ro_
zgmYE<Z?E2e^NnHRfBmI(%d7U^QN91n&qE{If0)&EWcsd=;1>V+sqgqlVv+br?~5Ot
zZ$9{8*Ix(W+?(^C4E}g}(a0~}Z$3IX<J;lwxfhn<oIP=Y-BES^eMbH}wR1u1t<k{s
z3%i1oeAk1gI^Pd`J{JPl_Iww#pIs2V-2aMi-eq^!zD+{`>uYayy*+ryx9yL)0qj5b
zOz?2k{{4pkGt(w4$E;0*_TyFWe`odkf4eb3{I7eX|Mma&-$(_@^j|mwBVk{41<-EC
ztG{wOwH5yc@P&RK_*U%6N>7?5{J}ITcWC8|(vt#rDtAT?>fG*Tw0g=3rRRg0aIrGM
zTTUpga5KS)8jT7uw3QP|Yt&3QQJK(QPAIJyGr@_P%L*}cloLudZzf!<Oz@QxN|kOV
ztVLaTg&6$hgi@`V2`4HOI?D;AdTYER2K3}aZK64bg2H38&9<a4b&hpu(Fk{Bc#Pdo
l67f>hOSs=e1KHueooc}i5E{5akJ00CZ;5Q|6rOL){Rdx2AL9T3

literal 0
HcmV?d00001

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
GIT binary patch
literal 6363
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
BOdbFL

literal 0
HcmV?d00001

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 00000000..5de618e0
--- /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 00000000..1a144f21
--- /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 bb3c1ced..f735ae17 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 9743577e..77d0e94b 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 00000000..ccca1ce8
--- /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 00000000..27d1b05a
--- /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 00000000..82d5b134
--- /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 00000000..d0b111dd
--- /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 00000000..e3a7d9a0
--- /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)
-- 
GitLab