imaging-server-kit 0.0.1__tar.gz → 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- imaging_server_kit-0.0.2/.dockerignore +2 -0
- imaging_server_kit-0.0.2/.gitattributes +35 -0
- imaging_server_kit-0.0.2/.gitignore +28 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/Dockerfile +6 -2
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/Dockerfile-GPU +4 -1
- imaging_server_kit-0.0.2/Dockerfile-registry +5 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/PKG-INFO +13 -3
- imaging_server_kit-0.0.2/build.sh +14 -0
- imaging_server_kit-0.0.2/examples/.gitkeep +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/pyproject.toml +12 -2
- imaging_server_kit-0.0.2/src/imaging_server_kit/__init__.py +20 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit/_version.py +2 -2
- imaging_server_kit-0.0.2/src/imaging_server_kit/assets/style.css +108 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/autostartup_registry.py +58 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/client.py +131 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/errors.py +41 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/geometry.py +148 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit/registry.py +42 -15
- imaging_server_kit-0.0.2/src/imaging_server_kit/serialization.py +135 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit/server.py +63 -11
- imaging_server_kit-0.0.2/src/imaging_server_kit/static/index.css +32 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/static/info.css +32 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/templates/index.html +25 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/templates/info.html +63 -0
- imaging_server_kit-0.0.2/src/imaging_server_kit/web_demo.py +393 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit.egg-info/PKG-INFO +13 -3
- imaging_server_kit-0.0.2/src/imaging_server_kit.egg-info/SOURCES.txt +36 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit.egg-info/requires.txt +7 -0
- imaging_server_kit-0.0.1/.dockerignore +0 -4
- imaging_server_kit-0.0.1/.gitignore +0 -87
- imaging_server_kit-0.0.1/Dockerfile-3.10 +0 -11
- imaging_server_kit-0.0.1/examples/example.py +0 -35
- imaging_server_kit-0.0.1/reference_deployment/LICENSE +0 -27
- imaging_server_kit-0.0.1/reference_deployment/README.md +0 -48
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/README.md +0 -2
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/cookiecutter.json +0 -5
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/requirements.txt +0 -1
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/serverkit-{{ cookiecutter.package }}/Dockerfile +0 -5
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/serverkit-{{ cookiecutter.package }}/README.md +0 -6
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/serverkit-{{ cookiecutter.package }}/main.py +0 -72
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/serverkit-{{ cookiecutter.package }}/requirements.txt +0 -1
- imaging_server_kit-0.0.1/reference_deployment/cookiecutter_template/serverkit-{{ cookiecutter.package }}/sample_images/blobs.tif +0 -0
- imaging_server_kit-0.0.1/reference_deployment/docker-compose.yml +0 -14
- imaging_server_kit-0.0.1/reference_deployment/registry/Dockerfile +0 -3
- imaging_server_kit-0.0.1/reference_deployment/servers/serverkit-skimage/Dockerfile +0 -5
- imaging_server_kit-0.0.1/reference_deployment/servers/serverkit-skimage/README.md +0 -6
- imaging_server_kit-0.0.1/reference_deployment/servers/serverkit-skimage/main.py +0 -141
- imaging_server_kit-0.0.1/reference_deployment/servers/serverkit-skimage/requirements.txt +0 -1
- imaging_server_kit-0.0.1/reference_deployment/servers/serverkit-skimage/sample_images/blobs.tif +0 -0
- imaging_server_kit-0.0.1/reference_deployment/servers/serverkit-skimage/tests/test_skimage.py +0 -26
- imaging_server_kit-0.0.1/src/imaging_server_kit/__init__.py +0 -8
- imaging_server_kit-0.0.1/src/imaging_server_kit/client.py +0 -116
- imaging_server_kit-0.0.1/src/imaging_server_kit/serialization.py +0 -49
- imaging_server_kit-0.0.1/src/imaging_server_kit.egg-info/SOURCES.txt +0 -43
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/.github/workflows/publish.yml +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/LICENSE +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/MANIFEST.in +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/README.md +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/main.py +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/setup.cfg +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit/encoding.py +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit.egg-info/dependency_links.txt +0 -0
- {imaging_server_kit-0.0.1 → imaging_server_kit-0.0.2}/src/imaging_server_kit.egg-info/top_level.txt +0 -0
- /imaging_server_kit-0.0.1/reference_deployment/registry/main.py → /imaging_server_kit-0.0.2/start_registry.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
|
2
|
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
|
3
|
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
4
|
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
|
5
|
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
|
6
|
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
|
7
|
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
|
8
|
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
|
9
|
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
|
10
|
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
|
11
|
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
|
12
|
+
*.model filter=lfs diff=lfs merge=lfs -text
|
|
13
|
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
|
14
|
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
|
15
|
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
|
16
|
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
|
17
|
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
|
18
|
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
|
19
|
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
|
20
|
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
|
21
|
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
|
22
|
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
|
23
|
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
|
24
|
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
|
25
|
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
26
|
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
27
|
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
|
28
|
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
|
29
|
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
|
30
|
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
|
31
|
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
|
32
|
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
|
33
|
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
|
34
|
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
|
35
|
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
old/
|
|
5
|
+
data/
|
|
6
|
+
env/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
downloads/
|
|
10
|
+
eggs/
|
|
11
|
+
.eggs/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
.installed.cfg
|
|
14
|
+
*.egg
|
|
15
|
+
.tox/
|
|
16
|
+
.coverage
|
|
17
|
+
.coverage.*
|
|
18
|
+
.cache
|
|
19
|
+
docs/_build/
|
|
20
|
+
.idea/
|
|
21
|
+
venv/
|
|
22
|
+
.vscode/
|
|
23
|
+
.ipynb_checkpoints
|
|
24
|
+
.python-version
|
|
25
|
+
.DS_Store
|
|
26
|
+
**/_version.py
|
|
27
|
+
|
|
28
|
+
push.sh
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
# Doc: https://github.com/conda-forge/miniforge-images
|
|
1
2
|
FROM condaforge/miniforge3:latest
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
ARG PYTHON_VERSION=3.9
|
|
5
|
+
RUN conda install python=$PYTHON_VERSION -y
|
|
6
|
+
|
|
7
|
+
WORKDIR /app
|
|
4
8
|
|
|
5
9
|
COPY . .
|
|
6
10
|
|
|
@@ -8,4 +12,4 @@ RUN python -m pip install -e .
|
|
|
8
12
|
|
|
9
13
|
EXPOSE 8000
|
|
10
14
|
|
|
11
|
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"
|
|
15
|
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# Doc: https://github.com/iot-salzburg/gpu-jupyter
|
|
1
2
|
FROM cschranz/gpu-jupyter:v1.6_cuda-12.0_ubuntu-22.04_python-only
|
|
3
|
+
|
|
2
4
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
5
|
+
|
|
3
6
|
USER root
|
|
4
7
|
|
|
5
8
|
COPY . .
|
|
@@ -8,4 +11,4 @@ RUN python -m pip install -e .
|
|
|
8
11
|
|
|
9
12
|
EXPOSE 8000
|
|
10
13
|
|
|
11
|
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"
|
|
14
|
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: imaging-server-kit
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Deploy image processing algorithms in FastAPI servers.
|
|
5
5
|
Author-email: Mallory Wittwer <mallory.wittwer@epfl.ch>
|
|
6
6
|
License: Copyright (c) 2024, EPFL
|
|
7
7
|
All rights reserved.
|
|
@@ -34,12 +34,15 @@ License: Copyright (c) 2024, EPFL
|
|
|
34
34
|
Project-URL: homepage, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
|
|
35
35
|
Project-URL: repository, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
|
|
36
36
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
37
|
+
Classifier: Intended Audience :: Developers
|
|
37
38
|
Classifier: License :: OSI Approved :: BSD License
|
|
39
|
+
Classifier: Operating System :: OS Independent
|
|
40
|
+
Classifier: Programming Language :: Python
|
|
38
41
|
Classifier: Programming Language :: Python :: 3
|
|
42
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
39
43
|
Classifier: Programming Language :: Python :: 3.9
|
|
40
44
|
Classifier: Programming Language :: Python :: 3.10
|
|
41
45
|
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
43
46
|
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
44
47
|
Requires-Python: >=3.9
|
|
45
48
|
Description-Content-Type: text/markdown
|
|
@@ -50,6 +53,13 @@ Requires-Dist: python-multipart
|
|
|
50
53
|
Requires-Dist: tifffile
|
|
51
54
|
Requires-Dist: numpy
|
|
52
55
|
Requires-Dist: scikit-image
|
|
56
|
+
Requires-Dist: geojson
|
|
57
|
+
Requires-Dist: docker
|
|
58
|
+
Requires-Dist: jinja2
|
|
59
|
+
Requires-Dist: pyyaml
|
|
60
|
+
Requires-Dist: dash
|
|
61
|
+
Requires-Dist: dash-bootstrap-components
|
|
62
|
+
Requires-Dist: pytest
|
|
53
63
|
|
|
54
64
|

