imaging-server-kit 0.0.3__tar.gz → 0.0.5__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.3 → imaging_server_kit-0.0.5}/Dockerfile +2 -1
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/PKG-INFO +20 -12
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/README.md +15 -9
- imaging_server_kit-0.0.5/build.sh +6 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/main.py +4 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/pyproject.toml +4 -2
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/__init__.py +2 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/_version.py +9 -4
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/client.py +6 -3
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/errors.py +14 -4
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/geometry.py +76 -26
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/registry.py +1 -1
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/serialization.py +17 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/server.py +46 -10
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/templates/info.html +1 -2
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/PKG-INFO +20 -12
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/SOURCES.txt +0 -1
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/requires.txt +2 -0
- imaging_server_kit-0.0.5/start_registry.py +8 -0
- imaging_server_kit-0.0.3/Dockerfile-registry +0 -5
- imaging_server_kit-0.0.3/build.sh +0 -14
- imaging_server_kit-0.0.3/start_registry.py +0 -4
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.dockerignore +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.gitattributes +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.github/workflows/publish.yml +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.gitignore +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/Dockerfile-GPU +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/LICENSE +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/MANIFEST.in +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/examples/.gitkeep +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/setup.cfg +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/assets/style.css +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/encoding.py +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/static/index.css +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/static/info.css +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/templates/index.html +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/web_demo.py +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/dependency_links.txt +0 -0
- {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: imaging-server-kit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
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
|
|
@@ -53,6 +53,8 @@ Requires-Dist: python-multipart
|
|
|
53
53
|
Requires-Dist: tifffile
|
|
54
54
|
Requires-Dist: numpy
|
|
55
55
|
Requires-Dist: scikit-image
|
|
56
|
+
Requires-Dist: opencv-contrib-python-headless
|
|
57
|
+
Requires-Dist: imantics
|
|
56
58
|
Requires-Dist: geojson
|
|
57
59
|
Requires-Dist: jinja2
|
|
58
60
|
Requires-Dist: pyyaml
|
|
@@ -64,7 +66,12 @@ Requires-Dist: pytest
|
|
|
64
66
|

|
|
65
67
|
# 🪐 Imaging Server Kit
|
|
66
68
|
|
|
67
|
-
Deploy image processing algorithms in FastAPI servers and easily run them
|
|
69
|
+
Deploy image processing algorithms in FastAPI servers and easily run them from Napari, QuPath, and more.
|
|
70
|
+
|
|
71
|
+
The algorithm servers implement a common set of API endpoints for processing and to document the project. These include:
|
|
72
|
+
- `/process`: Runs the algorithm
|
|
73
|
+
- `/info`: Displays documentation about the algorithm usage
|
|
74
|
+
- `/sample_images`: Provides one or multiple sample images
|
|
68
75
|
|
|
69
76
|
## Usage
|
|
70
77
|
|
|
@@ -72,22 +79,23 @@ Deploy image processing algorithms in FastAPI servers and easily run them in Nap
|
|
|
72
79
|
|
|
73
80
|
Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
|
|
74
81
|
|
|
75
|
-
- [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist)
|
|
76
|
-
- [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose)
|
|
77
|
-
- [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow)
|
|
78
|
-
- [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg)
|
|
79
|
-
- [
|
|
80
|
-
- [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy)
|
|
82
|
+
- [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist): Object detection with star-convex shapes
|
|
83
|
+
- [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose): A generalist algorithm for cellular segmentation
|
|
84
|
+
- [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow): Accurate and efficient spot detection
|
|
85
|
+
- [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg): A tool to remove images background
|
|
86
|
+
- [LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG): Laplacian of Gaussian filter
|
|
87
|
+
- [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy): Measurement of greyscale orientations
|
|
81
88
|
<!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
|
|
82
89
|
|
|
83
|
-
To
|
|
90
|
+
All of these algorithm servers can be installed locally or built and run with docker. To use docker and serve multiple algorithms, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
|
|
84
91
|
|
|
85
92
|
```{docker-compose.yml}
|
|
86
93
|
services:
|
|
87
94
|
servers_registry:
|
|
88
|
-
image: mallorywittwerepfl/imaging-server-kit:
|
|
95
|
+
image: mallorywittwerepfl/imaging-server-kit:latest
|
|
89
96
|
ports:
|
|
90
97
|
- "8000:8000"
|
|
98
|
+
command: python3 start_registry.py
|
|
91
99
|
rembg:
|
|
92
100
|
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
|
|
93
101
|
depends_on:
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|

|
|
2
2
|
# 🪐 Imaging Server Kit
|
|
3
3
|
|
|
4
|
-
Deploy image processing algorithms in FastAPI servers and easily run them
|
|
4
|
+
Deploy image processing algorithms in FastAPI servers and easily run them from Napari, QuPath, and more.
|
|
5
|
+
|
|
6
|
+
The algorithm servers implement a common set of API endpoints for processing and to document the project. These include:
|
|
7
|
+
- `/process`: Runs the algorithm
|
|
8
|
+
- `/info`: Displays documentation about the algorithm usage
|
|
9
|
+
- `/sample_images`: Provides one or multiple sample images
|
|
5
10
|
|
|
6
11
|
## Usage
|
|
7
12
|
|
|
@@ -9,22 +14,23 @@ Deploy image processing algorithms in FastAPI servers and easily run them in Nap
|
|
|
9
14
|
|
|
10
15
|
Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
|
|
11
16
|
|
|
12
|
-
- [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist)
|
|
13
|
-
- [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose)
|
|
14
|
-
- [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow)
|
|
15
|
-
- [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg)
|
|
16
|
-
- [
|
|
17
|
-
- [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy)
|
|
17
|
+
- [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist): Object detection with star-convex shapes
|
|
18
|
+
- [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose): A generalist algorithm for cellular segmentation
|
|
19
|
+
- [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow): Accurate and efficient spot detection
|
|
20
|
+
- [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg): A tool to remove images background
|
|
21
|
+
- [LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG): Laplacian of Gaussian filter
|
|
22
|
+
- [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy): Measurement of greyscale orientations
|
|
18
23
|
<!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
|
|
19
24
|
|
|
20
|
-
To
|
|
25
|
+
All of these algorithm servers can be installed locally or built and run with docker. To use docker and serve multiple algorithms, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
|
|
21
26
|
|
|
22
27
|
```{docker-compose.yml}
|
|
23
28
|
services:
|
|
24
29
|
servers_registry:
|
|
25
|
-
image: mallorywittwerepfl/imaging-server-kit:
|
|
30
|
+
image: mallorywittwerepfl/imaging-server-kit:latest
|
|
26
31
|
ports:
|
|
27
32
|
- "8000:8000"
|
|
33
|
+
command: python3 start_registry.py
|
|
28
34
|
rembg:
|
|
29
35
|
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
|
|
30
36
|
depends_on:
|
|
@@ -28,6 +28,8 @@ dependencies = [
|
|
|
28
28
|
"tifffile",
|
|
29
29
|
"numpy",
|
|
30
30
|
"scikit-image",
|
|
31
|
+
"opencv-contrib-python-headless",
|
|
32
|
+
"imantics",
|
|
31
33
|
"geojson",
|
|
32
34
|
"jinja2", # For the /info route
|
|
33
35
|
"pyyaml", # For the /info route
|
|
@@ -38,8 +40,8 @@ dependencies = [
|
|
|
38
40
|
]
|
|
39
41
|
|
|
40
42
|
[project.urls]
|
|
41
|
-
homepage = "https://
|
|
42
|
-
repository = "https://
|
|
43
|
+
homepage = "https://github.com/Imaging-Server-Kit/imaging-server-kit"
|
|
44
|
+
repository = "https://github.com/Imaging-Server-Kit/imaging-server-kit"
|
|
43
45
|
|
|
44
46
|
[build-system]
|
|
45
47
|
requires = ["setuptools>=42.0.0", "wheel", "setuptools_scm"]
|
|
@@ -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.5'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 5)
|
|
@@ -7,6 +7,7 @@ from imaging_server_kit.errors import (
|
|
|
7
7
|
AlgorithmServerError,
|
|
8
8
|
InvalidAlgorithmParametersError,
|
|
9
9
|
ServerRequestError,
|
|
10
|
+
AlgorithmTimeoutError,
|
|
10
11
|
)
|
|
11
12
|
|
|
12
13
|
|
|
@@ -29,7 +30,7 @@ class Client:
|
|
|
29
30
|
services = response.json().get("services")
|
|
30
31
|
self.algorithms = services
|
|
31
32
|
else:
|
|
32
|
-
raise AlgorithmServerError(response.status_code, response.
|
|
33
|
+
raise AlgorithmServerError(response.status_code, response.text)
|
|
33
34
|
|
|
34
35
|
@property
|
|
35
36
|
def server_url(self) -> str:
|
|
@@ -68,8 +69,10 @@ class Client:
|
|
|
68
69
|
return serverkit.deserialize_result_tuple(response.json())
|
|
69
70
|
elif response.status_code == 422:
|
|
70
71
|
raise InvalidAlgorithmParametersError(response.status_code, response.json())
|
|
72
|
+
elif response.status_code == 504:
|
|
73
|
+
raise AlgorithmTimeoutError(response.status_code, response.text)
|
|
71
74
|
else:
|
|
72
|
-
raise AlgorithmServerError(response.status_code, response.
|
|
75
|
+
raise AlgorithmServerError(response.status_code, response.text)
|
|
73
76
|
|
|
74
77
|
def get_algorithm_parameters(self, algorithm=None) -> Dict:
|
|
75
78
|
algorithm = self._validate_algorithm(algorithm)
|
|
@@ -84,7 +87,7 @@ class Client:
|
|
|
84
87
|
if response.status_code == 200:
|
|
85
88
|
return response.json()
|
|
86
89
|
else:
|
|
87
|
-
raise AlgorithmServerError(response.status_code, response.
|
|
90
|
+
raise AlgorithmServerError(response.status_code, response.text)
|
|
88
91
|
|
|
89
92
|
def get_sample_images(
|
|
90
93
|
self, algorithm=None, first_only: bool = False
|
|
@@ -13,17 +13,27 @@ class AlgorithmServerError(Exception):
|
|
|
13
13
|
def __init__(self, status_code, response_body, message="Algorithm server returned an unexpected status_code"):
|
|
14
14
|
self.status_code = status_code
|
|
15
15
|
self.response_body = response_body
|
|
16
|
-
self.message = f"{message}: {status_code}\nResponse
|
|
16
|
+
self.message = f"{message}: {status_code}\nResponse: {response_body}"
|
|
17
|
+
super().__init__(self.message)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AlgorithmTimeoutError(Exception):
|
|
21
|
+
"""Exception raised when a request to an algorithm server exceeds the time limit."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, status_code, response_text, message="Algorithm server returned an unexpected status_code"):
|
|
24
|
+
self.status_code = status_code
|
|
25
|
+
self.response_text = response_text
|
|
26
|
+
self.message = f"{message}: {status_code}\nResponse: {response_text}"
|
|
17
27
|
super().__init__(self.message)
|
|
18
28
|
|
|
19
29
|
|
|
20
30
|
class InvalidAlgorithmParametersError(Exception):
|
|
21
31
|
"""Exception raised when a request to an algorithm server is made with invalid algorithm parameters."""
|
|
22
32
|
|
|
23
|
-
def __init__(self, status_code,
|
|
33
|
+
def __init__(self, status_code, response_text, message="Algorithm parameters were invalidated by the server"):
|
|
24
34
|
self.status_code = status_code
|
|
25
|
-
self.
|
|
26
|
-
pydantic_details =
|
|
35
|
+
self.response_text = response_text
|
|
36
|
+
pydantic_details = response_text.get("detail")[0]
|
|
27
37
|
pydantic_validation_msg = pydantic_details.get("msg")
|
|
28
38
|
pydantic_failing_param = pydantic_details.get("loc")[1]
|
|
29
39
|
pydantic_failing_param_value = pydantic_details.get("input")
|
|
@@ -1,53 +1,103 @@
|
|
|
1
1
|
from typing import List
|
|
2
|
-
from skimage.measure import find_contours
|
|
3
2
|
from skimage.draw import polygon2mask
|
|
4
3
|
from geojson import Feature, Polygon, Point, LineString
|
|
5
4
|
import numpy as np
|
|
5
|
+
from imantics import Mask
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def instance_mask2features(segmentation_mask: np.ndarray) -> List[Feature]:
|
|
9
9
|
"""
|
|
10
10
|
Args:
|
|
11
11
|
segm_mask: Segmentation mask with the background pixels set to zero and the pixels assigned to a segmented
|
|
12
|
-
object set to an int value
|
|
12
|
+
object instance set to an int value
|
|
13
13
|
|
|
14
14
|
Returns:
|
|
15
15
|
A list containing the contours of each object as a geojson.Feature
|
|
16
16
|
"""
|
|
17
17
|
features = []
|
|
18
18
|
indices = np.unique(segmentation_mask)
|
|
19
|
-
indices =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
features.append(
|
|
19
|
+
indices = indices[indices != 0] # remove background
|
|
20
|
+
|
|
21
|
+
if indices.size == 0:
|
|
22
|
+
return features
|
|
23
|
+
|
|
24
|
+
for detection_id in indices:
|
|
25
|
+
mask = segmentation_mask == int(detection_id)
|
|
26
|
+
polygons = Mask(mask).polygons()
|
|
27
|
+
for contour in polygons.points:
|
|
28
|
+
coords = np.array(contour)
|
|
29
|
+
coords = np.vstack([coords, coords[0]]) # Close the polygon for QuPath
|
|
30
|
+
geom = Polygon(coordinates=[coords.tolist()])
|
|
31
|
+
feature = Feature(
|
|
32
|
+
geometry=geom,
|
|
33
|
+
properties={
|
|
34
|
+
"Detection ID": int(detection_id),
|
|
35
|
+
"Class": 1
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
features.append(feature)
|
|
39
|
+
|
|
40
|
+
return features
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def mask2features(segmentation_mask: np.ndarray) -> List[Feature]:
|
|
44
|
+
"""
|
|
45
|
+
Args:
|
|
46
|
+
segm_mask: Segmentation mask with the background pixels set to zero and the pixels assigned to a segmented
|
|
47
|
+
class set to an int value
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A list containing the contours of each object as a geojson.Feature
|
|
51
|
+
"""
|
|
52
|
+
features = []
|
|
53
|
+
indices = np.unique(segmentation_mask)
|
|
54
|
+
indices = indices[indices != 0] # remove background
|
|
55
|
+
|
|
56
|
+
if indices.size == 0:
|
|
57
|
+
return features
|
|
58
|
+
|
|
59
|
+
for pixel_class in indices:
|
|
60
|
+
mask = segmentation_mask == int(pixel_class)
|
|
61
|
+
polygons = Mask(mask).polygons()
|
|
62
|
+
for detection_id, contour in enumerate(polygons.points, start=1):
|
|
63
|
+
coords = np.array(contour)
|
|
64
|
+
coords = np.vstack([coords, coords[0]]) # Close the polygon for QuPath
|
|
65
|
+
geom = Polygon(coordinates=[coords.tolist()])
|
|
66
|
+
feature = Feature(
|
|
67
|
+
geometry=geom,
|
|
68
|
+
properties={
|
|
69
|
+
"Detection ID": detection_id,
|
|
70
|
+
"Class": pixel_class
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
features.append(feature)
|
|
74
|
+
|
|
39
75
|
return features
|
|
40
76
|
|
|
41
77
|
|
|
42
78
|
def features2mask(features, image_shape):
|
|
43
79
|
segmentation_mask = np.zeros(image_shape, dtype=np.uint16)
|
|
44
|
-
for
|
|
45
|
-
|
|
80
|
+
for feature in features:
|
|
81
|
+
feature_coordinates = np.array(feature["geometry"]["coordinates"])
|
|
82
|
+
feature_coordinates = np.squeeze(feature_coordinates)
|
|
83
|
+
feature_coordinates = feature_coordinates[:, ::-1] # Invert XY
|
|
84
|
+
feature_mask = polygon2mask(image_shape, feature_coordinates)
|
|
85
|
+
feature_properites = feature.get("properties")
|
|
86
|
+
feature_class = feature_properites.get("Class")
|
|
87
|
+
segmentation_mask[feature_mask] = feature_class
|
|
88
|
+
return segmentation_mask
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def features2instance_mask(features, image_shape):
|
|
92
|
+
segmentation_mask = np.zeros(image_shape, dtype=np.uint16)
|
|
93
|
+
for feature in features:
|
|
46
94
|
feature_coordinates = np.array(feature["geometry"]["coordinates"])
|
|
47
95
|
feature_coordinates = np.squeeze(feature_coordinates)
|
|
48
96
|
feature_coordinates = feature_coordinates[:, ::-1] # Invert XY
|
|
49
97
|
feature_mask = polygon2mask(image_shape, feature_coordinates)
|
|
50
|
-
|
|
98
|
+
feature_properites = feature.get("properties")
|
|
99
|
+
feature_id = feature_properites.get("Detection ID")
|
|
100
|
+
segmentation_mask[feature_mask] = feature_id
|
|
51
101
|
return segmentation_mask
|
|
52
102
|
|
|
53
103
|
|
|
@@ -74,7 +74,7 @@ class Registry:
|
|
|
74
74
|
# response = requests.get(f"{self.services.get(algorithm)}/demo")
|
|
75
75
|
# return HTMLResponse(content=response.text, status_code=response.status_code)
|
|
76
76
|
|
|
77
|
-
@self.app.get("/{algorithm}")
|
|
77
|
+
@self.app.get("/{algorithm}/")
|
|
78
78
|
async def get_algorithm(request: Request, algorithm: str):
|
|
79
79
|
algo_url = self.services.get(algorithm)
|
|
80
80
|
response = requests.get(f"{algo_url}/")
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/serialization.py
RENAMED
|
@@ -69,8 +69,13 @@ def serialize_result_tuple(result_data_tuple: List[Tuple]) -> List[Dict]:
|
|
|
69
69
|
if data_type == "image":
|
|
70
70
|
features = serverkit.encode_contents(data.astype(np.float32))
|
|
71
71
|
elif data_type == "labels":
|
|
72
|
+
data = data.astype(np.uint16)
|
|
72
73
|
features = serverkit.mask2features(data)
|
|
73
74
|
data_params['image_shape'] = data.shape
|
|
75
|
+
elif data_type == "instance_mask":
|
|
76
|
+
data = data.astype(np.uint16)
|
|
77
|
+
features = serverkit.instance_mask2features(data)
|
|
78
|
+
data_params['image_shape'] = data.shape
|
|
74
79
|
elif data_type == "labels3d":
|
|
75
80
|
features = serverkit.encode_contents(data.astype(np.uint16))
|
|
76
81
|
elif data_type == "points":
|
|
@@ -84,8 +89,13 @@ def serialize_result_tuple(result_data_tuple: List[Tuple]) -> List[Dict]:
|
|
|
84
89
|
features = serverkit.vectors2features(data)
|
|
85
90
|
elif data_type == "tracks":
|
|
86
91
|
features = serverkit.encode_contents(data.astype(np.float32))
|
|
92
|
+
elif data_type == "class":
|
|
93
|
+
features = data # A simple string
|
|
94
|
+
elif data_type == "text":
|
|
95
|
+
features = data # A text string
|
|
87
96
|
else:
|
|
88
97
|
print(f"Unknown data_type: {data_type}")
|
|
98
|
+
features = None
|
|
89
99
|
|
|
90
100
|
serialized_results.append(
|
|
91
101
|
{
|
|
@@ -113,6 +123,9 @@ def deserialize_result_tuple(serialized_results: List[Dict]) -> List[Tuple]:
|
|
|
113
123
|
elif data_type == "labels":
|
|
114
124
|
image_shape = data_params.pop('image_shape')
|
|
115
125
|
data = serverkit.features2mask(features, image_shape)
|
|
126
|
+
elif data_type == "instance_mask":
|
|
127
|
+
image_shape = data_params.pop('image_shape')
|
|
128
|
+
data = serverkit.features2instance_mask(features, image_shape)
|
|
116
129
|
elif data_type == "labels3d":
|
|
117
130
|
data = serverkit.decode_contents(features).astype(int)
|
|
118
131
|
elif data_type == "points":
|
|
@@ -125,6 +138,10 @@ def deserialize_result_tuple(serialized_results: List[Dict]) -> List[Tuple]:
|
|
|
125
138
|
data = serverkit.features2vectors(features)
|
|
126
139
|
elif data_type == "tracks":
|
|
127
140
|
data = serverkit.decode_contents(features).astype(float)
|
|
141
|
+
elif data_type == "class":
|
|
142
|
+
data = features # A simple string
|
|
143
|
+
elif data_type == "text":
|
|
144
|
+
data = features # A text string
|
|
128
145
|
else:
|
|
129
146
|
print(f"Unknown data_type: {data_type}")
|
|
130
147
|
|
|
@@ -5,15 +5,15 @@ from typing import List, Tuple, Type
|
|
|
5
5
|
import imaging_server_kit as serverkit
|
|
6
6
|
import requests
|
|
7
7
|
import yaml
|
|
8
|
-
from fastapi import FastAPI, Request, status
|
|
8
|
+
from fastapi import FastAPI, Request, status, HTTPException
|
|
9
9
|
from fastapi.responses import HTMLResponse
|
|
10
10
|
from fastapi.staticfiles import StaticFiles
|
|
11
11
|
from fastapi.templating import Jinja2Templates
|
|
12
12
|
from imaging_server_kit.web_demo import generate_dash_app
|
|
13
13
|
from pydantic import BaseModel, ConfigDict
|
|
14
14
|
from a2wsgi import WSGIMiddleware
|
|
15
|
-
# from starlette.middleware.wsgi import WSGIMiddleware
|
|
16
15
|
from contextlib import asynccontextmanager
|
|
16
|
+
import asyncio
|
|
17
17
|
|
|
18
18
|
templates_dir = importlib.resources.files("imaging_server_kit").joinpath("templates")
|
|
19
19
|
static_dir = importlib.resources.files("imaging_server_kit").joinpath("static")
|
|
@@ -45,10 +45,20 @@ class Parameters(BaseModel):
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class Server:
|
|
48
|
-
def __init__(
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
algorithm_name: str,
|
|
51
|
+
parameters_model: Type[BaseModel],
|
|
52
|
+
metadata_file: str = None,
|
|
53
|
+
):
|
|
49
54
|
self.algorithm_name = algorithm_name
|
|
50
55
|
self.parameters_model = parameters_model
|
|
51
56
|
|
|
57
|
+
if metadata_file is None:
|
|
58
|
+
self.metadata_file = "metadata.yaml"
|
|
59
|
+
else:
|
|
60
|
+
self.metadata_file = metadata_file
|
|
61
|
+
|
|
52
62
|
self.app = FastAPI(title=algorithm_name, lifespan=self.lifespan)
|
|
53
63
|
self.app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
54
64
|
|
|
@@ -65,9 +75,6 @@ class Server:
|
|
|
65
75
|
|
|
66
76
|
self.app.mount(f"/{algorithm_name}/demo", WSGIMiddleware(dash_app.server))
|
|
67
77
|
|
|
68
|
-
# self.app.on_event("startup")(self.register_with_registry)
|
|
69
|
-
# self.app.on_event("shutdown")(self.deregister_from_registry)
|
|
70
|
-
|
|
71
78
|
self.register_routes()
|
|
72
79
|
|
|
73
80
|
self.services = [self.algorithm_name]
|
|
@@ -120,7 +127,7 @@ class Server:
|
|
|
120
127
|
|
|
121
128
|
@self.app.get(f"/{self.algorithm_name}/info", response_class=HTMLResponse)
|
|
122
129
|
def info(request: Request):
|
|
123
|
-
algo_info = load_from_yaml(
|
|
130
|
+
algo_info = load_from_yaml(self.metadata_file)
|
|
124
131
|
algo_params_schema = get_algo_params()
|
|
125
132
|
algo_params = parse_algo_params_schema(algo_params_schema)
|
|
126
133
|
return templates.TemplateResponse(
|
|
@@ -132,10 +139,29 @@ class Server:
|
|
|
132
139
|
},
|
|
133
140
|
)
|
|
134
141
|
|
|
135
|
-
@self.app.post(
|
|
142
|
+
@self.app.post(
|
|
143
|
+
f"/{self.algorithm_name}/process", status_code=status.HTTP_201_CREATED
|
|
144
|
+
)
|
|
136
145
|
async def run_algo(algo_params: self.parameters_model):
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
try:
|
|
147
|
+
result_data_tuple = await asyncio.wait_for(
|
|
148
|
+
self._run_algorithm(**algo_params.dict()), timeout=600
|
|
149
|
+
)
|
|
150
|
+
except (
|
|
151
|
+
asyncio.TimeoutError
|
|
152
|
+
): # This works but doesn't actually kill the process... we need something else to terminate it
|
|
153
|
+
raise HTTPException(
|
|
154
|
+
status_code=504, detail="Request timeout during processing."
|
|
155
|
+
)
|
|
156
|
+
try:
|
|
157
|
+
serialized_results = await asyncio.wait_for(
|
|
158
|
+
self._serialize_result_tuple(result_data_tuple), timeout=600
|
|
159
|
+
)
|
|
160
|
+
except asyncio.TimeoutError:
|
|
161
|
+
raise HTTPException(
|
|
162
|
+
status_code=504, detail="Request timeout during serialization."
|
|
163
|
+
)
|
|
164
|
+
|
|
139
165
|
return serialized_results
|
|
140
166
|
|
|
141
167
|
@self.app.get(f"/{self.algorithm_name}/parameters", response_model=dict)
|
|
@@ -150,6 +176,16 @@ class Server:
|
|
|
150
176
|
]
|
|
151
177
|
return {"sample_images": encoded_images}
|
|
152
178
|
|
|
179
|
+
async def _serialize_result_tuple(self, result_data_tuple):
|
|
180
|
+
serialized_results = await asyncio.to_thread(
|
|
181
|
+
serverkit.serialize_result_tuple, result_data_tuple
|
|
182
|
+
)
|
|
183
|
+
return serialized_results
|
|
184
|
+
|
|
185
|
+
async def _run_algorithm(self, **algo_params):
|
|
186
|
+
result_data_tuple = await asyncio.to_thread(self.run_algorithm, **algo_params)
|
|
187
|
+
return result_data_tuple
|
|
188
|
+
|
|
153
189
|
def load_sample_images(self) -> List["np.ndarray"]:
|
|
154
190
|
raise NotImplementedError("Subclasses should implement this method")
|
|
155
191
|
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/templates/info.html
RENAMED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<p>Server Kit Repository</p>
|
|
39
39
|
</div>
|
|
40
40
|
</a>
|
|
41
|
-
<a href="
|
|
41
|
+
<a href="demo">
|
|
42
42
|
<div class="card-category">
|
|
43
43
|
<i class="fa-solid fa-rocket"></i>
|
|
44
44
|
<p>Web demo</p>
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
</a>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
|
-
<!-- <h3><strong>Parameters</strong></h3> -->
|
|
50
49
|
<table>
|
|
51
50
|
<thead>
|
|
52
51
|
<tr>
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: imaging-server-kit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
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
|
|
@@ -53,6 +53,8 @@ Requires-Dist: python-multipart
|
|
|
53
53
|
Requires-Dist: tifffile
|
|
54
54
|
Requires-Dist: numpy
|
|
55
55
|
Requires-Dist: scikit-image
|
|
56
|
+
Requires-Dist: opencv-contrib-python-headless
|
|
57
|
+
Requires-Dist: imantics
|
|
56
58
|
Requires-Dist: geojson
|
|
57
59
|
Requires-Dist: jinja2
|
|
58
60
|
Requires-Dist: pyyaml
|
|
@@ -64,7 +66,12 @@ Requires-Dist: pytest
|
|
|
64
66
|

|
|
65
67
|
# 🪐 Imaging Server Kit
|
|
66
68
|
|
|
67
|
-
Deploy image processing algorithms in FastAPI servers and easily run them
|
|
69
|
+
Deploy image processing algorithms in FastAPI servers and easily run them from Napari, QuPath, and more.
|
|
70
|
+
|
|
71
|
+
The algorithm servers implement a common set of API endpoints for processing and to document the project. These include:
|
|
72
|
+
- `/process`: Runs the algorithm
|
|
73
|
+
- `/info`: Displays documentation about the algorithm usage
|
|
74
|
+
- `/sample_images`: Provides one or multiple sample images
|
|
68
75
|
|
|
69
76
|
## Usage
|
|
70
77
|
|
|
@@ -72,22 +79,23 @@ Deploy image processing algorithms in FastAPI servers and easily run them in Nap
|
|
|
72
79
|
|
|
73
80
|
Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
|
|
74
81
|
|
|
75
|
-
- [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist)
|
|
76
|
-
- [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose)
|
|
77
|
-
- [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow)
|
|
78
|
-
- [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg)
|
|
79
|
-
- [
|
|
80
|
-
- [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy)
|
|
82
|
+
- [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist): Object detection with star-convex shapes
|
|
83
|
+
- [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose): A generalist algorithm for cellular segmentation
|
|
84
|
+
- [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow): Accurate and efficient spot detection
|
|
85
|
+
- [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg): A tool to remove images background
|
|
86
|
+
- [LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG): Laplacian of Gaussian filter
|
|
87
|
+
- [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy): Measurement of greyscale orientations
|
|
81
88
|
<!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
|
|
82
89
|
|
|
83
|
-
To
|
|
90
|
+
All of these algorithm servers can be installed locally or built and run with docker. To use docker and serve multiple algorithms, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
|
|
84
91
|
|
|
85
92
|
```{docker-compose.yml}
|
|
86
93
|
services:
|
|
87
94
|
servers_registry:
|
|
88
|
-
image: mallorywittwerepfl/imaging-server-kit:
|
|
95
|
+
image: mallorywittwerepfl/imaging-server-kit:latest
|
|
89
96
|
ports:
|
|
90
97
|
- "8000:8000"
|
|
98
|
+
command: python3 start_registry.py
|
|
91
99
|
rembg:
|
|
92
100
|
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
|
|
93
101
|
depends_on:
|
|
@@ -1,14 +0,0 @@
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/assets/style.css
RENAMED
|
File without changes
|
|
File without changes
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/static/index.css
RENAMED
|
File without changes
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/static/info.css
RENAMED
|
File without changes
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/templates/index.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/top_level.txt
RENAMED
|
File without changes
|