imaging-server-kit 0.0.4__tar.gz → 0.0.6__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.4 → imaging_server_kit-0.0.6}/Dockerfile +1 -2
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/Dockerfile-GPU +1 -1
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/PKG-INFO +15 -9
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/README.md +6 -5
- imaging_server_kit-0.0.6/main.py +10 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/pyproject.toml +6 -2
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/__init__.py +10 -2
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/_version.py +9 -4
- imaging_server_kit-0.0.4/src/imaging_server_kit/registry.py → imaging_server_kit-0.0.6/src/imaging_server_kit/algorithm_hub.py +1 -1
- imaging_server_kit-0.0.6/src/imaging_server_kit/authenticated_server.py +249 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/client.py +30 -4
- imaging_server_kit-0.0.6/src/imaging_server_kit/geometry.py +248 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/serialization.py +20 -4
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/server.py +66 -27
- imaging_server_kit-0.0.6/src/imaging_server_kit/static/js/login_script.js +24 -0
- imaging_server_kit-0.0.6/src/imaging_server_kit/static/js/register_script.js +15 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/templates/index.html +1 -1
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/templates/info.html +1 -1
- imaging_server_kit-0.0.6/src/imaging_server_kit/templates/login.html +27 -0
- imaging_server_kit-0.0.6/src/imaging_server_kit/templates/register.html +27 -0
- imaging_server_kit-0.0.6/src/imaging_server_kit/users_utils/__init__.py +3 -0
- imaging_server_kit-0.0.6/src/imaging_server_kit/users_utils/db.py +34 -0
- imaging_server_kit-0.0.6/src/imaging_server_kit/users_utils/schemas.py +15 -0
- imaging_server_kit-0.0.6/src/imaging_server_kit/users_utils/users.py +55 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/web_demo.py +6 -6
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit.egg-info/PKG-INFO +15 -9
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit.egg-info/SOURCES.txt +14 -5
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit.egg-info/requires.txt +4 -0
- imaging_server_kit-0.0.6/start_algorithm_hub.py +8 -0
- imaging_server_kit-0.0.4/main.py +0 -8
- imaging_server_kit-0.0.4/src/imaging_server_kit/geometry.py +0 -145
- imaging_server_kit-0.0.4/start_registry.py +0 -8
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/.dockerignore +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/.gitattributes +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/.github/workflows/publish.yml +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/.gitignore +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/LICENSE +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/MANIFEST.in +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/build.sh +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/examples/.gitkeep +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/setup.cfg +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/assets/style.css +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/encoding.py +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit/errors.py +0 -0
- {imaging_server_kit-0.0.4/src/imaging_server_kit/static → imaging_server_kit-0.0.6/src/imaging_server_kit/static/css}/index.css +0 -0
- {imaging_server_kit-0.0.4/src/imaging_server_kit/static → imaging_server_kit-0.0.6/src/imaging_server_kit/static/css}/info.css +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit.egg-info/dependency_links.txt +0 -0
- {imaging_server_kit-0.0.4 → imaging_server_kit-0.0.6}/src/imaging_server_kit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: imaging-server-kit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
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
|
|
@@ -31,8 +31,8 @@ License: Copyright (c) 2024, EPFL
|
|
|
31
31
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
32
32
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
33
33
|
|
|
34
|
-
Project-URL: homepage, https://
|
|
35
|
-
Project-URL: repository, https://
|
|
34
|
+
Project-URL: homepage, https://github.com/Imaging-Server-Kit/imaging-server-kit
|
|
35
|
+
Project-URL: repository, https://github.com/Imaging-Server-Kit/imaging-server-kit
|
|
36
36
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
37
37
|
Classifier: Intended Audience :: Developers
|
|
38
38
|
Classifier: License :: OSI Approved :: BSD License
|
|
@@ -50,9 +50,13 @@ License-File: LICENSE
|
|
|
50
50
|
Requires-Dist: fastapi
|
|
51
51
|
Requires-Dist: uvicorn[standard]
|
|
52
52
|
Requires-Dist: python-multipart
|
|
53
|
+
Requires-Dist: fastapi-users[sqlalchemy]
|
|
54
|
+
Requires-Dist: aiosqlite
|
|
53
55
|
Requires-Dist: tifffile
|
|
54
56
|
Requires-Dist: numpy
|
|
55
57
|
Requires-Dist: scikit-image
|
|
58
|
+
Requires-Dist: opencv-contrib-python-headless
|
|
59
|
+
Requires-Dist: imantics
|
|
56
60
|
Requires-Dist: geojson
|
|
57
61
|
Requires-Dist: jinja2
|
|
58
62
|
Requires-Dist: pyyaml
|
|
@@ -60,6 +64,7 @@ Requires-Dist: dash
|
|
|
60
64
|
Requires-Dist: dash-bootstrap-components
|
|
61
65
|
Requires-Dist: a2wsgi
|
|
62
66
|
Requires-Dist: pytest
|
|
67
|
+
Dynamic: license-file
|
|
63
68
|
|
|
64
69
|