|
|
55
65
|
# 🪐 Imaging Server Kit
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
PYTHON_VERSIONS=("3.9" "3.10" "3.11")
|
|
4
|
+
|
|
5
|
+
for version in "${PYTHON_VERSIONS[@]}"; do
|
|
6
|
+
echo "Building image for Python $version..."
|
|
7
|
+
docker build --build-arg PYTHON_VERSION=$version -t imaging-server-kit:$version .
|
|
8
|
+
done
|
|
9
|
+
|
|
10
|
+
# echo "Building image for Python 3.9, GPU-compatible..."
|
|
11
|
+
# docker build -t imaging-server-kit:gpu --file Dockerfile-GPU .
|
|
12
|
+
|
|
13
|
+
echo "Building image for Python 3.9, serverkit registry..."
|
|
14
|
+
docker build -t imaging-server-kit:registry --file Dockerfile-registry .
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "imaging-server-kit"
|
|
3
3
|
dynamic = ["version"]
|
|
4
|
-
description = "
|
|
4
|
+
description = "Deploy image processing algorithms in FastAPI servers. "
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.9"
|
|
7
7
|
license = { file = "LICENSE" }
|
|
@@ -9,12 +9,15 @@ authors = [{ name = "Mallory Wittwer", email = "mallory.wittwer@epfl.ch" }]
|
|
|
9
9
|
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Development Status :: 2 - Pre-Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
12
13
|
"License :: OSI Approved :: BSD License",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python",
|
|
13
16
|
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
14
18
|
"Programming Language :: Python :: 3.9",
|
|
15
19
|
"Programming Language :: Python :: 3.10",
|
|
16
20
|
"Programming Language :: Python :: 3.11",
|
|
17
|
-
"Programming Language :: Python :: 3.12",
|
|
18
21
|
"Topic :: Scientific/Engineering :: Image Processing",
|
|
19
22
|
]
|
|
20
23
|
|
|
@@ -25,6 +28,13 @@ dependencies = [
|
|
|
25
28
|
"tifffile",
|
|
26
29
|
"numpy",
|
|
27
30
|
"scikit-image",
|
|
31
|
+
"geojson",
|
|
32
|
+
"docker", # For the autostartup regsitry
|
|
33
|
+
"jinja2", # For the /info route
|
|
34
|
+
"pyyaml", # For the /info route
|
|
35
|
+
"dash", # For the dash app
|
|
36
|
+
"dash-bootstrap-components", # For the dash app
|
|
37
|
+
"pytest", # For testing
|
|
28
38
|
]
|
|
29
39
|
|
|
30
40
|
[project.urls]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from imaging_server_kit._version import __version__
|
|
2
|
+
from imaging_server_kit.client import Client
|
|
3
|
+
from imaging_server_kit.server import Server, Parameters
|
|
4
|
+
from imaging_server_kit.registry import Registry
|
|
5
|
+
from imaging_server_kit.autostartup_registry import AutoStartupRegistry
|
|
6
|
+
from imaging_server_kit.encoding import encode_contents, decode_contents
|
|
7
|
+
from imaging_server_kit.serialization import (
|
|
8
|
+
serialize_result_tuple,
|
|
9
|
+
deserialize_result_tuple,
|
|
10
|
+
)
|
|
11
|
+
from imaging_server_kit.geometry import (
|
|
12
|
+
mask2features,
|
|
13
|
+
features2mask,
|
|
14
|
+
boxes2features,
|
|
15
|
+
features2boxes,
|
|
16
|
+
points2features,
|
|
17
|
+
features2points,
|
|
18
|
+
vectors2features,
|
|
19
|
+
features2vectors,
|
|
20
|
+
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
body {
|
|
2
|
+
padding: 0;
|
|
3
|
+
margin: 0;
|
|
4
|
+
background-color: rgb(40, 40, 40);
|
|
5
|
+
color: white;
|
|
6
|
+
height: 100vh;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.main-container {
|
|
12
|
+
height: 100vh;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
/* background-color: orange; */
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
header {
|
|
19
|
+
margin: 10px 0px;
|
|
20
|
+
text-align: center;
|
|
21
|
+
height: 120px;
|
|
22
|
+
line-height: 120px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
footer {
|
|
26
|
+
margin: 10px 0px;
|
|
27
|
+
text-align: center;
|
|
28
|
+
height: 120px;
|
|
29
|
+
line-height: 120px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.main-row {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex: 1 1;
|
|
35
|
+
overflow: auto;
|
|
36
|
+
width: 100%;
|
|
37
|
+
margin: 10px;
|
|
38
|
+
margin-left: 0px;
|
|
39
|
+
/* background-color: purple; */
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.col {
|
|
43
|
+
padding: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.parameters-column,
|
|
47
|
+
.layer-column,
|
|
48
|
+
.graph-column {
|
|
49
|
+
margin: 0 !important;
|
|
50
|
+
padding: 15px;
|
|
51
|
+
background-color: rgb(50, 50, 50);
|
|
52
|
+
margin-bottom: 0px;
|
|
53
|
+
height: 100%;
|
|
54
|
+
overflow: auto;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.layer-column {
|
|
58
|
+
/* flex: 0 1 400px; */
|
|
59
|
+
/* background-color: blue; */
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.parameters-column {
|
|
63
|
+
/* flex: 0 1 400px; */
|
|
64
|
+
min-width: 400px;
|
|
65
|
+
/* background-color: green; */
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.graph-column {
|
|
69
|
+
padding: 0px;
|
|
70
|
+
margin: 0;
|
|
71
|
+
flex: 1;
|
|
72
|
+
min-width: 800px;
|
|
73
|
+
/* background-color: orange; */
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
.layer-column .btn-secondary,
|
|
78
|
+
.parameters-column .btn-primary {
|
|
79
|
+
width: 100%;
|
|
80
|
+
margin-top: 10px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.dash-dropdown * {
|
|
84
|
+
background-color: rgb(70, 70, 70);
|
|
85
|
+
color: white;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.dash-dropdown .Select-value-label {
|
|
89
|
+
color: white !important;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.upload-data {
|
|
93
|
+
background-color: rgb(0, 0, 0);
|
|
94
|
+
border: 1px dashed rgb(240, 240, 240);
|
|
95
|
+
padding: 10px;
|
|
96
|
+
text-align: center;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.graph-container {
|
|
100
|
+
background-color: burlywood;
|
|
101
|
+
flex: 1;
|
|
102
|
+
height: 90%;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#graph {
|
|
106
|
+
background-color: rgb(0, 0, 0);
|
|
107
|
+
height: 100%;
|
|
108
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
import docker
|
|
3
|
+
|
|
4
|
+
class AutoStartupRegistry:
|
|
5
|
+
def __init__(self, algo_servers) -> None:
|
|
6
|
+
self.algo_servers = algo_servers
|
|
7
|
+
self.app = FastAPI()
|
|
8
|
+
self.app.on_event("shutdown")(self.stop_all_algorithm_containers) # This doesn't work.
|
|
9
|
+
self.docker_client = docker.from_env()
|
|
10
|
+
self.services = {}
|
|
11
|
+
self.register_routes()
|
|
12
|
+
|
|
13
|
+
def register_routes(self):
|
|
14
|
+
@self.app.get("/")
|
|
15
|
+
def home():
|
|
16
|
+
return list_services()
|
|
17
|
+
|
|
18
|
+
@self.app.get("/services")
|
|
19
|
+
def list_services():
|
|
20
|
+
return {"services": list(self.services.keys())}
|
|
21
|
+
|
|
22
|
+
@self.app.get("/{algorithm}")
|
|
23
|
+
def get_algorithm_url(algorithm):
|
|
24
|
+
if algorithm not in self.services.keys():
|
|
25
|
+
container_url = self.start_algorithm_container(algorithm)
|
|
26
|
+
else:
|
|
27
|
+
container_url = self.services.get(algorithm).get("url")
|
|
28
|
+
return {"url": container_url}
|
|
29
|
+
|
|
30
|
+
def start_algorithm_container(self, algorithm):
|
|
31
|
+
algorithm_server_image = self.algo_servers[algorithm].get('image')
|
|
32
|
+
|
|
33
|
+
port = self.algo_servers[algorithm].get('port')
|
|
34
|
+
|
|
35
|
+
container = self.docker_client.containers.run(
|
|
36
|
+
image=algorithm_server_image,
|
|
37
|
+
ports={"8000/tcp": port},
|
|
38
|
+
detach=True,
|
|
39
|
+
remove=True,
|
|
40
|
+
name=f"serverkit-server-{algorithm}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
container_url = f"http://localhost:{port}"
|
|
44
|
+
|
|
45
|
+
self.services[algorithm] = {
|
|
46
|
+
"container": container,
|
|
47
|
+
"url": container_url,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return container_url
|
|
51
|
+
|
|
52
|
+
def stop_algorithm_container(self, algorithm):
|
|
53
|
+
container = self.services[algorithm].get("container")
|
|
54
|
+
container.stop()
|
|
55
|
+
|
|
56
|
+
def stop_all_algorithm_containers(self):
|
|
57
|
+
for algorithm in self.services.keys():
|
|
58
|
+
self.stop_algorithm_container(algorithm)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import List, Dict, Tuple
|
|
3
|
+
import numpy as np
|
|
4
|
+
import imaging_server_kit as serverkit
|
|
5
|
+
from imaging_server_kit.errors import (
|
|
6
|
+
AlgorithmNotFoundError,
|
|
7
|
+
AlgorithmServerError,
|
|
8
|
+
InvalidAlgorithmParametersError,
|
|
9
|
+
ServerRequestError,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Client:
|
|
14
|
+
def __init__(self, server_url: str = "") -> None:
|
|
15
|
+
self._server_url = server_url
|
|
16
|
+
self._algorithms = {}
|
|
17
|
+
if server_url != "":
|
|
18
|
+
self.connect(server_url)
|
|
19
|
+
|
|
20
|
+
def connect(self, server_url: str) -> None:
|
|
21
|
+
self.server_url = server_url
|
|
22
|
+
endpoint = f"{self.server_url}/services"
|
|
23
|
+
try:
|
|
24
|
+
response = requests.get(endpoint)
|
|
25
|
+
except requests.exceptions.RequestException as e:
|
|
26
|
+
raise ServerRequestError(endpoint, e)
|
|
27
|
+
|
|
28
|
+
if response.status_code == 200:
|
|
29
|
+
services = response.json().get("services")
|
|
30
|
+
self.algorithms = services
|
|
31
|
+
else:
|
|
32
|
+
raise AlgorithmServerError(response.status_code, response.json())
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def server_url(self) -> str:
|
|
36
|
+
return self._server_url
|
|
37
|
+
|
|
38
|
+
@server_url.setter
|
|
39
|
+
def server_url(self, server_url: str):
|
|
40
|
+
self._server_url = server_url
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def algorithms(self) -> Dict[str, str]:
|
|
44
|
+
return self._algorithms
|
|
45
|
+
|
|
46
|
+
@algorithms.setter
|
|
47
|
+
def algorithms(self, algorithms: Dict[str, str]):
|
|
48
|
+
self._algorithms = algorithms
|
|
49
|
+
|
|
50
|
+
def run_algorithm(self, algorithm=None, **algo_params) -> List[Tuple]:
|
|
51
|
+
algorithm = self._validate_algorithm(algorithm)
|
|
52
|
+
algo_params_encoded = self._encode_numpy_parameters(algo_params)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
endpoint = f"{self.server_url}/{algorithm}/"
|
|
56
|
+
response = requests.post(
|
|
57
|
+
endpoint,
|
|
58
|
+
json=algo_params_encoded,
|
|
59
|
+
headers={
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"accept": "application/json",
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
except requests.exceptions.RequestException as e:
|
|
65
|
+
raise ServerRequestError(endpoint, e)
|
|
66
|
+
|
|
67
|
+
if response.status_code == 201:
|
|
68
|
+
return serverkit.deserialize_result_tuple(response.json())
|
|
69
|
+
elif response.status_code == 422:
|
|
70
|
+
raise InvalidAlgorithmParametersError(response.status_code, response.json())
|
|
71
|
+
else:
|
|
72
|
+
raise AlgorithmServerError(response.status_code, response.json())
|
|
73
|
+
|
|
74
|
+
def get_algorithm_parameters(self, algorithm=None) -> Dict:
|
|
75
|
+
algorithm = self._validate_algorithm(algorithm)
|
|
76
|
+
|
|
77
|
+
endpoint = f"{self.server_url}/{algorithm}/parameters"
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
response = requests.get(endpoint)
|
|
81
|
+
except requests.exceptions.RequestException as e:
|
|
82
|
+
raise ServerRequestError(endpoint, e)
|
|
83
|
+
|
|
84
|
+
if response.status_code == 200:
|
|
85
|
+
return response.json()
|
|
86
|
+
else:
|
|
87
|
+
raise AlgorithmServerError(response.status_code, response.json())
|
|
88
|
+
|
|
89
|
+
def get_sample_images(
|
|
90
|
+
self, algorithm=None, first_only: bool = False
|
|
91
|
+
) -> List["np.ndarray"]:
|
|
92
|
+
algorithm = self._validate_algorithm(algorithm)
|
|
93
|
+
|
|
94
|
+
endpoint = f"{self.server_url}/{algorithm}/sample_images"
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
response = requests.get(endpoint)
|
|
98
|
+
except requests.exceptions.RequestException as e:
|
|
99
|
+
raise ServerRequestError(endpoint, e)
|
|
100
|
+
|
|
101
|
+
if response.status_code == 200:
|
|
102
|
+
images = []
|
|
103
|
+
for content in response.json().get("sample_images"):
|
|
104
|
+
encoded_image = content.get("sample_image")
|
|
105
|
+
image = serverkit.decode_contents(encoded_image)
|
|
106
|
+
images.append(image)
|
|
107
|
+
if first_only:
|
|
108
|
+
return image
|
|
109
|
+
return images
|
|
110
|
+
else:
|
|
111
|
+
print(
|
|
112
|
+
f"Error status while attempting to get algorithm sample images: {response.status_code}"
|
|
113
|
+
)
|
|
114
|
+
return -1
|
|
115
|
+
|
|
116
|
+
def _validate_algorithm(self, algorithm=None) -> str:
|
|
117
|
+
if algorithm is None:
|
|
118
|
+
if len(self.algorithms) > 0:
|
|
119
|
+
algorithm = self.algorithms[0]
|
|
120
|
+
else:
|
|
121
|
+
raise AlgorithmNotFoundError(algorithm)
|
|
122
|
+
else:
|
|
123
|
+
if algorithm not in self.algorithms:
|
|
124
|
+
raise AlgorithmNotFoundError(algorithm)
|
|
125
|
+
return algorithm
|
|
126
|
+
|
|
127
|
+
def _encode_numpy_parameters(self, algo_params: dict) -> dict:
|
|
128
|
+
for param in algo_params:
|
|
129
|
+
if isinstance(algo_params[param], np.ndarray):
|
|
130
|
+
algo_params[param] = serverkit.encode_contents(algo_params[param])
|
|
131
|
+
return algo_params
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class AlgorithmNotFoundError(Exception):
|
|
2
|
+
"""Exception raised when a specified algorithm is not found."""
|
|
3
|
+
|
|
4
|
+
def __init__(self, algorithm_name, message="Algorithm not found"):
|
|
5
|
+
self.algorithm_name = algorithm_name
|
|
6
|
+
self.message = f"{message}: {algorithm_name}"
|
|
7
|
+
super().__init__(self.message)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AlgorithmServerError(Exception):
|
|
11
|
+
"""Exception raised when a request to an algorithm server returns an unexpected status code."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, status_code, response_body, message="Algorithm server returned an unexpected status_code"):
|
|
14
|
+
self.status_code = status_code
|
|
15
|
+
self.response_body = response_body
|
|
16
|
+
self.message = f"{message}: {status_code}\nResponse body: {response_body}"
|
|
17
|
+
super().__init__(self.message)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InvalidAlgorithmParametersError(Exception):
|
|
21
|
+
"""Exception raised when a request to an algorithm server is made with invalid algorithm parameters."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, status_code, response_body, message="Algorithm parameters were invalidated by the server"):
|
|
24
|
+
self.status_code = status_code
|
|
25
|
+
self.response_body = response_body
|
|
26
|
+
pydantic_details = response_body.get("detail")[0]
|
|
27
|
+
pydantic_validation_msg = pydantic_details.get("msg")
|
|
28
|
+
pydantic_failing_param = pydantic_details.get("loc")[1]
|
|
29
|
+
pydantic_failing_param_value = pydantic_details.get("input")
|
|
30
|
+
self.message = f"{message}: Parameter: {pydantic_failing_param}. {pydantic_validation_msg}. Received: {pydantic_failing_param_value}."
|
|
31
|
+
super().__init__(self.message)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ServerRequestError(Exception):
|
|
35
|
+
"""Exception raised when HTTP requests fail."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, url: str, error: Exception, message="Request to server failed"):
|
|
38
|
+
self.url = url
|
|
39
|
+
self.error = error
|
|
40
|
+
self.message = f"{message} ({url=}): {error}"
|
|
41
|
+
super().__init__(self.message)
|