imaging-server-kit 0.0.1__py3-none-any.whl
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/__init__.py +8 -0
- imaging_server_kit/_version.py +16 -0
- imaging_server_kit/client.py +116 -0
- imaging_server_kit/encoding.py +29 -0
- imaging_server_kit/registry.py +73 -0
- imaging_server_kit/serialization.py +49 -0
- imaging_server_kit/server.py +97 -0
- imaging_server_kit-0.0.1.dist-info/LICENSE +27 -0
- imaging_server_kit-0.0.1.dist-info/METADATA +144 -0
- imaging_server_kit-0.0.1.dist-info/RECORD +12 -0
- imaging_server_kit-0.0.1.dist-info/WHEEL +5 -0
- imaging_server_kit-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from imaging_server_kit.client import Client
|
|
2
|
+
from imaging_server_kit.server import Server, Parameters
|
|
3
|
+
from imaging_server_kit.registry import Registry
|
|
4
|
+
from imaging_server_kit.encoding import encode_contents, decode_contents
|
|
5
|
+
from imaging_server_kit.serialization import (
|
|
6
|
+
serialize_result_tuple,
|
|
7
|
+
deserialize_result_tuple,
|
|
8
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.0.1'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import List, Dict, Tuple
|
|
3
|
+
import imaging_server_kit as serverkit
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Client:
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
self._server_url = ""
|
|
10
|
+
self._algorithms = {}
|
|
11
|
+
|
|
12
|
+
def connect(self, server_url: str) -> int:
|
|
13
|
+
self.server_url = server_url
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
response = requests.get(f"{self.server_url}/services")
|
|
17
|
+
except Exception:
|
|
18
|
+
self.algorithms = {}
|
|
19
|
+
return (-1, "Server unavailable.")
|
|
20
|
+
|
|
21
|
+
if response.status_code == 200:
|
|
22
|
+
services = response.json().get("services")
|
|
23
|
+
self.algorithms = services
|
|
24
|
+
return (response.status_code, services)
|
|
25
|
+
else:
|
|
26
|
+
response_body = response.json()
|
|
27
|
+
self.algorithms = {}
|
|
28
|
+
return (response.status_code, response_body)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def server_url(self) -> str:
|
|
32
|
+
return self._server_url
|
|
33
|
+
|
|
34
|
+
@server_url.setter
|
|
35
|
+
def server_url(self, server_url: str):
|
|
36
|
+
self._server_url = server_url
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def algorithms(self) -> Dict[str, str]:
|
|
40
|
+
return self._algorithms
|
|
41
|
+
|
|
42
|
+
@algorithms.setter
|
|
43
|
+
def algorithms(self, algorithms: Dict[str, str]):
|
|
44
|
+
self._algorithms = algorithms
|
|
45
|
+
|
|
46
|
+
def run_algorithm(self, algorithm: str = None, **algo_params) -> List[Tuple]:
|
|
47
|
+
if algorithm is None:
|
|
48
|
+
if len(self.algorithms) > 0:
|
|
49
|
+
algorithm = self.algorithms[0]
|
|
50
|
+
else:
|
|
51
|
+
print("No algorithms available.")
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
if algorithm not in self.algorithms:
|
|
55
|
+
print(f"Not an available algorithm: {algorithm}")
|
|
56
|
+
return []
|
|
57
|
+
|
|
58
|
+
# Encode all numpy array parameters
|
|
59
|
+
for param in algo_params:
|
|
60
|
+
if isinstance(algo_params[param], np.ndarray):
|
|
61
|
+
algo_params[param] = serverkit.encode_contents(algo_params[param])
|
|
62
|
+
|
|
63
|
+
response = requests.post(
|
|
64
|
+
f"{self.server_url}/{algorithm}", json=algo_params, timeout=300
|
|
65
|
+
)
|
|
66
|
+
if response.status_code == 201:
|
|
67
|
+
return serverkit.deserialize_result_tuple(response.json())
|
|
68
|
+
elif response.status_code == 422:
|
|
69
|
+
# This actually doesn't occurr when it should...
|
|
70
|
+
print(f"Algorithm parameters are not valid!")
|
|
71
|
+
return []
|
|
72
|
+
else:
|
|
73
|
+
print(
|
|
74
|
+
f"Algorithm server returned an error status code: {response.status_code}"
|
|
75
|
+
)
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
def get_algorithm_parameters(self, algorithm: str = None) -> Dict:
|
|
79
|
+
if algorithm is None:
|
|
80
|
+
if len(self.algorithms) > 0:
|
|
81
|
+
algorithm = self.algorithms[0]
|
|
82
|
+
else:
|
|
83
|
+
print("No algorithms available.")
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
response = requests.get(f"{self.server_url}/{algorithm}/parameters")
|
|
87
|
+
if response.status_code == 200:
|
|
88
|
+
return response.json()
|
|
89
|
+
else:
|
|
90
|
+
print(
|
|
91
|
+
f"Error status while attempting to get algoritm parameters: {response.status_code}"
|
|
92
|
+
)
|
|
93
|
+
return -1
|
|
94
|
+
|
|
95
|
+
def get_sample_images(self, algorithm: str = None) -> "np.ndarray":
|
|
96
|
+
if algorithm is None:
|
|
97
|
+
if len(self.algorithms) > 0:
|
|
98
|
+
algorithm = self.algorithms[0]
|
|
99
|
+
else:
|
|
100
|
+
print("No algorithms available.")
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
response = requests.get(f"{self.server_url}/{algorithm}/sample_images")
|
|
104
|
+
|
|
105
|
+
if response.status_code == 200:
|
|
106
|
+
images = []
|
|
107
|
+
for content in response.json().get("sample_images"):
|
|
108
|
+
encoded_image = content.get("sample_image")
|
|
109
|
+
image = serverkit.decode_contents(encoded_image)
|
|
110
|
+
images.append(image)
|
|
111
|
+
return images
|
|
112
|
+
else:
|
|
113
|
+
print(
|
|
114
|
+
f"Error status while attempting to get algorithm sample images: {response.status_code}"
|
|
115
|
+
)
|
|
116
|
+
return -1
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import base64
|
|
3
|
+
import numpy as np
|
|
4
|
+
import tifffile
|
|
5
|
+
|
|
6
|
+
def decode_contents(contents: str) -> np.ndarray:
|
|
7
|
+
"""Decodes base64 encoded image contents into a NumPy array.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
contents (str): The base64 encoded string of the image.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
np.ndarray: The decoded image represented as a NumPy array.
|
|
14
|
+
"""
|
|
15
|
+
return tifffile.imread(io.BytesIO(base64.b64decode(contents)))
|
|
16
|
+
|
|
17
|
+
def encode_contents(image: np.ndarray) -> str:
|
|
18
|
+
"""Encodes a NumPy array image into a base64 string.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
image (np.ndarray): The image to encode.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: The base64 encoded string representation of the image.
|
|
25
|
+
"""
|
|
26
|
+
img_byte_arr = io.BytesIO()
|
|
27
|
+
tifffile.imwrite(img_byte_arr, image)
|
|
28
|
+
|
|
29
|
+
return base64.b64encode(img_byte_arr.getvalue()).decode()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from fastapi import FastAPI, Request, status, HTTPException
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Registry:
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
self.app = FastAPI()
|
|
8
|
+
|
|
9
|
+
self.services = {}
|
|
10
|
+
self.register_routes()
|
|
11
|
+
|
|
12
|
+
def register_routes(self):
|
|
13
|
+
@self.app.get("/")
|
|
14
|
+
def home():
|
|
15
|
+
return list_services()
|
|
16
|
+
|
|
17
|
+
@self.app.get("/services")
|
|
18
|
+
def list_services():
|
|
19
|
+
return {"services": list(self.services.keys())}
|
|
20
|
+
|
|
21
|
+
@self.app.post("/register", status_code=status.HTTP_201_CREATED)
|
|
22
|
+
async def register_service(request: Request):
|
|
23
|
+
data = await request.json()
|
|
24
|
+
service_name = data.get("name")
|
|
25
|
+
service_url = data.get("url")
|
|
26
|
+
if service_name in self.services:
|
|
27
|
+
raise HTTPException(
|
|
28
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
29
|
+
detail=f"Service {service_name} is already registered.",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
self.services[service_name] = service_url
|
|
33
|
+
return {"message": "Service registered"}
|
|
34
|
+
|
|
35
|
+
@self.app.post("/deregister", status_code=status.HTTP_201_CREATED)
|
|
36
|
+
async def deregister_service(request: Request):
|
|
37
|
+
data = await request.json()
|
|
38
|
+
service_name = data.get("name")
|
|
39
|
+
if service_name in self.services:
|
|
40
|
+
del self.services[service_name]
|
|
41
|
+
return {"message": "Service deregistered"}
|
|
42
|
+
else:
|
|
43
|
+
raise HTTPException(
|
|
44
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
45
|
+
detail=f"Service {service_name} could not be deregistered because it is not in the list of services.",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@self.app.get("/{algorithm}")
|
|
49
|
+
def get_algorithm_status(algorithm):
|
|
50
|
+
response = requests.get(f"{self.services.get(algorithm)}/")
|
|
51
|
+
return response.json()
|
|
52
|
+
|
|
53
|
+
@self.app.post("/{algorithm}", status_code=status.HTTP_201_CREATED)
|
|
54
|
+
async def run_algorithm(algorithm, request: Request):
|
|
55
|
+
data = await request.json()
|
|
56
|
+
response = requests.post(
|
|
57
|
+
f"{self.services.get(algorithm)}/{algorithm}/", json=data
|
|
58
|
+
)
|
|
59
|
+
return response.json()
|
|
60
|
+
|
|
61
|
+
@self.app.get("/{algorithm}/parameters")
|
|
62
|
+
def get_algorithm_parameters(algorithm):
|
|
63
|
+
response = requests.get(
|
|
64
|
+
f"{self.services.get(algorithm)}/{algorithm}/parameters"
|
|
65
|
+
)
|
|
66
|
+
return response.json()
|
|
67
|
+
|
|
68
|
+
@self.app.get("/{algorithm}/sample_images")
|
|
69
|
+
def get_algorithm_sample_images(algorithm):
|
|
70
|
+
response = requests.get(
|
|
71
|
+
f"{self.services.get(algorithm)}/{algorithm}/sample_images"
|
|
72
|
+
)
|
|
73
|
+
return response.json()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Dict, List, Tuple
|
|
2
|
+
import numpy as np
|
|
3
|
+
import imaging_server_kit as serverkit
|
|
4
|
+
|
|
5
|
+
def serialize_result_tuple(result_data_tuple: List[Tuple]) -> List[Dict]:
|
|
6
|
+
"""Converts the result data tuple to dict that can be serialized as JSON (used by the server)."""
|
|
7
|
+
serialized_results = []
|
|
8
|
+
for (data, data_params, data_type) in result_data_tuple:
|
|
9
|
+
if data_type in ['image', 'points', 'shapes', 'vectors', 'tracks']:
|
|
10
|
+
serialized_results.append({
|
|
11
|
+
"type": data_type,
|
|
12
|
+
"data": serverkit.encode_contents(data.astype(np.float32)),
|
|
13
|
+
"data_params": data_params
|
|
14
|
+
})
|
|
15
|
+
elif data_type == 'labels':
|
|
16
|
+
serialized_results.append({
|
|
17
|
+
"type": data_type,
|
|
18
|
+
"data": serverkit.encode_contents(data.astype(np.uint32)),
|
|
19
|
+
"data_params": data_params
|
|
20
|
+
})
|
|
21
|
+
else:
|
|
22
|
+
print(f"Unknown data_type: {data_type}")
|
|
23
|
+
|
|
24
|
+
return serialized_results
|
|
25
|
+
|
|
26
|
+
def deserialize_result_tuple(serialized_results: List[Dict]) -> List[Tuple]:
|
|
27
|
+
"""Converts serialized JSON results to a results data tuple (used by the client)."""
|
|
28
|
+
result_data_tuple = []
|
|
29
|
+
for result_dict in serialized_results:
|
|
30
|
+
data_type = result_dict.get("type")
|
|
31
|
+
data = result_dict.get("data")
|
|
32
|
+
data_params = result_dict.get("data_params")
|
|
33
|
+
if data_type in ["image", 'points', 'shapes', 'vectors', 'tracks']:
|
|
34
|
+
data_tuple = (
|
|
35
|
+
serverkit.decode_contents(data).astype(float),
|
|
36
|
+
data_params,
|
|
37
|
+
data_type
|
|
38
|
+
)
|
|
39
|
+
elif data_type == "labels":
|
|
40
|
+
data_tuple = (
|
|
41
|
+
serverkit.decode_contents(data).astype(int),
|
|
42
|
+
data_params,
|
|
43
|
+
data_type
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
data_tuple = (data, data_params, "none")
|
|
47
|
+
result_data_tuple.append(data_tuple)
|
|
48
|
+
|
|
49
|
+
return result_data_tuple
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
from typing import Type, List, Tuple
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
from fastapi import FastAPI, status
|
|
6
|
+
import imaging_server_kit as serverkit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
REGISTRY_URL = os.getenv("REGISTRY_URL", "http://servers_registry:8000")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Parameters(BaseModel):
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(extra="forbid")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Server:
|
|
19
|
+
def __init__(self, algorithm_name: str, parameters_model: Type[BaseModel]):
|
|
20
|
+
self.algorithm_name = algorithm_name
|
|
21
|
+
self.parameters_model = parameters_model
|
|
22
|
+
|
|
23
|
+
self.app = FastAPI(title=algorithm_name)
|
|
24
|
+
|
|
25
|
+
self.app.on_event("startup")(self.register_with_registry)
|
|
26
|
+
self.app.on_event("shutdown")(self.deregister_from_registry)
|
|
27
|
+
|
|
28
|
+
self.register_routes()
|
|
29
|
+
|
|
30
|
+
self.services = [self.algorithm_name]
|
|
31
|
+
|
|
32
|
+
def register_with_registry(self):
|
|
33
|
+
try:
|
|
34
|
+
response = requests.get(f"{REGISTRY_URL}/")
|
|
35
|
+
except Exception:
|
|
36
|
+
print("Registry unavailable.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
response = requests.post(
|
|
40
|
+
f"{REGISTRY_URL}/register",
|
|
41
|
+
json={
|
|
42
|
+
"name": self.algorithm_name,
|
|
43
|
+
"url": f"http://{self.algorithm_name}:8000",
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
if response.status_code == 201:
|
|
47
|
+
print(f"Service {self.algorithm_name} registered successfully.")
|
|
48
|
+
else:
|
|
49
|
+
print(f"Failed to register {self.algorithm_name}: {response.json()}")
|
|
50
|
+
|
|
51
|
+
def deregister_from_registry(self):
|
|
52
|
+
deregister_url = f"{REGISTRY_URL}/deregister"
|
|
53
|
+
response = requests.post(deregister_url, json={"name": self.algorithm_name})
|
|
54
|
+
if response.status_code == 201:
|
|
55
|
+
print(f"Service {self.algorithm_name} deregistered.")
|
|
56
|
+
else:
|
|
57
|
+
print(f"Failed to deregister {self.algorithm_name}: {response.json()}")
|
|
58
|
+
|
|
59
|
+
def register_routes(self):
|
|
60
|
+
@self.app.get("/")
|
|
61
|
+
def home():
|
|
62
|
+
return list_services()
|
|
63
|
+
|
|
64
|
+
@self.app.get("/test")
|
|
65
|
+
def test():
|
|
66
|
+
return list_services()
|
|
67
|
+
|
|
68
|
+
@self.app.get("/services")
|
|
69
|
+
def list_services():
|
|
70
|
+
return {"services": self.services}
|
|
71
|
+
|
|
72
|
+
# I noted that the 422 error doesn't get raised when parameters are invalid. Weird?
|
|
73
|
+
@self.app.post(f"/{self.algorithm_name}/", status_code=status.HTTP_201_CREATED)
|
|
74
|
+
async def run_algo(
|
|
75
|
+
algo_params: self.parameters_model,
|
|
76
|
+
): # To check: this should automatically validate the parameters and return HTTP-422 otherwise?
|
|
77
|
+
result_data_tuple = self.run_algorithm(**algo_params.dict())
|
|
78
|
+
serialized_results = serverkit.serialize_result_tuple(result_data_tuple)
|
|
79
|
+
return serialized_results
|
|
80
|
+
|
|
81
|
+
@self.app.get(f"/{self.algorithm_name}/parameters", response_model=dict)
|
|
82
|
+
def get_algo_params():
|
|
83
|
+
return self.parameters_model.model_json_schema()
|
|
84
|
+
|
|
85
|
+
@self.app.get(f"/{self.algorithm_name}/sample_images", response_model=dict)
|
|
86
|
+
def get_sample_images():
|
|
87
|
+
images = self.load_sample_images()
|
|
88
|
+
encoded_images = [
|
|
89
|
+
{"sample_image": serverkit.encode_contents(image)} for image in images
|
|
90
|
+
]
|
|
91
|
+
return {"sample_images": encoded_images}
|
|
92
|
+
|
|
93
|
+
def load_sample_images(self) -> List["np.ndarray"]:
|
|
94
|
+
raise NotImplementedError("Subclasses should implement this method")
|
|
95
|
+
|
|
96
|
+
def run_algorithm(self, **algo_params) -> List[Tuple]:
|
|
97
|
+
raise NotImplementedError("Subclasses should implement this method")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2024, EPFL
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of copyright holder nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: imaging-server-kit
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Run image processing algorithms in a FastAPI server.
|
|
5
|
+
Author-email: Mallory Wittwer <mallory.wittwer@epfl.ch>
|
|
6
|
+
License: Copyright (c) 2024, EPFL
|
|
7
|
+
All rights reserved.
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
13
|
+
list of conditions and the following disclaimer.
|
|
14
|
+
|
|
15
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
16
|
+
this list of conditions and the following disclaimer in the documentation
|
|
17
|
+
and/or other materials provided with the distribution.
|
|
18
|
+
|
|
19
|
+
* Neither the name of copyright holder nor the names of its
|
|
20
|
+
contributors may be used to endorse or promote products derived from
|
|
21
|
+
this software without specific prior written permission.
|
|
22
|
+
|
|
23
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
24
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
25
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
26
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
27
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
28
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
29
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
30
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
31
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
32
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
33
|
+
|
|
34
|
+
Project-URL: homepage, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
|
|
35
|
+
Project-URL: repository, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
|
|
36
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
37
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
44
|
+
Requires-Python: >=3.9
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Requires-Dist: fastapi
|
|
48
|
+
Requires-Dist: uvicorn[standard]
|
|
49
|
+
Requires-Dist: python-multipart
|
|
50
|
+
Requires-Dist: tifffile
|
|
51
|
+
Requires-Dist: numpy
|
|
52
|
+
Requires-Dist: scikit-image
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
# 🪐 Imaging Server Kit
|
|
56
|
+
|
|
57
|
+
Run seamlessly interoperable algorithms on images using FastAPI.
|
|
58
|
+
|
|
59
|
+
## Setup
|
|
60
|
+
|
|
61
|
+
Build the `imaging-server-kit` image with `docker`:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
docker build -t imaging-server-kit:3.9 .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
or build specific images:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
docker build -t imaging-server-kit:3.10 --file Dockerfile-3.10 .
|
|
71
|
+
docker build -t imaging-server-kit:gpu --file Dockerfile-GPU .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Run an algorithm server**
|
|
75
|
+
|
|
76
|
+
The server will be running on `http://localhost:8000`.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
docker build -t serverkit/rembg .
|
|
80
|
+
docker run -it --rm -p 8000:8000 serverkit/rembg
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Run an algorithm server with multiple algorithms**
|
|
84
|
+
|
|
85
|
+
See [deployment](./reference_deployment/README.md). The server will be running on `http://localhost:7000`.
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
**Python client**
|
|
90
|
+
|
|
91
|
+
Install the `imaging-server-kit` package with `pip`:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
pip install git+https://gitlab.com/epfl-center-for-imaging/imaging-server-kit.git
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Connect to an algorithm server and run algorithms from Python:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from imaging_server_kit import Client
|
|
101
|
+
|
|
102
|
+
client = Client()
|
|
103
|
+
|
|
104
|
+
client.connect("http://localhost:7000")
|
|
105
|
+
|
|
106
|
+
print(client.algorithms)
|
|
107
|
+
# [`rembg`, `stardist`, `sam2`]
|
|
108
|
+
|
|
109
|
+
data_tuple = client.run_algorithm(
|
|
110
|
+
algorithm="rembg",
|
|
111
|
+
image=(...),
|
|
112
|
+
rembg_model_name="silueta",
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
More [examples](./examples/).
|
|
117
|
+
|
|
118
|
+
**Napari client**
|
|
119
|
+
|
|
120
|
+
Coming soon.
|
|
121
|
+
|
|
122
|
+
**Web client**
|
|
123
|
+
|
|
124
|
+
Coming soon.
|
|
125
|
+
|
|
126
|
+
**QuPath client**
|
|
127
|
+
|
|
128
|
+
Coming soon.
|
|
129
|
+
|
|
130
|
+
**Fiji plugin**
|
|
131
|
+
|
|
132
|
+
Coming soon.
|
|
133
|
+
|
|
134
|
+
## Contributing
|
|
135
|
+
|
|
136
|
+
Contributions are very welcome.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
This software is distributed under the terms of the [BSD-3](http://opensource.org/licenses/BSD-3-Clause) license.
|
|
141
|
+
|
|
142
|
+
## Issues
|
|
143
|
+
|
|
144
|
+
If you encounter any problems, please file an issue along with a detailed description.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
imaging_server_kit/__init__.py,sha256=6fdRi1RYnl6ibNuVQ3u29XhEpXUwWNmmz3aEKf3Ptkg,331
|
|
2
|
+
imaging_server_kit/_version.py,sha256=pMnmqZnpVmaqR5nqHztNWzbbtb1oy5bPN_v7uhOH8K8,411
|
|
3
|
+
imaging_server_kit/client.py,sha256=XHHWvhziwOdgvCvLiE4Pq7Eg1EOAZhxUET1VK8Pntr8,3917
|
|
4
|
+
imaging_server_kit/encoding.py,sha256=RulGIVLdQnDhSSkLHo-MvBgM56zKJnvYR_Xj86GG2Aw,799
|
|
5
|
+
imaging_server_kit/registry.py,sha256=rKo1UGccKuImG1MSDIFsUnKYfDz6KZ3Wl0u6HDyoKx4,2734
|
|
6
|
+
imaging_server_kit/serialization.py,sha256=RcZxfBcdqjwWjcwxJA7yon_ZizeGNGL5GHkOJgSQ5Js,1931
|
|
7
|
+
imaging_server_kit/server.py,sha256=fnFIVGUfB_19WqhJ4qILpVH3D5ZgNcnur9o3YCJx_qU,3481
|
|
8
|
+
imaging_server_kit-0.0.1.dist-info/LICENSE,sha256=rAHt9UjSWQOkFCt0GZ-gu0Cr8fOHFW0BD-OQhixH4mM,1478
|
|
9
|
+
imaging_server_kit-0.0.1.dist-info/METADATA,sha256=1iXByYLpPINWyzQeYdqAj2XzW-lpJvFjTiHE7XaSMuc,4469
|
|
10
|
+
imaging_server_kit-0.0.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
11
|
+
imaging_server_kit-0.0.1.dist-info/top_level.txt,sha256=TEy2d0Zzb2kI8tDesIfbMT624gXUO6z4_J5Due618Qg,19
|
|
12
|
+
imaging_server_kit-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
imaging_server_kit
|