|
|
65
70
|
# 🪐 Imaging Server Kit
|
|
@@ -89,18 +94,19 @@ All of these algorithm servers can be installed locally or built and run with do
|
|
|
89
94
|
|
|
90
95
|
```{docker-compose.yml}
|
|
91
96
|
services:
|
|
92
|
-
|
|
93
|
-
image: mallorywittwerepfl/imaging-server-kit:
|
|
97
|
+
algorithm_hub:
|
|
98
|
+
image: mallorywittwerepfl/imaging-server-kit:latest
|
|
94
99
|
ports:
|
|
95
100
|
- "8000:8000"
|
|
101
|
+
command: python3 start_algorithm_hub.py
|
|
96
102
|
rembg:
|
|
97
103
|
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
|
|
98
104
|
depends_on:
|
|
99
|
-
-
|
|
105
|
+
- algorithm_hub
|
|
100
106
|
skimage-log:
|
|
101
107
|
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-skimage-log:latest
|
|
102
108
|
depends_on:
|
|
103
|
-
-
|
|
109
|
+
- algorithm_hub
|
|
104
110
|
```
|
|
105
111
|
|
|
106
112
|
Start the server with
|
|
@@ -170,7 +176,7 @@ Build the `imaging-server-kit` docker image for a specific Python version:
|
|
|
170
176
|
docker build --build-arg PYTHON_VERSION=3.9 -t imaging-server-kit:3.9 .
|
|
171
177
|
```
|
|
172
178
|
|
|
173
|
-
Run [build.sh](./build.sh) to build docker images for Python 3.9, 3.10, 3.11, GPU and the
|
|
179
|
+
Run [build.sh](./build.sh) to build docker images for Python 3.9, 3.10, 3.11, GPU and the algorithm hub:
|
|
174
180
|
|
|
175
181
|
```
|
|
176
182
|
bash ./build.sh
|
|
@@ -26,18 +26,19 @@ All of these algorithm servers can be installed locally or built and run with do
|
|
|
26
26
|
|
|
27
27
|
```{docker-compose.yml}
|
|
28
28
|
services:
|
|
29
|
-
|
|
30
|
-
image: mallorywittwerepfl/imaging-server-kit:
|
|
29
|
+
algorithm_hub:
|
|
30
|
+
image: mallorywittwerepfl/imaging-server-kit:latest
|
|
31
31
|
ports:
|
|
32
32
|
- "8000:8000"
|
|
33
|
+
command: python3 start_algorithm_hub.py
|
|
33
34
|
rembg:
|
|
34
35
|
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
|
|
35
36
|
depends_on:
|
|
36
|
-
-
|
|
37
|
+
- algorithm_hub
|
|
37
38
|
skimage-log:
|
|
38
39
|
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-skimage-log:latest
|
|
39
40
|
depends_on:
|
|
40
|
-
-
|
|
41
|
+
- algorithm_hub
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Start the server with
|
|
@@ -107,7 +108,7 @@ Build the `imaging-server-kit` docker image for a specific Python version:
|
|
|
107
108
|
docker build --build-arg PYTHON_VERSION=3.9 -t imaging-server-kit:3.9 .
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
Run [build.sh](./build.sh) to build docker images for Python 3.9, 3.10, 3.11, GPU and the
|
|
111
|
+
Run [build.sh](./build.sh) to build docker images for Python 3.9, 3.10, 3.11, GPU and the algorithm hub:
|
|
111
112
|
|
|
112
113
|
```
|
|
113
114
|
bash ./build.sh
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import uvicorn
|
|
2
|
+
import imaging_server_kit as serverkit
|
|
3
|
+
|
|
4
|
+
server = serverkit.AlgorithmServer(
|
|
5
|
+
algorithm_name="foo", parameters_model=serverkit.Parameters
|
|
6
|
+
)
|
|
7
|
+
app = server.app
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
uvicorn.run("main:app", host="0.0.0.0", port=8000)
|
|
@@ -25,9 +25,13 @@ dependencies = [
|
|
|
25
25
|
"fastapi",
|
|
26
26
|
"uvicorn[standard]",
|
|
27
27
|
"python-multipart",
|
|
28
|
+
"fastapi-users[sqlalchemy]",
|
|
29
|
+
"aiosqlite",
|
|
28
30
|
"tifffile",
|
|
29
31
|
"numpy",
|
|
30
32
|
"scikit-image",
|
|
33
|
+
"opencv-contrib-python-headless",
|
|
34
|
+
"imantics",
|
|
31
35
|
"geojson",
|
|
32
36
|
"jinja2", # For the /info route
|
|
33
37
|
"pyyaml", # For the /info route
|
|
@@ -38,8 +42,8 @@ dependencies = [
|
|
|
38
42
|
]
|
|
39
43
|
|
|
40
44
|
[project.urls]
|
|
41
|
-
homepage = "https://
|
|
42
|
-
repository = "https://
|
|
45
|
+
homepage = "https://github.com/Imaging-Server-Kit/imaging-server-kit"
|
|
46
|
+
repository = "https://github.com/Imaging-Server-Kit/imaging-server-kit"
|
|
43
47
|
|
|
44
48
|
[build-system]
|
|
45
49
|
requires = ["setuptools>=42.0.0", "wheel", "setuptools_scm"]
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from ._version import version as __version__
|
|
2
|
+
|
|
2
3
|
try:
|
|
3
4
|
from ._version import version as __version__
|
|
4
5
|
except ImportError:
|
|
5
6
|
__version__ = "unknown"
|
|
6
7
|
|
|
7
8
|
from imaging_server_kit.client import Client
|
|
8
|
-
from imaging_server_kit.server import
|
|
9
|
-
from imaging_server_kit.
|
|
9
|
+
from imaging_server_kit.server import AlgorithmServer, Parameters
|
|
10
|
+
from imaging_server_kit.authenticated_server import AuthenticatedAlgorithmServer
|
|
11
|
+
from imaging_server_kit.algorithm_hub import AlgorithmHub
|
|
10
12
|
from imaging_server_kit.encoding import encode_contents, decode_contents
|
|
11
13
|
from imaging_server_kit.serialization import (
|
|
12
14
|
serialize_result_tuple,
|
|
@@ -14,7 +16,13 @@ from imaging_server_kit.serialization import (
|
|
|
14
16
|
)
|
|
15
17
|
from imaging_server_kit.geometry import (
|
|
16
18
|
mask2features,
|
|
19
|
+
instance_mask2features,
|
|
17
20
|
features2mask,
|
|
21
|
+
features2instance_mask,
|
|
22
|
+
features2mask_3d,
|
|
23
|
+
mask2features_3d,
|
|
24
|
+
instance_mask2features_3d,
|
|
25
|
+
features2instance_mask_3d,
|
|
18
26
|
boxes2features,
|
|
19
27
|
features2boxes,
|
|
20
28
|
points2features,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
20
|
+
__version__ = version = '0.0.6'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 6)
|
|
@@ -11,7 +11,7 @@ static_dir = importlib.resources.files("imaging_server_kit").joinpath("static")
|
|
|
11
11
|
templates = Jinja2Templates(directory=str(templates_dir))
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class AlgorithmHub:
|
|
15
15
|
def __init__(self) -> None:
|
|
16
16
|
self.app = FastAPI()
|
|
17
17
|
self.app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import importlib.resources
|
|
2
|
+
import os
|
|
3
|
+
from typing import List, Tuple, Type
|
|
4
|
+
|
|
5
|
+
import imaging_server_kit as serverkit
|
|
6
|
+
import requests
|
|
7
|
+
import yaml
|
|
8
|
+
from fastapi import FastAPI, Request, status, HTTPException, Depends
|
|
9
|
+
from fastapi.responses import HTMLResponse
|
|
10
|
+
from fastapi.staticfiles import StaticFiles
|
|
11
|
+
from fastapi.templating import Jinja2Templates
|
|
12
|
+
from imaging_server_kit.web_demo import generate_dash_app
|
|
13
|
+
from pydantic import BaseModel, ConfigDict
|
|
14
|
+
from a2wsgi import WSGIMiddleware
|
|
15
|
+
from contextlib import asynccontextmanager
|
|
16
|
+
import asyncio
|
|
17
|
+
|
|
18
|
+
templates_dir = importlib.resources.files("imaging_server_kit").joinpath("templates")
|
|
19
|
+
static_dir = importlib.resources.files("imaging_server_kit").joinpath("static")
|
|
20
|
+
|
|
21
|
+
templates = Jinja2Templates(directory=str(templates_dir))
|
|
22
|
+
|
|
23
|
+
PROCESS_TIMEOUT_SEC = 3600
|
|
24
|
+
|
|
25
|
+
from imaging_server_kit.users_utils import (
|
|
26
|
+
create_db_and_tables,
|
|
27
|
+
fastapi_users,
|
|
28
|
+
auth_backend,
|
|
29
|
+
UserRead,
|
|
30
|
+
UserUpdate,
|
|
31
|
+
UserCreate,
|
|
32
|
+
User,
|
|
33
|
+
current_active_user,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_from_yaml(file_path: str):
|
|
38
|
+
with open(file_path, "r") as file:
|
|
39
|
+
data = yaml.safe_load(file)
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse_algo_params_schema(algo_params_schema):
|
|
44
|
+
algo_params = algo_params_schema.get("properties")
|
|
45
|
+
required_params = algo_params_schema.get("required")
|
|
46
|
+
for param in algo_params.keys():
|
|
47
|
+
algo_params[param]["required"] = param in required_params
|
|
48
|
+
return algo_params
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
ALGORITHM_HUB_URL = os.getenv("ALGORITHM_HUB_URL", "http://algorithm_hub:8000")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Parameters(BaseModel):
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
model_config = ConfigDict(extra="forbid")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AuthenticatedAlgorithmServer:
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
algorithm_name: str,
|
|
64
|
+
parameters_model: Type[BaseModel],
|
|
65
|
+
metadata_file: str = None,
|
|
66
|
+
):
|
|
67
|
+
self.algorithm_name = algorithm_name
|
|
68
|
+
self.parameters_model = parameters_model
|
|
69
|
+
|
|
70
|
+
if metadata_file is None:
|
|
71
|
+
self.metadata_file = "metadata.yaml"
|
|
72
|
+
else:
|
|
73
|
+
self.metadata_file = metadata_file
|
|
74
|
+
|
|
75
|
+
self.app = FastAPI(title=algorithm_name, lifespan=self.lifespan)
|
|
76
|
+
|
|
77
|
+
# Users
|
|
78
|
+
self.app.include_router(
|
|
79
|
+
fastapi_users.get_auth_router(auth_backend),
|
|
80
|
+
prefix="/auth/jwt",
|
|
81
|
+
tags=["auth"],
|
|
82
|
+
)
|
|
83
|
+
self.app.include_router(
|
|
84
|
+
fastapi_users.get_register_router(UserRead, UserCreate),
|
|
85
|
+
prefix="/auth",
|
|
86
|
+
tags=["auth"],
|
|
87
|
+
)
|
|
88
|
+
self.app.include_router(
|
|
89
|
+
fastapi_users.get_reset_password_router(),
|
|
90
|
+
prefix="/auth",
|
|
91
|
+
tags=["auth"],
|
|
92
|
+
)
|
|
93
|
+
self.app.include_router(
|
|
94
|
+
fastapi_users.get_verify_router(UserRead),
|
|
95
|
+
prefix="/auth",
|
|
96
|
+
tags=["auth"],
|
|
97
|
+
)
|
|
98
|
+
self.app.include_router(
|
|
99
|
+
fastapi_users.get_users_router(UserRead, UserUpdate),
|
|
100
|
+
prefix="/users",
|
|
101
|
+
tags=["users"],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Info HTML
|
|
105
|
+
self.app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
106
|
+
|
|
107
|
+
# Web demo app
|
|
108
|
+
algo_params = parameters_model.model_json_schema().get("properties")
|
|
109
|
+
|
|
110
|
+
dash_app = generate_dash_app(
|
|
111
|
+
algorithm_name,
|
|
112
|
+
algo_params,
|
|
113
|
+
run_fnct=self.run_algorithm,
|
|
114
|
+
sample_image_fnct=self.load_sample_images,
|
|
115
|
+
prefix=f"/{algorithm_name}/demo/",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self.app.mount(f"/{algorithm_name}/demo", WSGIMiddleware(dash_app.server))
|
|
119
|
+
|
|
120
|
+
self.register_routes()
|
|
121
|
+
|
|
122
|
+
self.services = [self.algorithm_name]
|
|
123
|
+
|
|
124
|
+
@asynccontextmanager
|
|
125
|
+
async def lifespan(self, app: FastAPI):
|
|
126
|
+
await self.register_with_algohub()
|
|
127
|
+
await create_db_and_tables()
|
|
128
|
+
yield
|
|
129
|
+
await self.deregister_from_algohub()
|
|
130
|
+
|
|
131
|
+
async def register_with_algohub(self):
|
|
132
|
+
try:
|
|
133
|
+
response = requests.get(f"{ALGORITHM_HUB_URL}/")
|
|
134
|
+
except Exception:
|
|
135
|
+
print("Algorithm hub unavailable.")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
response = requests.post(
|
|
139
|
+
f"{ALGORITHM_HUB_URL}/register",
|
|
140
|
+
json={
|
|
141
|
+
"name": self.algorithm_name,
|
|
142
|
+
"url": f"http://{self.algorithm_name}:8000",
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
if response.status_code == 201:
|
|
146
|
+
print(f"Service {self.algorithm_name} registered successfully.")
|
|
147
|
+
else:
|
|
148
|
+
print(f"Failed to register {self.algorithm_name}: {response.json()}")
|
|
149
|
+
|
|
150
|
+
async def deregister_from_algohub(self):
|
|
151
|
+
deregister_url = f"{ALGORITHM_HUB_URL}/deregister"
|
|
152
|
+
response = requests.post(deregister_url, json={"name": self.algorithm_name})
|
|
153
|
+
if response.status_code == 201:
|
|
154
|
+
print(f"Service {self.algorithm_name} deregistered.")
|
|
155
|
+
else:
|
|
156
|
+
print(f"Failed to deregister {self.algorithm_name}: {response.json()}")
|
|
157
|
+
|
|
158
|
+
def register_routes(self):
|
|
159
|
+
@self.app.get("/")
|
|
160
|
+
def home():
|
|
161
|
+
return list_services()
|
|
162
|
+
|
|
163
|
+
@self.app.get("/services")
|
|
164
|
+
def list_services():
|
|
165
|
+
return {"services": self.services}
|
|
166
|
+
|
|
167
|
+
@self.app.get("/version")
|
|
168
|
+
def get_version():
|
|
169
|
+
return serverkit.__version__
|
|
170
|
+
|
|
171
|
+
@self.app.get(f"/{self.algorithm_name}/info", response_class=HTMLResponse)
|
|
172
|
+
def info(request: Request):
|
|
173
|
+
algo_info = load_from_yaml(self.metadata_file)
|
|
174
|
+
algo_params_schema = get_algo_params()
|
|
175
|
+
algo_params = parse_algo_params_schema(algo_params_schema)
|
|
176
|
+
return templates.TemplateResponse(
|
|
177
|
+
"info.html",
|
|
178
|
+
{
|
|
179
|
+
"request": request,
|
|
180
|
+
"algo_info": algo_info,
|
|
181
|
+
"algo_params": algo_params,
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
@self.app.get(f"/register", response_class=HTMLResponse)
|
|
186
|
+
def register(request: Request):
|
|
187
|
+
return templates.TemplateResponse("register.html", {"request": request})
|
|
188
|
+
|
|
189
|
+
@self.app.get(f"/login", response_class=HTMLResponse)
|
|
190
|
+
def login(request: Request):
|
|
191
|
+
return templates.TemplateResponse("login.html", {"request": request})
|
|
192
|
+
|
|
193
|
+
@self.app.post(
|
|
194
|
+
f"/{self.algorithm_name}/process", status_code=status.HTTP_201_CREATED
|
|
195
|
+
)
|
|
196
|
+
async def run_algo(
|
|
197
|
+
algo_params: self.parameters_model,
|
|
198
|
+
user: User = Depends(current_active_user),
|
|
199
|
+
):
|
|
200
|
+
try:
|
|
201
|
+
result_data_tuple = await asyncio.wait_for(
|
|
202
|
+
self._run_algorithm(**algo_params.dict()),
|
|
203
|
+
timeout=PROCESS_TIMEOUT_SEC,
|
|
204
|
+
)
|
|
205
|
+
except (
|
|
206
|
+
asyncio.TimeoutError
|
|
207
|
+
): # This works but doesn't actually kill the process... we need something else to terminate it
|
|
208
|
+
raise HTTPException(
|
|
209
|
+
status_code=504, detail="Request timeout during processing."
|
|
210
|
+
)
|
|
211
|
+
try:
|
|
212
|
+
serialized_results = await asyncio.wait_for(
|
|
213
|
+
self._serialize_result_tuple(result_data_tuple),
|
|
214
|
+
timeout=PROCESS_TIMEOUT_SEC,
|
|
215
|
+
)
|
|
216
|
+
except asyncio.TimeoutError:
|
|
217
|
+
raise HTTPException(
|
|
218
|
+
status_code=504, detail="Request timeout during serialization."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return serialized_results
|
|
222
|
+
|
|
223
|
+
@self.app.get(f"/{self.algorithm_name}/parameters", response_model=dict)
|
|
224
|
+
def get_algo_params():
|
|
225
|
+
return self.parameters_model.model_json_schema()
|
|
226
|
+
|
|
227
|
+
@self.app.get(f"/{self.algorithm_name}/sample_images", response_model=dict)
|
|
228
|
+
def get_sample_images():
|
|
229
|
+
images = self.load_sample_images()
|
|
230
|
+
encoded_images = [
|
|
231
|
+
{"sample_image": serverkit.encode_contents(image)} for image in images
|
|
232
|
+
]
|
|
233
|
+
return {"sample_images": encoded_images}
|
|
234
|
+
|
|
235
|
+
async def _serialize_result_tuple(self, result_data_tuple):
|
|
236
|
+
serialized_results = await asyncio.to_thread(
|
|
237
|
+
serverkit.serialize_result_tuple, result_data_tuple
|
|
238
|
+
)
|
|
239
|
+
return serialized_results
|
|
240
|
+
|
|
241
|
+
async def _run_algorithm(self, **algo_params):
|
|
242
|
+
result_data_tuple = await asyncio.to_thread(self.run_algorithm, **algo_params)
|
|
243
|
+
return result_data_tuple
|
|
244
|
+
|
|
245
|
+
def load_sample_images(self) -> List["np.ndarray"]:
|
|
246
|
+
raise NotImplementedError("Subclasses should implement this method")
|
|
247
|
+
|
|
248
|
+
def run_algorithm(self, **algo_params) -> List[Tuple]:
|
|
249
|
+
raise NotImplementedError("Subclasses should implement this method")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import requests
|
|
2
|
+
import webbrowser
|
|
2
3
|
from typing import List, Dict, Tuple
|
|
3
4
|
import numpy as np
|
|
4
5
|
import imaging_server_kit as serverkit
|
|
@@ -17,6 +18,8 @@ class Client:
|
|
|
17
18
|
self._algorithms = {}
|
|
18
19
|
if server_url != "":
|
|
19
20
|
self.connect(server_url)
|
|
21
|
+
|
|
22
|
+
self.token = None
|
|
20
23
|
|
|
21
24
|
def connect(self, server_url: str) -> None:
|
|
22
25
|
self.server_url = server_url
|
|
@@ -31,6 +34,31 @@ class Client:
|
|
|
31
34
|
self.algorithms = services
|
|
32
35
|
else:
|
|
33
36
|
raise AlgorithmServerError(response.status_code, response.text)
|
|
37
|
+
|
|
38
|
+
# def browser_login(self):
|
|
39
|
+
# webbrowser.open(f"{self.server_url}/login")
|
|
40
|
+
# # How to retreive the access_token when the user logs in via the web page?
|
|
41
|
+
# # self.token = [...]
|
|
42
|
+
# # This might be complicated...
|
|
43
|
+
|
|
44
|
+
def login(self, username, password):
|
|
45
|
+
endpoint = f"{self.server_url}/auth/jwt/login"
|
|
46
|
+
try:
|
|
47
|
+
response = requests.post(
|
|
48
|
+
endpoint,
|
|
49
|
+
data={
|
|
50
|
+
"username": username,
|
|
51
|
+
"password": password,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
except requests.exceptions.RequestException as e:
|
|
55
|
+
raise ServerRequestError(endpoint, e)
|
|
56
|
+
|
|
57
|
+
if response.status_code == 200:
|
|
58
|
+
token = response.json().get("access_token")
|
|
59
|
+
self.token = token
|
|
60
|
+
else:
|
|
61
|
+
raise AlgorithmServerError(response.status_code, response.text)
|
|
34
62
|
|
|
35
63
|
@property
|
|
36
64
|
def server_url(self) -> str:
|
|
@@ -60,6 +88,7 @@ class Client:
|
|
|
60
88
|
headers={
|
|
61
89
|
"Content-Type": "application/json",
|
|
62
90
|
"accept": "application/json",
|
|
91
|
+
"Authorization": f"Bearer {self.token}",
|
|
63
92
|
},
|
|
64
93
|
)
|
|
65
94
|
except requests.exceptions.RequestException as e:
|
|
@@ -111,10 +140,7 @@ class Client:
|
|
|
111
140
|
return image
|
|
112
141
|
return images
|
|
113
142
|
else:
|
|
114
|
-
|
|
115
|
-
f"Error status while attempting to get algorithm sample images: {response.status_code}"
|
|
116
|
-
)
|
|
117
|
-
return -1
|
|
143
|
+
raise AlgorithmServerError(response.status_code, response.text)
|
|
118
144
|
|
|
119
145
|
def _validate_algorithm(self, algorithm=None) -> str:
|
|
120
146
|
if algorithm is None:
|